Co to jest „automatyczne rozszerzanie stosu”?

13

getrlimit (2) ma następującą definicję na stronach podręcznika:

RLIMIT_AS Maksymalny rozmiar pamięci wirtualnej procesu (przestrzeni adresowej) w bajtach. Limit ten wpływa na wywołania do brk (2), mmap (2) i mremap (2), które kończą się błędem ENOMEM po przekroczeniu tego limitu. Również automatyczne rozszerzanie stosu zakończy się niepowodzeniem (i wygeneruje SIGSEGV, który zabije proces, jeśli nie zostanie udostępniony alternatywny stos przez sigaltstack (2)). Ponieważ wartość jest długa, na komputerach o długości 32 bitów albo ten limit wynosi maksymalnie 2 GiB, albo ten zasób jest nieograniczony.

Co oznacza tutaj „automatyczne rozwijanie stosu”? Czy stos w środowisku Linux / UNIX rośnie w miarę potrzeb? Jeśli tak, jaki jest dokładny mechanizm?

głośno i wyraźnie
źródło

Odpowiedzi:

1

Tak, stosy rosną dynamicznie. Stos znajduje się w górnej części pamięci i rośnie w kierunku sterty.

--------------
| Stack      |
--------------
| Free memory|
--------------
| Heap       |
--------------
     .
     .

Sterta rośnie w górę (za każdym razem, gdy robisz malloc), a stos rośnie w dół, gdy wywoływane są nowe funkcje. Sterta jest obecna tuż nad sekcją BSS programu. Co oznacza rozmiar twojego programu i sposób przydzielania pamięci w stercie również wpływa na maksymalny rozmiar stosu dla tego procesu. Zwykle rozmiar stosu jest nieograniczony (dopóki obszary stosu i stosu nie spotkają się i / lub zastąpią, co spowoduje przepełnienie stosu i SIGSEGV :-)

Dotyczy to tylko procesów użytkownika. Stos jądra jest zawsze ustalany (zwykle 8 KB)

Santosh
źródło
„Zwykle rozmiar stosu jest nieograniczony”, nie, zwykle jest ograniczony przez 8 Mb ( ulimit -s).
Eddy_Em,
tak, masz rację w większości systemów. Sprawdzasz komendę ulimit powłoki, jeśli tak, to istnieje sztywny limit wielkości stosu, który jest nieograniczony (ulimit -Hs). W każdym razie chodziło o podkreślenie, że stos i stos rosną w przeciwnych kierunkach.
Santosh
Czym zatem „automatyczna ekspansja” różni się od „pchania elementów na stos”? Z twojego wyjaśnienia mam wrażenie, że są takie same. Czułem też, że punkty początkowe stosu i sterty są znacznie większe niż 8 MB, więc stos może urosnąć tyle, ile potrzebuje (lub trafi na stos). Czy to prawda? Jeśli tak, w jaki sposób system operacyjny zdecydował, gdzie umieścić stos i stos?
loudandclear
Są takie same, ale stosy nie mają ustalonego rozmiaru, chyba że mocno ograniczysz rozmiar za pomocą rlimit. Stos jest umieszczany na końcu obszaru pamięci wirtualnej, a sterta jest bezpośrednio * * segmentem danych pliku wykonywalnego.
Santosh
Rozumiem dzięki. Jednak nie sądzę, że dostaję część „stosy nie mają ustalonego rozmiaru”. W takim przypadku, po co jest miękki limit 8 Mb?
loudandclear
8

Dokładny mechanizm podany jest tutaj, w systemie Linux: w obchodzenia się błąd strony na anonimowych odwzorowań Państwo sprawdzić, czy jest to „dorosły ma przydział” , że należy poszerzyć jak stos. Jeśli rekord obszaru maszyny wirtualnej mówi, że powinieneś, to dostosuj adres początkowy, aby rozwinąć stos.

Gdy wystąpi błąd strony, w zależności od adresu, można go naprawić (i usunąć błąd) za pomocą rozszerzenia stosu. To zachowanie „malejące w wyniku błędu” pamięci wirtualnej może być wymagane przez dowolne programy użytkownika z MAP_GROWSDOWNflagą przekazywaną do mmapwywołania systemowego.

Z tym mechanizmem możesz również zadzierać w programie użytkownika:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

int main() {
        long page_size = sysconf(_SC_PAGE_SIZE);
        void *mem = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_GROWSDOWN|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        if (MAP_FAILED == mem) {
                perror("failed to create growsdown mapping");
                return EXIT_FAILURE;
        }

        volatile char *tos = (char *) mem + page_size;

        int i;
        for (i = 1; i < 10 * page_size; ++i)
                tos[-i] = 42;

        fprintf(stderr, "inspect mappping for originally page-sized %p in /proc... press any key to continue...\n", mem);
        (void) getchar();

        if (munmap(mem, page_size))
                perror("failed munmap");

        return EXIT_SUCCESS;
}

Po wyświetleniu monitu znajdziesz identyfikator programu (via ps) i przyjrzyj /proc/$THAT_PID/mapssię, jak rozrósł się oryginalny obszar.

cdleary
źródło
Czy można wywołać munmap dla oryginalnej pamięci i rozmiaru strony, nawet jeśli region pamięci urósł przez MAP_GROWSDOWN? Zakładam, że tak, ponieważ w przeciwnym razie byłoby to bardzo skomplikowane użycie API, ale dokumentacja nie mówi nic w tej sprawie
i.petruk 20.04.2016
2
MAP_GROWSDOWN nie powinien być używany i został usunięty z glibc ( dlaczego na lwn.net/Articles/294001 ).
Collin,