Che cos'è "espansione automatica dello stack"?


13

getrlimit (2) ha la seguente definizione nelle pagine man:

RLIMIT_AS La dimensione massima della memoria virtuale del processo (spazio degli indirizzi) in byte. Questo limite riguarda le chiamate a brk (2), mmap (2) e mremap (2), che falliscono con l'errore ENOMEM al superamento di questo limite. Anche l'espansione automatica dello stack fallirà (e genererà un SIGSEGV che uccide il processo se nessuno stack alternativo è stato reso disponibile tramite sigaltstack (2)). Poiché il valore è lungo, su macchine con una lunghezza di 32 bit o questo limite è al massimo di 2 GiB o questa risorsa è illimitata.

Cosa si intende per "espansione automatica dello stack" qui? Lo stack in un ambiente Linux / UNIX cresce secondo necessità? Se sì, qual è il meccanismo esatto?

Risposte:


1

Sì, le pile crescono dinamicamente. Lo stack è nella parte superiore della memoria che cresce verso il basso verso l'heap.

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

L'heap cresce verso l'alto (ogni volta che esegui malloc) e lo stack cresce verso il basso come e quando vengono chiamate nuove funzioni. L'heap è presente appena sopra la sezione BSS del programma. Ciò significa che la dimensione del programma e il modo in cui assegna memoria all'heap influiscono anche sulla dimensione massima dello stack per quel processo. Di solito la dimensione dello stack è illimitata (fino a quando le aree di heap e stack si incontrano e / o sovrascrivono, il che genererà un overflow dello stack e SIGSEGV :-)

Questo è solo per i processi utente, lo stack del kernel è sempre fisso (di solito 8 KB)


"Di solito la dimensione dello stack è illimitata", no, di solito è limitata a 8Mb ( ulimit -s).
Eddy_Em,

sì, hai ragione nella maggior parte dei sistemi. Stai controllando il comando ulimit della shell, in tal caso esiste un limite rigido per la dimensione dello stack, che è illimitato (ulimit -Hs). Ad ogni modo, quel punto era sottolineare che stack e heap crescono nelle direzioni opposte.
Santosh,

Allora, in che modo "l'espansione automatica" differisce da "spingere gli elementi nello stack"? Dalla tua spiegazione, ho la sensazione che siano uguali. Inoltre, ho sentito che i punti di partenza di stack e heap sono molto più di 8 MB, quindi lo stack può crescere tanto quanto serve (o colpisce l'heap). È vero? Se sì, come fa il sistema operativo a decidere dove posizionare l'heap e lo stack?
voce il

Sono uguali, ma le pile non hanno una dimensione fissa a meno che non si limiti la dimensione usando rlimit. Lo stack viene posizionato alla fine dell'area di memoria virtuale e l'heap è immediatamente dopo il segmento di dati dell'eseguibile.
Santosh,

Capisco, grazie. Tuttavia, non credo di ottenere la parte "stack non hanno dimensioni fisse". In tal caso, qual è il limite soft di 8 Mb?
loudandclear,

8

Il meccanismo esatto è dato qui, su Linux: nella gestione di un errore di pagina su mappature anonime si controlla se si tratta di una "allocazione cresciuta" che si dovrebbe espandere come uno stack. Se il record dell'area della VM dice che dovresti, allora regola l'indirizzo iniziale per espandere lo stack.

Quando si verifica un errore di pagina, a seconda dell'indirizzo, può essere riparato (e l'errore eliminato) tramite l'espansione dello stack. Questo comportamento "crescente verso il basso in caso di guasto" per la memoria virtuale può essere richiesto da programmi utente arbitrari con il MAP_GROWSDOWNflag che viene passato alla mmapsyscall.

Puoi scherzare con questo meccanismo anche in un programma utente:

#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;
}

Quando richiesto, trovi il pid del programma (via ps) e osserva /proc/$THAT_PID/mapscome è cresciuta l'area originale.


Va bene chiamare munmap per il mem originale e page_size anche se l'area di memoria è cresciuta tramite MAP_GROWSDOWN? Presumo di sì, perché altrimenti sarebbe un'API molto complessa da usare, ma la documentazione non dice esplicitamente nulla in merito
i.petruk

2
MAP_GROWSDOWN non dovrebbe essere usato ed è stato rimosso da glibc (vedi lwn.net/Articles/294001 per il motivo).
Collin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.