Sunday, July 12, 2009

Gerenciamento de Memória no Linux

(Créditos para Gustavo Pinheiro e Leandro Lima)
  • Características gerais
Assim como todos sistemas Unix recentes, Linux oferece uma abstração para gerenciamento de memória chamada memória virtual (virtual memory). Tal camada permite que muitos processos sejam executados simultaneamente, aplicações sejam executadas mesmo se necessitarem de quantidade de memória física superior à existente, entre outras vantagens.

Em geral, um esquema que mistura paginação e segmentação é utilizado para contruir o esquema de memória virtual. Entretanto, o Linux utiliza segmentação de forma extremamente limitada. Linux prefere paginação à segmentação por motivos de portabilidade e para tornar o gerenciamento de memória mais simples. Na versão 2.4, por exemplo, segmentação é utilizada apenas quando exigida pela arquitetura 80x86.

Como a maioria dos processos utilizam apenas uma pequena porção do espaço total de endereço virtual, Linux utiliza uma estrutra hierárquica para a tabela de páginas, constituída por três níveis. Isto permite que as subárvores correspondentes a regiões não utilizadas do espaço de endereço estejam ausentes, economizando espaço.

Proteção é garantida já que cada processo no sistema tem seu próprio espaço de endereço virutal. Estes estão separados de modo que um processo executando determinada apliacação não pode afetar um outro. Além disso, mecanismos de hardware permitem que determinadas regiões de memória estejam protegidas contra escrita. Isso evita que código e dado sejam sobrescritos por aplicações maliciosas.

  • Funcionamento do Translation Lookaside Buffer (TLB)
TLBs são utilizados para acelerar o processo de tradução de endereços virtual para o físico correpsondende. Quando um endereço virtual é utilizado pela primeira vez, o endereço físico correspondente é computado através de acessos lentos a tabela de páginas. O endereço físico encontrado é então armazenado em uma entrada TLB para que futuras referêcias àquele endereço virtual tenham tradução rápida.
Assim, cada vez que uma referência a um endereço virtual é feita, o processador tentará encontrar uma entrada correspondente no cache (TLB). Se não encontrar na TLB, deve sinalizar para o Sistema Operacional que uma TLB miss ocorreu. O SO então gera uma nova entrada TLB. Quando a exceção for eliminada, o processador tentará mais uma vez buscar o endereço na TLB, desta vez com sucesso. A desvantagem deste esquema é que tempo e espaço são gastos para manter tal cache.

  • Tabelas de páginas (page tables)
As tabelas de páginas são as estruturas de dados que mapeiam endereços virtuais para endereços físicos e se encontram na mémoria principal (main memory). Devem ser apropriadamente inicializadas pelo kernel antes que a unidade de paginação seja iniciada.
O acesso à tabela de páginas é realizado em três passos, o que corresponde ao esquema de três níveis adotado em Linux. As três tabelas são: Page Global Directory, Page Middle Directory e Page Table. O objetivo desse esquema é reduzir a quantidade de RAM necessária por tabela de página por processo. Ele reduz a memória necessária já que requer Page Tables apenas para as regiões da memória virtual de fato utilizadas por um processo.

  • Remoção de Páginas no Linux
O mecanismo de swappping é extremamente valioso por permitir a expansão do espaço de endereços (address space) utilizável por um processo, assim como a quantidade de RAM necessária para carregar determinada aplicação. Para que seja efetivamente bem sucedida, faz-se necessário eficientes algoritmos para a remoção de páginas, permitindo que o swap ocorra.

Vários kernels têm utilizado algoritmos Least Recently Used (LRU). A prinicpal idéia é utilizar um contador que armazena a idade da página junto de cada página na RAM (isto é, o intervalo de tempo desde o último acesso à referida página). A página mais velha pode então ser removida.
  • Interfaces para gerenciamento de memória
Em Linux, processos são criados através das system calls clone(), fork() e vfork(). Esta última cria um processo que compartilha o espaço de endereços do procesos pai. A flag CLONE_VM permite demandar que o processo filho compartilhe o memory descriptor e todas tabelas de páginas.
Quando da criação de um novo processo, o kernel invoca copy_mm() para criar o espaço de endereços do processo e settar todas tabelas de páginas e memory descriptor do novo processo.
Quando um processo termina, o kernel invoca exit_mm() para liberar o espaço de endereços daquele processo.
Note ainda que um processo pode criar um novo mapeamento de memória através da chamada mmap().

  • Compartilhamento de Memória no Linux
Um dos mecanismos mais úteis para intercomunicação de processos é o compartilhamento de memória, o qual permite que processos acessem estruturas de dados comuns em uma região de memória compartilhada. Cada processo que desejar acessar as estruturas de dados presentes em uma região de compartilhamento deve adicionar ao seu próprio espaço de endereços uma nova região de memória que mapeia as páginas associadas à região compartilhada. Tais page frames podem então ser facilmente manipuladas pelo kernel através do mecanismo de paginação por demanda.
  • Mapeamento de arquivos na memória virtual
Através deste mapeamento, arquivos são carregados espaço de endereços de um processo. À medida que necessário, as partes dos arquivos são carregadas em memória. O kernel fica então responsável por traduzir o acesso aos bytes dentro de uma página em uma operação no arquivo correspondente. As estruturas de dados utilizadas para tal procedimento são:
  • inode associado com o arquivo mapeado
  • address_space
  • file object para cada mapeamento feito por um processo diferente
  • vm_area_struct
  • page descriptor
  • Tratamento de áreas de memória fixas
Pelo menos 128MB de endereços virtuais são sempre deixados disponíveis pois o kernel utiliza tal espaço para implementar alocação de memória não-contígua e área de memória de mapeamento fixos. Basicamente, um área de memória de mapeamento fixo é um endereço virtual cosnte como 0xfffff0 cujo endereço físico correspondente pode ser settado de forma arbitrária. Assim, cada endereço fixo mapeia um frame de memória física. Com relação a ponteiros de variáveis, estes endereços são mais eficientes. Basta notar que para dereferenciar um ponteiro de varíavel requer um acesso de memória a mais que dereferenciar um endereço constante imediato.

  • Segurança
Tradicionalmente, sistemas Unix associam credenciais a cada processo, as quais ligam o refererido processo a um usuário específico de determinado user group. Tais credenciais são importantes em sistemas multi-usuários pois determinam o que cada processo pode ou não fazer, preservando portanto a integridade dos arquivos de um usuário e a estabilidade do sistema como um todo.
O uso desste mecanismo requer suporte tanto nas estruturas de dados do processo assim como naquilo que está sendo protegido, por exemplo, arquivos. Assim, no sistema de arquivos Ext2, cada arquivo pertence a um usuário específico, o qual decide que tipo de operações são permitidas naquele arquivo. Quando um processo tenta acessar um arquivo, o VFS sempre checa se tal acesso é permitido, de acordo com as permissões estabelecidadas pelo dono do usuário e credenciais do processo.
  • Área de swap
As páginas removidas de memória são armazenadas na área de swap, a qual pode ser implementada tanto como uma partição de disco própria ou como um arquivo em uma partição maior. Muitas áreas de swap podem ser definidas, até um um número máximo especificado por
MAX_SWAPFILES (normalmente 32).
As informações contidas em uma área de swap são úteis enquanto o sistema estiver ligado. Quando este for desligado, todos os processos são destrúidos, então toda informação armazena nas áreas de swap pelos processos é descartada.
O tamanho máximo de uma área de swap é determinado pelo número de bits disponíveis para identificar um slot de página. Na arquitetura 80x86, 24 bits estão disponíveis portanto o limite para o tamanho da área de swap é 224 slots (64 GB).

  • Experimentos
A fim de testar os limites do sistema, alguns experimentos foram realizados. Utilizamos o MacOSX, o qual utiliza o kernel Darwin (compatível com a Single UNIX Specification). Utilizando o comando ulimit -a , o sistema informa que o número máximo de processos de usuário possível é 266. Um simples programa que chama fork() continuamente foi executado, e tal valor foi confirmado.
Para testar o máximo tamananho de área heap, podemos executar um programa que continuamente pede alocação de memória para um inteiro, por exemplo.
O tamanho máximo de pilha é 8MB. Como mencionado anteriormente, existe um tamanho máximo para a área de swap, portanto ela pode ser esgotada.

  • Referencias
Understand the Linux Kernel. By Daniel P. Bovet, Marco Cesati

No comments:

Post a Comment