Como um kernel é notificado quando um processo precisa de novas páginas para quadros de pilha?

2

Eu entendo que uma MMU em um chip usa endereços virtuais e os converte em endereços de memória física. A MMU faz o seguinte:

(1) Consulta a tabela de páginas específica do processo

(2) Se a página correspondente ao endereço virtual estiver no conjunto residente, então o endereço será convertido em um endereço físico

(3) Se a página correspondente ao endereço virtual NÃO estiver no conjunto residente, então gera uma falha de página a ser tratada pelo Kernel

Agora, entendo que a criação e a exclusão de páginas em várias seções de um processo exigem chamadas do sistema, como brk() , sbrk() , mmap() e munmap() . Portanto, o Kernel sempre terá a oportunidade de atualizar a tabela de páginas do processo sempre que essas chamadas forem feitas.

No entanto, um processo em execução pode aumentar a área da pilha solicitando que o ponteiro de pilha %rsp seja reduzido em 10.000, o que pode exigir várias páginas de alocação para acomodar o aumento na profundidade da pilha.

Se meu entendimento de MMU estiver correto acima, então no caso de %rsp mudar, a MMU não gerará uma falha de página (porque o endereço não está na tabela de processo para começar). O que a MMU faz nessa situação para notificar o Kernel?

    
por sshekhar1980 28.06.2017 / 19:51

1 resposta

1

Acessos para páginas não mapeadas causam falha. O que mais você acha que eles poderiam fazer?

Eu achei que Ulrich removeu MAP_GROWSDOWN da glibc. Assim, a questão específica é provavelmente apenas de interesse acadêmico.

EDIT: Acontece que minha resposta abaixo estava bastante desatualizada. Provavelmente, é melhor começar lendo o LWN mais recente no StackSmash . Embora não explique tudo para mim.

Se você ler a documentação do kernel para MAP_GROWSDOWN , está documentado como tendo uma única página de guarda. Para que funcione de forma confiável, isso implica que o idioma deve implementar os probes de pilha, ou seja, garantir o toque incremental das páginas, para que a página de guarda seja sempre atingida primeiro. Portanto, a descrição nesta questão sobre a alocação de várias páginas de uma só vez parece confusa.

O LLVM no Linux não implementou probes de pilha - isso é um TODO para a linguagem Rust segura para a memória. Há menção que o GCC faz, mas somente se você passar -fstack-check . "Infelizmente, -fstack-check na verdade não é adequado para nossos propósitos" para várias razões .

link

A postagem de Ulrich é um pouco confusa v.s. a página man. A página man descreve uma página de guarda, o que normalmente significa um mapeamento PROT_NONE (sempre falha). No entanto, o post de Ulrich é formulado dizendo que MAP_GROWSDOWN não aloca uma página de guarda. A página onde a proteção seria simplesmente não é alocada, e o kernel detecta as falhas causadas por ele para manipulação especial. Isso cria o problema de que um mapeamento poderia ser facilmente alocado para o mesmo endereço da página de guarda inexistente, derrotando o sistema.

Eu poderia estar descrevendo isso de forma enganosa, pois não li o código, por favor, leia a postagem original.

link

+ Why: The two flags cannot be used securely because using them means that
+ collisions with other allocations cannot be avoided. Assume a stack
+ of size S is allocated using mmap with the MAP_GROWSDOWN flag set.
+ The address is determined by the kernel to be A. To make the
+ MAP_GROWSDOWN flag useful no guard page(s) can be allocated just
+ below the stack (i.e., just below address A). This means the
+ address space just below A is unallocated.
+
+ Now assume a second, unrelated mmap call allocates a block in the
+ range [B, B+T), B+T < A. Nothing will prevent the growing-down
+ stack from sooner or later reaching that block. At this point
+ the stack will overwrite the memory in that second block and vice
+ versa.
+
+ The only way to prevent this is to change _every_ allocation via
+ mmap to include guard pages at either end. This is completely
+ inpractical, expensive, and not a full protection any way.

Fora de MAP_GROWSDOWN: a pilha é MAP_ANONYMOUS, leia a manpage amigável para mmap() . A pilha inicial mapeada pelo kernel tem uma página de guarda (real, mapeada PROT_NONE). EDIT: isso foi adicionado em 2010 . Pilhas para threads além do primeiro são configuradas inteiramente pelo userspace. Quando você toca em qualquer página PROT_NONE, o kernel entrega o SIGBUS, e o userspace pode manipulá-lo da maneira que ele gosta.

Nota: Mas como você pode executar um manipulador de sinal se você não tiver nenhuma pilha acessível? Resposta: você reserva um pouco com antecedência usando sigaltstack() .

(Eu estou supondo que o kernel não implementa MAP_GROWSDOWN na pilha inicialmente alocada, uma vez que Ulrich teria formulado isso de forma mais clara e urgente se esse fosse o caso).

    
por 28.06.2017 / 20:48