Minix 3: Gerenciamento de Memória

Entendendo o funcionamento do gerenciador de memórias do sistema operacional Minix.

Introdução

O Minix é um sistema operacional derivado da família UNIX, com código de fonte aberta, feito exclusivamente para fins educativos, desenvolvido em 1987 por Andrew S. Tanenbaum. Neste sistema operacional, o gerenciamento de memória é diferente dos demais SOs, uma vez que ele não se utiliza de paginação ou swapping. Além do fato que, o gerenciamento de memória é mesclado com o gerenciamento de processos.
Utiliza-se de um sistema de lacunas, onde quando ocorrem chamadas de sistema fork ou exec, a lista é percorrida até que seja encontrado uma primeira lacuna que couber e que seja suficientemente grande, também conhecida como first-fit.
Não ocorrer a paginação e nem o swapping é bem incomum, no entanto, tem certas explicações para tal decisão, como: sistema simples e fácil compreensão, facilidade em portar o sistema Minix 3 para qualquer outro hardware e também porque foi projetada para uma IBM PC que nem continha uma MMU (Memory Manager Unit), impossibilitando de vez a paginação. Levando o Minix a ser usado em unidades de sistemas embarcados, como DVD Players, câmeras digitais e equipamentos de som e etc..

Gerenciamento de memória

Alocação dos espaços em memória

Os programas do MINIX 3 podem ser compilados para usar os espaços I (instruções-código) e D (dados e pilha) combinados, nos quais todas as partes do processo (texto, dados e pilha) compartilham um bloco de memória alocado e liberado como uma unidade. Entretanto, o padrão é compilar programas para usar espaços I e D separados. Os processos que utilizam os espaços I e D separados podem usar a memória mais eficientemente, mas tirar proveito desse recurso complica as coisas.
Os programas com espaços I e D separados tiram proveito de um modo de gerenciamento de memória melhorado, chamado texto compartilhado. Quando tal processo executa uma operação fork, é alocada apenas a quantidade de memória necessária para uma cópia dos dados e da pilha do novo processo. Tanto o pai como o filho compartilham o código executável que já está sendo usado pelo pai. Além disso, quando um novo processo utiliza-se da operação exec, será pesquisado se existe outro processo com um código compartilhável, se sim é apenas alocado seus dados e pilhas na memória.


Processos na memória

A estrutura de um processo contendo o endereço virtual, o endereço físico e o tamanho do segmento, são todos medidos em clicks, em vez de bytes. O tamanho de um click depende da implementação. Para o MINIX 3 ele tem 1024 bytes. Todos os segmentos devem começar em um limite de click e ocupar um número inteiro de clicks.
Na letra (b) da imagem abaixo vemos como são os campos de endereço virtual, físico e de comprimento de cada um dos três segmentos, supondo que o processo não tenha espaços I e D separados. Nesse modelo, o segmento de texto está sempre vazio e o segmento de dados contém tanto texto como dados. O endereço virtual em que a pilha começa depende inicialmente da quantidade total de memória alocada para o processo, e que o crescimento da pilha por um click reduziria a lacuna a nada, caso não houvesse nenhum aumento da alocação de memória total.
Assim no MINIX 3 o descritor de segmento de dados e o descritor de segmento de pilha são sempre idênticos. Os segmentos de dados e de pilha do MINIX 3 usam parte desse espaço e, assim, um deles ou ambos podem expandir-se na lacuna entre eles. A CPU não tem meios de detectar erros envolvendo a lacuna, pois no que diz respeito ao hardware, a lacuna é uma parte válida da área de dados e da área de pilha. É claro que o hardware pode detectar um erro muito grande, como uma tentativa de acessar memória fora da área de dados, lacuna e pilha combinadas. Isso protegerá um processo dos erros de outro processo, mas não é suficiente para proteger um processo de si mesmo.
A letra (c) da figura abaixo mostra as entradas de segmento para o layout de memória da letra (a), para espaços I e D separados. Aqui, os segmentos de texto e de dados têm tamanho diferente de zero. O array mp_seg mostrado na (b) ou (c) é usado principalmente para fazer o mapeamento de endereços virtuais em endereços de memória físicos. Dado um endereço virtual e o espaço ao qual ele pertence, é simples ver se o endereço virtual é válido ou se cai dentro de um segmento e, se for válido, qual é o endereço físico correspondente. A função de núcleo umap_local realiza esse mapeamento para as tarefas de E/S e de cópia em espaço de usuário (e dele), por exemplo.


Se o processo usa espaços I e D separados, é feita uma pesquisa dos campos mp_dev, mp_ino e mp_ctime em cada entrada de mproc.
Além das informações de segmento, mproc também contém informações adicionais sobre o processo. Isso inclui a ID do processo (PID) em si e de seu pai, os UIDs e GIDs (reais e efetivos), informações sobre sinais e o status de saída, caso o processo já tenha terminado, mas seu pai ainda não executou uma operação wait para ele.


Lacunas

Os espaços entre os segmentos de dados e de pilha não são considerados lacunas; eles já foram alocados para processos. Conseqüentemente, eles não estão contidos na lista de lacunas livres. Cada entrada da lista de lacunas tem três campos: o endereço de base da lacuna, em clicks; o tamanho da lacuna, também em clicks; e um ponteiro para a próxima entrada na lista. A lista é de encadeada simples, de modo que é fácil encontrar a próxima lacuna a partir de qualquer lacuna dada, mas para encontrar a lacuna anterior, você precisa pesquisar a lista inteira desde o início, até chegar à lacuna dada. As principais operações na lista de lacunas são a alocação de uma parte da memória de determinado tamanho e o retorno de uma alocação existente.

Quando um processo termina sua limpeza, sua memória de dados e de pilha é retornada para a lista de lacunas livres. Se ele utiliza I e D combinados, isso libera toda a sua memória, pois esses programas nunca têm uma alocação de memória separada para texto.
Se o programa usa I e D separados, e uma pesquisa da tabela de processos identificar que nenhum outro processo está compartilhando o texto, a área de memória do texto também será liberada. Uma vez que, com texto compartilhado, as regiões de texto e dados não são necessariamente adjacentes, duas regiões de memória podem ser liberadas. Para cada região retornada, se um dos vizinhos dessa região, ou ambos, forem lacunas, eles serão fusionados, de modo que nunca ocorrem lacunas adjacentes. Assim, o número, a localização e os tamanhos das lacunas variam continuamente durante a operação do sistema.

Autor

Gulliver Tadra Waldmann
Engenharia de Computação