Gerenciamento de Memória no Linux

INSTITUTO TECNOLÓGICO DE AERONÁUTICA

Alunos:

Bruno César Alves
Pedro de Sousa Cau Ramos Salles
Marcus Leandro Rosa Santos

Professor: Yano
Curso: 2º ano de Computação

Descrição

Este relatório faz parte de um trabalho realizado na matéria CES-33 do curso de Computação do Instituto Tecnológico de Aeronáutica e tem o objetivo de responder algumas questões relativas ao gerenciamento de memória no Sistema Operacional Linux.
A memória do Linux é feita ultilizando paginação para armazenamento dos seus dado. Esta paginação não é pura, o Linux a organiza em três níveis, como mostra a figura a seguir:

fig1.JPG

Como funciona a TLB no Linux?

TLB é a abreviação de Translation Lookaside Buffer. É o que armazena algumas entradas para a tradução de endereços virtuais para endereços físicos. O Linux suporta os modelos R2000 e R4000 de TLB, que são usados pela grande maioria de sistemas capases de rodar Linux e estão descritos a seguir:
-R2000
Este TLB sempre consiste de 64 entradas TLB. Cada entrada TLB mapeia uma única pagina. As primeiras 8 entradas são conectadas entre si, ou seja, elas não são automaticamente substituídas através da instrução tlbwr. Cada entrada TLB somente mapeia uma pagina de tamanho 4kb. O flushing da TLB é otimizado por Tags de 6 bits chamados PID. Manipulação TLB é feita através de 4 instruções especiais: tlbp, tlbr, tlbwi e tlbwr.
-R4000
Este TLB foi implementado com um numero variável de entradas entre 32 e 64 sendo 48 provavelmente o valor mais comum. Cada entrada TLB pode ter seu próprio tamanho de pagina. Tipicamente o tamanho de uma pagina varia de 4kb a 16mb. Alguns CPUs suportam 64 MB ou até 256 mb de tamanho de pagina. No menor caso, alguns TLBs podem ate suportar paginas de 1kb. Cada entrada TLB mapeia um par adjacente de paginas. Como resultado, o endereço virtual de uma entrada TLB precisa ser alinhado a duas vezes o tamanho da pagina. Isso é uma restrição incomum e pode criar alguns problemas em sistemas operacionais que foram escritos sem considerar este fato. O registrador conectado permite trocar o numero de entradas conectadas. De novo, o flushing do TLB é otimizado por um Tag de 8 bits, que diferentemente do R2000, neste é chamado de ASID. Do mesmo modo que no R2000, a manipulação TLB é feita atravéz de 4 instruções especiais: tlbp, tlbr, tlbwi e tlbwr.

Tabela de páginas

É frequente a necessidade de uma memória maior do que a memória fisica disposta no computadores. Desde os primórdios da computação, diversas estratégias foram desenvolvidas e a memória virtual tem sido a mais bem sucedida. A memória virtual tem o objetivo de permitir o acesso indireto a memória física. Para organizar esse endereçamento com a memória virtual, o Kernel do Linux utiliza tabelas de páginas.
Durante a inicialização do Kernel, a função paging_int() é chamada, está função chama a função page_table_init() para inicializar a tabela de páginas. Durante a inicialização da tabela de páginas, são definidas todas suas propriedades (tamanho, directory, middle directory etc).
Considerando que na forma padrão, os 3 primeiros Gigas da memória física são alocados para o usuário e o último Giga para o Kernel. A inicialização da tabela de páginas define sua posição em 0x1000 acima do root, isto é 0xC0001000 ( 3G + 4 K).
O acesso a tabela de páginas é feito através de um endereço linear que permite uma navegação até a entrada desejada na tabela de páginas. A figura mostrada acima mostra como é feita a interpretação de um endereço linear.

Criação de processos

Os processos no Linux podem ser criados a partir da função fork() ou exec(), essas funções gerenciam a criação de um novo processo, definindo seu ID e associando seu endereço na memória física à memória virtual criando uma tabela de páginas correspondente.

Troca de contexto de processos

No Linux cada processo é formado por estrutura de dados chamada task_struct. A troca de contexto de processos no Linux é feita baseada em seu escalonador de processos ou em alguma interrupção, seja ela por hardware ou por software. No momento em que o processo é solicitado como novo processo atual, o escalonador salva o task_struct do processo atual e aponta o ponteiro de tarefas para o task_struct do novo processo.

Page-fault

Quando ocorre um page-fault, o page-fault handler do Linux bloqueia outras threads através do semáforo mmap_sem para que outras threads não continuem gerando page-faults. Em seguida, seleciona uma entrada para ser excluida da tabela de páginas do processo, sobrescreve seu conteúdo com zeros e finalmente cria uma nova entrada com a pagina desejada na tabela de páginas.

Remoção de processo

Linux usa o algoritmo LRU (Least Recently Used) para a seleção de paginas a serem removidas do sistema. Nesse esquema , cada pagina possui no sistema uma idade que varia quando a pagina é acessada. Quanto mais acessada mais nova. E quanto menos acessada mais velhas vão ficando e se tornando candidatas ao swapping.

Compartilhamento de memória

O compartilhamento de memória É a forma mais rápida de comunicação inter-processos (IPC). Pode ser implementada através da criação de uma área reservada na memória onde mais de um processo tem acesso à mesma posição de memória e podem enxergar qualquer alteração realizada.
No Linux, as regiões de memória compartilhada são armazenadas no Region Table. O Linux possui um vetor (Shared Memory Table) em que cada entrada possui informação referente ao nome, permissões e tamanho da região correspondente, assim como o apontador para a Region Table.
No Linux há os seguintes comandos relativos à memória compartilhada:
• shmget
– Cria (ou usa) um segmento de memória compartilhada.
– O retorno da chamada é o identificador da área de memória ou –1 em caso de erro.
• shmat
– Liga o processo a área de memória compartilhada existente.
– O retorno da chamada é –1 em caso de erro.
• shmdt
– Permite desligar uma região de memória compartilhada do espaço de endereçamento virtual do processo invocador.
• shmctl
– Oferece operações de controle.

Problemas da memória compartilhada:

• Os blocos devem ser liberados quando não forem mais necessários.
• O fim do processo não libera o bloco de memória.
• É necessário garantir a sincronização no acesso à região de memória compartilhada (utilizar semáforos).

Mapeamento de arquivos na memória virtual

Para evitar o acesso lento a um arquivo no Hard Disk, o Linux cria um mapeamento do arquivo em uso na memória virtual. Esse mapeamento é feito, de forma que, supondo que o arquivo esteja localizado na posição x da memória, o acesso à uma posição y do arquivo é feito pela posição x+y na memória virtual. Desta forma, o arquivo é visto como uma pagina qualquer na memória.

fig2.JPG

Quando uma procura por arquivo é feita na região de mapeamento, ocorre um page-fault e o arquivo é trazido a memória. Quando ao uso do arquivo é finalizado, ele é retirado da memória e salvo no HardDisk.

Tratamento de áreas de memória fixas

O procedimento básico do gerenciamento de memória é dividir esta em varias partições de tamanho fixo onde cada partição acomoda somente um programa em execução. O programa residido na memória será limitado pelo numero de partições. Quando o programa terminar, esta partição ficará livre para outro programa na fila.
O Linux possui duas estratégias para decidir como alocar as partições livres para novos processos:
-First Fit: Este procedimento aloca a primeira partição livre que acomode o processo.
-Best Fit: Este procedimento aloca a menor partição livre que supre os requerimentos do processo.
Ambas estratégias requerem uma Partition Description Table para encontrar as partições livres. Porém, a first fit termina após encontrar a primeira partição, enquanto o Best fit continua procurando por um tamanho quase exato. Como resultado, o first fit executa mais rápido enquanto o Best fit consegue uma melhor utilização da memória.

Segurança no Linux

No Linux, o esquema de proteção é feito dividindo o sistema em duas áreas: área de kernel e área de usuário. Dentro desse esquema, um processo do usuário nunca tem permissão para acessar memória que não pertença a ele e isso é forçado no próprio hardware. Porém, se o processo tem interesse em acessar endereços com código do kernel, ele deve fazer isso indiretamente provocando uma interrupção. O Kernel examina a interrupção e toma a ação apropriada. Desta forma, é possível que o processo acesse a memória do Kernel, mas com limitações e com a supervisão dele. Quando o processo tenta acessar uma área da memória sem permissão, o Kernel responde com um segmentation fault.

fig3.JPG

Área de swap

O Linux tem suporte a memória virtual, isto é, ele utiliza o disco como extensão da memória RAM, fazendo com que o tamanho de memória disponível cresça consideravelmente. A parte do disco que é usada como memória virtual é chamada área de troca ou área de swap. O Linux pode usar tanto um arquivo normal de um sistema de arquivos quanto uma partição separada para área de troca.
Usando a área de troca
Uma área de troca é ativada através do comando swapon. Este comando diz ao kernel que a área de troca pode ser usada. O caminho para a área de troca é dado como um argumento:

  1. swapon /extra-swap

Áreas de troca podem ser usadas automaticamente ao serem listadas no arquivo /etc/fstab:
/dev/hdb6 swap swap defaults 0 0
O script de inicialização /etc/rc.d/rc.sysinit executará o comando swapon -a, inicializando as área de troca listadas neste arquivo.
O tamanho da área de swap costuma ser o dobro da quantidade de memória RAM do computador. Por exemplo, se o computador possui 1 gb de memória RAM, a área de swap deve ser de 2gb.

Testando o limite do Linux

Para testar o Linux, foi criado um programa que realiza o seu travamento criando um excesso de processos em sua mémoria. Segue o código utilizado:

Criando processos sem fim:

#include <Stdio.h>
#include <string.h>

int i;

int main()
{
File *file;
int fd[2];
pipe(fd);
while(1)
{
file = fopen("processos","a");
if (fork () == 0)
{
fprintf(file,"Processo ID:%d\n",getpid());
}else{
fprintf(file,"Processo ID:%d\n",getpid());
}
sleep(4)
sclose(file);
}
return 0;
}

Foram criados 3312 processos antes que o sistema travasse.

Criando variaveis locais sem fim:

#include <Stdio.h>
#include <string.h>

void estourar_pilha()
{
estourar_pilha();
}

int main()
{
estourar_pilha();
return 0;
}

Referências utilizadas

http://www.bala-krishna.com/memory-management-with-fixed-partitions-mft
http://linux.about.com/od/lsa_guide/a/gdelsa41.htm
http://www2.fct.unesp.br/posti/download/solinux2.pdf
http://jno.glas.net/data/prog_books/lin_kern_2.6/0596005652/understandlk-CHP-2-SECT-5.html
http://linux-mm.org/
http://www.ibm.com/developerworks/linux/library/l-linux-process-management/
http://oss.sgi.com/projects/page_fault_performance/
http://www.cc.gatech.edu/classes/AY2001/cs3210_fall/labs/syscalls.html/
http://www.vivaolinux.com.br/artigo/Funcionamento-da-memoria/
http://jno.glas.net/data/prog_books/lin_kern_2.6/0596005652/understandlk-CHP-2-SECT-5.html
http://linux-mm.org/
http://www.ibm.com/developerworks/linux/library/l-linux-process-management/
http://oss.sgi.com/projects/page_fault_performance/
http://www.cc.gatech.edu/classes/AY2001/cs3210_fall/labs/syscalls.html/
http://www.vivaolinux.com.br/artigo/Funcionamento-da-memoria/
http://lzanuz.sites.uol.com.br/processos.htm

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License