Você está na página 1de 26

4.

4 Definição de gerenciamento de memória

A necessidade de manter múltiplos programas ativos na memória do sistema impõe outra, a


necessidade de controlarmos como esta memória é utilizada por estes vários programas. O
gerenciamento de memória é, portanto, o resultado da aplicação de duas práticas distintas
dentro de um sistema computacional:

1. Como a memória principal é vista, isto é, como pode ser utilizada pelos processos
existentes neste sistema.
2. Como os processos são tratados pelo sistema operacional quanto às suas necessidades
de uso de memória.

Como a memória é um recurso caro, cuja administração influencia profundamente na eficiência


e performance de um sistema computacional, é necessário considerar-se três estratégias para
sua utilização:

1. Estratégias de busca
As estratégias de busca (fetch strategies) preocupam-se em determinar qual o próximo
bloco de programa ou dados que deve ser transferido da memória secundária para a
memória primária. Usualmente se utilizam estratégias de demanda, ou seja, são
transferidos os blocos determinados como necessários para a continuação do
processamento.
2. Estratégias de posicionamento
São as estratégias relacionadas com a determinação das regiões da memória primária
(física) que serão efetivamente utilizados pelos programas e dados, ou seja, pela
determinação do espaço de endereçamento utilizado (placement strategies).
3. Estratégias de reposição ou substituição
São as estratégias preocupadas em determinar qual bloco será enviado a memória
secundária para disponibilização de espaço na memória principal para execução de
outros programas, ou seja, determinam quais blocos de memória serão substituídos por
outros (replacement strategies).

Minimamente, todo sistema computacional possui alguma estratégia de busca e alguma


estratégia básica de posicionamento. O aumento da sofistica ção dos sistemas computacionais
exige a utilização de estratégias de busca posicionamento mais sofisticadas. Para maximizar-se
as capacidades dos sistemas computacionais são necessárias as estratégias de reposição.

Historicamente, o desenvolvimento da organização e gerenciamento de memória foi


grandemente afetado pelo próprio desenvolvimento dos computadores e evolução dos sistemas
operacionais. Os modos básicos de organiza ção da memória dos sistemas são:
• monoprogramado
• multiprogramados com armazenamento real, particionamento fixo e endereçamento
absoluto
• multiprogramados com armazenamento real, particionamento fixo e endereçamento
relocável
• multiprogramados com armazenamento real, de particionamento variável
• multiprogramados com armazenamento virtual paginado
• multiprogramados com armazenamento virtual segmentado
• multiprogramados com armazenamento virtual combinado

Na Figura 4.6 temos um quadro onde se ilustra o relacionamento dos modelos básicos de
organização da memória e, de certa forma, sua evolução.

Com relação ao primeiro aspecto básico da gerência de memória, para entendermos como os
processos enxergam a memória, é necessário conhecer em detalhe como os programas se
comportam durante sua execução.

O comportamento exibido pelos programas durante sua execução cria determinadas limitações
que devem ser observadas cuidadosamente pelo sistema operacional através de seu
gerenciamento de memória. Por outro lado, os programas também devem se comportar dentro
de regras estabelecidas pelo próprio sistema operacional, as quais compõem o modelo de
administração de memória empregado pelo sistema.

Para sabermos como se comporta um programa durante sua execução e quais são suas
limitações quanto a utilização da memória, devemos analisar todo o processo de criação dos
programas.

Figura 6: Evolução da organização da memória

4.5 Criação de programas

Os programas são criados a partir de arquivos-texto, que contêm um roteiro estruturado de


passos e ações a serem executadas pelo programa que se deseja., ou seja, estes arquivos-
texto são uma representação dos algoritmos que se desejam programar. Estes passos e ações
estão descritos dentro do arquivo-texto através de uma linguagem de programação e por isso
são usualmente chamados de arquivo-fonte do programa (resumidamente arquivo-fonte ou
fonte). As linguagens de programação utilizadas podem ser de alto, médio ou baixo nível, mas
qualquer que seja a linguagem, seu tipo e a forma de estruturação do programa, o arquivo-
fonte continua a ser simplesmente um texto, análogo à uma redação, sujeito à regras de
sintaxe e de contexto.

Da mesma forma que os computadores não entendem a nossa linguagem, ou seja a linguagem
que naturalmente utilizamos para nossa comunicação, estas máquina tão pouco entendem as
linguagens de programação diretamente. Existem entidades especiais responsáveis pela
transformação do arquivo-fonte do programa em uma forma passível de execução pelo
computador. Estas entidades estão ilustradas na Figura 7.

O compilador (compiler) é um programa especial que traduz o arquivo fonte em um arquivo


binário que contêm instruções, dados e endereços (representados binariamente) que permitem
executar as ações necessárias através das instruções em linguagem de máquina do
processador existente no computador em questão. Os arquivos binários produzidos pelo
compilador são os arquivos-objeto ou resumidamente objeto. Note que cada compilador é
apropriado para uma única linguagem de programação.

Figura 7: Esquema de criação de programas

O ligador (linker), quando necessário, apenas encadeia dois ou mais arquivos objeto sob a
forma de um único arquivo de programa executável ou arquivo-executável. O arquivo-
executável é aquele que pode ser transferido para a memória do computador possibilitando a
execução do programa.

Assim como os compiladores, o ligador também é uma entidade deste processo de geração de
programas e também está sujeito a operar com arquivos objeto produzidos apenas por
determinados compiladores.

Devemos ressaltar que até agora os arquivos fonte, objeto e executável constituem arquivos,
ou seja, estão armazenados nas estruturas de memória secundária (unidades de disco rígido,
discos flexíveis, fitas, cartuchos ou discos ópticos).

Existe uma outra entidade especial, chamada carregador (loader), que é parte integrante do
sistema operacional, responsável por transportar os arquivos de programa executável da
memória secundária para a memória principal, onde se dará a execução do programa
carregado.
Os carregadores constituem uma parte do sistema operacional porque a colocação de
programas na memória e a execução dos mesmos são funções deste, responsável por
controlar eficientemente as estruturas de memória primária, de armazenamento secundário e
o processamento do sistema computacional.

Após o transporte do arquivo executável para a memória principal é possível iniciar sua
execução, onde ele mesmo se transforma numa imagem executável, que representa a
expansão do código de programa contido no arquivo executável em código executável, áreas
de memória reservadas para variáveis do programa, pilha retorno e área extra para alocação
dinâmica por parte do programa. A bem da verdade, o sistema operacional, antes da carga do
módulo de código, deve conhecer de antemão seu tamanho total e a quantidade mínima de
memória extra necessária. Tais informações residem geralmente num cabeçalho (header)
localizado no início do arquivo de programa executável, que não é copiado para memória, mas
apenas lido pelo sistema operacional.

4.5.1 Espaços lógicos e físicos

Retomemos os conceitos envolvidos com os arquivos de programa fonte.

Qual é o objetivo básico de um programa? A resposta é: ensinar o computador a executar um


seqüência de passos, manuseando dados de forma interativa ou não, com o objetivo final de
realizar cálculos ou transformações com os dados fornecidos durante a execução do programa.

Para isto, após o entendimento do problema, idealiza-se conceitualmente uma forma de


representação do dados a serem manipulados e depois disso um conjunto de operações
especiais que manipularão as estruturas criadas possibilitando a obtenção dos resultados
esperados do programa.

Notem que ao projetar-se um programa, por mais simples ou complexo que ele seja, define-se
um espaço lógico que reúne todas as abstrações feitas para criar-se o programa, sejam elas de
natureza estrutural ou procedural/funcional. Tais abstrações são os objetos lógicos do
programa. O espaço lógico contém todas as definições necessárias para o programa, mas sem
qualquer vínculo com as linguagens de programação ou com os processadores e computadores
que executarão os programas criados a partir desta concepção. O espaço lógico é a
representação abstrata da solução do problema, também abstrata.

Durante a implementação dos programas utilizam-se, como meios de expressão, as linguagens


de programação que possibilitam expressar de maneira concreta (apesar das limitações
impostas por qualquer linguagem de programação) as formulações contidas no espaço lógico.
Pode-se dizer assim que os programas fonte representam, numa dada linguagem de
programação, o espaço lógico do programa. Num outro extremo, dentro do computador a
execução do programa tem que ocorrer dentro da memória principal, como conseqüência e
limitação da arquitetura de Von Neumann.

Seja qual for o computador e a particularização da arquitetura de seu hardware, a memória


principal pode sempre ser expressa como um vetor, unidimensional, de posições de memória
que se iniciam num determinado ponto, usualmente o zero, e terminam em outro,
536.870.912 para um computador com 512 Mbytes de memória, por exemplo. Cada posição
desta estrutura de memória é idêntica, podendo armazenar o que se chama de palavra de
dados do computador, na prática um conjunto de bits.

Se a palavra de dados tem 4 bits , a memória está organizada em nibbles. Palavras de dados
de 8, 16 e 32 bits representam, respectivamente, organizações de memória em bytes, words e
double words. Tipicamente se organizam as memórias dos microcomputadores em bytes,
assim cada posição de memória poderia armazenar um byte de informação, sendo que nos
referenciamos as posições de memória pelos seus números de posição, os quais são chamados
de endereços. Como a memória de um computador é um componente eletrônico, fisicamente
palpável, dizemos que os endereços de memória representam fisicamente a organização de
memória de um computador.
Sabemos que um programa quando em execução na memória principal de um computador se
chama imagem executável e que esta imagem ocupa um região de memória finita e bem
determinada. Ao conjunto de posições de memória utilizado por uma imagem se dá o nome de
espaço físico desta imagem.

De um lado a concepção do programa, representada e contida pelo seu espaço lógico. Do outro
lado, durante a execução da imagem temos que as posições de memória usadas constituem o
espaço físico deste mesmo programa.

De alguma forma, em algum instante o espaço lógico do programa foi transformado e ligado a
organização de memória do sistema computacional em uso, constituindo o espaço físico deste
mesmo programa.

A ligação entre o espaço lógico e físico representa, na verdade, a um processo de


mapeamento, onde cada elemento do espaço lógico é unido de forma única a uma posição de
memória do computador, acabando por definir um espaço físico. A este processo de ligação se
dá o nome de mapeamento ou binding (amarração), como representado na Figura 8. No nosso
contexto binding significa o mapeamento do espaço lógico de um programa no espaço físico
que possibilita sua execução dentro do sistema computacional em uso.

Figura 8: Representação do binding

Veremos a seguir que o binding tem que ser realizado por uma das entidades envolvidas no
processo de criação de programas, ou seja, em alguns instantes da compilação, ligação,
carregamento ou mesmo execução. é possível também que o binding seja realizado por mais
de uma destas entidades, onde cada uma realiza uma parcela deste processo de mapeamento.

4.5.2 Compiladores (compilers)

Como já foi definido, um compilador é um programa especial capaz de traduzir um arquivo


escrito em uma linguagem de programação específica em um outro arquivo contendo as
instruções, dados e endereços que possibilitam a execução do programa por um processador
particular. O compilador é, portanto, capaz de entender um algoritmo expresso em termos de
uma linguagem de programação, convertendo-o nas instruções necessárias para sua execução
por um processador particular (vide Figura 9).

Figura 9: Compilador e cross-compilador

Todas as definições internas são transformadas em código. Funções, estruturas de dados e


variáveis externas, tem apenas o local de chamada marcado em tabelas de símbolos externos
para ligação posterior com bibliotecas ou outros módulos de código. Além de considerar
oprocessador que executar á tais instruções, alguns aspectos da arquitetura e do sistema
operacional devem ser observados pelos compiladores como forma de produzir código
verdadeiramente útil para uma dada arquitetura computacional.

No Exemplo 1 temos um trecho de código escrito em linguagem de alto nível e um possível


resultado de compilação.

Os compiladores podem gerar código de duas maneiras básicas, isto é, empregando dois
modos de endereçamento: o absoluto e o relocável.

Quando no modo de endereçamento absoluto, o compilador imagina que o programa será


sempre executado numa única e bem determinada região de memória. Sendo assim, durante a
compilação, o compilador associa diretamente posições de memória a estruturas de dados,
variáveis, endereços de rotinas e funções do programa. Em outras palavras, o compilador fixa
os endereços de execução do programa, realizando por completo o binding, tornando-se assim
compiladores absolutos.
// trecho de código fonte; trecho de código compilado
while (...) { 0200:
...
...
a = a + 1; LOAD 500
...
...
printf("%d\n", a); CALL printf
...
...
} JNZ 0200

Exemplo 1 Resultado da compilação

Figura 10: Arquivo objeto gerado através de compilação absoluta Na Figura 10, onde se
apresenta a estrutura interna de arquivos gerados através de compilação absoluta, temos os
elementos seguintes:

• Cabeçalho
Região onde são colocadas informações gerais sobre o arquivo objeto e suas partes.
Também conhecido como header.
• Código
Segmento onde reside o código do programa, propriamente dito. É gerado da mesma
forma que na compilação absoluta.
• TSE
A tabela de símbolos externos é o local onde são listadas as posições de chamada de
símbolos externos (variáveis, estruturas ou funções).

Os arquivos objeto produzidos tem seus endereços calculados a partir de um endereço de


origem padronizado ou informado antes da compilação. Este endereço de origem, a partir do
qual os demais são definidos, é chamado de endereço base de compilação ou apenas de
endereço base. Desta maneira a compilação se torna mais simples, mas como conseqüência
direta disto temos que:
• um arquivo de programa executável só pode ser executado numa região fixa de
memória;
• não podem existir duas ou mais instâncias do mesmo programa executável na
memória, a não ser que se realizem compilações adicionais forçando a geração do
código para uso em diferentes regiões de memória;
• dois ou mais diferentes programas executáveis não podem ser carregados na memória
a não ser que tenham sido compilados prevendo exatamente a ordem de carregamento
e as áreas adicionais de memória que venham a utilizar;
• duas ou mais imagens executáveis não podem se sobrepor na memória (ocupar os
mesmos endereços de memória) total ou parcialmente;
• uma imagem executável deve sempre ocupar uma região contínua de memória;
• a soma das imagens possíveis de serem carregadas em memória para execução
paralela tem que ser menor ou igual a quantidade total de memória disponível.

As razões para estas limitações são simples e todas baseadas no fato de que dentro do arquivo
objeto só existem números binários. Tais números representam tanto os códigos das
instruções do processador como os dados constantes do programa e também os endereços
determinados pelo compilador. Como existem apenas números binários em todo o arquivo
objeto, não é trivial a distinção entre instruções, dados e endereços, tornando praticamente
impossível:
• reverter a compilação, pois o binding se tornou irreversível, não sendo possível
reconstituir-se o espaço lógico que originou o programa;
• modificar o endereçamento do programa pois não se pode distinguir que são os
endereços dentro dos arquivos objeto gerados pelos compiladores absolutos.
Quando no modo de endereçamento relocável, o compilador continua realizando todas as
suas tarefas, como no caso da compilação absoluta, fixando os endereços de execução durante
a compilação, mas além de gerar o código o compilador relocável monta o arquivo objeto da
seguinte forma:

O único elemento novo no arquivo objeto é a TER (tabela de endereços relocáveis) onde são
relacionadas as posições de todos os endereços existentes dentro do bloco de código cujo valor
depende da posição inicial do código, ou seja, lista todos os endereços relativos existentes.

Figura 11: Arquivo objeto gerado através de compilação relocável

Desta forma, a TER relaciona onde estão os endereços que deveriam ser modificados para
permitir a transposição da imagem executável de uma região de memória para outra,
constituindo o segredo dos compiladores relocáveis, pois é através desta tabela que o binding
torna-se reversível, ou melhor, alterável, o que corresponde dizer que o binding não se realizou
por completo. Portanto temos as seguintes implicações:
• ainda não é possível reverter-se a compilação em si pois apesar do binding ser
alterável, ainda é impossível reconstituir-se o espaço lógico original do programa;
• é possível que outra entidade venha a modificar o atual endereçamento do programa,
pois os endereços estão evidenciados na TER, sendo que tal modificação possibilita
determinar o espaço físico do programa em função da disponibilidade de memória do
sistema.

De qualquer forma o uso de compiladores relocáveis proporcionará as seguintes situações:


• um arquivo de programa executável poderá ser executado em diversas regiões de
memória, a serem determinadas pelo sistema operacional;
• poderão existir duas ou mais instâncias do mesmo programa executável na memória,
sem a necessidade da realização de compilações adicionais para forçar a geração do
código para uso em diferentes regiões de memória;
• dois ou mais diferentes programas executáveis poderão ser carregados na memória
sem que seja necessária a previsão da ordem exata de carregamento e as áreas
adicionais de memória que venham a ser utilizadas;
• duas ou mais imagens executáveis não podem se sobrepor na memória (ocupar os
mesmos endereços de memória) total ou parcialmente;
• uma imagem executável deve sempre ocupar uma região contínua de memória;
• a soma das imagens possíveis de serem carregadas em memória para execução
paralela tem que ser menor ou igual a quantidade total de memória disponível.

Vemos que uma parte das limitações provocadas pelo uso de compiladores absolutos podem
ser contornadas com o uso do modelo de compilação relocável. Como os ligadores não
exercem participação no binding no tocante a modificação ou finalização do binding, temos que
os carregadores são os candidatos naturais a finalização do binding no caso da compilação
relocável.

4.5.3 Ligadores (linkers)

Programas capazes de unir parcelas de código, compiladas separadamente, em um único


arquivo de programa executável. Através de símbolos e posições relacionados em tabelas de
símbolos geradas pelos compiladores, os ligadores são capazes de unir trechos de código
existentes em diferentes arquivos objeto em um único arquivo executável.

Os símbolos destas tabelas representam funções ou estruturas de dados que podem, dentro de
certas regras, ser definidas e criadas em certos arquivos.

Segundo estas mesmas regras, outros arquivos de programa fonte podem utilizar-se destas
funções e estruturas sem a necessidade de redefini-las, bastando a indicação adequada de sua
existência no exterior destes arquivos fontes. Assim sendo temos:
• Módulos exportadores
Aqueles onde são indicadas funções, estruturas de dados e variáveis que serão
utilizadas por módulos externos. Utilizam variações de cláusulas extern ou export.

// Exportação de estruturas e variáveis em linguagem C


// estrutura de dados
typedef struct {
char FuncName[ID LEN];
int Loc;
} FuncType;
// vetor de estruturas exportado
extern FuncType FuncTable[];
// variável inteira exportada
extern int CallStack[NUM FUNC];

Exemplo 2: Declarações de exportação

• Módulos importadores
Aqueles onde são indicadas quais funções, estruturas de dados e variáveis encontram-
se declaradas e implementadas em módulos externos. Utilizam variações de cláusulas
import ou include.

// Importação de módulos em linguagem C


#include <vcl\Forms.hpp>
#include <vcl\Classes.hpp>
#include <vcl\ Windows.hpp>
#include <dos.h>

Exemplo 3: Declarações de importação

Cabe ao ligador a tarefa de unir os arquivos que contém estas definições aos arquivos que as
utilizam, gerando disto um único arquivo de programa, como ilustrado na Figura 12. A ligação
nunca afeta a maneira com que o binding foi ou será realizado, constituindo um elemento
neutro dentro da criação dos programas quando analisada sob o aspecto de modelo de
endereçamento.

Os ligadores participam do binding efetuando a união dos espaços físicos dos módulos a serem
ligados como o programa executável, que determina o espaço físico definitivo. Os ligadores
não exercem papel de modificação ou finalização do binding, tarefa que fica a cargo das
entidades anteriores (os compiladores) ou posteriores (os carregadores e relocadores).

Figura 12: Esquema de compilação em separado e uso de ligador

A compilação em separado, com a conseqüente união posterior dos módulos produzidos


através de um ligador, tem os seguintes objetivos:
1. Reduzir o tempo de desenvolvimento diminuindo os tempos consumidos durante a
compilação através da partição do programa fonte em pedaços (logicamente divididos e
encadeados). Pode-se a partir desta divisão concentrar-se o trabalho em uma das
partes de cada vez, que por ser menor toma um menor tempo de compilação. Quando
se considera o resultado final, as diversas compilações intermediárias durante o
desenvolvimento totalizam um menor tempo quando feita por partes do que quando o
programa era manuseado por inteiro.
2. Permitir a divisão do trabalho dado que o programa pode ser dividido em partes.
Seguindo o mesmo princípio que o da redução do tempo de desenvolvimento, as
diversas partes podem ser implementadas paralelamente por equipes de 2 ou mais
programadores. Assim os totais gastos são os mesmos quando se consideram a soma
dos tempos gastos por cada elemento da equipe ou o custo de tal trabalho, mas tem-se
a indiscutível vantagem de que o desenvolvimento pode ser realizado num prazo
bastante inferior dado que as partes podem ser desenvolvidas em paralelo. Tal divisão
requer cuidadoso projeto e especificação detalhada e consistente das partes do
programa.
3. Permitir a padronização de código e a construção de bibliotecas de funções e estruturas
de dados. Dado que é possível a compilação de uma parte de um programa contendo
apenas funções (de uso geral ou específico) e estruturas de dados associadas, pode-se
com isto distribuir-se esta funções e estruturas sob a forma compilada, ou seja um
módulo objeto. Outros programadores poderão utilizar este módulo em seus
programas, mas não poderão alterá-lo, daí obtém-se a padronização segura de funções
e estruturas de dados. Para que isto seja possível basta seguir as regras de compilação
por partes e acompanhar o módulo objeto de uma descrição das suas funções
(parâmetros de entrada, resultados retornados) criando-se assim bibliotecas.
4. Permitir o uso de diferentes linguagens de programação dentro de um mesmo
programa. Considerando a capacidade limitada dos Ligadores em interpretar as tabelas
de símbolos geradas pelos compiladores, se vários compiladores, mesmo que de
diferentes linguagens de programação, são capazes de gerar um formato compatível de
informação simbólica, então um ligador apropriado será capaz de unir estes diferentes
módulos num único arquivo de programa executável, mesmo que os módulos tenham
sido escrito em diferentes linguagens. Na verdade, durante a implementação destes
módulos, devem ser observadas as convenções de chamada para rotinas externas
escritas em outras linguagens específicas para cada linguagem. Com isto podem ser
aproveitadas bibliotecas escritas numa certa linguagem (as bibliotecas matemáticas do
FORTRAN, por exemplo) em programas escritos em outras linguagens (C ou PASCAL).
Através destas técnicas, podem ser melhor exploradas certas caraterísticas das
linguagens em programas envolvendo várias linguagens (C e Clipper, C e DBase, C e
SQL, C e PASCAL , VisualBasic e C, etc).

4.5.4 Carregadores (loaders)

Os carregadores são os programas responsáveis pelo transporte dos arquivos de programa


executáveis das estruturas de armazenamento secundário (unidades de disco ou fita) para a
memória principal. Os carregadores tem suas ações dirigidas pelo sistema operacional, que
determina qual módulo executável deve ser carregado e em que região de memória isto deve
ocorrer (endereço base de execução). Após o término do processo de carregamento, o
carregador sinaliza ao sistema operacional que o programa foi carregado, neste ponto o
sistema operacional determinará quando se iniciará a execução do programa, que se
transforma em uma imagem executável ao iniciar sua execução efetivamente.

Quando o arquivo objeto foi gerado em modo absoluto, os carregadores apropriados para esta
situação apenas realizam uma cópia do arquivo de programa executável, transferindo dados do
dispositivo de armazenamento secundário (unidades de disco ou fita) para a memória
principal. Nesta situação, tais carregadores são chamados de carregadores absolutos e
recebem do sistema operacional apenas as informações do nome do arquivo executável a ser
carregado e o endereço de carga (endereço a partir de onde se iniciará a cópia do código na
memória principal).

Se o endereço de carga for o mesmo que o endereço base da compilação, a imagem resultante
será executada sem problemas, de acordo com o que foi programado no fonte. Caso contrário
as conseqüências são imprevisíveis, resultando geralmente na interrupção abrupta do
programa pelo sistema operacional, na invasão da área de dados/código de outros programas,
no cálculo impróprio dos resultados ou na perda de controle do sistema.

Quando temos que o arquivo objeto foi gerado em modo relocável, devem ser utilizados
carregadores relocáveis, ou seja, carregadores capazes de interpretar o conteúdo da TER
(tabela de endereços relocáveis) de forma a transpor a imagem executável da área original
(iniciada/definida pelo endereço base de compilação) para uma outra área de memória. A
transposição da área de memória se baseia no fato de que durante a transferência do
programa executável para a memória principal o carregador relocável, através da TER
identifica quem são os endereços componente do código. Cada vez que o carregador relocável
lê um endereço dentro do código, ele soma ao endereço lido (endereço original da compilação)
o valor do endereço de carga fornecido pelo sistema operacional e com isto se realiza o
modificação do binding (iniciado pelo compilador relocável) finalizandose o mapeamento com a
transposição da imagem para uma nova região de memória, determinada pelo sistema
operacional e não pelo compilador.

Figura 3

A TSE (tabela de símbolos externos) é lida pelo sistema operacional para que os módulos
externos necessários ao programa sejam carregados previamente.

Caso tais módulos sejam usados globalmente, isto é, compartilhados por vários programas
(como as DLLs dos sistemas MS-Windows 95/98), o sistema operacional só realiza o
carregamento quando tais módulos não estão presentes na memória. Finalmente devemos
observar que tanto o cabeçalho existente no arquivo de programa executável como a TSE não
são transferidas para memória principal.

No entanto a área ocupada pelo programa não corresponde apenas ao segmento de código
contido no arquivo executável, sendo substancialmente maior, como ilustrado na Figura 13

Figura 13: Estrutura típica de imagem executável

Tal expansão do código ocorre devido as necessidades dos programas de possuirem uma área
adicional de memória para armazenamento da pilha dos endereços de retorno (stack) e outra
área extra para armazenamento de conteúdo dinâmico do programa (heap), tais como
variáveis locais e blocos de memória alocados dinâmicamente. Por essa razão, após a
transferência do código do armazenamento secundário para memória, chamamos tal conteúdo
de memória de imagem executável ou simplesmente imagem.

4.5.5 Relocadores (swappers)

Os relocadores são rotinas especiais do sistema operacional responsáveis pela movimentação


do conteúdo de certas áreas de memória primária para memória secundária (especificamente
dispositivos de armazenamento como unidades de disco) e vice-versa, como ilustrado na
Figura 14. A existência de relocadores num sistema depende do tipo de gerenciamento de
memória oferecido pelo sistema operacional. Antes de verificarmos quais modelos de
gerenciamento de memória podem fazer uso dos relocadores, devemos compreender melhor a
natureza de seu trabalho.

Figura 14: Conceito de relocação

Considerando que num sistema multiprogramado existem vários processos ativos (em estado
ready), bloqueados (em estado blocked) e suspensos (em estado suspended), sabemos que
apenas um processo está efetivamente em execução, isto é, utilizando o processador (estado
running), então, a medida de suas necessidades, este processo em execução poderá solicitar
áreas adicionais de memória. Estes pedidos de alocação de memória podem ser atendidos de
duas formas: áreas efetivamente livres são cedidas ao processo ou aciona-se o relocador para
liberação de áreas de memória pertencentes aos demais processos ativos e inativos através da
remoção de seu conteúdo para os arquivos de troca, no que se denomina operação de troca ou
swapping.

Seguindo instruções do sistema operacional, que detém o gerenciamento da memória e dos


processos, um relocador pode ser comandado para retirar o conteúdo de uma área de
memória armazenado-a em disco. O espaço em memória disponibilizado através desta
operação é pode ser usado para atender pedidos de alocação de memória do processo
correntemente em execução. Com isto, partes de alguns processos serão transferidas para o
disco. Quando estes processos, cuja algumas de suas partes estão armazenadas no disco,
necessitarem de tais conteúdos, uma nova operação de relocação pode ser efetuada para,
novamente disponibilizar espaço, de modo que este seja agora usado para que o conteúdo das
partes anteriormente copiadas em disco, seja recolocada na memória. Todas estas operações
ocorrem sob orientação do sistema operacional.

O que geralmente ocorre é que o relocador realiza uma cópia das área de memória
movimentadas para o disco em um arquivo especial denominado arquivo de troca ou swap file.
Ao copiar tais áreas de memória para o disco, estas são assinaladas como livres, tornando-se
disponíveis para outros processos. Também se efetua um registro do que foi copiado para
memória possibilitando recuperar este conteúdo quando necessário.

Nesta situação, se avaliarmos a soma total de memória utilizada por todos os processos ativos,
isto é, processos nos estados de running e também em ready, temos que a quantidade de
memória utilizada por eles pode ser significativamente maior do que a memória física instalada
no sistema, pois alguns destes processos tiveram suas área de memória transferidas para a
unidade de disco quando não estavam em execução. Este é o princípio básico que possibilita a
implementação de memória virtual como será tratado a seguir (seção 1.6).

1.6 Memória virtual

O conceito de relocação de memória possibilitou o desenvolvimento de um mecanismo mais


sofisticado de utilização de memória que se denominou memória virtual ou virtual memory.
Segundo Deitel:

O termo memória virtual é normalmente associado com a habilidade de um sistema


endereçar muito mais memória do que a fisicamente disponível [DEI92, p. 215].

Este conceito é antigo: surgiu em 1960 no computador Atlas, construído pela Universidade de
Manchester (Inglaterra), embora sua utilização mais ampla só tenha acontecido muitos anos
depois. Tanenbaum simplifica a definição do termo:

A idéia básica da memória virtual é que o tamanho combinado do programa, dados e


pilha podem exceder a quantidade de memória física disponível para o mesmo [TAN92,
p. 89].

Outra definição possível de memória virtual é a quantidade de memória excedente a memória


física instalada em um sistema computacional que esta aparentemente em uso quando se
consideram a soma das quantidades totais de memória utilizadas por todos os processos
existentes num dado momento dentro deste sistema. Na Figura 15 temos uma representação
da memória real e virtual.

Figura 15: Representação da memória real e virtual

Por sua vez, Silberschatz e Galvin propõem as seguintes definições:

Memória Virtual é uma técnica que permite a execução de processos que podem não
estar completamente na memória [SG94, p. 301].

Memória Virtual é a separação da memória lógica vista pelo usuário da memória física
[SG94, p. 302].

De qualquer forma, o termo memória virtual indica que o sistema computacional possui a
capacidade de oferecer mais memória do que a fisicamente instalada, ou seja, é capaz de
disponibilizar uma quantidade aparente de memória maior do que a memória de fato (real)
existente do sistema.

Os maiores benefícios obtidos através da utilização de sistemas que empregam mecanismos de


memória virtual são:
• Percepção por parte de programadores e usuários de que a quantidade de memória
potencialmente disponível é maior do que a realmente existente no sistema.
• Abstração de que a memória é um vetor unidimensional, contínuo, dotado de
endereçamento linear iniciado na posição zero.
• Maior eficiência do sistema devido à presença de um número maior de processos,
permitindo uso equilibrado e sustentado dos recursos disponíveis.

A memória física tem seu tamanho usualmente limitado pela arquitetura do processador ou do
sistema, ou seja, possui um tamanho máximo que é fixo e conhecido. Já a memória virtual
tem seu tamanho limitado, freqüentemente, pela quantidade de espaço livre existente nas
unidade de disco do sistema possuindo, portanto, um valor variável e mais flexível (pois é mais
fácil acrescentar novas unidades de disco a um sistema ou substituir as unidades do sistema
por outras de maior capacidade).

Do ponto de vista de velocidade, a velocidade de acesso da memória física é substancialmente


maior do que da memória virtual, mas a velocidade da memória total do sistema tende a ser
uma média ponderada das velocidades de acesso da memória física e virtual cujos pesos são
as quantidades envolvidas. De qualquer modo, a velocidade média de acesso a memória do
sistema torna-se uma valor intermediário entre as velocidades de acesso da memória física e
virtual sendo que quanto maior a quantidade de memória virtual utilizada menor será a
velocidade média de acesso a memória.

V elMemFisica > V elMemTotal > V elMemV irtual

A memória virtual pode ser implementada basicamente através de mecanismos de:


• Paginação
Técnica em que o espaço de endereçamento virtual é dividido em blocos, denominados
unidades de alocação, de tamanho e posição fixas, geralmente de pequeno tamanho, os
quais se associa um número. O sistema operacional efetua um mapeamento das
unidades de alocação em endereços de memória, determinando também quais estão
presentes na memória física e quais estão nos arquivos de troca.
• Segmentação
Técnica em que o espaço de endereçamento virtual é dividido em blocos de tamanho
fixo ou variável, definidos por um início e um tamanho, cuja posição também pode ser
fixa ou variável, mas identificados univocamente. O sistema operacional mapeia estes
blocos em endereços de memória, efetuando um controle de quais blocos estão
presentes na memória física e quais estão nos arquivos de troca.

Atualmente, os mecanismos mais populares de implementação de memória virtual são através


da paginação. A segmentação é um alternativa menos utilizada, embora mais adequada do
ponto de vista de programação, de forma que em alguns poucos sistemas se usam ambas as
técnicas.

Figura 16: MMU e Relocação dinâmica

Estas duas técnicas só podem ser implementadas se for possível a desassociação dos
endereços referenciados pelos processos em execução dos efetivamente utilizados na memória
física do sistema. Isto eqüivale a dizer que o binding deve se completar no momento da
execução de cada instrução, permitindo adiar até o último momento o mapeamento do espaço
lógico de um programa em seu espaço físico definitivo de execução, isto é o que chamamos de
relocação dinâmica. Para isto o processador deve dispor de mecanismos de deslocamento dos
endereços referenciados pelo programa para as regiões de memória que efetivamente serão
usadas. é óbvio que tal tarefa só pode ser completada com um sofisticado mecanismo de
endereçamento de memória, mantido pelo sistema operacional. Tais mecanismos são
geralmente imple mentados como uma unidade de gerenciamento de memória ou memory
management unit (MMU) esquematizada na Figura 53.

Além dos mecanismos de paginação ou segmentação, a memória virtual exige a


disponibilização de espaço nos dispositivos de armazenamento secundário para a criação de
um (ou mais) arquivos de troca, os swap files.

Em função da velocidade dos dispositivos de E/S, as unidades de disco são quase sempre
utilizadas, minimizando o impacto das transferências entre memória primária (memória física
do sistema) e memória secundária (unidades de disco).

A memória total de um sistema é, portanto, a soma de sua memória física (de tamanho fixo)
com a memória virtual do sistema. O tamanho da memória virtual do sistema é definida por,
basicamente, o menor valor dentre os seguintes:
• capacidade de endereçamento do processador,
• capacidade de administração de endereços do sistema operacional e
• capacidade de armazenamento dos dispositivos de armazenamento secund ário
(unidades de disco).

Nos sistemas Win32 (Windows 95/98 e Windows NT) são oferecidas funções específicas para o
gerenciamento de memória virtual. Suas API (Application Program Interface) oferecem, dentre
outras, as importantes funções relacionadas na Tabela 3 [CAL96, p. 252-262].

Tabela 3: Funções de gerenciamento de memória virtual da API Win32

Através destas funções o usuário pode administrar o uso da memória virtual do sistema,
determinando a utilização da memória física, tamanho e utilização do arquivo de troca,
tamanho do heap etc. Além disso pode efetuar a alocação de novas áreas de memória para
sua aplicação, o que permite a criação de um mecanismo particular de utilização e controle do
espaço de endereçamento virtual que opera de forma transparente com relação aos
mecanismos de memória virtual implementados pelo sistema operacional.

Nos Exemplos 4 e 5 temos exemplos de utilização de algumas destas funções. O Exemplo 4


utiliza a função da API GlobalMemoryStatus para determinar o tamanho da memória física
instalada, a quantidade de memória física disponível, o tamanho do arquivo de troca e sua
utilização.

{ Para Borland Delphi 2.0 ou superior. }


procedure TForm1.UpdateMemStatus;
var
Status: TMemoryStatus;
function ToKb(Value: DWORD): DWORD;
begin
result := Value div 1024;
end;

begin
{ Obt^em status da memória }
Status.dwLength := sizeof(TMemoryStatus);
GlobalMemoryStatus(Status);
with Status do { Atualiza labels e gauges }
begin
Label1.Caption:=IntToStr(ToKb(dwTotalPhys))+’ Kb’;
Label2.Caption:=IntToStr(ToKb(dwTotalPhys -
dwAvailPhys))+’ Kb’;
Label3.Caption:=IntToStr(ToKb(dwTotalPageFile))+’ Kb’;
Label4.Caption:=IntToStr(ToKb(dwTotalPageFile -
dwAvailPageFile))+’ Kb’;
Gauge1.MaxValue:=dwTotalPhys;
Gauge1.Progress:=dwTotalPhys - dwAvailPhys;
Gauge2.MaxValue:=dwTotalPageFile;
Gauge2.Progress:=dwTotalPageFile - dwAvailPageFile;
end;
end;
Exemplo 4 Uso de GlobalMemoryStatus

Já no Exemplos 4.5 que aloca um bloco de memória de tamanho Size, permitindo seu uso
através de um ponteiro para a área alocada, efetuando sua liberação após o uso. A rotina
possui um tratamento mínimo de erros.

{ Para Borland Delphi 2.0 ou superior. }


P := VirtualAlloc(nil, Size,
memCommit or mem Reserve,
Page ReadWrite);
if P = nil then
ShowMessage("Alocação não foi possível")
else
begin
{ Uso da área alocada através do ponteiro P. }
...
{ Liberação da área alocada após uso. }
if not VirtualFree(P, 0, mem Release) then
ShowMessage("Erro liberando memória.");
end;

Exemplo 5 Alocação de bloco de memória com VirtualAlloc

4.7 Modelos de gerenciamento de memória

Como ilustrado na Figura 44, existem vários diferentes modelos para a organização e o
gerenciamento de memória os quais trataremos brevemente:
• Monoprogramado com armazenamento real
• Multiprogramado com partições fixas sem armazenamento virtual
• Multiprogramado com partições variáveis sem armazenamento virtual
• Multiprogramado com armazenamento virtual através de paginação
• Multiprogramado com armazenamento virtual através de segmentação
• Multiprogramado com armazenamento virtual através de paginação e segmentação
combinadas

4.7.1 Monoprogramado com armazenamento real

Neste modelo de gerenciamento a memória é dividida em duas partições distintas, de


tamanhos diferentes, onde uma é utilizada pelo sistema operacional e a outra é utilizada pelo
processo do usuário conforme ilustrado na Figura 17. Este modelo, também chamado de
modelo de alocação contínua, armazenamento direto ou monoprogramado com
armazenamento real, era a forma mais comum de gerenciamento de memória até meados da
década de 1960. Também era a técnica mais comum usada pelos sistemas operacionais das
primeiras gerações de microcomputadores.

Figura 17: Organização da memória em modo monoprogramado real

Esta forma de gerenciamento de memória é bastante simples e permite que apenas um


processo seja executado de cada vez, o que limita a programação a construção de programas
estritamente seqüenciais. Na prática este esquema de gerenciamento só está preparado para a
execução de um programa de cada vez, sendo que raramente a memória será inteiramente
utilizada, sendo freqüente a existência de uma área livre ao final da área de programa
destinada ao usuário.

Dado que o espaço de endereçamento corresponde a quantidade de memória primária


fisicamente instalada no sistema, que não são utilizados mecanismos de memória virtual e que
usualmente apenas um processo (programa) era executado de cada vez, este modelo de
organização também é conhecido como organização monoprogramada real.
Como exemplo o PC-DOS/MS-DOS (Disk Operating System), sistema operacionais dos
microcomputadores IBM e seus compatíveis, utiliza um esquema semelhante, onde o sistema
operacional ficava residente na primeira parte da memória e a área de programa destinada aos
usuários utilizava o espaço restante dos 640 Kbytes de espaço de endereçamento disponíveis.

O CP/M (Control Program/Monitor ), dos microcomputadores Apple e compatíveis utilizava


esquema semelhante. No caso do DOS, vários outros esquemas adicionais forma criados para
estender as capacidades básicas (e bastante limitadas) de endereçamento do sistema
operacional, entre elas os mecanismos de extensão de memória e os overlays.

Os overlays (do termo recobrimento), são o resultado da estruturação dos procedimentos de


um programa em forma de árvore, onde no topo estão os procedimentos mais usados e nos
extremos os menos utilizados. Esta estruturação deve ser feita pelo usuário, satisfazendo as
restrições do programa a ser desenvolvido e da memória disponível no sistema. Uma biblioteca
de controle dos overlays, que funcionava como um sistema de gerenciamento de memória
virtual, deve ser adicionada ao programa e mantida na memória todo o tempo, procura manter
apenas os procedimentos de uma seção vertical da árvore, minimizando a quantidade
necessária de memória física e assim superando as limitações do DOS [GUI86, p. 184].

4.7.2 Particionamento fixo

Dada as vantagens dos sistemas multiprogramados sobre os monoprogramados, é necessário


que a memória seja dividida de forma tal a possibilitar a presença de vários processos
simultaneamente. A maneira mais simples de realizar-se esta tarefa é efetuar a divisão da
memória primária do sistema em grandes blocos os quais são denominados partições. As
partições, embora de tamanho fixo, não são necessariamente iguais, possibilitando diferentes
configurações para sua utilização, como ilustrado na Figura 18.

Figura 18: Organização da memória em modo multiprogramado com partições fixas

Enquanto o sistema operacional utiliza permanentemente uma destas partições, usualmente a


primeira ou a última, os processos dos usuários podem ocupar as demais partições, cujo
número dependerá do tamanho total da memória do sistema e dos tamanhos das partições
realizadas.

Geralmente as partições eram determinadas através da configuração do sistema operacional, o


que poderia ser feito de tempos em tempos ou até mesmo diariamente pelo operador do
sistema. Até uma nova definição dos tamanhos das partições, os tamanhos e posições
anteriormente definidos eram fixos.

Os processos poderão então ocupar as partições de memória a partir de uma fila única de
processos que encaminhará o processo para a partição disponível. Tanto o modelo de
endereçamento absoluto como relocável podem ser utilizados pois:
• Nos sistemas batch os programas eram compilados no instante da execução
possibilitando o uso de compiladores absolutos, dado que a posição que o programa
utilizaria na memória (partição) era conhecida;
• Se utilizado compiladores relocáveis, um carregador relocável poderia transpor o código
corretamente para a partição escolhida.

Quando do uso de partições iguais, uma única fila de processos poderia atender a contento a
tarefa de definir qual processo ocuparia uma certa partição, embora ocorresse perda
significativa de memória pela não utilização integral das partições. O uso de partições fixas de
diferentes tamanhos permitia fazer melhor uso da memória, pois nesta situação poderiam ser
utilizadas filas diferentes de processos para cada partição, baseadas no tamanho do processo/
artição. Ainda assim poderíamos ter uma situação de partições livres e uma, em especial, com
uma fila de processos. A melhor solução encontrada foi adotar uma única fila de processos e
critérios de elegibilidade para designação de partições para processos visando bom uso da
memória e um throughput adequado.

Torna-se evidente que a determinação da partição para a execução de um dado processo


influencia no desempenho do sistema. Para esta tarefa podemos utilizar um dos seguintes
critérios, que correspondem a estratégias de posicionamento (placement strategies):
• First fit: Aloca-se o processo para a primeira partição encontrada que comporte o
processo, minimizando o trabalho de procura.
• Best fit: O processo é alocado para a menor partição que o comporte, produzindo o
menor desperdício de áreas de memória, exige pesquisa em todas as partições livres.
• Worst fit: O processo é alocado para a maior partição que o comporte, produzindo o
maior desperdício de áreas de memória, exige pesquisa em todas as partições livres.

Langsam et al. [LAT96, p. 625] sugerem alguns algoritmos em linguagem C para a alocação de
blocos de memória utilizando o first fit e best fit, bem como para seleção da melhor partição a
ser liberada.

De qualquer forma, o espaço de endereçamento corresponde ao tamanho da memória primária


do sistema, ou seja, a somatória dos tamanhos das partições e, portanto, do tamanho máximo
dos processos em execução, é igual a memória física instalada no sistema. Assim, o
particionamento fixo é um esquema de organização de memória que não utiliza memória
virtual.

Vários sistemas comerciais de grande porte utilizavam este esquema de gerenciamento de


memória, onde o operador ou o administrador do sistema definia o número e o tamanho das
partições da memória principal.

4.7.3 Particionamento variável

O particionamento variável é bastante semelhante à organização de memória em partições


fixas, exceto pelo fato de que agora é o sistema operacional efetua o particionamento da
memória. A cada novo processo, a memória é dividida, de forma que partições de diferentes
tamanhos sejam posicionadas na memória do sistema. A medida que os processos sejam
finalizados, suas partições tornam-se livres, podendo ser ocupadas no todo ou em parte por
novos processos como esquematizado na Figura 19. Este esquema de organização de memória
também é denominado de particionamento por demanda.

Figura 19: Organização da memória em modo multiprogramado com partições variáveis

Neste tipo de sistema, a estratégia de posicionamento worst fit é bastante útil pois permite
maximizar o tamanho das área livres (buracos) obtidas a cada alocação, aumentando as
possibilidade de sucesso de transformação da área desocupada em uma nova partição livre
para um novo processo.

Mesmo utilizando-se o algoritmo worst fit ainda é possível que existam regiões livres de
memória entre as partições efetivamente alocadas. Este fenômeno, que tende a aumentar
conforme a utilização do sistema e número de processos presentes na memória, é denominado
fragmentação interna.

Desta forma, uma certa porção da memória total do sistema pode continuar permanecendo
sem uso, anulando alguns dos benefícios do particionamento variável e do algoritmo worst fit.
Um estratégia possível para eliminar a fragmentação interna é a da compactação de memória,
onde todas as partições ocupadas são deslocadas em direção ao início da memória, de forma
que todas as pequenas áreas livres componham uma única área livre maior no final da
memória, como indicado na Figura 20.

A compactação de memória é uma técnica raramente utilizada devido ao alto consumo de


processador para o deslocamento de todas as partições e manutenção das estruturas de
controle da memória. O trabalho despendido no reposicionamento de uma partição pode ser
suficiente para finalizar o processo que a ocupa ou outro presente na memória, tornando a
movimentação de partições um ônus para os processos em execução.

Figura 20: Compactação de memória

Da mesma forma que na organização da memória através de partições fixas, no


particionamento variável o espaço de endereçamento é igual ao tamanho da memória primária
existente no sistema e, portanto, um esquema de organização de memória que também não
utiliza memória virtual.

4.7.4 Paginação

A paginação é um esquema de organização de memória que faz uso da memória virtual, ou


seja, o espaço de endereçamento é maior que o tamanho da memória fisicamente presente no
sistema, como representado na Figura 21.

O espaço de endereçamento total do sistema, denominado de espaço de endereçamento


virtual é dividido em pequenos blocos de igual tamanho chamados páginas virtuais (virtual
pages) ou apenas páginas (pages). Cada página é identificada por um número próprio. Da
mesma forma a memória física é dividida em blocos iguais, do mesmo tamanho das páginas,
denominados molduras de páginas (page frames). Cada moldura de página também é
identificada por um número, sendo que para cada uma destas molduras de página corresponde
uma certa região da memória física do sistema, como mostra a Figura 22.

Para que este esquema de divisão seja útil, o sistema operacional deve realizar um
mapeamento de forma a identificar quais páginas estão presentes na memória física, isto é,
deve determinar quais os page frames que estão ocupados e quais páginas virtuais (virtual
pages) estão nele armazenados.

O sistema operacional também deve controlar quais páginas virtuais estão localizadas nos
arquivos de troca (swap files), que em conjunto com a memória física do sistema representam
a memória virtual do sistema (vide Figura 21: Espaços de endereçamento virtual e real na
paginação Figura 15).

A medida que os programas vão sendo executados, o sistema operacional vai relacionando
quais páginas virtuais estão sendo alocadas para cada um destes programas, sem se
preocupar com o posicionamento contíguo de partes de um mesmo programa. No instante
efetivo da execução a MMU (memory management unit) converte os endereços virtuais em
endereços físicos utilizando as tabelas de páginas, como esquematizado na Figura 61.

Neste mesmo momento a MMU detecta se uma dada página está ou não presente na memória
física, realizando uma operação de page fault (falta de página) caso não esteja presente.

Quando ocorre um page fault é acionada uma rotina do sistema operacional que busca na
memória virtual (nos arquivos de troca) a página necessária, trazendo-a para a memória física.
Esta operação é particularmente complexa quando já não existe espaço livre na memória
física, sendo necessária a utilização de um algoritmo de troca de páginas para proceder-se a
substituição de páginas.

Os page faults são um decorrência da existência de um mecanismo de memória virtual e


embora sejam operações relativamente lentas quando comparadas ao processamento,
propiciam grande flexibilidade ao sistema. é comum a implementação de mecanismos de
contabilização dos page faults em sistemas de maior porte, onde pode existir até mesmo um
limite, configurado pelo administrador do sistema, para a ocorrência de troca de páginas.

A conversão de endereços por parte da MMU é necessária porque cada programa imagina
possuir um espaço de endereçamento linear originado no zero quando na verdade compartilha
blocos isolados da memória física com outros programas que também estão em execução.
Figura 22: Endereçamento Virtual e Real na Paginação

Outra implicação deste mecanismo é que os blocos fisicamente ocupados na memória principal
não necessitam estar continuamente nem ordenadamente posicionados. Isto permite tanto a
execução de um processo com apenas uma de suas páginas presente na memória física, como
a execução de um processo cujo tamanho total é maior que o armazenamento primário do
sistema. Sendo assim, a paginação é um esquema extremamente flexível.

O mapeamento das páginas virtuais nos efetivos endereços de memória é realizado pela MMU
com o auxílio de tabelas de páginas, que determinam a relação entre as páginas do espaço de
endereçamento virtual e as molduras de páginas do espaço de endereçamento físico, ou seja,
oferendo suporte para as operações de conversão de endereços necessárias ao uso deste
esquema de organização de memória.

Num sistema de paginação pura, os endereços virtuais (veja a Figura 23) são denominados v,
tomando a forma de pares ordenados (p, d), onde p representa o número da página virtual e
da posição desejada, ou seja, o deslocamento (displacement ou offset) a partir da origem
desta página.

Figura 23: Formato do endereço virtual para sistema de paginação pura

Já as posições das molduras de páginas (page frames), isto é, seus endereços iniciais são
determinados da seguinte forma: como as molduras de páginas possuem o mesmo tamanho
das páginas virtuais, os endereços iniciais dos page frames são múltiplos integrais do tamanho
das páginas, não podendo ser designadas de outra forma. A Tabela 4 exibe a relação entre as
molduras de páginas e seu endereçamento na memória física.

Tabela 4: Endereçamento das Molduras de Páginas

O funcionamento da MMU, conforme esquematizado na Figura 24, pode ser descrito


resumidamente nos passos relacionados abaixo:
1. MMU recebe o endereço virtual contido nas instruções do programa.
2. O número de página virtual é usado como índice na tabela de páginas.
3. Obtêm-se o endereço físico da moldura de página que contêm o endereço virtual
solicitado ou ocorre um page fault.
4. MMU compõe o endereço final usando o endereço da moldura de página e uma parte do
endereço virtual (displacement).

Para o funcionamento apropriado da MMU é necessária a existência de tabelas de páginas,


mantidas total ou parcialmente na memória primária pelo sistema operacional. Cada entrada
da tabela de páginas contêm, geralmente:
• um bit indicando presença ou ausência da página na memória principal;
• o número da moldura de página (page frame number); e
• dependendo da implementação, o endereço da página no armazenamento secundário
(swap files) quando ausente da memória principal.

Figura 24: Conversão de endereços pela MMU

O mecanismo de conversão de endereços depende da organização das tabelas de páginas (um,


dois ou múltiplos níveis) e da forma do mapeamento (mapeamento direto, mapeamento
associativo e mapeamento combinado associativo/direto). Em função do tamanho do espaço
de endereçamento virtual, do tamanho da página e do tamanho da memória real, os arranjos
das tabelas de páginas podem se tornar grandes e complexos. Diversos estudos e estratégias
já foram realizados para sugerir organizações mais eficientes para o mapeamento e a
conversão de endereços.

Temos assim que a paginação permite a execução de programas individualmente maiores que
a memória física do sistema ou, no caso mais comum, a execução de diversos programas cuja
soma dos seus tamanhos exceda o tamanho da memória física. Graças a MMU, implementada
no hardware do processador, as aplicações podem ser desenvolvidas imaginando um espaço de
endereçamento linear, contínuo e de grande tamanho, simplificando bastante o trabalho de
programação. A paginação é o sistema de organização de memória mais utilizado atualmente.

Exemplos de sistemas computacionais que utilizam a paginação pura são:


• DEC PDP-11, minicomputador de 16 bits popular da década de 1970, contando com um
espaço de endereçamento virtual de 16 bits, páginas de 8 KBytes e até 4 MBytes de
memória física, utilizando tabelas de páginas de um único nível [TAN92, p. 97].
• DEC VAX (Virtual Addresses eXtensions), sucessor do DEC PDP-11, minicomputador de
32 bits, possuindo um espaço de endereçamento virtual de 32 bits e pequenas páginas
de 512 bytes. Os modelos de sua família contavam com no mínimo 2 MBytes de
memória física até 512 MBytes. As tabelas de páginas possuíam dois níveis [TAN92, p.
98].
• IBM OS/2 2.0 (Operating System/2), operando em plataforma Intel 80386 ou 80486,
oferecia até 512 MBytes de espaço lógico linear por processo num esquema de
endereçamento de 32 bits, tamanho de página de 4 KBytes com paginação por
demanda [IBM92b, p. 11].
• IBM AS/400, minicomputador de 64 bits que utiliza um esquema de tabela de páginas
invertidas (inverted page table) [STA96, p. 248].
• Microsoft Windows 95, dirigido para processadores 80386 ou superior, oferece espaço
de endereçamento linear virtual de 2 GBytes (endereços de 32 bits), páginas de 4
KBytes [PET96, p. 293].

4.7.5 Segmentação

Enquanto que a organização da memória através da paginação é um modelo puramente


unidimensional, isto é, o espaço de endereçamento virtual oferecido a cada um dos diversos
processos é único e linear, a segmentação propõe um modelo bidimensional, onde cada
processo pode utilizar-se de diversos espaços de endereçamento virtuais independentes. Este
conceito foi introduzido nos sistemas Burroughs e Multics [GUI86, p. 137].

Num esquema de memória segmentada, o espaço de endereçamento virtual é dividido em


blocos de tamanho variável, onde cada bloco pode assumir também um posicionamento
variável, isto é, para um dado processo, enquanto cada segmento deve ocupar um espaço de
endereçamento contínuo na memória física, não existe necessidade dos diversos segmentos
deste processo estarem alocados de forma contígua ou sequer ordenada. Estes blocos são
denominados segmentos de memória ou simplesmente segmentos, como ilustrado na Figura
25.

É comum que os segmentos possuam um tamanho máximo, limitado ao tamanho da memória


física do sistema e um número máximo de segmentos distintos. Cada segmento representa um
espaço de endereçamento linear independente dos demais segmentos, isto permite que cada
segmento possa crescer ou diminuir conforme suas necessidades e livremente de outros
segmentos.

Uma situação possível e comum é a de um processo que possui um segmento de código (o


programa em si), um ou mais segmentos de dados e um segmento para sua pilha (stack),
todos com diferentes tamanhos.

Dado que um segmento é uma unidade lógica, o programador deve explicitamente determinar
sua utilização. Embora seja possível ter-se código, dados e pilha num único segmento, isto
representa uma má utilização desta estrutura de organização da memória, invalidando seus
benefícios.

Figura 25: Armazenamento primário na segmentação

A organização da memória em segmentos favorece e simplifica a organização de estruturas de


dados, principalmente as de tamanho variável em tempo de execução. Além disto oferece
importantes facilidades do ponto de vista de compartilhamento e proteção. Por exemplo, uma
biblioteca de funções pode ser colocada num segmento e compartilhada pelas diversas
aplicações que as necessitem. A cada segmento podem ser associados tipos de acesso que
especifiquem as operações que podem ser executadas no segmento, tais como leitura (read),
escrita (write), execução (execute) e anexação (append). Tais operações podem ainda ser
associadas a modos de acesso específicos, criando um amplo conjunto de possibilidades úteis
para implantação de esquemas de segurança [DEI92, p. 233].

Num sistema de segmentação pura, os endereços virtuais, cuja estrutura se indica na Figura
26, são denominados v e tomam a forma de pares ordenados (s, d), onde s representa o
número do segmento e d a posição desejada, ou seja, o deslocamento (displacement ou
offset) a partir da origem deste segmento. Notamos que a formação dos endereços na
segmentação é semelhante a existente na paginação.

Figura 26: Formato do endereço virtual para sistema de segmentação

Um processo somente pode ser executado se ao menos um de seus segmentos contendo


código estiver presente na memória física. Para isto segmentos devem ser transferidos da
memória secundária para a memória primária da mesma forma que as páginas no esquema de
paginação, ou seja, cada segmento deve ser transferido inteiramente e posicionado numa
região contínua de memória.

Isto indica que os segment faults são operações mais lentas, dado que os segmentos são
usualmente maiores do que as páginas, e também menos freqüentes, pois o número de
segmentos de um processo é tipicamente menor que o número de páginas equivalente. Outro
ponto é que deve ser determinada qual região de memória permite a colocação do novo
segmento, operação que pode ser realizada através de algoritmos que apliquem as estratégias
de posicionamento (placement strategies). O segmento que deve ser substituído, em caso de
um segment fault, deve ser obtido através de algoritmos que implementem as estratégias de
substituição (replacement strategies).

O mapeamento dos endereços virtuais em endereços reais pertencentes aos segmentos


corretos se faz de maneira idêntica à paginação, ou seja, utiliza um esquema de mapeamento
e tabelas de mapeamento de segmentos (segment map tables):
1. MMU recebe o endereço virtual contido nas instruções do programa.
2. O número de segmento virtual é usado como índice na tabela de segmentos.
3. Obtêm-se o endereço físico de início do segmento ou ocorre um segment fault.
4. MMU compõe o endereço final usando o endereço de início do segmento e uma parte do
endereço virtual (displacement).

O mecanismo de conversão de endereços depende da organização das tabelas de segmentos e


da forma do mapeamento (mapeamento direto ou mapeamento associativo).

Exemplos de sistemas computacionais que utilizaram a segmentação pura são:


• Burroughs B6700, computador do início da década de 60, com arquitetura tipo pilha
[GUI86, p.157].
• HP 3000, minicomputador tipo pilha cujo espaço lógico consistia de até 255 segmentos
de código executável de 16 KBytes cada e um único segmento de dados de 64 KBytes
manipulado por hardware [GUI86, p.172].
• Intel 8086/8088, microprocessador de 8 bits, oferecia um espaço de endereçamento
lógico de 1 MByte, podendo efetuar o endereçamento físico de no máximo 64 Kbytes,
tamanho máximo dos segmentos que administrava [BOR92, p. 342].
• IBM OS/2 1.x (Operating System/2), voltado para o microprocessador Intel 80286,
utilizava segmentação pura, onde o tamanho máximo dos segmentos era 64 Kbytes,
espaço de endereçamento virtual de 512 MBytes por aplicação e memória física máxima
de 16 Mbytes [IBM92b, p. 11][LET89, p. 142].
• Microsoft Windows 3.x, também dirigido para o microprocessador Intel 80286, usava
segmentação pura, segmentos de no máximo 64 Kbytes, 48MBytes de espaço de
endereçamento virtual e memória física máxima de 16 MBytes [IBM92b, p. 14].

Apesar de ser um esquema de organização de memória que oferece uma série de vantagens, a
segmentação apresenta uma grande desvantagem: conforme os segmentos se tornam
grandes, as operações de posicionamento e substituição tendem a se tornar lentas conduzindo
o sistema a uma situação de ineficiência. Existe ainda o problema maior de um segmento
individual se tornar maior que a memória física disponível no sistema. A solução desta
desvantagem se dá na utilização conjunta dos esquemas de segmentação e paginação, como
veremos mais a frente.

4.7.6 Paginação versus Segmentação

Como visto, os esquemas de organização de memória através de paginação e segmentação


possuem vantagens e desvantagens. Na Tabela 5 temos um quadro comparativo, tal como
proposto por Deitel [DEI92, p. 131], onde se avaliam estas formas de organização do
armazenamento primário.

Podemos notar que a paginação é um esquema de organização de mem ória mais simples,
principalmente para o programador, enquanto que a segmentação, a custo de uma maior
complexidade, oferece mecanismos mais sofisticados para organização e compartilhamento de
dados ou procedimentos.

A razão para isto se encontra no porque destes esquemas terem sido inventados.

Enquanto a paginação foi desenvolvida para ser um esquema de organização invisível ao


programador, proporcionando um grande espaço de endereçamento linear, maior que a
memória física e de uso simples, o propósito da segmentação foi permitir que programas e
dados pudessem ser logicamente divididos em espaços de endereçamento independentes
facilitando o compartilhamento e proteção [STA96, p. 249].

Enquanto o grande inconveniente da paginação pura é sua excessiva simplicidade como


modelo de programação, a segmentação pura impõe dificuldades no gerenciamento da
memória virtual, pois a troca de segmentos entre o armazenamento primário e secundário se
torna lento para segmentos de grande tamanho, penalizando o desempenho do sistema.

Tabela 5: Quadro comparativo paginação versus segmentação

4.7.7 Paginação e segmentação combinadas

De forma que possam ser obtidas as maiores vantagens dos esquemas de paginação e
segmentação, desenvolveu-se o uso combinado destas duas técnicas em sistemas com
esquemas híbridos de gerenciamento de memória, mais conhecidos como sistemas
multiprogramados com paginação e segmentação combinadas.

A paginação proporciona grande espaço de endereçamento linear e facilidade para o


desenvolvimento embora não ofereça mecanismos mais sofisticados de organização de código
e dados bem como de compartilhamento, segurança e proteção. Por sua vez, a segmentação
oferece tais mecanismos de organização, compartilhamento e proteção, mas deixa de ser
conveniente quando os segmentos tornam-se grandes além de impor um modelo de
desenvolvimento de software um pouco mais complexo. Combinando-se paginação e
segmentação de forma que os segmentos tornem-se paginados, associam-se as vantagens de
cada um destes esquemas eliminando suas maiores deficiências as custas de uma organização
um pouco mais complexa mas transparente para o desenvolvedor.

Num sistema com paginação/segmentação combinadas, os segmentos devem necessariamente


ter tamanho múltiplo do tamanho das páginas, não mais necessitando ser armazenado
inteiramente na memória e tão pouco de forma contígua e ordenada. Todos os benefícios da
segmentação são mantidos, ou seja, os programas podem ser divididos em múltiplos espaços
de ndereçamento virtuais que, ao serem paginados, não necessitam de armazenamento
contínuo na memória real. Se desejado, todo programa e dados podem ser concentrados num
único segmento, fazendo que o resultado sejam semelhante a um sistema paginado puro.
Desta forma, num sistema de paginação/segmentação combinadas, os endereços virtuais,
como indicado na Figura 27, denominados v, tomam a forma de triplas ordenadas (s, p, d),
onde s representa o número do segmento, p representa o número da página virtual e d a
posição desejada, ou seja, o deslocamento (displacement ou offset) a partir da origem da
página indicada dentro deste segmento.

Figura 27: Formato do endereço virtual para sistema de paginação e segmentação combinadas

Notamos que o espaço de endereçamento virtual oferecido é tridimensional, tornando-se


necessário a existência de uma estrutura de controle mais sofisticada nestes sistemas.
Geralmente o sistema operacional mantém uma tabela de mapa de segmentos por processo,
cuja indicação figura no PCB (Process Control Block abordado na seção 2.4.1), e uma tabela de
páginas para cada segmento individual. Para se resolver um endereço virtual determinando-se
o endereço real torna-se necessária a utilização de informações em três tabelas diferentes.

O funcionamento da MMU nestes sistemas, se encontra esquematizado na Figura 28 e pode ser


descrito, resumidamente, como segue:
1. MMU recebe o endereço virtual contido nas instruções do programa.
2. A partir da tabela de controle dos processos (tabela de PCB), é selecionada a tabela de
mapa de segmentos pertencente ao processo.
3. O número de segmento virtual é usado como índice na tabela de segmentos obtendo-se
o número de página virtual.
4. é utilizada a tabela de páginas relativa ao segmento em uso.
5. O número de página virtual é usado como índice na tabela de páginas.
6. Obtêm-se o endereço físico da moldura de página que contêm o endereço virtual
solicitado ou ocorre um page fault.
7. MMU compõe o endereço final usando o endereço da moldura de página e uma parte do
endereço virtual (displacement).

A manutenção desta estrutura complexa requer cuidadoso projeto para que não consuma
recursos excessivos e processamento significativo nos sistemas que as utilizam.

Exemplos de sistemas computacionais que utilizam a paginação e segmentação combinadas


são:
• Honeywell 6000, computadores das décadas de 1960 e 1970, operando com sistema
operacional MULTICS suportando processos com até 218 (262.144) segmentos cada um
com até 64 KBytes de tamanho [TAN92, p. 132].
• IBM System/360, computador do final da década de 1960, com espaço lógico de 16
MBytes divididos em 16 segmentos de 1 MByte [GUI86, p.154].
• IBM MVS (Multiple Virtual Storage System), operando na arquitetura ESA/370, provê
cada processo com até 2 GBytes de, nos quais poderiam existir 2048 segmentos de 256
páginas de 4096 bytes [DEI92, p. 677].
• Família Intel P6, suportando até 64 TBytes de endereçamento virtual e um máximo de 4
GBytes de memória física, oferecendo até 8192 segmentos de até 4 GBytes cada um,
compostos de páginas de 4096 bytes [STA96, p. 252].
4.7.8 Tabelas de páginas

Como visto, tanto a organização de memória através de paginação como de segmentação e os


sistemas híbridos que utilizam a paginação combinada com segmentação, são implementadas
tabelas para realizar a conversão de endereços virtuais em endereços físicos. Estas tabelas,
suportadas diretamente pelo hardware do sistema e mantidas pelo sistema operacional são,
juntamente com os mecanismos de conversão de endereços, o ponto central destes esquemas
de organização de memória.

A idéia básica é que o endereço virtual é composto de duas partes, um número da página
virtual e um deslocamento dentro da página. O número da página virtual é usado como índice
numa tabela de páginas, ou seja, é somado ao endereço de base da tabela de páginas,
mantido num registrador qualquer do processador, obtendo-se uma referência para uma
entrada da tabela que contêm o endereço real da moldura de página desejada. Somando-se o
deslocamento contido no endereço virtual ao endereço da moldura de página obtido da tabela
de páginas obtêm-se o endereço real completo.

Na Figura 29 temos uma ilustração que esquematiza as operações realizadas na conversão de


um endereço virtual para um outro real. Este esquema de conversão de endereços é
denominado conversão ou tradução de endereços por mapeamento direto, ou ainda, paginação
com um nível de tabelas.

Figura 28: Estrutura de tabelas para sistemas com paginação e segmenta


ção combinadas

Figura 29: Conversão de endereços por mapeamento direto

Embora de relativa simplicidade e eficiência, o mapeamento indireto pode apresentar dois


problemas importantes a medida que o espaço de endere çamento virtual se torna
relativamente muito maior que a memória física disponível ou possível de ser implementada no
sistema [TAN92, p. 93].

Os problemas identificados são:


1. A tabela de páginas pode se tornar extremamente grande.
Grandes espaços virtuais de endereçamento requerem tabelas com muitas entradas. Se
as tabelas são grandes, uma porção preciosa da memória pode ser consumida para este
fim, reduzindo a memória disponível para os processos que podem ser executados pelo
sistema.
2. Tabelas de páginas em memória versus troca de tabela de páginas.
Como cada processo possui suas tabelas de páginas, ao esgotar-se o seu quantum, a
sua execução é interrompida sendo substituída por outro processo. Se for realizada a
troca das tabelas durante o chaveamento de processos, economiza-se memória
primária embora tornando a operação de troca de contexto lenta. Se forem mantidas
todas as tabelas de páginas em memória primária, a troca de contexto torna-se rápida,
mas isto pode exaurir a memória primária do sistema.
3. O mapeamento pode se tornar lento para tabelas grandes ou complexos.
Como cada referência a memória deve necessariamente ter seu endereço convertido,
quanto maiores ou mais complexas as tabelas, mais numerosas e complicadas serão as
operações de conversão e, assim, será maior o overhead imposto pela conversão dos
endereços, fazendo que o maepamento se torne inconvenientemente lento, afetando de
forma significativa a performance do sistema.

Para ilustrar esta situação, analisemos a seguinte situação: na arquitetura DEC VAX, cada
processo pode possuir até 2 GBytes (231 bytes) de espaço de endereçamento. Como as
páginas deste sistema possuem apenas 512 bytes (29 bytes), então é necessário uma tabela
de páginas contendo 222 entradas para cada processo existente no sistema, ou seja,
4.194.304 entradas.
Se uma tabela desta magnitude já é indesejável, que tal um sistema Pentium, que no modo
segmentado/paginado oferece 64 TBytes (246 bytes) de memória virtual? Com páginas de
4096 bytes (212 bytes) e sabendo que metade do endereçamento virtual é oferecido
individualmente para cada processo, uma tabela simples por processo deveria conter 234 2
entradas, ou seja, 8.589.934.592 de entradas!

Uma primeira solução seria submeter a tabela de páginas à paginação, como qualquer outra
área de memória, armazenando-a na memória virtual, fazendo que apenas uma parte dela
esteja necessariamente presente na memória primária.

Outra solução para evitar a presença de enormes tabelas na memória, conforme indicado por
Tanembaum [TAN92, p. 94], é divisão destas numa estrutura de múltiplos níveis, como
indicado na Figura 68. Um espaço virtual de 32 bits poderia ter seu endereço divido em três
partes: (1) um número de tabela de páginas TP1 de 10 bits, (2) um número de página virtual
TP2 de 10 bits e (3) um deslocamento (offset) de 12 bits.

O valor TP1 atua como índice para a tabela de páginas de primeiro nível, selecionando uma
das tabelas do segundo nível. Na tabela de segundo nível selecionada, o valor TP2 atua como
outro índice, obtendo-se assim o endereço real da moldura de página (page frame). A MMU
compõe o endereço real final a partir do endereço da moldura de página ,obtido na tabela de
segundo nível, e do deslocamento (offset), retirado do endereço virtual.

Com a divisão de uma única tabela (de primeiro nível) em uma tabela de entrada (de primeiro
nível) e tabelas de páginas auxiliares (de segundo nível) passa a ser possível administrar-se a
paginação das tabelas de páginas de maneira mais flexível. Como regra geral, cada tabela de
páginas nunca é maior que o tamanho de uma página [STA96, p. 248].

De forma análoga, um sistema de tabela de páginas de dois níveis pode ser expandido para
três, quatro ou mais nível, embora a flexibilidade adicionada torna-se menos valiosa do que a
complexidade inerente ao maior número de níveis. Cada hardware específico possui uma
estrutura de tabelas particular, que leva em conta as peculiaridades da implementação tais
como o tamanho do espaço de endereçamento virtual, a máxima quantidade física de memória
endereçável e o tamanho da página.

Figura 30: Estrutura multinível para tabelas de páginas

Independentemente do número de níveis e do layout das tabelas, as entradas típicas das


tabelas de páginas possuem vários campos utilizados para o controle adequado da memória: o
número da moldura de página (page frame number) que indica o endereço real da página, o
bit presente/ausente (present/absent) que sinaliza se a página está ou não na memória
primária, os bits de proteção (protection) que especificam as operações que podem ser
realizadas na página, um bit de habilitação do cache (caching disabled) usado para evitar que
a página seja colocada no cache e os bits de referência (referenced) e modificação (modified)
utilizados para controlar o uso e alteração do conteúdo da página.

Figura 31: Entrada Típica de uma Tabela de Páginas

Exemplos de sistemas que se utilizam de diferentes formas para a implementação e


administração de suas tabelas de páginas são:
• Paginação em um nível: DEC PDP-11.
• Paginação em dois níveis: DEC VAX.
• Paginação em três níveis: Sun Spark.
• Paginação em quatro níveis: Motorola 68030.
• Paginação via memória associativa (nível zero): MIPS R2000.
• Paginação via tabelas invertidas: IBM RS6000 (sistemas RISC), IBM PowerPC, IBM
AS/400.
4.7.9 Algoritmos de troca de páginas

Os mecanismos de memória virtual se baseiam no fato de que porções dos processos são
armazenadas em arquivos especiais denominados arquivos de troca. Quando um processo
necessita acessar uma porção de seu código contida fora do espaço de endereçamento real,
ocorre um page fault, ou seja, é detectada a falta de uma página que deverá ser trazida
novamente para a memória principal.

As operações de substituição de páginas são lentas pois envolvem o acesso à memória


secundário, ou seja, necessitam acessar dispositivos de entrada e saída, muito mais lentos do
que a memória primária. é óbvio que se a página substituída for necessária em breve ou for
preciso um número muito grande de substituições para execução dos programas ativos,
ocorrerá a hiperpaginação (hiperpaging ou thrashing) ou seja, uma degradação significativa da
performance do sistema devido ao excesso de operações de troca de páginas.

Sendo assim estes algoritmos devem procurar substituir páginas pouco utilizadas ou não
utilizadas por aquelas que são freqüentemente utilizadas pelos processos ativos, minimizando
as operações de substituição de páginas. O grande problema consiste então em determinar
qual página será substituída ou copiada para os arquivos de troca como forma de liberar
espaço para aquele página que se tornou necessária. Um algoritmo ótimo de troca de páginas
deveria ser capaz de identificar qual página não mais será utilizada ou estabelecer aquela que
demorará mais a ser novamente utilizada, minimizando a ocorrência de page faults e com isto
as operações de troca de páginas. Como não é possível realizar tal previsão, outras formas de
se definir qual página será substituída são empregadas [DEI92, p. 254].

Note que se uma página não tiver sido modificada então não necessita ser copiada para a
emória virtual (armazenamento secundário), podendo ser simplesmente sobrescrita pela
página que tomará seu lugar na memória primária, ou seja, uma operação de substituição
simples. Se a página foi modificada então deverá ser copiada para o armazenamento
secundário antes da substituição pela nova página, numa operação mais lenta do que uma
substituição simples.

Os algoritmos que tratam deste problema são aqueles que implementam as estratégias de
substituição (replacement strategies) e são denominados algoritmos de troca ou algoritmos de
substituição de páginas. Os algoritmos de substituição de páginas mais comuns são:
• Random
• First In First Out (FIFO)
• Second Chance
• Clock
• Last Recently Used (LRU)
• Last Frequently Used (LFU)
• Not Recently Used (NRU)

Troca de páginas aleatória

Algoritmo de baixa sobrecarga que seleciona aleatoriamente qual página deverá ser
substituída. Quanto maior o número de páginas existentes, maior são as chances de sucesso
imediato deste algoritmo. Embora seja rápido e de implementação simples, é raramente
utilizado dado que a página substituída pode ser a próxima a ser necessária. Também é
chamado de random page replacement.
Troca de páginas FIFO

A idéia central deste algoritmo é que as páginas que estão a mais tempo na memória podem
ser substituídas, ou seja, as primeiras que entram são as primeiras que saem (FIFO ou First In
First Out). Para isto associa-se um marca de tempo (timestamp) para cada página, criando-se
uma lista de páginas por idade, permitindo a identificação das mais antigas.

Este mecanismo de substituição, embora provável e lógico, não necessariamente se traduz em


verdade, pois processos de longa duração pode continuar necessitando de suas páginas mais
do que processos de curta duração que entram e saem rapidamente enquanto os outro
permanecem. Dada esta razão não é utilizado na forma pura, mas sim variações deste
algoritmo.

Troca de páginas segunda chance

O algoritmo de troca de páginas segunda chance (second chance) é uma variação da


estratégia FIFO. Como visto, a deficiência do algoritmo de troca de páginas FIFO é que uma
página de uso intenso, presente a muito tempo na memória, pode ser indevidamente
substituída.

No algoritmo de troca de páginas Segunda Chance a seleção primária da página a ser


substituída e semelhante ao FIFO, ou seja, escolhe-se a página mais antiga. Antes de
proceder-se a substituição propriamente dita, verificasse o bit de referência da página. Se o bit
estiver em 1, significa que a página foi usada, daí o algoritmo troca sua marca de tempo por
uma nova e ajusta o bit de referência para zero, simulando uma nova página na memória, ou
seja, uma segunda chance de permanência na memória primária. Nesta situação outra página
deve ser escolhida para substituição. Se o bit de referência estivesse em 0 a página seria
substituída. Com este comportamento, se uma página antiga é utilizada, seu bit de referência
sempre será 1, fazendo com que permaneça na memória primária a despeito de sua idade
real.

Troca de páginas relógio

O algoritmo de troca de páginas relógio (clock) é uma outra variação da estratégia FIFO. Para
superar a deficiência do algoritmo de troca de páginas FIFO, que é a substituição de uma
página de uso intenso em função de sua idade na memória, o algoritmo segunda chance
verifica o bit de referência mantendo na memória páginas em uso através da renovação de sua
marca de tempo. Tal comportamento eqüivale a dizer que as páginas no início da lista (mais
velhas) são reposicionadas no fim da lista (mais novas).

A estratégia do relógio é manter uma lista circular, onde se o bit de referência é 1, seu valor é
trocado por 0 e a referência da lista movida conforme os ponteiros de um relógio. Caso
contrário a página é substituída.

Troca de páginas LRU

A atuação do algoritmo de troca de páginas LRU (least recently used ou menos usada
recentemente) se baseia em identificar a página que não foi utilizada pelo maior período de
tempo, assumindo que o passado é um bom indicativo do futuro.

Para isto é necessário que cada página possua uma marca de tempo (timestamp) atualizada a
cada referência feita à página, o que significa uma sobrecarga substancial. A implementação
pode ser feita através de listas contendo uma entrada para cada page frame, sendo o
elemento da lista correspondente a uma página utilizada sendo posicionado no final da lista.

Este algoritmo não costuma ser usado sem otimizações devido à sobrecarga que impõe. Além
disso, em laços longos ou chamadas com muitos níveis de profundidade, a próxima página a
ser usada pode ser exatamente uma das menos usadas recentemente, colocando o sistema
numa situação de operações desnecessárias devido a page faults.
O sistema operacional MS Windows 95 utiliza esta estratégia de substituição de páginas
[PET96, p. 725].

Troca de páginas LFU

Uma variante do algoritmo LRU é a estratégia conhecida como LFU (least frequently used ou
menos freqüentemente usada) ou ainda NFU (not frequently used ou não usada
freqüentemente). Neste algoritmo pretende-se calcular a freqüência de uso das páginas, de
forma a se identificar qual página foi menos intensivamente utilizada.

Apesar de melhorar o desempenho do algoritmo LRU, ainda é possível que páginas


pesadamente utilizadas durante uma certa etapa do processamento permaneçam
desnecessariamente na memória primária em relação a outras etapas mais curtas cujas
páginas não terão uso tão intenso, levando a substituições inúteis.

Troca de páginas NRU

As entradas de cada página na tabela de páginas possuem geralmente bits de referência e


modificação (bits referenced e modified, conforme Figura 69) cujos valores combinados
definem quatro situações ou grupos de páginas, como relacionado na Tabela 6.

Tabela 6: Grupos de páginas

A atuação do algoritmo de troca de páginas NRU (not recently used ou não recentemente
usada) ou NUR (not used recently ou não usada recentemente) se baseia em remover uma
página, aleatoriamente escolhida, do grupo de menor utilização que contiver páginas nesta
situação. Neste algoritmo se dá preferência a remoção de uma página modificada sem uso no
último ciclo do que uma sem modificação que tenha sido utilizada.

Este algoritmo é utilizado em vários sistemas devido aos seus pontos fortes: simplicidade, fácil
implementação e performance adequada.