Memória Virtual
Vantagens:
- Executar programas maiores que o tamanho da memória.
- Executar programas carregados parcialmente.
- Permitir mais do que um programa ao mesmo tempo.
- Permitir código independente de posição.
- Libertar programadores da alocação de memória.
- Permitir partilha
- Originalmente segmentos de 64KB(+64KB):
- programadores faziam "software overlay";
- Swapping usado para libertar espaço;
- Paginação sobre demanda: 3BSD no VAX 11 (1978);
- Nova versão em SunOS 4 influenciou o desenvolvimento de
paginação para SVR4 e Solaris;
- Paginador para Mach foi eventualmente adaptado para BSD4.4.
Precisamos de:
- Gestão do espaço do endereços do processo;
- Tradução de endereços (via MMU e PF);
- Gestão optimizada da memória física;
- Proteção de memória:
- processos não podem ler páginas do kernel ou de outros
processos;
- Partilha de Memória:
- partilha de parte do espaço de enderaçamento;
- partilha de frames (eg, depois de fork()).
- Reagir bem a cargas elevadas.
Precisamos de
- swap-map para descrever a posição de páginas swapped;
- Não é preciso guardar páginas de texto.
Precisamos de vários mapas:
- Traduções de endereços em HW: podem ser TLBs e/ou tabelas de
páginas, depende do HW mas gerido por SO;
- Mapa de espaços de endereços: usado num PF para verificar se a
página é válida e carregar uma tradução de HW;
- Mapa da memória física: eg, para remover páginas;
- Mapa da área de backup, que pode ser o arquivo ou swap.
Várias técnicas são possíveis:
- Ideal: página morta, que nunca mais será usada, eg, de
processo terminado;
- Local: cada processo tem um núm de páginas;
- Global: olhar para sistema;
- Working Set: páginas que vão ser precisas no futuro
próximo;
- Localidade de Referências: conj. de pág. mais
importantes muda devagar;
- LRU: libertar a página acedida à mais tempo.
Unidade de Gestão de Memória (MMU):
- Tradução de endereços virtuais;
- Tabelas de páginas:
- Uma para endereços de kernel;
- Uma ou mais para cada processo;
- Entrada (PTE) com 32 bits e incluem núm. de frame,
info. de protecção, se válida, se modificada, se
referida;
- MMU usa apenas as tabelas activas;
- Erros de tradução geram "Page Fault" (PF):
- Endereço fora de limites;
- Página inválida;
- Erro de Protecção
- Problema do tamanho: 512 mil entradas para 2GB e pág. de 4KB:
- Context Switch.
Cada acesso a memória obrigaria a acedar PT:
- Adicionar uma cache rápida procurada antes da PT (L0):
- Endereço físico;
- Endereço virtual (HP).
- Cache associativa com as traduções mais recentes, TLB:
- controlada pela MMU;
- SO tem que manter coerência se mudar PT;
- Alterações podem ser explicitas ou side-effect de instruções;
- Alguns sistemas só usam TLB.
- Suporte para paginação e segmentação;
- 32 bits: 4GB de endereços;
- Paginação pode ser desabilitada usando CR0;
- Até 8K segmentos: segment descriptor descreve o segmento
que é visto pela LDT do processo mais GDT global;
- Unix só usa segmentação para proteção de memória, entrada no
kernel, e mudança de contexto:
- Todos segmentos do usuário têm base 0 e tamanho grande;
- seg. especiais: call gate para entrada no kernel e task
state segment para contexto.
- Dois níveis de páginas;
- Directório de páginas: contém PTEs que mapeiam as próprias
páginas (1024 de 4B);
- CR3 ou PDBR armazena endereço físico de dir;
- Cada PTE contém pág física, RW, e se válido,
referido e modificado;
- Escrita em CR3 faz reset da TLB;
- 4 níveis de privilégio.
- RS/6000 usa tabela de páginas invertida:
- Entrada por frame;
- Hash é usada para encontrar página virtual;
- Compacto, mas lento;
- RS/6000 usa espaço de endereços de 52-bits;
- Processo tem 16 segmentos:
- kernel text;
- user text;
- dados privados;
- dados partilhados (7);
- dados de VM (2);
- texto partilhado;
- dados do kernel;
- I/0.
- 4 bits de cima são convertidos em 24 bits de selecção.
Baseado no 3BSD:
- Originalmente para VAX-11: emulação da arquitectura do VAX em
sofware;
- Estruturas:
- core map descreve memória física;
- page tables descrevem memória virtual;
- disk maps descrevem áreas de swap;
- proc e u-area.
Três áreas:
- Nonpaged pool: código do kernel e memória do kernel
alocada estáticamente;
- Error buffer: mensagens de erro em crash;
- Páginas de processos e páginas de memória dinâmica do kernel (nonpageable).
Core map sobre cada frame:
- Nome processo dono, tipo, e VPN:
- dono de pág de texto é uma estrutura texto;
- Lista de Páginas Livres
- Cache de Páginas de Texto
- Sincronização
Emula VAX-11:
- 4 áreas iguais,
- P0:
- texto e dados do processo;
- P1:
- pilha de kernel e usuário, u-area;
- S0:
- texto e dados do kernel;
- S1:
- não é usada.
- PTs com formato baseado no VAX:
- 1 de sistema para S0;
- cd processo tem mapa P0 e P1;
- Contíguas em memória virtual e alocadas por mapa em
Userptmap de S0;
- Mapa de recursos <base,size> descreve área que estão usadas
(first-fit);
- Swapper é usado para libertar entradas em Userptmap
- Partilha em P0 é possível em regiões alinhadas com
64KB, mas complicada: partilha explicita.
3 Estados:
- Residente:
- em memória física;
- Fill-On-Demand:
- ainda não foi referida, pode ser
fill-from-text ou zero-fill;
- Outswapped:
- recuperáveis de swap,
bits válido e fill-on-demand a 0, e PFN a 0.
Partições cruas, sem filesystem
- Várias partições: usa interleaving para melhorar performance;
- Alocação de swap apenas para páginas que vão ser enviadas: reduz
swap
- pode levar a memory overcommit;
- BSD é conservador: kernel aloca espaço necessário à partida.
- Páginas de texto não precisam de swap, dá problema:
- inicialmente bloco está na PTE;
- Reescrito com PFN quando página trazido;
- Páginas são guardadas em swap.
- Alocação usa estrutura dmap:
- array de tamanho fixo com chunks no swap;
- u-area guarda mapas para dados e swap;
- texto em text structure.
Gestão de Memória:
- Alocação de swap para dados e pilha;
- alocaçào de PTEs em Userptmap, senão swapping de outro
processo;
- Área-u é inicializada e colocada no map Forkmap;
- Região de texto: filho é adicionado aos processos usando esta
estrutura;
- Dados e texto são copiados pág a pág.
Caro!!
- Copy-on-write: obriga a contadores de referência por página;
- vfork() apenas cria u-area e proc,
muito rápido.
Numa PF o sistema guarda informação de estado e chama rotina de PF:
- Se violação de limites, SIGSEGV ou aumenta pilha, senão
chama pagein();
- Se simulação de referenced, coloca valid a
1;
- Se PF residente mas na free lista, valid a 1 e
entrada cmap fora de free list;
- Se pág de texto e outro processo está a ler: locked e
in-transit; usa wanted e sleep()
- segundo processo pode ter perdido a pág.
- Procurar tabela de hash para páginas de texto que não na PTE;
- Ler do swap se em swap;
- Alocar e colocar a 0s para zero-fill;
- Ler de executável para fill-from-text: primeiro na
buffer-cache, se lá flush, ler de disco.
Se memória cheia, qual a melhor pág para remover?
- Pág de processo terminados;
- Senão usar LRU:
- LRU completo é impossível;
- NRU: relógio com 2 mãos, uma desactiva bit referido e
outra verifica o bit.
- kernel mantém páginas livres entre minfree e
maxfree com pagedaemon:
- mapeia páginas no seu espaço de endereçamento;
- escreve directamente no swap;
- usa write assíncronos;
- em completion coloca na lista limpa donde são retornados para
lista de memória livre.
Sistema geralmente funciona bem, mas
- Carga pesada pode entrar em thrashing: não conseguimos
manter todos os working sets em memória;
- Solução: desactivar processos com swapper:
- se Userptmap está muito fragmentado libertar um
processo corrente;
- Se freemem abaixo de limites desejáveis por muito
tempo;
- Se processo inactivo por muito tempo.
- Candidatos:
- processo que dorme há mais de 20 segundos;
- dos 4 maiores, o que está em mem. há mais tempo.
Boa funcionalidade e poucas exigências sobre HW, mas:
- Não há acesso a arquivos remotos;
- Não há partilha de memória;
- vfork() limitado;
- Cada processo com PT para texto partilhado: gasto extra e
sincronização;
- Arquivos mapeados em memória e librarias partilhadas?
- Breakpoints do debugger causam confusão.
- Alocação de swap muito conservadora;
- Swap remoto;
- Influência do VAX;
- Código não é modular.
Ideal para disco grande, CPU lento, e pouca memória.
Ideias:
- Mapeamento de Arquivos (privados e partilhados):
- Util para usuários;
- mas não substitui read e write;
- Fundamental no kernel.
- Unificação de Acesso a ficheiros e de memória virtual: evitar
chamadas excessivas a SO.
- Permitir swapping dinâmico em ficheiros.
- Partilha de Memória entre processos: read-only e read-write.
- Estrutura orientada para objectos.
O sistema usa
- page, página;
- as, espaço de
endereçamento;
- seg, segmento;
- hat, tradução de
endereços em hardware;
- anon, páginas anónimas:
Memória é dividida em regiões paginadas e não paginadas:
- Cada page descreve página lógica (grupo de pág
físicas);
- pág tem inf sobre <no-v,offset>, e está na lista do nó-v:
permite nome único mesmo se partilhada;
- pág em hash-chain baseada no <no-v,offset>;
- nó-v mantém listas de páginas;
- pág pode estar na free list.
- Ref. count e flags de sincronização, mais cópias dos bits
modifed e referenced;
- informação do HAT usada para obter todas as traduções da página.
proc aponta para as:
- Lista de mapeamentos do processo, seg;
- Inclui o hat;
- hint sobre o último seg. com PF;
- Flags;
- Operações sobre as:
- as_alloc() dá novo as;
- as_free() liberta as;
- as_dup() duplica.
- Operações sobre grupos de páginas:
- as_map() e as_unmap() coloca objectos no
as;
- as_setprot() e as_checkprot();
- as_fault(), começa PF;
- as_faulta() faz fault ahead.
Existem vários tipos de segmentos:
- Campos públicos são base, tamanho, as;
- Funções virtuais em seg_ops:
- dup() duplica mapeamento;
- fault() e faulta();
- setprot() e checkprot();
- unmap() termina;
- swapout() do swapper;
- sync() sincroniza.
- Existe ainda create() que é chamado da função genérica
para alocar o segmento.
- Outras funções genéricas: libertar, attach e unmap.
Criada quando processo modifica página MAP_PRIVATE:
- Modificações não afectam o objecto original;
- Páginas inicializadas tornam-se anónimas quando são modificadas;
- Armazenadas em swap;
- Objecto anónimo:
- único no sistema;
- representação: v-nó NULL;
- usa swap para backup storage;
- refs a páginas são contadas;
- anon exporta dup, free,
private (cópia privada), zero e getpage.
Operações dependentes do HW são isoladas no HAT. É acedido por uma
interface:
- HAT: alocação, dealocação, dup, swapin e swapout.
- conjunto de páginas: hat_chgprt(),
hat_unload(), hat_memload() e
hat_devload() (últimos para uma única página).
- todas as traduções de uma página: hat_pageunload(),
hat_pagesync().
Informação no HAT é redundante e altamente dependente do HW:
- Traduções para página mantidas em lista
(hat_unload());
- Ref. x86;
- Tradução é chamada de chunk.
Existem diferentes tipos de segmentos:
- seg_vn: endereços para ficheiros regulares ou anónimo;
- seg_map: permite fazer acessos do tipo read e
write;
- seg_dev mapeia devices como frame buffers, memória.
- seg_kmem mapeia regiões do kernel em memória alocada não
dinâmicamente.
Mapeia:
- nó-v, ou,
- anónimos: NULL, /dev/zero
Dados incluem:
- proteções correntes e máximas;
- tipo de mapeamento
(partilhado ou privado);
- ptr para nó-v;
- offset do segmento no arquivo;
- ptr para
mapa anónimo;
- ptr para um array de protecções por página, se pags com
protecções diferentes.
Protecão máxima é a inicial.
Responsabilidade do anon layer:
- Rotina swap_xlate mapeia estruturas tipo anon e
páginas outswapped.
- Dispositivos de swapping podem ser colocados e removidos
dinâmicamente do sistema por swapctl(). Cada um deles tem uma estrutura tipo
swapinfo.
- Segmentos alocam todas as páginas de swap que vão precisar antes
de começar.
- Regiões novas são mapeadas por exec ou por
mmap.
- Operação inclui localizar o nó-v para o ficheiro, chamar
VOP_MAP() , verificar se não há sobreposição de
mapeamentos, chamar as_map() que aloca um seg e
chama o create() apropriado.
- Permissões não podem exceder aquelas com que o ficheiro foi o
aberto.
- exec estabele mapas privados para texto, dados
e pilha. Pode também incluir mapeamentos para bibliotecas
partilhadas.
- Páginas anónimas são criadas quando util. escreve para ficheiro
com MAP_PRIVATE, ou em acesso a páginas partilhadas.
- anon array inclui núm de referências. Pág. são removidas quando
núm. de ref. desce a zero.
- As estruturas de dados são criadas na primeira escrita a pág.
privada.
- fork() chama as_dup() para duplicar o espaço
do pai;
- as_dup() primeiro chama as_alloc() e chama o
dup() de cada segmento;
- Duplicação aloca um novo struct seg, campos são
copiados do pai:
- mapeamntos para texto, pilha e dados são colocados a
MAP_PRIVATE no pai e filho. MAP_SHARED
continuam.
- hat_chgprot() protege contra escrita todas as págs
anónimas;
- anon_dup() duplica anon_map: copia todos os
ptrs no array e incrementa ref counts;
- hat_dup() duplica hat e informação de
tradução.
- hat_dup() pode alocar novas PTs.
Uso de swap device reference array permite que partilha seja
por página:
Depois de PF:
pagedaemon implementa relógio:
- mão da frente usa hat_pagesync() para remover
referência e alterar hat;
- páginas sujas são enviadas para disco por
VOP_PUTPAGE(), que implementa clustering;
- hat_pageunload() invalida páginas que saem para lista
livre
- Bit de referência pode ter que ser simulado.
Processo swapper tem PID 0 e é chamado cada segundo:
- se memória livre < t_gpgslo, envia processo para
fora usando CL_SWAPOUT();
- chama as_swapout() para enviar um processo:
- chama swapout() por segmento;
- maioria dos segmentos usa segvn_swapout().
- No fim faz swap de u_area;
- Swapin: basta trazer u_area.
Relação simbiótica:
- VOP_GETPAGE() é chamada para obter páginas;
- VOP_PUTPAGE() é chamada para enviar páginas sujas;
- Desenho muito modular, OO;
- Isolar HW no hat;
- suporta COW, MAP_SHARED, arquivos.
- suporta mmap();
- suporta bibliotecas partilhadas;
- beneficia do nó-v;
- Unifica buffer-cache com VM cache;
- breakpoints funcionam pq texto é MAP_PRIVATE.
- Mais informação (40B por pág vs. 16B);
- Páginas de arquivos não são guardadas em swap: procura mais lenta;
- alg. mais complexos e mais lentos;
- não computa endereço no disco durante exec(): procura
mais lenta;
- Tuning é mais dificil por ser OO;
- COW pode ser mais lento que cópia puura;
- Swap é alocado por página, perde clustering.
Tipos de modificações:
- Válido para inválido: propagação imediata;
- Inválida para válida: 2 processadores partilham a pág,
- preguiçoso: segundo processo não vê transição;
- vs. propagação imediata.
- Resulta em muitos PFs, e mais lento com OO;
- COW pode ser caro: 1 em 4 págs têm que ser copiadas;
Número de PF grande na versão inicial.
Alguns melhoramentos:
- Inicializar mapas de tradução em fork():
- filho executa sempre algum código
- Inicializar parte do smapas de tradução depois de
exec():
- para cada nó-v, inicializar as entradas para páginas do nó-v
em memória;
- é estimar o conjunto de trabalho como o conjunto de pág em
memória
- Estudar o comportamento de sh e amigos para copiar
algumas páginas imediatamente.
- Páginas são inicialmente COW ou zero-fill;
- vm_area descreve um segmento;
- Linux assume tabela de três níveis:
- Tabela de pág descreve vm_obj;
- fork() copia PTEs,
- páginas swapped out são representadas na PTE,
- Não pode fazer swap out de PTs,
- Toda a memória física é colocada em KVM:
- evita indirecções,
- problemas com configurações grandes;
- em x86 tabela do meio tem 1 entrada!
- mem_map_t descreve página física:
- count é o número de usuários;
- age é a idade;
- map_nr é o PFN.
- Alocação a partir de free_area
- lru_page aponta para a cabeça da fila;
- quando pág é libertada é colocada no fim da fila;
- shrink_mmap() tenta libertar páginas;
- Usa um bit de referência para manter idade:
- Idade inicial é 3;
- Quanto pág é tocada pelo MM idade aumenta de 3;
- Envelhecimento por swap decrementa de 1
kswapd mantém memória livre:
- Geralmente na fila kswapd_wait;
- Acordado pelo alocador quando memória livre desce abaixo de
certo valor:
- Tenta libertar páginas livres;
- Tenta diminuir dentry;
- Tenta diminuir inode;
- Tenta colocar memória partilhada fora;
- Tenta libertar páginas sujas.
- Gestor de VM tenta não escrever muitas pág no swap ao mesmo
tempo;
- Alocação de swap é linear.
vitor@cos.ufrj.br