Was ist "automatische Stapelerweiterung"?

13

getrlimit (2) hat in den Manpages folgende Definition:

RLIMIT_AS Die maximale Größe des virtuellen Speichers (Adressraum) des Prozesses in Byte. Dieses Limit wirkt sich auf Aufrufe von brk (2), mmap (2) und mremap (2) aus, die beim Überschreiten dieses Limits mit dem Fehler ENOMEM fehlschlagen. Auch die automatische Stapelerweiterung schlägt fehl (und generiert ein SIGSEGV, das den Prozess abbricht, wenn kein alternativer Stapel über Sigaltstack (2) verfügbar gemacht wurde). Da der Wert a long ist, beträgt dieser Grenzwert auf Computern mit einer Länge von 32 Bit höchstens 2 GiB, oder diese Ressource ist unbegrenzt.

Was ist hier mit "automatischer Stapelerweiterung" gemeint? Wächst der Stack in einer Linux / UNIX-Umgebung nach Bedarf? Wenn ja, welchen genauen Mechanismus gibt es?

laut und klar
quelle

Antworten:

1

Ja, Stapel wachsen dynamisch. Der Stapel befindet sich oben im Speicher und wächst nach unten zum Haufen.

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

Der Heap wächst nach oben (wann immer Sie malloc machen) und der Stack wächst nach unten, wenn neue Funktionen aufgerufen werden. Der Heap befindet sich direkt über dem BSS-Abschnitt des Programms. Dies bedeutet, dass die Größe Ihres Programms und die Art und Weise, wie der Speicher im Heapspeicher aufgeteilt wird, auch die maximale Stapelgröße für diesen Prozess beeinflusst. Normalerweise ist die Stapelgröße unbegrenzt (bis sich Heap- und Stapelbereiche treffen und / oder überschreiben, was zu einem Stapelüberlauf und SIGSEGV führt :-)

Dies ist nur für die Benutzerprozesse, Der Kernel-Stack ist immer fest (in der Regel 8KB)

Santosh
quelle
"Normalerweise ist die Stapelgröße unbegrenzt", nein, normalerweise ist sie auf 8 MB ( ulimit -s) begrenzt.
Eddy_Em
Ja, Sie haben in den meisten Systemen recht. Sie überprüfen den Befehl ulimit der Shell. In diesem Fall gibt es eine feste Grenze für die Stapelgröße, die unbegrenzt ist (ulimit -Hs). Auf jeden Fall sollte betont werden, dass Stapel und Haufen in entgegengesetzte Richtungen wachsen.
Santosh
Wie unterscheidet sich dann "automatische Erweiterung" von "Elemente auf den Stapel schieben"? Aus Ihrer Erklärung habe ich das Gefühl, dass sie gleich sind. Außerdem hatte ich das Gefühl, dass die Startpunkte von Stack und Heap weit über 8 MB liegen, sodass der Stack beliebig wachsen kann (oder auf den Heap trifft). Ist das wahr? Wenn ja, wie hat das Betriebssystem entschieden, wo der Heap und der Stack platziert werden sollen?
laut und klar
Sie sind die gleichen, aber Stapel haben keine feste Größe, es sei denn, Sie begrenzen die Größe mit rlimit. Der Stapel wird am Ende des virtuellen Speicherbereichs abgelegt, und der Heap befindet sich unmittelbar nach dem Datensegment der ausführbaren Datei.
Santosh
Ich verstehe, danke. Ich glaube jedoch nicht, dass ich den Teil "Stapel haben keine feste Größe" bekomme. Wenn dies der Fall ist, wozu dient das 8-MB-Softlimit?
laut und klar
8

Der genaue Mechanismus ist hier unter Linux angegeben: Wenn Sie einen Seitenfehler bei anonymen Zuordnungen behandeln , überprüfen Sie , ob es sich um eine "gewachsene Do-Zuweisung" handelt , die Sie wie einen Stapel erweitern sollten. Wenn der VM-Bereichsdatensatz angibt, dass Sie sollten, passen Sie die Startadresse an, um den Stapel zu erweitern.

Wenn ein Seitenfehler auftritt, kann er abhängig von der Adresse über die Stapelerweiterung behoben (und der Fehler gelöscht) werden. Dieses Verhalten für den virtuellen Speicher, das bei einem Fehler nach unten wächst, kann von beliebigen Benutzerprogrammen angefordert werden, wobei das MAP_GROWSDOWNFlag an den mmapSystemaufruf übergeben wird.

Mit diesem Mechanismus können Sie auch in einem Anwenderprogramm herumspielen:

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

Wenn Sie dazu aufgefordert werden, finden Sie die PID des Programms (über ps) und sehen /proc/$THAT_PID/maps, wie der ursprüngliche Bereich gewachsen ist.

cdleary
quelle
Ist es in Ordnung, munmap für das ursprüngliche mem und page_size aufzurufen, auch wenn die Speicherregion über MAP_GROWSDOWN gewachsen ist? Ich nehme ja an, weil es sonst eine sehr komplexe API zu verwenden wäre, aber die Dokumentation sagt nichts explizit in dieser Angelegenheit aus
i.petruk
2
MAP_GROWSDOWN sollte nicht verwendet werden und wurde aus glibc entfernt (siehe lwn.net/Articles/294001 für den Grund).
Collin