O que é "expansão automática de pilha"?

13

O getrlimit (2) tem a seguinte definição nas páginas do manual:

RLIMIT_AS O tamanho máximo da memória virtual do processo (espaço de endereço) em bytes. Esse limite afeta as chamadas para brk (2), mmap (2) e mremap (2), que falham com o erro ENOMEM ao exceder esse limite. A expansão automática da pilha também falhará (e gerará um SIGSEGV que interrompe o processo se nenhuma pilha alternativa tiver sido disponibilizada via sigaltstack (2)). Como o valor é longo, em máquinas com comprimento de 32 bits, esse limite é de no máximo 2 GiB ou esse recurso é ilimitado.

O que se entende por "expansão automática da pilha" aqui? A pilha em um ambiente Linux / UNIX cresce conforme necessário? Se sim, qual é o mecanismo exato?

alto e claro
fonte

Respostas:

1

Sim, as pilhas crescem dinamicamente. A pilha está no topo da memória crescendo para baixo em direção ao heap.

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

A pilha cresce para cima (sempre que você faz malloc) e a pilha cresce para baixo à medida que novas funções são chamadas. A pilha está presente logo acima da seção BSS do programa. O que significa que o tamanho do seu programa e a maneira como ele aloca memória na pilha também afetam o tamanho máximo da pilha para esse processo. Normalmente, o tamanho da pilha é ilimitado (até as áreas de pilha e pilha se encontrarem e / ou sobrescreverem, o que causará um estouro de pilha e SIGSEGV :-)

Isso é apenas para os processos do usuário. A pilha do kernel é corrigida sempre (geralmente 8 KB)

Santosh
fonte
"Normalmente, o tamanho da pilha é ilimitado", não, geralmente é limitado por 8Mb ( ulimit -s).
Eddy_Em
Sim, você está certo na maioria dos sistemas. Você está verificando o comando ulimit do shell, se houver um limite rígido no tamanho da pilha, que é ilimitado (ulimit -Hs). Enfim, esse ponto era enfatizar que a pilha e a pilha crescem nas direções opostas.
Santosh
Então, como "expansão automática" é diferente de "empurrar elementos para a pilha"? Pela sua explicação, sinto que eles são os mesmos. Além disso, eu senti que os pontos iniciais da pilha e da pilha são muito superiores a 8 MB, portanto a pilha pode crescer o quanto for necessário (ou atinge a pilha). Isso é verdade? Se sim, como o sistema operacional decidiu onde colocar a pilha e a pilha?
loudandclear
Eles são iguais, mas as pilhas não têm um tamanho fixo, a menos que você limite o tamanho usando rlimit. A pilha é colocada no final da área de memória virtual e a pilha é imediatamente após o segmento de dados do executável.
Santosh 05/02
Eu entendo obrigado. No entanto, acho que não recebo a parte "pilhas não têm tamanho fixo". Se for esse o caso, para que serve o limite flexível de 8 Mb?
loudandclear
8

O mecanismo exato é fornecido aqui, no Linux: ao lidar com uma falha de página em mapeamentos anônimos, você verifica se é uma "alocação crescente" que você deve expandir como uma pilha. Se o registro da área da VM disser que você deveria, ajuste o endereço de início para expandir a pilha.

Quando ocorre uma falha na página, dependendo do endereço, ela pode ser reparada (e a falha anulada) via expansão da pilha. Esse comportamento "crescente em caso de falha" da memória virtual pode ser solicitado por programas arbitrários do usuário, com o MAP_GROWSDOWNsinalizador sendo passado para o mmapsyscall.

Você também pode mexer com esse mecanismo em um programa do usuário:

#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 solicitado, você encontra o pid do programa (via ps) e /proc/$THAT_PID/mapsveja como a área original cresceu.

cdleary
fonte
Posso chamar munmap para o mem e o tamanho da página originais, mesmo que a região da memória tenha crescido via MAP_GROWSDOWN? Eu presumo que sim, porque caso contrário, seria uma API muito complexo para uso, mas a documentação não diz nada explicitamente sobre este assunto
i.petruk
2
MAP_GROWSDOWN não deve ser usado e foi removido da glibc (consulte lwn.net/Articles/294001 para saber o porquê).
Collin