Qu'est-ce que «l'expansion automatique de la pile»?

13

getrlimit (2) a la définition suivante dans les pages de manuel:

RLIMIT_AS La taille maximale de la mémoire virtuelle du processus (espace d'adressage) en octets. Cette limite affecte les appels à brk (2), mmap (2) et mremap (2), qui échouent avec l'erreur ENOMEM lors du dépassement de cette limite. L' expansion automatique de la pile échouera également (et générera un SIGSEGV qui tue le processus si aucune autre pile n'a été mise à disposition via sigaltstack (2)). Étant donné que la valeur est longue, sur les machines de 32 bits, cette limite est au maximum de 2 Gio ou cette ressource est illimitée.

Qu'entend-on ici par "expansion automatique de la pile"? La pile dans un environnement Linux / UNIX croît-elle au besoin? Si oui, quel est le mécanisme exact?

fort et clair
la source

Réponses:

1

Oui, les piles se développent dynamiquement. La pile se trouve en haut de la mémoire et descend vers le tas.

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

Le tas se développe vers le haut (chaque fois que vous faites malloc) et la pile se développe vers le bas au fur et à mesure que de nouvelles fonctions sont appelées. Le tas est présent juste au-dessus de la section BSS du programme. Ce qui signifie que la taille de votre programme et la façon dont il alloue de la mémoire en tas affectent également la taille maximale de la pile pour ce processus. Habituellement, la taille de la pile est illimitée (jusqu'à ce que les zones de tas et de pile se rencontrent et / ou écrasent, ce qui donnera un débordement de pile et SIGSEGV :-)

Ceci est uniquement pour les processus utilisateur, la pile du noyau est toujours fixe (généralement 8 Ko)

Santosh
la source
"Habituellement, la taille de la pile est illimitée", non, généralement elle est limitée à 8 Mo ( ulimit -s).
Eddy_Em
oui, vous avez raison dans la plupart des systèmes. Vous vérifiez la commande ulimit du shell, si c'est le cas, la taille de la pile est illimitée (ulimit -Hs). Quoi qu'il en soit, ce point était de souligner que la pile et le tas poussent dans des directions opposées.
Santosh
Alors, en quoi "l'expansion automatique" est-elle différente de "pousser des éléments vers la pile"? D'après votre explication, j'ai l'impression que ce sont les mêmes. De plus, j'avais l'impression que les points de départ de la pile et du tas sont bien plus de 8 Mo, donc la pile peut augmenter autant qu'elle a besoin (ou elle frappe le tas). Est-ce vrai? Si oui, comment le système d'exploitation a-t-il décidé où placer le tas et la pile?
loudandclear
Ce sont les mêmes, mais les piles n'ont pas de taille fixe, sauf si vous limitez fortement la taille en utilisant rlimit. La pile est placée à la fin de la zone de mémoire virtuelle et le tas se trouve immédiatement après le segment de données de l'exécutable.
Santosh
Je comprends, merci. Cependant, je ne pense pas avoir la partie "les piles n'ont pas de taille fixe". Si tel est le cas, à quoi sert la limite logicielle de 8 Mo?
loudandclear
8

Le mécanisme exact est donné ici, sous Linux: lors de la gestion d'un défaut de page sur des mappages anonymes, vous vérifiez si c'est une "allocation de développement" que vous devez développer comme une pile. Si l'enregistrement de zone VM indique que vous devez, vous ajustez l'adresse de début pour étendre la pile.

Lorsqu'un défaut de page se produit, en fonction de l'adresse, il peut être réparé (et le défaut annulé) via l'extension de la pile. Ce comportement "croissant vers le bas en cas d'erreur" pour la mémoire virtuelle peut être demandé par des programmes utilisateur arbitraires avec l' MAP_GROWSDOWNindicateur transmis à l' mmapappel système.

Vous pouvez également jouer avec ce mécanisme dans un programme utilisateur:

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

Quand il vous invite, vous trouverez le pid du programme (via ps) et regardez /proc/$THAT_PID/mapspour voir comment la zone d'origine a augmenté.

cdleary
la source
Est-il correct d'appeler munmap pour le mem et la page_size d'origine même si la région mémoire a augmenté via MAP_GROWSDOWN? Je suppose que oui, car sinon ce serait une API très complexe à utiliser, mais la documentation ne dit rien explicitement à ce sujet
i.petruk
2
MAP_GROWSDOWN ne doit pas être utilisé et a été supprimé de la glibc (voir pourquoi lwn.net/Articles/294001 ).
Collin