Você está na página 1de 8

T164s Tanenbaum, Andrewa S.

Sistemas operacionais [recurso eletrônico] : projeto e


implementação / Andrew S. Tanenbaum, Albert S. Woodhull ;
tradução João Tortello. – 3. ed. – Dados eletrônicos. – Porto
Alegre : Bookman, 2008.

Editado também como livro impresso em 2008.


ISBN 978-85-7780-285-2

1. Sistemas operacionais. I. Woodhull, Albert S. II. Título.

CDU 004.4

Catalogação na publicação: Mônica Ballejo Canto – CRB 10/1023


CAPÍTULO 4 • GERENCIAMENTO DE MEMÓRIA 383

O compartilhamento de páginas também pode ser usado para implementar um sistema


de passagem de mensagens de alto desempenho. Normalmente, quando as mensagens são
passadas, os dados são copiados de um espaço de endereçamento para outro a um custo
considerável. Se os processos puderem controlar seus mapas de página, uma mensagem po-
derá ser passada, com o processo remetente desfazendo o mapeamento da(s) página(s) que
contém(êm) a mensagem e o processo receptor mapeando-a(s) novamente. Aqui, apenas os
nomes de página precisam ser copiados, em vez de todos os dados.
Uma outra técnica avançada de gerenciamento de memória é a memória comparti-
lhada distribuída (Feeley et al., 1995; Li e Hudak, 1989; e Zekauskas et al., 1994). A idéia
aqui é permitir que vários processos em uma rede compartilhem um conjunto de páginas,
possivelmente (mas não necessariamente) como um único espaço de endereçamento linear
compartilhado. Quando um processo referencia uma página que não está correntemente ma-
peada, obtém uma falta de página. Então, a rotina de tratamento de falta de página, que pode
estar em espaço de núcleo ou em espaço de usuário, localiza a máquina que contém a página
e envia para ela uma mensagem pedindo para que desfaça o mapeamento da página e a envie
pela rede. Quando a página chega, é mapeada e a instrução que provocou a falta de página é
reiniciada.

4.6 SEGMENTAÇÃO
A memória virtual discutida até aqui é unidimensional, pois os endereços virtuais vão de 0 até
algum endereço máximo, um endereço após o outro. Para muitos problemas, ter dois ou mais
espaços de endereçamento virtuais separados pode ser muito melhor do que ter apenas um.
Por exemplo, um compilador tem muitas tabelas que são construídas à medida que a compi-
lação prossegue, possivelmente incluindo:
1. O salvamento do texto do código-fonte para a listagem impressa (em sistemas de
lote).
2. A tabela de símbolos, contendo os nomes e atributos das variáveis.
3. A tabela contendo todas as constantes inteiras e em ponto flutuante usadas.
4. A árvore de análise, contendo a análise sintática do programa.
5. A pilha usada para chamadas de função dentro do compilador.
Cada uma das quatro primeiras tabelas cresce continuamente, à medida que a compila-
ção prossegue. A última aumenta e diminui de maneiras imprevisíveis durante a compilação.
Em uma memória unidimensional, para essas cinco tabelas, teriam de ser alocados trechos
adjacentes do espaço de endereçamento virtual, como se vê na Figura 4-21.
Considere o que acontecerá se um programa tiver um número excepcionalmente grande
de variáveis, mas uma quantidade normal do restante. O trecho do espaço de endereçamento
alocado para a tabela de símbolos poderá ser totalmente preenchido, mas ainda poderá haver
muito espaço disponível nas outras tabelas. Naturalmente, o compilador poderia simplesmen-
te emitir uma mensagem dizendo que a compilação não pode continuar devido à existência de
variáveis demais, mas fazer isso não parece muito justo, quando resta espaço sem utilização
nas outras tabelas.
Outra possibilidade é brincar de Robin Hood, roubando espaço das tabelas com excesso
de espaço e dando-o para as tabelas com pouco espaço. Essa troca pode ser feita, mas é aná-
logo a gerenciar os próprios overlays – na melhor das hipóteses, um incômodo, e, na pior, um
trabalho enorme e sem recompensa.
384 SISTEMAS OPERACIONAIS

Espaço de endereçamento virtual

Pilha de chamadas

Espaço de Livre
endereçamento
alocado para a Espaço correntemente
Árvore de análise
árvore de análise usado pela árvore de análise

Tabela de
constantes

Texto do
código-fonte
A tabela de símbolos
Tabela de símbolos colidiu com a tabela
do texto do código-fonte

Figura 4-21 Em um espaço de endereçamento unidimensional com tabelas que crescem,


uma tabela pode colidir com outra.

O que é realmente necessário é uma maneira de fazer com que o programador não tenha
que gerenciar o aumento e a redução das tabelas, da mesma maneira que a memória virtual
elimina a preocupação de organizar o programa em overlays.
Uma solução simples e extremamente geral é fornecer à máquina vários espaços de
endereçamento completamente independentes, chamados de segmentos. Cada segmento con-
siste em uma seqüência linear de endereços, de 0 até algum máximo. O comprimento de cada
segmento pode ser qualquer um, de 0 até o máximo permitido. Diferentes segmentos podem
ter (e normalmente têm) comprimentos diferentes. Além disso, o comprimento dos segmen-
tos pode mudar durante a execução. O comprimento de um segmento de pilha pode aumentar
quando algo for colocado na pilha e diminuir quando algo for retirado dela.
Como cada segmento constitui um espaço de endereçamento separado, diferentes seg-
mentos podem aumentar ou diminuir independentemente, sem afetar uns aos outros. Se uma
pilha em determinado segmento precisa de mais espaço de endereçamento para crescer, ela
pode tê-lo, pois não há mais nada em seu espaço de endereçamento para colidir. É claro que
um segmento pode ser preenchido, mas os segmentos normalmente são muito grandes, de
modo que essa ocorrência é rara. Para especificar um endereço nessa memória segmentada,
ou bidimensional, o programa precisa fornecer um endereço de duas partes, um número de
segmento e um endereço dentro do segmento. A Figura 4-22 ilustra uma memória segmen-
tada sendo usada para as tabelas de compilador discutidas anteriormente. Cinco segmentos
independentes são mostrados aqui.
Salientamos que, em sua forma mais pura, um segmento é uma entidade lógica, da qual
o programador está ciente e a usa como tal. Um segmento pode conter uma ou mais funções,
um array, uma pilha ou um conjunto de variáveis escalares, mas normalmente ele não contém
uma mistura de tipos diferentes.
Uma memória segmentada tem outras vantagens, além de simplificar a manipulação de
estruturas de dados que crescem ou diminuem. Se cada função ocupa um segmento separado,
com o endereço 0 como seu endereço inicial, a ligação de funções compiladas separadamente
é bastante simplificada. Depois que todas as funções que constituem um programa tiverem
CAPÍTULO 4 • GERENCIAMENTO DE MEMÓRIA 385

20K

16K 16K

12K 12K 12K 12K


Tabela de
símbolos
8K 8K 8K Árvore 8K
de análise
Texto do Pilha
código-fonte
4K 4K 4K 4K

Constantes
0K 0K 0K 0K 0K
Segmento Segmento Segmento Segmento Segmento
0 1 2 3 4

Figura 4-22 Uma memória segmentada permite que cada tabela aumente ou diminua inde-
pendentemente das outras tabelas.

sido compiladas e ligadas, uma chamada para a função no segmento n usará o endereço de
duas partes (n, 0) para endereçar a palavra 0 (o ponto de entrada).
Se a função no segmento n for subseqüentemente modificada e recompilada, nenhuma
outra função precisará ser alterada (pois nenhum endereço inicial foi modificado), mesmo que
a nova versão seja maior do que a antiga. Com uma memória unidimensional, as funções são
concisamente empacotadas uma ao lado da outra, sem nenhum espaço de endereçamento entre
elas. Conseqüentemente, alterar o tamanho de uma função pode afetar o endereço inicial de ou-
tras funções não relacionadas. Isso, por sua vez, exige modificar todas as funções que chamam
qualquer uma das funções deslocadas por essa alteração para incorporar seus novos endereços
iniciais. Se um programa contém centenas de funções, esse processo pode ser dispendioso.
A segmentação também facilita o compartilhamento de funções ou dados entre vários
processos. Um exemplo comum é o de biblioteca compartilhada. As estações de trabalho mo-
dernas que executam avançados sistemas de janelas, freqüentemente têm bibliotecas gráficas
extremamente grandes compiladas em praticamente todo programa. Em um sistema segmenta-
do, a biblioteca gráfica pode ser colocada em um segmento e compartilhada por vários proces-
sos, eliminando a necessidade de tê-la no espaço de endereçamento de cada processo. Embora
também seja possível ter bibliotecas compartilhadas nos sistemas de paginação puros, isso é
muito mais complicado. Na verdade, esses sistemas fazem isso simulando a segmentação.
Como cada segmento forma uma entidade lógica da qual o programador está ciente,
como uma função, um array ou uma pilha, diferentes segmentos podem ter diferentes tipos
de proteção. Um segmento de função pode ser especificado como apenas de execução, proi-
bindo tentativas de leitura ou de armazenamento de dados nele. Um array em ponto flutuante
pode ser especificado como de leitura/escrita, mas não execução, e as tentativas de “executá-
lo” serão detectadas. Tal proteção é útil na identificação de erros de programação.
Você deve tentar entender por que a proteção faz sentido em uma memória segmentada,
mas não em uma memória paginada unidimensional. Em uma memória segmentada, o usuá-
rio está ciente do que há em cada segmento. Normalmente, um segmento não conteria uma
função e uma pilha, por exemplo, mas uma ou a outra. Como cada segmento contém apenas
um tipo de objeto, o segmento pode ter a proteção apropriada para esse tipo em particular. A
paginação e a segmentação são comparadas na Figura 4-23.
386 SISTEMAS OPERACIONAIS

Consideração Paginação Segmentação

O programador precisa estar ciente de Não Sim


que essa técnica está sendo utilizada?

Quantos espaços de endereços lineares 1 Muitos


existem?

O espaço de endereçamento total pode Sim Sim


ultrapassar o tamanho da memória
física?

As funções e os dados podem Não Sim


ser distinguidos e protegidos
separadamente?

As tabelas cujo tamanho varia podem Não Sim


ser acomodadas facilmente?

O compartilhamento de funções entre os Não Sim


usuários é facilitado?

Por que essa técnica foi inventada? Para se obter Para permitir que
um espaço de programas e dados sejam
endereçamento linear divididos em espaços de
sem ter de comprar endereçamento logicamente
mais memória física independentes e para ajudar
no compartilhamento e na
proteção

Figura 4-23 Comparação entre paginação e segmentação.

De certo modo, o conteúdo de uma página é acidental. O programador ignora até mes-
mo o fato de que a paginação está ocorrendo. Embora fosse possível colocar alguns bits em
cada entrada da tabela de páginas para especificar o acesso permitido, o programador para
utilizar esse recurso teria de monitorar onde estariam todos os limites de página em seu espa-
ço de endereçamento. Entretanto, a paginação foi inventada para eliminar precisamente esse
tipo de gerenciamento mais complexo. Como o usuário de uma memória segmentada tem a
ilusão de que todos os segmentos estão o tempo todo na memória principal – isto é, ele pode
endereçá-los como se estivessem lá –, ele pode proteger cada segmento separadamente, sem
precisar se preocupar com a administração de overlays.

4.6.1 Implementação da segmentação pura


A implementação da segmentação difere da paginação de uma maneira fundamental: as
páginas têm tamanho fixo e os segmentos, não. A Figura 4-24(a) mostra um exemplo de me-
mória física contendo inicialmente cinco segmentos. Agora, considere o que acontece se o
segmento 1 é eliminado e o segmento 7, que é menor, for colocado em seu lugar. Chegamos
à configuração de memória da Figura 4-24(b). Entre o segmento 7 e o segmento 2 existe uma
área não utilizada – isto é, uma lacuna. Então, o segmento 4 é substituído pelo segmento 5,
como na Figura 4-24(c), e o segmento 3 é substituído pelo segmento 6, como na Figura 4-
24(d). Depois que o sistema tiver executado por algum tempo, a memória será dividida em
várias porções, algumas contendo segmentos e outras contendo lacunas. Esse fenômeno,
chamado de checkboarding (formação de um tabuleiro de xadrez) ou fragmentação exter-
na, desperdiça memória nas lacunas. Isso pode ser tratado com compactação, como se vê na
Figura 4-24(e).
CAPÍTULO 4 • GERENCIAMENTO DE MEMÓRIA 387

(3K) (3K)
Segmento 4 Segmento 4
(7K) (7K) Segmento 5 Segmento 5 (10K)
(4K) (4K)
(4K)
Segmento 3 Segmento 3 Segmento 3
Segmento 5
(8K) (8K) (8K) Segmento 6 (4K)
(4K)
Segmento 6
Segmento 2 Segmento 2 Segmento 2 Segmento 2 (4K)
(5K) (5K) (5K) (5K)
Segmento 2
(3K) (3K) (3K) (5K)
Segmento 1
(8K) Segmento 7 Segmento 7 Segmento 7 Segmento 7
(5K) (5K) (5K) (5K)
Segmento 0 Segmento 0 Segmento 0 Segmento 0 Segmento 0
(4K) (4K) (4K) (4K) (4K)
(a) (b) (c) (d) (e)

Figura 4-24 (a)-(d) Desenvolvimento da fragmentação externa. (e) Eliminação da fragmen-


tação externa pela compactação.

4.6.2 Segmentação com paginação: o Pentium Intel


O Pentium suporta até 16K segmentos, cada um com até 232 bytes de espaço de endereça-
mento virtual. O Pentium pode ser configurado (pelo sistema operacional) para usar apenas
segmentação, apenas paginação ou ambos. A maioria dos sistemas operacionais, incluindo
o Windows XP e todos os tipos de UNIX, usa o modelo de paginação puro, no qual cada
processo tem um único segmento de 232 bytes. Como o Pentium é capaz de fornecer aos pro-
cessos um espaço de endereçamento muito maior, e apenas um sistema operacional (OS/2)
usava todo esse poder de endereçamento, descreveremos o funcionamento da memória virtual
do Pentium em toda sua generalidade.
O centro da memória virtual do Pentium consiste em duas tabelas, a LDT (Local Des-
criptor Table – tabela de descritores local) e a GDT (Global Descriptor Table – tabela de
descritores global). Cada programa tem sua própria LDT, mas há apenas uma GDT, compar-
tilhada por todos os programas no computador. A LDT descreve os segmentos locais de cada
programa, incluindo seu código, dados, pilha etc., enquanto a GDT descreve os segmentos de
sistema, incluindo o sistema operacional em si.
Para acessar um segmento, um programa primeiro carrega um seletor para esse segmen-
to em um dos seis registradores de segmento do processador. Durante a execução, o registra-
dor CS armazena o seletor do segmento de código e o registrador DS armazena o seletor do
segmento de dados. Os outros registradores de segmento são menos importantes. Cada seletor
é um número de 16 bits, como se vê na Figura 4-25.
Um dos bits do seletor informa se o segmento é local ou global (isto é, se ele está na
LDT ou na GDT). Outros 13 bits especificam o número de entrada da LDT ou da GDT; por-

Bits 13 1 2

Índice

0 = GDT/1 = LDT Nível de privilégio (0-3)

Figura 4-25 Um seletor do Pentium.

Você também pode gostar