Você está na página 1de 10

Traduzido do Inglês para o Português - www.onlinedoctranslator.

com

Implementação UNIX

K. Thompson

Laboratórios Bell
Murray Hill, Nova Jersey 07974

ABSTRATO

Este artigo descreve em termos de alto nível a implementação do residente


UNIX† núcleo. Esta discussão está dividida em três partes. A primeira parte descreve como oUNIX
o sistema visualiza processos, usuários e programas. A segunda parte descreve o sistema de E/
S. A última parte descreve oUNIXsistema de arquivo.

1. INTRODUÇÃO
OUNIXkernel consiste em cerca de 10.000 linhas de código C e cerca de 1.000 linhas de código assembly. O
código assembly pode ser dividido em 200 linhas incluídas por uma questão de eficiência (elas poderiam ter sido
escritas em C) e 800 linhas para executar funções de hardware que não são possíveis em C.
Esse código representa de 5 a 10 por cento do que foi agrupado na ampla expressão "oUNIX
sistema operacional.'' O kernel é o únicoUNIXcódigo que não pode ser substituído por um usuário ao seu gosto. Por
esse motivo, o kernel deve tomar o mínimo possível de decisões reais. Isso não significa permitir ao usuário um
milhão de opções para fazer a mesma coisa. Em vez disso, significa permitir apenas uma maneira de fazer uma
coisa, mas que seja o mínimo divisor comum de todas as opções que podem ter sido fornecidas.
O que é ou não implementado no kernel representa uma grande responsabilidade e um grande poder. É uma
plataforma de palanque sobre "como as coisas devem ser feitas". Mesmo assim, se "o caminho" for muito radical, ninguém
o seguirá. Cada decisão importante foi pesada cuidadosamente. Em todo o processo, a simplicidade foi substituída pela
eficiência. Algoritmos complexos são usados apenas se sua complexidade puder ser localizada.

2. CONTROLE DE PROCESSO
NoUNIXsistema, um usuário executa programas em um ambiente chamado processo de usuário. Quando uma
função do sistema é necessária, o processo do usuário chama o sistema como uma sub-rotina. Em algum ponto desta
chamada, há uma mudança distinta de ambientes. Depois disso, o processo é dito ser um processo do sistema. Na
definição normal de processos, os processos do usuário e do sistema são fases diferentes do mesmo processo (eles nunca
são executados simultaneamente). Para proteção, cada processo do sistema possui sua própria pilha.

O processo do usuário pode ser executado a partir de um segmento de texto somente leitura, que é compartilhado
por todos os processos que executam o mesmo código. Não háfuncionalbeneficiar de segmentos de texto compartilhado.
Umeficiência O benefício vem do fato de que não há necessidade de trocar segmentos somente leitura porque a cópia
original na memória secundária ainda é atual. Este é um grande benefício para programas interativos que tendem a ser Além
trocados enquanto espera pela entrada do terminal. disso, se dois processos estiverem sendo executados simultaneamente
da mesma cópia de um segmento somente leitura, apenas uma cópia precisa residir na memória primária. Isso é
um efeito secundário, porque a execução simultânea de um programa não é comum. É irônico que esse
efeito, que reduz o uso da memória primária, só surja quando há superabundância de memória primária, ou
seja, quando há memória suficiente para manter carregados os processos em espera.

__________________
†UNIX é uma marca comercial da Bell Laboratories.
-2-

Todos os segmentos de texto somente leitura atuais no sistema são mantidos a partir dotabela de texto. Uma entrada de
tabela de texto contém a localização do segmento de texto na memória secundária. Se o segmento for carregado, essa tabela
também contém a localização da memória principal e a contagem do número de processos que compartilham essa entrada.
Quando essa contagem é reduzida a zero, a entrada é liberada junto com qualquer memória primária e secundária que contenha o
segmento. Quando um processo executa pela primeira vez um segmento de texto compartilhado, uma entrada na tabela de texto é
alocada e o segmento é carregado na memória secundária. Se um segundo processo executa um segmento de texto que já está
alocado, a contagem de referência de entrada é simplesmente incrementada.

Um processo de usuário tem alguns dados de leitura/gravação estritamente privados contidos em seu segmento de dados. Na
medida do possível, o sistema não usa o segmento de dados do usuário para manter os dados do sistema. Em particular, não há buffers de E/
S no espaço de endereço do usuário.

O segmento de dados do usuário tem dois limites crescentes. Um, aumentado automaticamente pelo sistema como
resultado de falhas de memória, é usado para uma pilha. O segundo limite só é aumentado (ou reduzido) por solicitações
explícitas. O conteúdo da memória primária recém-alocada é inicializado em zero.

Também associado e trocado com um processo está um pequeno segmento de dados do sistema de tamanho fixo.
Este segmento contém todos os dados sobre o processo que o sistema precisa apenas quando o processo está ativo.
Exemplos do tipo de dados contidos no segmento de dados do sistema são: registradores do processador central salvos,
descritores de arquivos abertos, informações contábeis, área de dados rascunhos e a pilha para a fase do sistema do
processo. O segmento de dados do sistema não é endereçável do processo do usuário e, portanto, está protegido.

Por último, há uma tabela de processos com uma entrada por processo. Esta entrada contém todos os dados
necessários ao sistema quando o processo énãoativo. Exemplos são o nome do processo, a localização dos outros
segmentos e informações de agendamento. A entrada da tabela de processos é alocada quando o processo é criado e
liberada quando o processo termina. Esta entrada de processo é sempre diretamente endereçável pelo kernel.

A Figura 1 mostra as relações entre os vários dados de controle do processo. De certo modo, a tabela de processos
é a definição de todos os processos, pois todos os dados associados a um processo podem ser acessados a partir da
entrada na tabela de processos.

TEXTO
PROCESSO MESA
MESA ENTRADA
ENTRADA

RESIDENTE
TABELA DE TEXTO
TABELA DE PROCESSOS

SISTEMA
PERMUTÁVEL
DADOS
SEGMENTO DO UTILIZADOR

TEXTO
DO UTILIZADOR
SEGMENTO
DADOS
SEGMENTO

DO UTILIZADOR

ENDEREÇO
ESPAÇO

Fig. 1—Estrutura de dados de controle de processo.

2.1. Criação de processos e execução de programas

Os processos são criados pela primitiva do sistemagarfo. O processo recém-criado (filho) é uma cópia do processo
original (pai). Não há compartilhamento detectável de memória primária entre os dois processos. (Claro, se o processo pai
estiver executando a partir de um segmento de texto somente leitura, o filho compartilhará o segmento de texto.) Cópias
de todos os segmentos de dados graváveis são feitas para o processo filho. Arquivos que foram abertos antes dogarfo
são verdadeiramente compartilhados após ogarfo. Os processos são informados quanto à sua parte no relacionamento
para permitir que eles selecionem seu próprio destino (geralmente não idêntico). O pai podeespere
-3-

pela rescisão de qualquer um de seus filhos.

Um processo podeexecutivoum arquivo. Isso consiste em trocar os segmentos de texto e dados atuais do processo por
novos segmentos de texto e dados especificados no arquivo. Os segmentos antigos são perdidos. fazendo umexecutivo faznão
processos de mudança; o processo que fezexecutivopersiste, mas após oexecutivoele está executando um programa diferente.
Arquivos que foram abertos antes doexecutivopermanecer aberto após oexecutivo.

Se um programa, digamos a primeira passagem de um compilador, deseja se sobrepor a outro programa, digamos
a segunda passagem, então ele simplesmenteexecutivoé o segundo programa. Isso é análogo a um ''goto''. Se um
programa deseja recuperar o controle apósexecutivoum segundo programa, devegarfoum processo filho, faça com que o
filho executivoo segundo programa, e ter o paiesperepara a criança. Isso é análogo a uma "chamada". Quebrar a
chamada em uma ligação seguida por uma transferência é semelhante à ligação de sub-rotina no SL-5.1

2.2. Trocando
Os principais dados associados a um processo (o segmento de dados do usuário, o segmento de dados do
sistema e o segmento de texto) são trocados de e para a memória secundária, conforme necessário. O segmento
de dados do usuário e o segmento de dados do sistema são mantidos na memória primária contígua para reduzir
a latência de troca. (Quando dispositivos de baixa latência, como bolhas, CCDs ou dispositivos de dispersão/
reunião, são usados, esta decisão terá que ser reconsiderada.) A alocação de memória primária e secundária é
executada pelo mesmo algoritmo simples de primeiro ajuste. Quando um processo cresce, um novo pedaço de
memória primária é alocado. O conteúdo da memória antiga é copiado para a nova memória. A memória antiga é
liberada e as tabelas são atualizadas. Se não houver memória primária suficiente, a memória secundária será
alocada. O processo é trocado para a memória secundária,
Um processo separado no kernel, o processo de troca, simplesmente troca os outros processos para dentro e para
fora da memória primária. Ele examina a tabela de processos procurando por um processo que foi trocado e está pronto
para ser executado. Ele aloca memória primária para esse processo e lê seus segmentos na memória primária, onde esse
processo compete pelo processador central com outros processos carregados. Se nenhuma memória principal estiver
disponível, o processo de troca disponibiliza a memória examinando a tabela de processos em busca de processos que
podem ser trocados. Ele seleciona um processo para trocar, grava-o na memória secundária, libera a memória primária e
depois volta para procurar um processo para trocar.

Assim, existem dois algoritmos específicos para o processo de troca. Qual dos possivelmente muitos processos que
são trocados deve ser trocado? Isso é decidido pelo tempo de residência do armazenamento secundário. Aquele com o
tempo limite mais longo é trocado primeiro. Há uma pequena penalidade para processos maiores. Qual dos possivelmente
muitos processos carregados deve ser trocado? Processos que estão esperando por eventos lentos (isto é, não rodando no
momento ou esperando por E/S de disco) são escolhidos primeiro, por idade na memória primária, novamente com
penalidades de tamanho. Os outros processos são examinados pelo mesmo algoritmo de idade, mas não são retirados a
menos que tenham pelo menos alguma idade. Isso adiciona histerese à troca e evita a debulha total.

Esses algoritmos de troca são os mais suspeitos no sistema. Com memória primária limitada, esses
algoritmos causam troca total. Isso não é ruim em si, porque a troca não afeta a execução dos processos
residentes. No entanto, se o dispositivo de troca também deve ser usado para armazenamento de arquivos, o
tráfego de troca afeta severamente o tráfego do sistema de arquivos. São exatamente esses pequenos sistemas
que tendem a dobrar o uso de recursos de disco limitados.

2.3. Sincronização e agendamento


A sincronização do processo é realizada fazendo com que os processos esperem por eventos. Os eventos são representados
por inteiros arbitrários. Por convenção, os eventos são escolhidos para serem endereços de tabelas associadas a esses eventos. Por
exemplo, um processo que está aguardando o término de qualquer um de seus filhos aguardará um evento que seja o endereço de
sua própria entrada na tabela de processos. Quando um processo termina, ele sinaliza o evento representado pela entrada da
tabela de processos de seu pai. Sinalizar um evento no qual nenhum processo está esperando não tem efeito. Da mesma forma,
sinalizar um evento no qual muitos processos estão esperando despertará todos eles. Isso difere consideravelmente das operações
de sincronização P e V de Dijkstra,2em que nenhuma memória está associada a eventos. Portanto, não há necessidade de alocação
de eventos antes de seu uso. Os eventos existem simplesmente por serem usados.
-4-

Do lado negativo, como não há memória associada aos eventos, nenhuma noção de ''quanto'' pode ser
sinalizada por meio do mecanismo de eventos. Por exemplo, processos que precisam de memória podem esperar.
em um evento associado à alocação de memória. Quando qualquer quantidade de memória fica disponível, o
evento seria sinalizado. Todos os processos concorrentes então acordariam para lutar pela nova memória.
(Na realidade, o processo de troca é o único processo que espera que a memória principal fique disponível.)

Se ocorrer um evento entre o momento em que um processo decide esperar por esse evento e o momento em que
o processo entra no estado de espera, o processo aguardará um evento que já ocorreu (e pode nunca mais ocorrer). Essa
condição de corrida ocorre porque não há memória associada ao evento para indicar que o evento ocorreu; a única ação
de um evento é mudar um conjunto de processos do estado de espera para o estado de execução. Esse problema é
amplamente aliviado pelo fato de que a alternância de processos só pode ocorrer no kernel por chamadas explícitas ao
mecanismo de espera de evento. Se o evento em questão for sinalizado por outro processo, não há problema. Mas se o
evento for sinalizado por uma interrupção de hardware, deve-se tomar cuidado especial. Essas corridas de sincronização
representam o maior problema quandoUNIXé adaptado para configurações de múltiplos processadores.3

O código de espera de evento no kernel é como uma ligação de co-rotina. A qualquer momento, todos, exceto um
dos processos, chamaram event-wait. O processo restante é aquele em execução no momento. Quando ele chama event-
wait, um processo cujo evento foi sinalizado é selecionado e esse processo retorna de sua chamada para event-wait.

Qual dos processos executáveis deve ser executado a seguir? Associado a cada processo está uma prioridade. A prioridade
de um processo do sistema é atribuída pelo código que emite a espera em um evento. Isso é aproximadamente equivalente à
resposta que se esperaria em tal evento. Os eventos de disco têm alta prioridade, os eventos de teletipo são baixos e os eventos de
hora do dia são muito baixos. (A partir da observação, a diferença nas prioridades do processo do sistema tem pouco ou nenhum
impacto no desempenho.) Todas as prioridades do processo do usuário são inferiores à prioridade mais baixa do sistema. As
prioridades do processo do usuário são atribuídas por um algoritmo com base na proporção recente da quantidade de tempo de
computação em relação ao tempo real consumido pelo processo. Um processo que tem usado muito
o tempo de computação na última unidade de tempo real é atribuído a uma prioridade de usuário baixa. Porque os processos interativos são
caracterizado por baixas taxas de computação para tempo real, a resposta interativa é mantida sem nenhum
arranjo especial.
O algoritmo de escalonamento simplesmente escolhe o processo com a prioridade mais alta, escolhendo assim todos os processos
do sistema primeiro e depois os processos do usuário. A proporção de computação em tempo real é atualizada a cada segundo. Assim, todas
as outras coisas sendo iguais, os processos de usuário em loop serão programados round-robin com um quantum de 1 segundo. A ativação
de um processo de alta prioridade interromperá um processo em execução de baixa prioridade. O algoritmo de escalonamento tem um
caráter de feedback negativo muito desejável. Se um processo usar sua alta prioridade para monopolizar o computador, sua prioridade cairá.
Ao mesmo tempo, se um processo de baixa prioridade for ignorado por muito tempo, sua prioridade aumentará.

3. SISTEMA DE E/S
O sistema de I/O é dividido em dois sistemas completamente separados: o sistema de I/O de blocos e o sistema de
I/O de caracteres. Em retrospecto, os nomes deveriam ter sido ''E/S estruturada'' e ''E/S não estruturada'', respectivamente;
enquanto o termo ''E/S de bloco'' tem algum significado, ''E/S de caractere'' é um nome completamente impróprio.

Os dispositivos são caracterizados por um número de dispositivo principal, um número de dispositivo secundário e uma
classe (bloco ou caractere). Para cada classe, há uma matriz de pontos de entrada nos drivers de dispositivo. O número do
dispositivo principal é usado para indexar a matriz ao chamar o código para um driver de dispositivo específico. O número do
dispositivo secundário é passado para o driver do dispositivo como um argumento. O número menor não tem outro significado
senão aquele que lhe é atribuído pelo condutor. Normalmente, o driver usa o número menor para acessar um dos vários
dispositivos físicos idênticos.

O uso da matriz de pontos de entrada (tabela de configuração) como a única conexão entre o código do
sistema e os drivers de dispositivo é muito importante. As primeiras versões do sistema tinham uma conexão
muito menos formal com os drivers, de modo que era extremamente difícil criar sistemas configurados de maneira
diferente. Agora é possível criar novos device drivers em média em poucas horas. A tabela de configuração em
-5-

na maioria dos casos é criado automaticamente por um programa que lê a lista de peças do sistema.

3.1. Bloquear o sistema de E/S

O dispositivo de E/S do bloco de modelo consiste em blocos de memória secundária de 512 bytes cada, endereçados
aleatoriamente. Os blocos são endereçados uniformemente 0, 1, . . . até o tamanho do dispositivo. O driver do dispositivo de bloco
tem a função de emular este modelo em um dispositivo físico.

Os dispositivos de E/S de bloco são acessados por meio de uma camada de software de buffer. O sistema mantém
uma lista de buffers (normalmente entre 10 e 70), cada um atribuído a um nome de dispositivo e um endereço de
dispositivo. Este buffer pool constitui um cache de dados para os dispositivos de bloco. Em uma solicitação de leitura, o
cache é procurado pelo bloco desejado. Se o bloco for encontrado, os dados são disponibilizados ao solicitante sem
nenhuma E/S física. Se o bloco não estiver no cache, o bloco usado menos recentemente no cache é renomeado, o driver
de dispositivo correto é chamado para preencher o buffer renomeado e, em seguida, os dados são disponibilizados. As
solicitações de gravação são tratadas de maneira análoga. O buffer correto é encontrado e rotulado novamente, se
necessário. A gravação é executada simplesmente marcando o buffer como ''sujo''. A E/S física é então adiada até que o
buffer seja renomeado.

Os benefícios na redução de E/S física desse esquema são substanciais, especialmente considerando a
implementação do sistema de arquivos. Existem, no entanto, algumas desvantagens. A natureza assíncrona
do algoritmo torna quase impossível o relatório de erros e o tratamento significativo de erros do usuário. A
abordagem cavalheiresca para o tratamento de erros de E/S noUNIXsistema é parcialmente devido à
natureza assíncrona do sistema de E/S de bloco. Um segundo problema está nas gravações atrasadas. Se o
sistema parar inesperadamente, é quase certo que há muitas E/S logicamente completas, mas fisicamente
incompletas, nos buffers. Existe uma primitiva de sistema para liberar toda atividade de E/S pendente dos
buffers. O uso periódico dessa primitiva ajuda, mas não resolve o problema. Finalmente, a associatividade
nos buffers pode alterar a sequência de I/O física daquela da sequência de I/O lógica. Isso significa que há
momentos em que as estruturas de dados no disco são inconsistentes, mesmo que o software tenha o
cuidado de executar a E/S na ordem correta. Em dispositivos não aleatórios, principalmente fita magnética,
as inversões de gravações podem ser desastrosas.

3.2. Sistema de E/S de personagem

O sistema de I/O de caracteres consiste em todos os dispositivos que não se enquadram no modelo de I/O de
bloco. Isso inclui os dispositivos de caracteres "clássicos", como linhas de comunicação, fita de papel e impressoras de
linha. Também inclui fitas magnéticas e discos quando não são usados de maneira estereotipada, por exemplo, registros
físicos de 80 bytes em fita e cópias em disco de faixa por vez. Resumindo, a interface de E/S de caractere significa ''tudo
menos o bloco''. As solicitações de E/S do usuário são enviadas ao driver de dispositivo essencialmente inalteradas. A
implementação dessas solicitações, é claro, depende do driver do dispositivo. Existem diretrizes e convenções para ajudar
na implementação de certos tipos de drivers de dispositivo.

3.2.1. Drivers de disco

Os drivers de disco são implementados com uma fila de registros de transações. Cada registro
contém um sinalizador de leitura/gravação, um endereço de memória primária, um endereço de
memória secundária e uma contagem de bytes de transferência. A troca é realizada passando esse
registro para o driver do dispositivo de troca. A interface de E/S do bloco é implementada passando
esses registros com solicitações para preencher e esvaziar os buffers do sistema. A interface de E/S de
caracteres para os drivers de disco cria um registro de transação que aponta diretamente para a área
do usuário. A rotina que cria esse registro também garante que o usuário não seja trocado durante
essa transação de E/S. Assim, implementando o driver de disco geral, é possível usar o disco como um
dispositivo de bloco, um dispositivo de caractere e um dispositivo de troca.

3.2.2. listas de personagens

Dispositivos reais orientados a caracteres podem ser implementados usando o código comum para lidar com caracteres.
listas. Uma lista de caracteres é uma fila de caracteres. Uma rotina coloca um personagem em uma fila. Outro recebe um
personagem de uma fila. Também é possível perguntar quantos personagens estão atualmente em uma fila.
O armazenamento de todas as filas no sistema vem de um único pool comum. Colocando um personagem em uma fila
-6-

irá alocar espaço do pool comum e vincular o personagem à estrutura de dados que define a fila.
Obter um personagem de uma fila retorna o espaço correspondente ao pool.
Um dispositivo típico de saída de caracteres (furador de fita de papel, por exemplo) é implementado
passando caracteres do usuário para uma fila de caracteres até que um número máximo de caracteres
esteja na fila. A E/S é estimulada a iniciar assim que houver algo na fila e, uma vez iniciada, é sustentada por
interrupções de conclusão de hardware. Cada vez que há uma interrupção de conclusão, o driver obtém o
próximo caractere da fila e o envia para o hardware. O número de caracteres na fila é verificado e, conforme
a contagem cai em algum nível intermediário, um evento (o endereço da fila) é sinalizado. O processo que
está passando caracteres do usuário para a fila pode estar aguardando o evento, e recarrega a fila ao
máximo quando o evento ocorrer.
Um dispositivo de entrada de caracteres típico (por exemplo, um leitor de fita de papel) é tratado de maneira muito
semelhante.

Outra classe de dispositivos de caracteres são os terminais. Um terminal é representado por três filas de
caracteres. Existem duas filas de entrada (raw e canônica) e uma fila de saída. Personagens indo para a
saída de um terminal são tratadas por código comum exatamente como descrito acima. A principal diferença é
que também há código para interpretar o fluxo de saída como caracteres ASCII e para executar algumas traduções, por
exemplo, escapes para terminais deficientes. Outro aspecto comum dos terminais é o código para inserir real-
atraso de tempo após certos caracteres de controle.

A entrada nos terminais é um pouco diferente. Os personagens são coletados do terminal e colocados em um
fila de entrada bruta. Alguma conversão de código dependente de dispositivo e interpretação de escape são tratadas aqui.
Quando uma linha é concluída na fila bruta, um evento é sinalizado. O código que captura esse sinal copia uma linha da
fila bruta para uma fila canônica, executando o apagamento de caracteres e a edição de eliminação de linha. As
solicitações de leitura do usuário nos terminais podem ser direcionadas para as filas brutas ou canônicas.

3.2.3. Outros dispositivos de personagem

Finalmente, existem dispositivos que não se encaixam em nenhuma categoria geral. Esses dispositivos são configurados como
drivers de E/S de caracteres. Um exemplo é um driver que lê e grava na memória primária não mapeada como um dispositivo de E/S. Alguns
dispositivos são muito rápidos para serem tratados como um caractere de cada vez, mas não se encaixam no molde de E/S do disco.
Exemplos são linhas de comunicação rápidas e impressoras de linha rápida. Esses dispositivos têm seus próprios buffers ou "pegam
emprestados" os buffers de E/S do bloco por um tempo e depois os devolvem.

4. O SISTEMA DE ARQUIVOS

NoUNIXsistema, um arquivo é uma matriz (unidimensional) de bytes. Nenhuma outra estrutura de arquivos está implícita no sistema.
Os arquivos são anexados em qualquer lugar (e possivelmente se multiplicam) em uma hierarquia de diretórios. Os diretórios são
simplesmente arquivos que os usuários não podem escrever. Para uma discussão mais aprofundada sobre a visão externa de arquivos e
diretórios, consulte a Ref. 4.

OUNIXsistema de arquivos é uma estrutura de dados de disco acessada completamente por meio do sistema de E/S de blocos.
Conforme declarado anteriormente, a visão canônica de um ''disco'' é uma matriz endereçável aleatoriamente de blocos de 512 bytes. A
sistema de arquivos divide o disco em quatro regiões de auto-identificação. O primeiro bloco (endereço 0) não é usado
o sistema de arquivos. Ele é deixado de lado para procedimentos de inicialização. por O segundo bloco (endereço 1) contém o so-
chamado ''super-bloco.'' Esse bloco, entre outras coisas, contém o tamanho do disco e os limites. Em seguida, vem a i-
das outras regiões. estrutura,
list, uma lista de definições de arquivo. Cada definição de arquivo é um arquivo de 64 bytes
chamada de i-node. O deslocamento de um i-node específico dentro da i-list é chamado de i-number. O
combinação de nome do dispositivo (números principais e secundários) e i-number serve para nomear exclusivamente um arquivo
específico. Após o i-list, e até o final do disco, vêm os blocos de armazenamento gratuitos que estão disponíveis para o conteúdo
dos arquivos.

O espaço livre em um disco é mantido por uma lista encadeada de blocos de disco disponíveis. Cada bloco
nesta cadeia contém um endereço de disco do próximo bloco na cadeia. O espaço restante contém o endereço de
até 50 blocos de disco também livres. Assim, com uma operação de I/O, o sistema obtém 50 blocos livres e um
ponteiro onde encontrar mais. Os algoritmos de alocação de disco são muito diretos. Como toda alocação é em
blocos de tamanho fixo e há uma contabilidade rigorosa de espaço, não há necessidade de compactação ou coleta
de lixo. No entanto, à medida que o espaço em disco se torna disperso, a latência aumenta gradualmente.
-7-

Algumas instalações optam por compactar ocasionalmente o espaço em disco para reduzir a latência.

Um i-node contém 13 endereços de disco. Os primeiros 10 desses endereços apontam diretamente para os
primeiros 10 blocos de um arquivo. Se um arquivo for maior que 10 blocos (5.120 bytes), o décimo primeiro endereço
aponta para um bloco que contém os endereços dos próximos 128 blocos do arquivo. Se o arquivo ainda for maior que
isso (70.656 bytes), o décimo segundo bloco aponta para até 128 blocos, cada um apontando para 128 blocos do arquivo.
Arquivos ainda maiores (8.459.264 bytes) usam o décimo terceiro endereço para um endereço ''triplo indireto''. O
algoritmo termina aqui com o tamanho máximo do arquivo de 1.082.201.087 bytes.

Uma hierarquia lógica de diretórios é adicionada a essa estrutura física plana simplesmente adicionando um novo tipo de
arquivo, o diretório. Um diretório é acessado exatamente como um arquivo comum. Ele contém entradas de 16 bytes que
consistem em um nome de 14 bytes e um i-number. A raiz da hierarquia está em um número i conhecido (isto é,2). A estrutura do
sistema de arquivos permite um gráfico arbitrário e direcionado de diretórios com arquivos regulares vinculados em locais
arbitrários neste gráfico. Na verdade, muito cedoUNIXsistemas utilizados tal estrutura. A administração dessa estrutura tornou-se
tão caótica que os sistemas posteriores ficaram restritos a uma árvore de diretórios. Mesmo agora, com arquivos regulares
vinculados se multiplicando em locais arbitrários na árvore, a contabilização do espaço se tornou um problema. Pode ser
necessário restringir toda a estrutura a uma árvore e permitir uma nova forma de ligação que seja subserviente à estrutura da
árvore.

O sistema de arquivos permite fácil criação, fácil remoção, fácil acesso aleatório e muito fácil alocação de espaço.
Com a maioria dos endereços físicos confinados a uma pequena seção contígua do disco, também é fácil descarregar,
restaurar e verificar a consistência do sistema de arquivos. Arquivos grandes sofrem de endereçamento indireto, mas o
cache impede a maior parte da E/S física implícita sem adicionar muita execução. As propriedades de sobrecarga de
espaço desse esquema são muito boas. Por exemplo, em um determinado sistema de arquivos, existem 25.000 arquivos
contendo 130M bytes de conteúdo de arquivo de dados. A sobrecarga (i-node, blocos indiretos e quebra do último bloco) é
de cerca de 11,5 milhões de bytes. A estrutura de diretórios para suportar esses arquivos tem cerca de 1.500 diretórios
contendo 0,6 M bytes de conteúdo de diretório e cerca de 0,5 M bytes de sobrecarga no acesso aos diretórios. Adicionado
de qualquer maneira, isso resulta em menos de 10% de sobrecarga para os dados reais armazenados. A maioria dos
sistemas tem essa sobrecarga apenas em espaços em branco à direita acolchoados.

4.1. Implementação do sistema de arquivos

Como o i-node define um arquivo, a implementação do sistema de arquivos gira em torno do acesso ao i-
node. O sistema mantém uma tabela de todos os i-nodes ativos. À medida que um novo arquivo é acessado, o
sistema localiza o i-node correspondente, aloca uma entrada na tabela de i-node e lê o i-node na memória
primária. Como no cache do buffer, a entrada da tabela é considerada a versão atual do i-node. As modificações no
i-node são feitas na entrada da tabela. Quando o último acesso ao i-node desaparece, a entrada da tabela é
copiada de volta para a i-list do armazenamento secundário e a entrada da tabela é liberada.
Todas as operações de E/S em arquivos são realizadas com o auxílio da entrada da tabela de i-node
correspondente. O acesso a um arquivo é uma implementação direta dos algoritmos mencionados anteriormente.
O usuário não está ciente dos i-nodes e i-numbers. As referências ao sistema de arquivos são feitas em termos de
nomes de caminhos da árvore de diretórios. A conversão de um nome de caminho em uma entrada de tabela de i-
node também é direta. Começando em algum i-node conhecido (a raiz ou o diretório atual de algum processo), o
próximo componente do nome do caminho é pesquisado pela leitura do diretório. Isso fornece um número i e um
dispositivo implícito (o do diretório). Assim, a próxima entrada da tabela i-node pode ser acessada. Se esse foi o
último componente do nome do caminho, esse i-node é o resultado. Caso contrário, este i-node é o diretório
necessário para procurar o próximo componente do nome do caminho,
O processo do usuário acessa o sistema de arquivos com certas primitivas. Os mais comuns são abrir,criar,
ler,escrever,procurar, efechar. As estruturas de dados mantidas são mostradas na Fig. 2.
-8-

POR USUÁRIO ABERTO


TABELA DE ARQUIVOS

TROCADO
POR/USUÁRIO

ABRIR ARQUIVO
I-NODE ATIVO
MESA MESA

RESIDENTE
POR/SISTEMA

I-NODE
SECUNDÁRIO
ARMAZENAR

{ ALGORITMOS } POR/SISTEMA DE ARQUIVOS


ARQUIVO

ARQUIVO MAPEAMENTO

Fig. 2—Estrutura de dados do sistema de arquivos.

No segmento de dados do sistema associado a um usuário, há espaço para alguns (geralmente entre 10 e 50) arquivos abertos.
Essa tabela de arquivo aberto consiste em ponteiros que podem ser usados para acessar entradas de tabela de i-node
correspondentes. Associado a cada um desses arquivos abertos está um ponteiro de E/S atual. Este é um deslocamento de byte da
próxima operação de leitura/gravação no arquivo. O sistema trata cada solicitação de leitura/gravação como aleatória com uma
busca implícita no ponteiro de E/S. O usuário geralmente pensa no arquivo como sequencial com o ponteiro de E/S contando
automaticamente o número de bytes que foram lidos/gravados no arquivo. O usuário pode, é claro, executar E/S aleatória
definindo o ponteiro de E/S antes das leituras/gravações.

Com o compartilhamento de arquivos, é necessário permitir que processos relacionados compartilhem um ponteiro de E/S
comum e ainda tenham ponteiros de E/S separados para processos independentes que acessam o mesmo arquivo. Com essas duas
condições, o ponteiro de E/S não pode residir na tabela i-node nem na lista de arquivos abertos para o processo. Uma nova tabela
(a tabela de arquivo aberto) foi inventada com o único propósito de conter o ponteiro de E/S. Processos que compartilham o
mesmo arquivo aberto (resultado degarfos) compartilham uma entrada de tabela de arquivo aberto comum. Uma abertura
separada do mesmo arquivo compartilhará apenas a entrada da tabela i-node, mas terá entradas distintas da tabela de arquivos
abertos.

As principais primitivas do sistema de arquivos são implementadas da seguinte maneira.abrirconverte um nome de


caminho do sistema de arquivos em uma entrada de tabela de i-node. Um ponteiro para a entrada da tabela i-node é colocado em
uma entrada de tabela de arquivo aberto recém-criada. Um ponteiro para a entrada da tabela de arquivos é colocado no segmento
de dados do sistema para o processo. criarfiprimeiro cria uma nova entrada de i-node, grava o i-number em um diretório e, em
seguida, constrói a mesma estrutura de umabrir.lereescreverbasta acessar a entrada i-node conforme descrito acima.procurar
simplesmente manipula o ponteiro de E/S. Nenhuma busca física é feita.fecharapenas libera as estruturas construídas porabrir e
criar. As contagens de referência são mantidas nas entradas da tabela de arquivo aberto e nas entradas da tabela i-node para
liberar essas estruturas após a última referência desaparecer.desvincularsimplesmente diminui a contagem do número de
diretórios apontando para o i-node fornecido. Quando a última referência a uma entrada da tabela i-node desaparece, se o i-node
não tiver diretórios apontando para ele, o arquivo será removido e o i-node será liberado. Essa remoção atrasada de arquivos evita
problemas decorrentes da remoção de arquivos ativos. Um arquivo pode ser removido enquanto ainda está aberto. O arquivo sem
nome resultante desaparece quando o arquivo é fechado. Este é um método de obtenção de arquivos temporários.

Existe um tipo de arquivo FIFO sem nome chamadocano.Implementação decanos consiste em implícito
procurars antes de cadalerouescreverpara implementar o primeiro a entrar, o primeiro a sair. Há também
verificações e sincronização para evitar que o escritor supere grosseiramente o leitor e para evitar que o leitor
ultrapasse o escritor.
-9-

4.2. Sistemas de arquivos montados

O sistema de arquivos de umUNIXo sistema começa com algum dispositivo de bloco designado formatado conforme
descrito acima para conter uma hierarquia. A raiz desta estrutura é a raiz doUNIXsistema de arquivo. Um segundo
dispositivo de bloco formatado pode ser montado em qualquer folha da hierarquia atual. Isso estende logicamente a
hierarquia atual. A implementação da montagem é trivial. Uma tabela de montagem é mantida contendo pares de nós de
folha designados e dispositivos de bloco. Ao converter um nome de caminho em um i-node, é feita uma verificação para
ver se o novo i-node é uma folha designada. Se for, o i-node da raiz do dispositivo de bloco o substitui.

A alocação de espaço para um arquivo é obtida do pool livre no dispositivo no qual o arquivo reside. Assim, um sistema de
arquivos que consiste em muitos dispositivos montados não possui um pool comum de espaço de armazenamento secundário
livre. Essa separação de espaço em diferentes dispositivos é necessária para facilitar a desmontagem de um dispositivo.

4.3. Outras funções do sistema

Há algumas outras coisas que o sistema faz para o usuário – um pouco de contabilidade, um pouco de
rastreamento/depuração e um pouco de proteção de acesso. A maioria dessas coisas não está muito bem
desenvolvida porque nosso uso do sistema na pesquisa em ciência da computação não precisa delas. Existem
alguns recursos que são perdidos em alguns aplicativos, por exemplo, melhor comunicação entre processos.
OUNIXO kernel é mais um multiplexador de E/S do que um sistema operacional completo. Isto é como deveria ser.
Devido a essa perspectiva, muitos recursos são encontrados na maioria dos outros sistemas operacionais que estão
faltando noUNIXnúcleo. Por exemplo, oUNIXkernel não suporta métodos de acesso a arquivos, disposição de arquivos,
formatos de arquivo, tamanho máximo de arquivo, spooling, linguagem de comando, registros lógicos, registros físicos,
atribuição de nomes de arquivos lógicos, nomes de arquivos lógicos, mais de um conjunto de caracteres, um console do
operador, um operador, login ou logout. Muitas dessas coisas são sintomas e não características. Muitos desses
as coisas são implementadas no software do usuário usando o kernel como uma ferramenta. Um bom exemplo disso é o com-A
linguagem mando.5Cada usuário pode ter sua própria linguagem de comando. manutenção desse código é tão fácil
como manter o código do usuário. A idéia de implementar o código do ''sistema'' com primitivas gerais do usuário vem
diretamente do MULTICS.6

Referências

1. RE Griswold e DR Hanson, ''An Overview of SL5,''SIGPLAN Avisos12(4), pp.40-50 (abril de 1977).

2. EW Dijkstra, ''Cooperating Sequential Processes'', pp. 43-112 emLinguagens de programação, ed.


F. Genuys, Academic Press, Nova York (1968).
3. JA Hawley e WB Meyer, ''MUNIX, Uma versão de multiprocessamento de UNIX,'' MS Thesis, Naval
Postgraduate School, Monterey, Cal. (1975).
4. DM Ritchie e K. Thompson, ''The UNIXSistema de compartilhamento de tempo,''Bell Sys. Tecnologia j.57(6),
pp.1905-1929 (1978).
5. SR Bourne, ''UNIXSistema de compartilhamento de tempo: The UNIXConcha,''Bell Sys. Tecnologia j.57(6), pp.1971-1990
(1978).

6. EI Organick,ElesULTICSSistema,MIT Press, Cambridge, Mass. (1972).

Você também pode gostar