sistemas_operacionais
TRANSCRIPT
SISTEMAS OPERACIONAIS
JANEIRO 2013
Sistemas Operacionais ii
Sumário
1 Introdução ................................................................................................................... 5
1.1 O Hardware ................................................................................................................................ 5 1.2 O Software ................................................................................................................................. 5 1.3 Máquina de Níveis ..................................................................................................................... 5 1.4 O Sistema Operacional ............................................................................................................... 6
1.4.1 O Sistema Operacional como uma Máquina Estendida ...................................................... 6
1.4.2 O Sistema Operacional como um Gerenciador de Recursos .............................................. 6 1.5 Histórico dos Sistemas Operacionais ......................................................................................... 7
1.5.1 1ª Geração (1945-1955) - válvulas e painéis de conectores ................................................ 7 1.5.2 2ª Geração (1955-1965) - transistores e sistemas de lote .................................................... 7
1.5.3 3ª Geração (1965-1980) - circuitos integrados ................................................................... 8 1.5.4 4ª Geração (1980-hoje) - integração em larga escala .......................................................... 8
1.6 Conceitos de Hardware .............................................................................................................. 8
1.7 Conceitos de Software ............................................................................................................. 10 1.8 Conceitos de Sistema Operacional ........................................................................................... 10
1.8.1 Processos ........................................................................................................................... 10 1.8.2 Arquivos ............................................................................................................................ 11
1.8.3 Interpretador de Comandos (Shell) ................................................................................... 11 1.8.4 Chamadas de Sistema ........................................................................................................ 12
1.9 Tipos de Sistemas Operacionais .............................................................................................. 14 1.9.1 Multiprocessamento .......................................................................................................... 15
1.10 Sistemas Multiprogramáveis .................................................................................................. 16
1.11 Estrutura do Sistema Operacional .......................................................................................... 17 1.11.1 Modos de Operação ........................................................................................................ 17
1.11.2 Ativação e Desativação do Sistema ................................................................................ 17 1.11.3 Sistemas Monolíticos ...................................................................................................... 18
1.11.4 Sistemas em Camadas ..................................................................................................... 18 1.11.5 Máquinas Virtuais ........................................................................................................... 19 1.11.6 Sistemas Microkernel ...................................................................................................... 20
1.12 Exercícios ............................................................................................................................... 20
2 Processos .................................................................................................................. 22
2.1 Mudança de Contexto .............................................................................................................. 23
2.2 Subprocesso e Thread .............................................................................................................. 24 2.3 Tipos de Processos ................................................................................................................... 25 2.4 Processos Cooperativos............................................................................................................ 25 2.5 Exercícios ................................................................................................................................. 26
3 Comunicação Entre Processos.................................................................................. 27
3.1 Condição de Corrida ................................................................................................................ 27
3.2 Região Crítica .......................................................................................................................... 28 3.3 Sincronização Condicional ...................................................................................................... 28 3.4 Exclusão Mútua por Hardware ................................................................................................ 29
3.4.1 Desabilitação de Interrupções ........................................................................................... 29 3.4.2 Instrução TSL (Test and Set Lock) ................................................................................... 29
3.5 Exclusão Mútua por Software com Espera Ativa .................................................................... 30
Sistemas Operacionais iii
3.5.1 Alternância Estrita............................................................................................................. 30 3.5.2 Solução de Peterson .......................................................................................................... 31
3.6 Exclusão Mútua por Software sem Espera Ativa ..................................................................... 31 3.6.1 Semáforos.......................................................................................................................... 31 3.6.2 Monitores .......................................................................................................................... 33
3.6.3 Troca de Mensagens.......................................................................................................... 35 3.7 Deadlock .................................................................................................................................. 37
3.7.1 Prevenção de Deadlock ..................................................................................................... 37 3.7.2 Detecção do Deadlock ...................................................................................................... 38 3.7.3 Correção do Deadlock ....................................................................................................... 38
3.8 Problemas Clássicos de Comunicação Interprocessos ............................................................. 38 3.8.1 Problema dos Filósofos Comendo .................................................................................... 38 3.8.2 Problema dos Leitores e dos Escritores ............................................................................ 40 3.8.3 Problema do Barbeiro Adormecido .................................................................................. 40
3.9 Exercícios ................................................................................................................................. 41
4 Gerência do Processador .......................................................................................... 43
4.1 Algoritmos de Escalonamento ................................................................................................. 44 4.1.1 Escalonamento First In First Out (FIFO) .......................................................................... 44 4.1.2 Escalonamento Shortest Job First (SJF)............................................................................ 44
4.1.3 Escalonamento Cooperativo ............................................................................................. 44 4.1.4 Escalonamento Circular (Round Robin) ........................................................................... 44
4.1.5 Escalonamento por Prioridade .......................................................................................... 45 4.1.6 Escalonamento por Múltiplas Filas ................................................................................... 46 4.1.7 Escalonamento por Múltiplas Filas com Realimentação .................................................. 46
4.1.8 Escalonamento em Sistemas de Tempo Real .................................................................... 47 4.2 Escalonamento com Múltiplas UCPs ....................................................................................... 47
4.3 Exercícios ................................................................................................................................. 47
5 Gerência de Memória ............................................................................................... 49
5.1 Atribuição de Endereços .......................................................................................................... 49 5.2 Carregamento Dinâmico .......................................................................................................... 49
5.2.1 Ligação Dinâmica ............................................................................................................. 50 5.3 Alocação Contígua Simples ..................................................................................................... 50 5.4 Alocação Particionada .............................................................................................................. 50
5.4.1 Alocação Particionada Estática ......................................................................................... 50 5.4.2 Alocação Particionada Dinâmica ...................................................................................... 51
5.4.3 Estratégias para Escolha da Partição ................................................................................. 51 5.5 Swapping .................................................................................................................................. 53 5.6 Memória Virtual ....................................................................................................................... 53
5.6.1 Espaço de Endereçamento Virtual .................................................................................... 54 5.6.2 Paginação .......................................................................................................................... 54
5.6.3 Segmentação ..................................................................................................................... 60
5.6.4 Segmentação com Paginação ............................................................................................ 61
5.6.5 Segmentação com Paginação: O Pentium Intel ................................................................ 62 5.6.6 Proteção ............................................................................................................................. 63 5.6.7 Compartilhamento de Memória ........................................................................................ 63 5.6.8 Trashing ............................................................................................................................ 63
5.7 Exercícios ................................................................................................................................. 64
Sistemas Operacionais iv
6 Sistema de Arquivos ................................................................................................. 67
6.1 Arquivos ................................................................................................................................... 67
6.1.1 Organização de Arquivos .................................................................................................. 67 6.1.2 Métodos de Acesso ........................................................................................................... 68 6.1.3 Tipos de Arquivos ............................................................................................................. 68 6.1.4 Operações de Entrada/Saída .............................................................................................. 69 6.1.5 Atributos............................................................................................................................ 69
6.2 Diretórios ................................................................................................................................. 69 6.3 Alocação de Espaço em Disco ................................................................................................. 70
6.3.1 Alocação Contígua ............................................................................................................ 71 6.3.2 Alocação por Lista Encadeada .......................................................................................... 71 6.3.3 Alocação por Lista Encadeada Utilizando Índice ............................................................. 72
6.3.4 Nós-I (Alocação Indexada) ............................................................................................... 72 6.4 Exemplos de Implementação de Diretórios ............................................................................. 73
6.4.1 Diretórios no CP/M ........................................................................................................... 73
6.4.2 Diretórios no MS-DOS ..................................................................................................... 73 6.4.3 Diretórios no UNIX .......................................................................................................... 74
6.5 Proteção de Acesso .................................................................................................................. 74 6.5.1 Senha de Acesso................................................................................................................ 74
6.5.2 Grupos de Usuários ........................................................................................................... 74 6.5.3 Lista de Controle de Acesso .............................................................................................. 75
6.6 Implementação de Cache ......................................................................................................... 75 6.7 Exercícios ................................................................................................................................. 75
7 Gerência de Dispositivos .......................................................................................... 77
7.1 O Hardware de Entrada/Saída .................................................................................................. 77 7.1.1 Espera em Ciclo ................................................................................................................ 78
7.1.2 Interrupções ....................................................................................................................... 79 7.1.3 Acesso Direto à Memória ................................................................................................. 79
7.2 Operações de Entrada/Saída ..................................................................................................... 79 7.3 Subsistema de Entrada/Saída ................................................................................................... 80
7.4 Drivers de Dispositivo ............................................................................................................. 80 7.5 Controladores ........................................................................................................................... 81 7.6 Dispositivos de Entrada/Saída ................................................................................................. 82
7.6.1 Discos de RAM ................................................................................................................. 82 7.6.2 Disco Rígido ..................................................................................................................... 82
7.7 Exercícios ................................................................................................................................. 85
8 Bibliografia ............................................................................................................... 87
Sistemas Operacionais 5
1 Introdução
1.1 O Hardware
Um sistema computacional moderno consiste em:
1 ou mais processadores;
memória RAM;
disco;
interface de rede;
impressora;
placa de vídeo;
etc.
É extremamente difícil escrever um programa para controlar todos os dispositivos. É preciso
isolar o programador do hardware.
Solução: Colocar uma camada de software por sobre o hardware básico para gerenciar o
sistema e oferecer ao usuário uma máquina virtual.
1.2 O Software
O software permite ao computador recuperar, armazenar e processar informações.
De forma geral pode ser dividido em duas espécies: programas de sistema e programas
aplicativos. Os programas de sistema gerenciam a operação do computador, enquanto os
programas aplicativos executam o trabalho que o usuário deseja.
O sistema operacional é o principal programa de sistema. Ele fornece a base para que
programas aplicativos possam ser escritos.
1.3 Máquina de Níveis
O hardware não tem muita utilidade isoladamente. São os programas (softwares) que realizam
as operações que os usuários desejam. O hardware é o responsável pela execução das instruções do
software para que seja realizada alguma tarefa.
Tanto o hardware quanto o software são logicamente equivalentes, interagindo de forma única
para o usuário.
Nos primeiros computadores, a programação era realizada em painéis, através de fios, o que
era uma grande dificuldade para os programadores da época. A solução foi o surgimento do sistema
operacional, que tornou a interação entre usuário e computador mais simples, confiável e eficiente.
A partir desse acontecimento a parte física do computador tornou-se transparente para o usuário.
Partindo desse princípio, podemos considerar o computador como uma máquina de níveis,
onde inicialmente existem dois níveis: o nível do hardware e o nível do sistema operacional. Assim
o usuário pode enxergar a máquina como sendo apenas o sistema operacional (como se o hardware
não existisse).
Um computador possui tantos níveis quantos forem necessários para adequar o usuário às
suas diversas aplicações. Quando o usuário está trabalhando em um desses níveis, não necessita
saber da existência das outras camadas.
Sistemas Operacionais 6
Os programas de sistema que executam acima do Sistema Operacional executam no modo
usuário, enquanto o sistema operacional executa no modo kernel (ou modo supervisor), sendo
protegido do usuário pelo hardware.
A linguagem de máquina não é um hardware propriamente dito, mas considera-se que ela faz
parte do hardware.
O microprograma só existe em máquinas CISC. Ele tem a função de interpretar a linguagem
de máquina.
1.4 O Sistema Operacional
O sistema operacional é um programa que atua como um intermediário entre o usuário e o
hardware. Seu propósito é fornecer um ambiente no qual os usuários possam executar seus
programas e tem como principal objetivo tornar conveniente o uso de um sistema computacional
usando o hardware de maneira eficiente.
O sistema operacional realiza basicamente duas funções sem muita correlação, funcionando
como uma máquina estendida e como um gerenciador de recursos.
1.4.1 O Sistema Operacional como uma Máquina Estendida
A arquitetura da maior parte dos computadores no nível de linguagem de máquina é difícil de
programar (principalmente E/S). Para escrever em um disco, por exemplo, é preciso escolher a
trilha, a cabeça e o setor, formatar, inicializar a controladora e o dispositivo, etc., tudo isto
escrevendo em registradores.
O sistema operacional é o programa que esconde do programador a complexidade do
hardware, fornecendo-lhe uma interface bastante simples.
O sistema operacional também oculta detalhes relacionados a interrupções, temporizadores,
gerenciamento de memória e outros recursos de baixo nível.
Deste ponto de vista, a função do sistema operacional é apresentar ao usuário uma máquina
estendida (ou máquina virtual), que apesar de equivalente ao verdadeiro hardware é mais fácil de
programar. Ele facilita o acesso aos recursos do sistema.
1.4.2 O Sistema Operacional como um Gerenciador de Recursos
Computadores consistem em processadores, memórias, temporizadores, discos, etc. Sob um
ponto de vista alternativo ao anterior, a função do sistema operacional é oferecer um esquema de
alocação ordenada e controlada dos dispositivos que compõem o computador, entre os programas
que competem por ele.
Exemplo: Acesso à impressora.
Quando um computador tem múltiplos usuários a necessidade de gerenciar e proteger a
memória e outros recursos é ainda maior, para evitar que um usuário interfira no trabalho de outro.
Sistema Bancário
Reserva de Passagem
Navegador WEB
Compila-dores Editores Shell
Sistema Operacional
Linguagem de Máquina
Microprograma
Dispositivos Físicos
Programas aplicativos
Programas de sistema
Hardware
Sistemas Operacionais 7
Este ponto de vista sustenta que a tarefa principal do sistema operacional é monitorar quem
está utilizando qual recurso, atender requisições de recursos, medir a utilização dos recursos e medir
as requisições conflitantes de diferentes processos.
1.5 Histórico dos Sistemas Operacionais
A história dos sistemas operacionais está intimamente relacionada à história da arquitetura das
máquinas nas quais eles rodam.
Os primeiros sistemas operacionais foram desenvolvidos para facilitar o uso do hardware. A
medida que os sistemas operacionais foram desenvolvidos tornou-se evidente que mudanças no
projeto de hardware poderia simplificá-los. Desta forma, os sistemas operacionais não só foram
influenciados pela arquitetura dos computadores, como também influenciaram no desenvolvimentos
destas arquiteturas.
1.5.1 1ª Geração (1945-1955) - válvulas e painéis de conectores
Um único grupo de pessoas projetava, construía, programava e operava cada máquina. Toda a
programação era feita em linguagem de máquina ligando fios aos painéis de conectores (não existia
sequer a linguagem Assembly).
Não existia o conceito de sistema operacional.
O ENIAC (Electronic Numerical Integrator and Computer) foi o primeiro computador
eletrônico digital de propósito geral. Ele possuía 18.000 válvulas, 10.000 capacitores, 70.000
resistores e pesava 30 toneladas. Quando em operação consumia 140 quilowatts e era capaz de
realizar 5.000 adições por segundo.
O UNIVAC I (Universal Automatic Computer) foi a primeira máquina comercial a ser
construída, criada especialmente para o censo americano de 1950.
1.5.2 2ª Geração (1955-1965) - transistores e sistemas de lote
Com a invenção do transistor os computadores se tornaram menores, mais confiáveis, mais
rápidos e passaram a consumir menos energia. As memórias de núcleo magnético foram
substituídas por memórias mais rápidas e com maior capacidade de armazenamento.
Os computadores passaram a ser fabricados e vendidos com a expectativa de uma
durabilidade maior.
Nesta geração surgiram as primeiras linguagens de programação. Para executar um “job” o
programador escrevia seus programas em cartões perfurados (Fortran, Assembly ou Cobol). Os
usuários não interagiam diretamente com o sistema o operador pegava o conjunto de cartões do
programador e os carregava na máquina junto com o conjunto de cartões do compilador. O
resultado era gerado em uma impressora.
O sistema operacional destas máquinas tinha a função de carregar os cartões em memória e
transferir o controle da máquina para o programa recém carregado. Os sistemas operacionais
passaram a ter seu próprio conjunto de rotinas para operações de entrada e saída (E/S), facilitando o
trabalho dos programadores.
Para agilizar o processamento, serviço com necessidades semelhantes eram agrupados
formando um mesmo lote (batch), e executados como um grupo.
Neste ambiente a UCP ficava quase sempre ociosa devido à velocidade dos mecanismos de
E/S.
As máquinas desta geração executavam cerca de 200.000 instruções por segundo.
Sistemas Operacionais 8
1.5.3 3ª Geração (1965-1980) - circuitos integrados
No início da década de 60 a maioria dos fabricantes de computadores tinham duas linhas de
produtos, uma linha científica e uma linha comercial.
A IBM desenvolveu o Sistema 360 (System 360), uma família de máquinas compatíveis a
nível de software, onde elas diferiam em preço e performance. Foi preciso desenvolver um sistema
operacional que executasse em todos os modelos, sendo bom tanto em ambientes científicos quanto
em ambientes comerciais.
O OS/360 era um sistema operacional muito grande escrito em Assembly que continha muitos
problemas, necessitando de um fluxo contínuo de versões. Com ele foram introduzidos os conceitos
de multiprogramação, spool (para entrada e saída de jobs) e tempo compartilhado (com terminais
on-line).
Em 1969 surge o sistema operacional UNIX, construído com base no MULTICS.
As máquinas desta geração executavam cerca de 5 milhões de instruções por segundo.
1.5.4 4ª Geração (1980-hoje) - integração em larga escala
O desenvolvimento de circuitos LSI e VLSI permitiu o desenvolvimento de computadores
pessoais. Surgiram também as estações de trabalho (computadores pessoais mais poderosos,
geralmente ligados em rede).
Surgiram os sistemas operacionais DOS (Disk Operation System) e VMS (Virtual Memory
System).
Surgiram os conceitos de multitarefa e multiprocessamento, assim como máquinas com mais
de um processador, impulsionados por aplicações que exigiam uma grande quantidade de cálculos.
Com a difusão das WANs foram desenvolvidos vários protocolos de rede para a interconexão
de computadores. Os softwares de rede passaram a estar intimamente relacionados ao sistema
operacional e surgem os sistemas operacionais de rede.
As máquinas mais modernas desta geração executam bilhões de instruções de ponto flutuante
por segundo.
1.6 Conceitos de Hardware
A unidade central de processamento (UCP), ou simplesmente processador, controla as
funções realizadas por cada unidade funcional do restante do sistema. Ela executa as instruções dos
programas através de duas unidades básicas: unidade de controle (UC) e unidade lógica e
aritmética (ULA). Sua velocidade é medida pelo número de instruções que ela executa por segundo
(MIPS e FLOPS).
A unidade lógica e aritmética tem por função realizar operações sobre os dados que lhe são
entregues, enquanto cabe à unidade de controle enviar sinais de controle a todas as partes do
sistema para que essas partes possam funcionar de forma coordenada a fim de executar as
instruções que são entregues à UCP.
Memória
Entrada
SaídaUnidade de
Controle
Unidade Lógica e Aritmética
UCP
Sistemas Operacionais 9
O relógio (clock) gera pulsos periódicos para sincronizar o sistema. O sinal do relógio é usado
pela unidade de controle para a execução das instruções. Em conjunto com a UCP determina a
velocidade do sistema.
Registradores são dispositivos de alta velocidade localizados na UCP para armazenamento
temporário de dados. O número de registradores varia de acordo com a arquitetura da UCP. Existem
registradores de uso específico e registradores de uso geral. Alguns exemplos de registradores de
uso específico são: contador de instruções - CI (program counter - PC), apontador da pilha - AP
(stack pointer – SP) e registrador de estado - RE (program status word - PSW).
Na memória principal são armazenados instruções e dados durante a execução de um
programa. É composta por unidades chamadas células, que contém bits. A célula é acessada através
de seu endereço. Uma célula é endereçada através do registrador de endereço de memória - REM
(memory address register - MAR) e do registrador de dados da memória - RDM (Memory Buffer
Register - MBR), ambos localizados na UCP.
As memórias podem ser classificadas, de acordo com a sua volatilidade, em: R/W (RAM),
ROM, PROM, EPROM e EEPROM. Podem ainda ser estáticas ou dinâmicas.
A memória cache é uma memória volátil de alta velocidade que tem seu uso limitado devido
ao alto custo. Ela possui algoritmo próprio de operação, podendo ser separada em cache de
instruções e cache de dados.
A memória secundária é um meio não volátil de armazenamento de programas e dados. Tem
acesso lento quando comparado à memória principal. Disco magnético, CD-ROM e unidade de fita
são exemplos de memória secundária.
Os dispositivos de entrada e saída (E/S) permitem a comunicação entre o computador e o
mundo externo. Existem duas categorias de dispositivos de E/S: dispositivos utilizados como
memória secundária e dispositivos utilizados para a comunicação homem-máquina.
O barramento é um conjunto de condutores onde trafegam dados, endereços e sinais de
controle, interligando os vários componentes do sistema. Exemplos de barramento para
microcomputadores incluem: ISA, EISA, PCI, PCI-Express, AGP, USB, SCSI e SATA.
Processadores RISC (Reduced Instruction Set Computer) se caracterizam por possuírem
poucas instruções, que são executadas diretamente pelo hardware. Costumam possuir um grande
número de registradores. Exemplos de processadores RISC são Sparc (Sun), RS-6000 (IBM), Alpha
(DEC) e o MIPS.
Unidade de Controle
Unidade Lógica e Aritmética
Registradores
Acumulador RDM
REM
CI
RI
Decodificador de instruções
UC
Clock
Memória Principal
Barramentode dados
Barramentode endereços
ULA
Bar
ram
en
toin
tern
o
Sistemas Operacionais 10
Processadores CISC (Complex Instruction Set Computer) possuem instruções complexas que
são interpretadas pelo microprograma da UCP e costumam possuir um pequeno número de
registradores. Exemplos de processadores CISC são a linha x86 e Pentium da Intel e a linha 68xxx
da Motorola.
1.7 Conceitos de Software
O hardware do computador tem pouca utilização prática se não houver um conjunto de
softwares que permitam ao usuário utilizar a máquina.
O tradutor faz o mapeamento de uma linguagem em outra. Quando traduz da linguagem
Assembly para um código objeto é conhecido como montador (assembler). Quando traduz de uma
linguagem de alto nível para um código objeto é conhecido como compilador.
O interpretador é um software que no momento da execução do programa traduz cada
instrução e a executa. A maior desvantagem dos interpretadores é o tempo gasto com a tradução no
momento da execução. Sua vantagem é permitir tipos de dados dinâmicos.
O linker é o responsável por gerar, a partir de um ou mais módulos objeto, um único
programa executável. Ele deve procurar em bibliotecas as funções referenciadas nos objetos.
O loader é o programa responsável por carregar um programa em memória para execução.
O depurador (debugger) é um utilitário que permite ao usuário controlar a execução de um
programa a fim de detectar erros.
A linguagem de máquina é a linguagem que a UCP entende e pode ser processada
diretamente por seu hardware.
O microprograma é um programa interno às UCPs CISC que interpreta a linguagem de
máquina destas UCPs. Processadores microprogramáveis permitem a criação de novas instruções
através da modificação de seu microprograma.
1.8 Conceitos de Sistema Operacional
A interface entre o sistema operacional e os programas de usuário é definida pelo conjunto de
instruções estendidas que o sistema operacional proporciona. Estas instruções estendidas são
conhecidas por chamadas de sistema (ou system calls).
As chamadas de sistema variam de um sistema operacional para outro.
1.8.1 Processos
Um processo é basicamente um programa em execução mais seu espaço de endereçamento
(incluindo valores de variáveis) e conjunto de registradores.
Associado a cada processo está seu espaço de endereçamento (ou imagem de núcleo). O
espaço de endereçamento é uma lista de endereços da memória onde o processo pode ler e escrever,
contendo o programa executável, os dados e a pilha.
Também associado com cada processo está um conjunto de registradores (contador de
instruções, ponteiro da pilha, etc.).
Periodicamente o sistema operacional pode parar de executar um processo e começar a
executar outro. Mais tarde o processo deve ser reiniciado no mesmo estado em que estava quando
parou. As informações sobre o processo são armazenadas em uma tabela conhecida como tabela de
processos.
As principais chamadas do sistema de gerenciamento de processos são as que lidam com a
criação e com o encerramento de processos. Se um processo pode criar um ou mais processos filho,
que podem criar novos processos filho, obtém-se uma árvore de processos.
Sistemas Operacionais 11
Processos que cooperam para executar determinada tarefa costumam se comunicar. Essa
comunicação é chamada de comunicação interprocessos.
Ocasionalmente é necessário transmitir informações para um processo que não as está
aguardando. Neste caso envia-se um sinal ao processo.
A cada usuário do sistema corresponde um uid (identificação de usuário). Cada processo tem
a uid do usuário que o iniciou.
1.8.2 Arquivos
Uma das funções do sistema operacional é esconder as peculiaridades dos discos e outros
dispositivos de E/S. São necessárias chamadas de sistema para criar, remover, ler e escrever
arquivos.
Para fornecer um local para manter os arquivos existe o conceito de diretório. São necessárias
chamadas de sistema para criar e remover diretórios, assim como inserir e remover arquivos de
diretórios. As entradas de diretórios podem ser arquivos ou diretórios.
Cada arquivo dentro da hierarquia de diretórios pode ser especificado por seu nome de
caminho a partir do diretório raiz (por seu caminho absoluto) ou por seu caminho a partir do
diretório de trabalho atual (caminho relativo). Processos podem mudar seu diretório de trabalho
emitindo uma chamada de sistema.
Arquivos e diretórios costumam ser protegidos. As permissões são verificadas no momento
em que é solicitada a abertura de um arquivo. Se o acesso for permitido é devolvido um número
inteiro chamado descritor de arquivo.
Alguns sistemas operacionais permitem acesso a um sistema de arquivos somente se ele
estiver montado em um sistema de arquivos raiz ou primário.
Existe um recurso denominado pipe que é um tipo de pseudo-arquivo que pode ser utilizado
para conectar dois processos.
1.8.3 Interpretador de Comandos (Shell)
Um dos componentes mais importantes de um sistema operacional é o interpretador de
comandos (shell), que constitui a interface entre o usuário e o sistema operacional. Alguns sistemas
operacionais incluem o interpretador de comandos em seu núcleo, enquanto outros tratam o
interpretador de comandos como um programa especial.
Uma forma usual de requerer que uma tarefa seja realizada pelo sistema operacional é por
meio do uso de comandos de controle. Nestes sistemas, um programa que lê e interpreta comandos
de controle é automaticamente executado. Tal programa é frequentemente conhecido como a shell
(casca) do sistema. Sua tarefa é bastante simples: obter o próximo comando e providenciar para que
ele seja executado.
Nesse aspecto, os sistemas operacionais costumam ser bastante diferentes uns dos outros. Um
exemplo de interface amigável pode ser encontrado no Macintosh e no Windows, com janelas,
menus e mouse. Outros usuários preferem interpretadores de comandos com interfaces mais
poderosas, complexas e mais difíceis de aprender, onde são digitados comandos que são mostrados
em uma tela ou terminal. As interfaces dos interpretadores de comandos do UNIX e do MS-DOS
são desta forma.
O interpretador de comandos, faz uso pesado de muitos recursos do sistema operacional,
sendo a interface primária entre o usuário e o sistema operacional.
Exemplos de comandos:
date
date > arq
Sistemas Operacionais 12
sort <arq1 >arq2
cat arq1 arq2 arq3 | sort > /dev/lp
cat arq1 arq2 arq3 | sort > /dev/lp &
1.8.4 Chamadas de Sistema
A interação entre os processos dos usuários e o sistema operacional é feita por meio de
chamadas de sistema (system calls). Em geral as chamadas de sistema estão disponíveis em
linguagem Assembly, mas alguns sistemas permitem que as chamadas de sistema sejam feitas
diretamente em uma linguagem de alto nível.
Serão vistos alguns exemplos de chamadas de sistemas do padrão POSIX divididos em seis
categorias:
gerenciamento de processos;
sinais;
gerenciamento de arquivos;
gerenciamento de diretório;
proteção;
gerenciamento de tempo.
Chamadas de sistema para gerenciamento de processos:
pid = fork()→ Cria um processo filho.
pid = waitpid(pid, &statloc, opts) → Bloqueia até que o processo filho termine.
s = wait(&status) → Bloqueia até que um processo filho termine. Obsoleta.
s = execve(name, arg, envp) → Substitui a imagem de memória por um processo.
exit(status) → Termina o processo.
size = brk(addr) → Especifica o segmento de dados.
pid = getpid()→ Retorna o id do processo.
pid = getpgrp()→ Retorna o id do grupo do processo.
l = ptrace(req, pid, addr, data) → Permite controlar a execução de outro processo.
Exemplo de um shell simples:
while (1) {
ler_comando(comando, parametros);
if (fork() != 0)
waitpid(-1, &status, 0); /* código pai */
else
execve(comando, parametros, 0); /* executa comando */
}
Chamadas de sistema para sinalização:
s = sigaction(sig, &act, &oldact) → Especifica a ação a ser executada para um sinal.
s = sigreturn(&context) → Retorna de uma rotina de tratamento de sinal.
s = sigprocmask(how, &set, &old) → Altera a lista de sinais bloqueados.
s = sigpending(set) → Retorna o conjunto de sinais pendentes por loqueio.
s = sigsuspend(mask) → Suspende o processo até que chegue um dos sinais selecionados.
s = kill(pid, sig) → Envia um sinal a um processo.
residual = alarm(seconds) → Programa um sinal SIGALARM.
s = pause() → Suspende o processo até a chegada de um sinal.
Exemplo:
Sistemas Operacionais 13
void handler(int sig)
{
printf("Terminada a execucao\n");
exit(0);
}
main()
{
signal(SIGINT, handler);
while (1);
}
Chamadas de sistema para gerenciamento de arquivos:
fd = create(name, mode) → Cria um novo arquivo. É uma chamada obsoleta.
fd = mknod(name, mode, addr) → Cria arquivos especiais.
fd = open(file, how, ...) → Abre um arquivo, criando se necessário.
s = close(fd) → Fecha o arquivo.
n = read(fd, buffer, nbytes) → Lê dados de um arquivo.
n = write(fd, buffer, nbytes) → Grava dados em um arquivo.
pos = lseek(fd, offset, whence) → Move o ponteiro do arquivo.
s = stat(name, &buf) → Retorna informações sobre um arquivo.
s = fstat(fd, &buf) → Retorna informações sobre um arquivo aberto.
fd = dup(fd) → Cria cópia do descritor de arquivos.
s = pipe(&fd[0]) → Cria um par de descritor de arquivos ligados por um pipe.
s = ioctl(fd, request, argp) → Operações em arquivos especiais.
f = access(name, amode) → Verifica se o processo tem permissão para acessar o arquivo.
s = rename(old, new) → Muda o nome do arquivo.
s = fcntl(fd, cmd, ...) → Executa operações especiais em arquivos.
Exemplo de configuração de pipeline entre 2 processos:
pipeline (char *process1, char *process2)
{
int fd[2];
pipe(&fd[0]);
if (fork() != 0) { /* processo pai */
close(fd[0]);
close(STD_OUTPUT);
dup(fd[1]);
close(fd[1]);
execve(process1, NULL, 0);
}
else { /* processo filho */
close(fd[1]);
close(STD_INPUT);
dup(fd[0]);
close(fd[0]);
execve(process2, NULL, 0);
}
Chamadas de sistema para gerenciamento de diretórios:
s = mkdir(name, mode) → Cria diretório.
s = rmdir(name) → Remove diretório vazio.
s = link(name1, name2) → Cria uma nova entrada para um arquivo em um diretório.
s = unlink(name) → Remove uma entrada de diretório.
s = mount(special, name, flag) → Monta um sistema de arquivos.
s = umount(special) → Desmonta um sistema de arquivos.
s = sync()→ Força a escrita de blocos em cache no disco.
Sistemas Operacionais 14
s = chdir(dirname) → Muda o diretório de trabalho.
s = chroot(dirname) → Muda o diretório raiz.
Chamadas de sistema para proteção:
s = chmod(name, mode) → Muda as permissões de um arquivo.
uid = getuid()→ Retorna o id do usuário do processo.
gid = getgid()→ Retorna o id do grupo do usuário do processo.
s = setuid(uid) → Muda o id do usuário do processo.
s = setgid(gid) → Muda o id do grupo do processo.
s = chown(name, owner, group) → Muda o dono do arquivo.
oldmask = umask(complmode) → Muda a máscara de criação de arquivos.
Chamadas de sistema para gerenciamento de tempo:
seconds = time(&seconds) → Obtém a hora.
s = stime(tp) → Ajusta o relógio.
s = utime(file, timep) → Altera a hora de último acesso do arquivo.
s = times(buffer) → Obtém quanto tempo de UCP o processo utilizou.
1.9 Tipos de Sistemas Operacionais
Os sistemas operacionais podem ser classificados em sistema monoprogramável/monotarefa,
sistema multiprogramável/multitarefa e sistema com múltiplos processadores.
O sistema monoprogramável/monotarefa é voltado para a execução de um único programa.
Para executar outro programa, o programa corrente em execução deve terminar antes. Processador,
memória, periféricos e outros recursos são dedicados exclusivamente ao programa em execução.
Nesses sistemas enquanto um programa aguarda por um evento o processador permanece ocioso.
Caso a memória não seja totalmente utilizada pelo programa ela é subutilizada. Não existe
preocupação com a proteção do sistema.
No sistema multiprogramável/multitarefa vários programas podem estar em execução
“simultaneamente”. O sistema operacional se preocupa em gerenciar, de forma ordenada e
protegida, o acesso concorrente aos seus recursos. Dependendo do número de usuários que podem
usar o sistema, ele pode ser classificado como monousuário ou multiusuário. Um sistema
monousuário é aquele em que somente um usuário pode estar utilizando o sistema, mesmo que
executando mais de um programa em um determinado instante. Já um sistema multiusuário permite
que mais de um usuário utilize o sistema simultaneamente. As vantagens dos sistemas
multiprogramáveis são o aumento da produtividade dos usuários e a redução de custos a partir do
compartilhamento dos diversos recursos do sistema.
Os sistemas multiprogramáveis/multitarefa podem ser classificados pela forma como suas
aplicações são gerenciadas em:
Sistemas batch → Programas ficam armazenados em discos ou fitas, esperando para
serem executados sequencialmente.
Sistemas batch multiprogramados → Programas ficam armazenados na memória. Quando
um programa que está sendo executado e precisa esperar por uma tarefa, em vez da UCP
ficar ociosa ela passa a executar outro programa, e assim por diante.
Sistemas de tempo compartilhado (time-sharing) → São uma extensão da
multiprogramação. Permitem a interação de usuários com o sistema através de terminais.
Cada usuário recebe uma fatia do tempo do processador.
Sistemas Operacionais 15
Sistemas de tempo real → Semelhantes aos sistemas de tempo compartilhado, atendendo
ao tempo de resposta exigido para as aplicações. O escalonamento não se dá por fatia de
tempo, mas sim por prioridade.
Os sistemas com múltiplos processadores são estudados no tópico a seguir
(multiprocessamento).
1.9.1 Multiprocessamento
Um sistema multiprocessado é um sistema que possui mais de uma UCP, localizadas
próximas uma das outras, se comunicando e compartilhando um barramento, por onde são
transmitidos os dados entre os componentes do sistema computacional.
A principal vantagem do multiprocessamento é o aumento da quantidade de trabalho
realizado por unidade de tempo (throughput) do sistema como um todo.
Um sistema multiprocessado pode ser desenvolvido para apresentar maior confiabilidade,
sendo construído de forma que a parada de um processador não provoque a parada do sistema como
um todo.
Os sistemas com múltiplos processadores caracterizam-se por possuir duas ou mais UCPs
interligadas, trabalhando em conjunto. Em função da forma como as UCPs se comunicam e do grau
de compartilhamento de memória e outros recursos, os sistemas com múltiplos processadores
podem ser classificados em fortemente acoplados ou fracamente acoplados.
Nos sistemas fortemente acoplados existem dois ou mais processadores compartilhando uma
única memória e controlados por um único sistema operacional. São geralmente utilizados no
processamento de aplicações que fazem uso intensivo da UCP. Sua grande vantagem é o aumento
do throughput. Subdividem-se ainda em sistemas simétricos e sistemas assimétricos.
Nos sistemas fortemente acoplados assimétricos existe um processador principal,
responsável pelo controle dos demais processadores e pela execução do sistema operacional. O
processador principal também é responsável por realizar todas as operações de entrada e saída.
Nos sistemas fortemente acoplados simétricos todos os processadores realizam a mesma
função, exceto pela inicialização do sistema (boot), que é realizada por um único processador. O
sistema operacional deve ser construído de forma a evitar conflitos entre os processadores.
Nos sistemas fracamente acoplados existem dois ou mais sistemas de computação
conectados através de linhas de comunicação. Cada sistema possui seu próprio sistema operacional
e funciona de forma independente. Sua utilização é caracterizada pelo processamento distribuído
entre os vários processadores. Subdividem-se ainda em sistemas operacionais de rede e sistemas
operacionais distribuídos.
No sistema operacional de rede cada nó possui seu próprio sistema operacional, podendo
inclusive ser diferente dos demais. Caso haja perda da conexão entre os nós, os sistemas podem
operar normalmente, podendo apenas ter alguns recursos indisponíveis. Permite, entre outras, cópia
remota de arquivos, emulação de terminal, impressão remota, gerência remota e correio eletrônico.
Os usuários estão cientes da existência de múltiplos computadores e podem se conectar a máquinas
remotas.
No sistema operacional distribuído cada nó da rede possui seu próprio sistema operacional,
mas com um relacionamento mais forte entre os nós. Geralmente os sistemas operacionais dos nós é
o mesmo. Aparece para os usuários como um sistema tradicional. O usuário não sabe onde seus
programas estão sendo executados e onde seus arquivos são armazenados. Permite que um
aplicativo execute em mais de um processador. Os sistemas distribuídos podem ser considerados
como uma evolução dos sistemas fortemente acoplados, onde uma aplicação pode ser executada por
qualquer processador. Sua grande vantagem é a possibilidade de redundância do sistema.
Sistemas Operacionais 16
Mesmo que um sistema não tenha programas que tenham sido escritos para tirar vantagem da
máquina ter mais de um processador, é vantajoso ter mais de um processador na máquina, uma vez
que isto permitirá que mais de um processo seja executado simultaneamente, aumentando o
throughput do sistema.
1.10 Sistemas Multiprogramáveis
A possibilidade de periféricos e dispositivos funcionarem simultaneamente, juntamente com a
UCP, permitiu a execução de tarefas concorrentes, que é o princípio para projeto de sistemas
multiprogramáveis.
Nos sistemas multiprogramáveis vários processos podem estar residentes em memória
concorrendo pela utilização da UCP. Quando um processo solicita uma operação de E/S, outros
processos poderão estar disponíveis para utilizar a UCP.
A utilização concorrente da UCP deve ser implementada de maneira que, quando um processo
perde seu uso e depois retorna para continuar o processamento, seu estado deve ser idêntico ao do
momento em que foi interrompido.
Durante a execução de um processo, alguns eventos podem ocorrer, obrigando a intervenção
do sistema operacional. Este tipo de intervenção é chamado interrupção ou exceção. O que
diferencia uma interrupção de uma exceção é o tipo de evento que gera esta condição.
Uma interrupção é gerada pelo sistema operacional ou por algum dispositivo e independe do
programa que está sendo executado.
No momento em que a unidade de controle detecta a ocorrência de algum tipo de interrupção,
o programa em execução é interrompido, e o controle é desviado para uma rotina responsável pelo
tratamento da interrupção. Muitas vezes, após a execução dessa rotina, o controle deve voltar ao
programa que estava sendo processado. Para isso acontecer é necessário que, no momento da
interrupção, um conjunto de informações sobre a execução do programa seja preservado. Essas
informações consistem no conteúdo de alguns registradores que deverão ser restaurados
posteriormente para a continuação do programa.
No momento que uma interrupção acontece, a UCP deve saber para qual rotina de tratamento
deverá ser desviado o fluxo de execução. Essa informação está em uma estrutura do sistema
chamada vetor de interrupção.
A interrupção é o mecanismo que tornou possível a implementação da concorrência nos
computadores, sendo o fundamento dos sistemas multiprogramáveis.
Uma exceção é resultado direto da execução de uma instrução do próprio programa. O
mecanismo de tratamento de exceções é semelhante ao de interrupções e podem ser escritas pelo
próprio programador. Dessa forma, é possível evitar que um programa seja encerrado no caso de
ocorrer, por exemplo, uma divisão por zero.
Obtém o endereço da rotina de tratamento
Salva os registradores
Identifica a origemda interrupção
Restaura os registradores
Programa
Rotina de tratamento
Interrupção
Sistemas Operacionais 17
1.11 Estrutura do Sistema Operacional
O sistema operacional é formado por um conjunto de rotinas que oferecem serviços às
aplicações dos usuários do sistema, bem como a outras rotinas do próprio sistema. Esse conjunto de
rotinas é chamado de kernel ou núcleo do sistema.
As principais funções do kernel são:
tratamento de interrupções;
criação e eliminação de processos;
sincronização e comunicação entre processos;
escalonamento e controle dos processos;
gerência de memória;
gerência do sistema de arquivos;
operações de E/S;
contabilização e segurança do sistema.
Para proteger o kernel, os usuários solicitam serviços através de chamadas de sistemas.
1.11.1 Modos de Operação
Para que um sistema operacional possa trabalhar de forma segura é preciso existir um
esquema de proteção que proteja o sistema operacional e todos os outros programas e dados de
qualquer programa incorreto. Assim é necessário que o hardware ofereça pelo menos dois modos de
operação para diferenciar os modos de execução. Estes modos são conhecidos como modo usuário
e modo kernel. O modo kernel também é conhecido como modo supervisor ou modo privilegiado.
Existem instruções que só devem ser executadas pelo sistema operacional para impedir
problemas de segurança do sistema. Instruções que tem o poder de comprometer o sistema são
conhecidas como instruções privilegiadas, enquanto instruções que não oferecem perigo ao sistema
são conhecidas como instruções não privilegiadas.
Quando o sistema está no modo usuário as aplicações podem executar somente instruções
não privilegiadas. Quando o sistema está no modo kernel as aplicações podem executar qualquer
instrução do processador.
Durante a inicialização do sistema (boot) o sistema está no modo kernel. Quando o sistema
operacional é carregado ele inicia a execução de processos do usuário no modo usuário. Sempre que
ocorre uma interrupção o modo de operação muda para o modo kernel e, antes de transferir o
controle da máquina para um processo do usuário, ele coloca a UCP novamente no modo usuário.
Sempre que uma aplicação necessita de um serviço privilegiado ela deve fazer uma chamada
de sistema. A chamada de sistema muda o modo de operação para o modo kernel, executando o
serviço e voltando ao modo usuário.
Caso uma aplicação tente executar uma instrução privilegiada sem estar no modo kernel uma
exceção será gerada.
1.11.2 Ativação e Desativação do Sistema
Toda vez que um computador é ligado, é necessário que o sistema operacional seja carregado
da memória secundária para a memória principal. Esse processo é denominado ativação do sistema
(boot).
AplicaçãoChamada
de SistemaNúcleo Hardware
Sistemas Operacionais 18
Além da carga do sistema operacional, a ativação do sistema também consiste na execução de
arquivos de inicialização. Nestes arquivos são especificados procedimentos de inicialização e
configuração de hardware e software específicos para cada ambiente.
Na maioria dos sistemas também existe o processo de desativação (shutdown). Este
procedimento permite que as aplicações e componentes do sistema sejam desativados de forma
ordenada, garantindo a integridade do sistema.
1.11.3 Sistemas Monolíticos
Não existe uma estrutura real. O sistema operacional é escrito como uma coleção de
procedimentos, cada um podendo chamar qualquer um dos outros. Os procedimentos são
compilados e “linkados” em um único programa executável. Cada procedimento é visível aos
demais.
É possível ter um mínimo de estrutura requisitando-se chamadas de sistema ao colocar os
parâmetros em locais bem definidos e chamar interrupções. A máquina é comutada para o modo
kernel e o controle é transferido ao sistema operacional. Quando a chamada termina o controle é
devolvido ao programa de usuário.
1.11.4 Sistemas em Camadas
São definidas camadas sobrepostas onde cada camada oferece um conjunto de funções que
podem ser utilizadas pelas camadas superiores.
A vantagem da estruturação em camadas é isolar as funções do sistema operacional, criando
uma hierarquia e protegendo as camadas mais internas.
A depuração de erros também é simplificada. Como cada nível usa apenas operações
fornecidas por níveis inferiores, o sistema operacional pode ser depurado da camada mais interna
para a mais externa sem grandes dificuldades.
Um problema com a implementação em camadas é a queda de desempenho provocada no
sistema, uma vez que qualquer operação deve passar por todas as camadas inferiores até chegar ao
hardware.
Programa de usuário 2
Programa de usuário 1
1
2
3
4
Programas deusuário executados
no modo usuário
Sistema operacional executado nomodo kernel
1. Interrupção gerada para o kernel2. S.O. determina o serviço necessário3. S.O. chama o procedimento de serviço4. Controle retorna para o programa de usuário
Kernel
Executivo
Supervisor
Usuário
VMS
Operador
Programas de usuário
Entrada/Saída
Comunicação
Gerência de memória
Multiprogramação
MULTICS
0
1
2
3
4
5
Sistemas Operacionais 19
Abaixo segue uma breve descrição das camadas do MULTICS.
A camada 0 lida com a alocação do processador alternando entre processos quando ocorrem
interrupções ou quando temporizadores expiram. Acima da camada 0 os processos não precisam se
preocupar se existe mais de um processo sendo executado.
A camada 1 faz o gerenciamento de memória, alocando espaço para os processos. Acima dela
os processos não precisam se preocupar se estão em memória ou em disco.
A camada 2 manipula a comunicação de cada processo com a console do operador. Acima
dela cada processo tem seu próprio console de operador.
A camada 3 cuida de gerenciar os dispositivos de E/S. Acima dela cada processo pode lidar
com dispositivos de E/S abstratos amigáveis.
Na camada 4 residem os programas de usuários, que não precisam se preocupar com
gerenciamento de processos, memória, console e E/S.
O processo do operador do sistema se localiza na camada 5.
1.11.5 Máquinas Virtuais
No hardware básico é executado o monitor de máquina virtual, oferecendo várias máquinas
virtuais à camada superior. Tais máquinas virtuais são cópias exatas do hardware básico e cada uma
pode executar qualquer sistema operacional.
Quando um processo executa uma chamada de sistema:
A chamada é interceptada pelo sistema operacional da máquina virtual;
O sistema operacional emite as instruções normais de E/S de hardware;
As instruções são interceptadas pelo monitor de máquina virtual que as executa
simulando o hardware real.
Colocando a multiprogramação como responsabilidade do monitor de máquina virtual pode-
se oferecer uma máquina estendida mais simples, flexível e fácil de manter.
O modelo de máquina virtual não só oferece a possibilidade de executar diferentes sistemas
operacionais em um mesmo hardware como também garante o isolamento total entre as máquinas,
fornecendo um excelente nível de segurança entre elas.
Em uma outra abordagem a máquina virtual pode ser criada com um subconjunto dos recursos
da máquina real. Em uma camada mais baixa é executado um programa chamado exokernel, que
atribui recursos às máquinas virtuais e garante que nenhuma máquina virtual utilize recursos de
outra. Cada máquina virtual pode ter seu próprio sistema operacional. Tem a vantagem de
economizar uma camada de mapeamento necessária ao monitor de máquina virtual, já que o
monitor de máquina virtual precisa manter uma tabela dos recursos em uso por cada máquina
virtual.
processos
kernel
hardware
processos
kernel
processos
kernel
processos
kernel
Monitor de máquina virtual
hardware
Máquina sem monitorde máquina virtual
Máquina com monitorde máquina virtual
Sistemas Operacionais 20
1.11.6 Sistemas Microkernel
Uma tendência nos sistemas operacionais modernos é mover o máximo do código para as
camadas superiores, implementando parte das funções do sistema operacional em processos de
usuário. Isto faz com que o núcleo seja menor e mais simples.
Para requisitar um serviço um processo de usuário (processo cliente) envia uma requisição a
um processo servidor. O kernel gerencia a comunicação entre os processos.
Tal divisão torna as partes menores e mais fáceis de gerenciar. Para trocar alguma
característica do sistema operacional basta modificar o servidor específico.
Se algum servidor falhar o sistema operacional não ficará completamente comprometido.
O modelo se adapta facilmente a sistemas com múltiplos processadores e sistemas
distribuídos. Quando o cliente se comunica com um servidor ele não precisa saber em que
processador ou máquina o servidor se encontra.
Nem todos os processos servidores podem executar em modo usuário, já que alguns precisam
utilizar instruções privilegiadas (drivers, por exemplo). O kernel deve incorporar estas funções ou
devem existir alguns processos servidores críticos executando em modo kernel. Outra solução é
implementar mecanismos no kernel deixando a política para os servidores no espaço do usuário.
1.12 Exercícios
1) Qual a diferença entre programas aplicativos e programas de sistema? O sistema operacional é
um programa de sistema ou um programa aplicativo?
2) Qual o objetivo em se instalar um sistema operacional em um computador?
3) Quais as dificuldades que um programador teria no desenvolvimento de uma aplicação para um
ambiente sem sistema operacional?
4) Explique a seguinte afirmação: “O sistema operacional pode ser visto como uma máquina
estendida ou como um gerenciador de recursos.”
5) Explique o conceito de máquina estendida. Qual sua vantagem?
6) Qual a função da unidade central de processamento (UCP) de um computador?
7) Quais são os componentes de uma UCP e quais são suas funções?
8) Qual a diferença entre registrador, memória cache, memória principal e memória secundária?
9) O que são memórias voláteis e não voláteis?
10) Quais as diferenças entre processadores CISC e processadores RISC?
11) O que se entende por “ativação do sistema (boot)” e “desativação do sistema (shutdown)”.
12) Qual a diferença entre tradutor e interpretador?
13) Qual a diferença entre um compilador e um linker?
14) O que é um microprograma?
15) Por que o código objeto gerado por um compilador ainda não pode ser executado?
16) Por que a execução de programas interpretados é mais lenta do que a de programas compilados?
17) Qual a função do linker?
Processocliente
Processocliente
Processoservidor
Servidorterminal ...... Servidor
arquivosServidormemória
kernel
Modo usuário
Modo kernel
Sistemas Operacionais 21
18) Qual a função do loader?
19) Como um depurador pode facilitar o trabalho de um programador?
20) O que são as chamadas de sistema de um sistema operacional?
21) O que é um interpretador de comandos (shell)?
22) O que diferencia um sistema operacional monoprogramável/monotarefa de um sistema
operacional multiprogramável/multitarefa? Que tipo de importância cada um deles costuma dar
à proteção do sistema?
23) Por que diz-se que um sistema operacional monoprogramável subutiliza o sistema?
24) Por que a proteção do sistema é tão importante para um sistema operacional multitarefa?
25) O que vem a ser um sistema com múltiplos processadores? Qual a vantagem?
26) É vantajoso ter mais de uma UCP em uma máquina somente se esta máquina executar
programas aplicativos desenvolvidos para explorar o paralelismo da máquina? Explique.
27) O que vem a ser um sistema multiprogramável?
28) O que caracteriza um sistema batch?
29) Como funcionam os sistemas de tempo compartilhado? Qual a vantagem na sua utilização?
30) Qual a diferença entre sistemas de tempo compartilhado e de tempo real?
31) Quando se deve utilizar um sistema operacional de tempo real?
32) Qual a diferença entre sistema multiprocessados simétricos e assimétricos?
33) Qual a importância do mecanismo de interrupção para um sistema operacional multitarefa?
34) Qual a diferença entre uma interrupção e uma exceção?
35) Explique o funcionamento do mecanismo de interrupções.
36) O que é o núcleo (kernel) do sistema operacional? Cite 2 funções importantes.
37) Qual a diferença entre um processo estar executando no modo usuário ou no modo kernel?
38) O que são instruções privilegiadas e não privilegiadas?
39) O que é uma chamada de sistema? Qual sua importância para a segurança do sistema?
40) Como um programador pode fazer para executar uma chamada de sistema em seu programa?
41) O que é um sistema operacional com estrutura monolítica?
42) Cite uma vantagem de se projetar um sistema operacional em camadas.
43) Qual a função do monitor de máquina virtual?
44) O que se pode falar sobre a interferência do funcionamento de uma máquina virtual em outra
dentro de um mesmo computador?
45) O que caracteriza um sistema operacional com estrutura microkernel?
46) Como funciona o modelo cliente-servidor na arquitetura microkernel? Cite vantagens na sua
utilização?
Sistemas Operacionais 22
2 Processos
Os primeiros sistemas permitiam a execução de apenas um programa de cada vez. Esse
programa tinha o controle completo do sistema e acesso a todos os seus recursos. Os sistema atuais
permitem que diversos programas sejam carregados na memória e executados simultaneamente.
Essa evolução tornou necessário um controle maior na divisão de tarefas dos vários programas,
resultando na noção de processo.
Em um sistema multiprogramável a UCP alterna entre processos, dedicando um pouco de seu
tempo a cada um, dando a ilusão de paralelismo. Este esquema costuma ser chamado de
pseudoparalelismo.
Neste modelo, todo software executado no computador é organizado em processos
sequenciais, ou, simplesmente, processos. Este modelo foi desenvolvido para tornar o paralelismo
mais fácil de tratar, uma vez que monitorar atividades paralelas é muito difícil.
Um processo é um programa em execução, incluindo os valores atuais dos registradores e
variáveis, assim como seu espaço de endereçamento. Um programa por si só não é um processo,
mas uma entidade passiva. Um processo é uma entidade ativa, com um contador de instruções que
especifica a próxima instrução a ser executada e um conjunto de registradores a ele associado.
Embora dois processos possam estar associados a um mesmo programa, são duas sequências
de execução distintas.
Conceitualmente, cada processo tem sua própria UCP. Com a UCP alternando entre os
processos, a velocidade com que um processo executa não será uniforme.
É preciso dispor de uma forma de criar e destruir processos quando for necessário. No UNIX
os processos são criados pela chamada de sistema “fork” que cria uma cópia exata do processo em
execução. Em outros sistemas existem chamadas para criar um processo, carregar sua memória, e
começar a executar. O Windows pode criar processos das duas formas.
Eventualmente um processo que está em execução necessita parar momentaneamente sua
execução por estar aguardando uma informação ainda não disponível ou porque já executou por
muito tempo e precisa liberar a UCP para outro processo.
Um processo pode mudar de estado, podendo estar nos estados novo, executando, pronto,
bloqueado ou terminado.
Para implementar o modelo de processos o sistema operacional mantém uma tabela de
processos, com uma entrada por processo. Esta entrada é chamada de Bloco de Controle de
Processo – BCP (Process Control Block – PCB), e contém todas as informações do processo.
Algumas entradas do BCP são:
AB
CD
Pro
cess
o
Tempo
4 processos concorrendo
Executando
BloqueadoNovo Terminado
Pronto
15
2
3
4 6
1. O processo é admitido no sistema2. O escalonador põe o processo selecionado em execução3. O processo é interrompido4. O processo bloqueia para E/S ou evento5. Fim de E/S ou ocorrência de evento esperado6. O processo termina sua execução
Sistemas Operacionais 23
estado do processo
prioridade do processo
número do processo
registradores da UCP
informações relativas ao gerenciamento de memória
informações de contabilidade
informações sobre operações de E/S
etc.
Essa visão dá origem ao seguinte modelo:
O nível mais baixo do sistema operacional é o escalonador (também conhecido como
agendador). Ele cuida do gerenciamento de interrupções e dos detalhes de como iniciar e parar
processos. Também costuma ser muito pequeno.
Um processo passa pelas várias filas de seleção durante sua execução. Cabe ao escalonador
selecionar processos destas filas e decidir qual será o próximo a ser executado.
O escalonador é chamado com muita frequência. Um processo pode ser executado por apenas
alguns milissegundos e ter que esperar por ter feito uma requisição de E/S. O escalonador costuma
ser chamado pelo menos uma vez a cada 100 ms para realizar a troca de processos. Devido ao
pequeno intervalo de tempo entre as chamadas ao escalonador sua execução deve ser bastante
rápida para que não se gaste muito tempo de UCP com trabalho de gerência.
2.1 Mudança de Contexto
Para transferir o controle da UCP de um processo para outro é necessário guardar o estado do
processo em execução e carregar o estado do processo a entrar em execução. Esta tarefa é
conhecida como mudança de contexto (ou troca de contexto).
O tempo gasto na mudança de contexto varia e depende de fatores como velocidade da
memória, quantidade de registradores e existência de instruções especiais. Este tempo costuma
variar de 1 a 1000 s.
O contexto de um processo pode ser dividido em 3 elementos básicos: contexto de hardware,
contexto de software e espaço de endereçamento.
O contexto de hardware constitui-se basicamente do conteúdo dos registradores. No
momento em que o processo perde a UCP, o sistema salva suas informações. Ele é fundamental
para a implementação dos sistemas multiprogramáveis.
O contexto de software especifica características do processo que vão influir na execução de
um programa. Ele define basicamente 3 grupos de informações sobre um processo: identificação,
quotas e privilégios. A identificação identifica o processo para o sistema de forma única, através de
seu pid, uid e gid. Quotas são os limites de cada recurso que o sistema operacional pode alocar,
como número de arquivos abertos, quantidade de memória, número subprocessos que podem ser
criados, etc. Privilégio é o que o processo pode ou não fazer em relação ao sistema e outros
processos.
O espaço de endereçamento é a área de memória do processo onde o programa será
executado e a área de memória onde os dados do processo serão armazenados. Cada processo
possui seu próprio espaço de endereçamento, que deve ser protegido dos demais.
processos sequenciais0 1 2 3 ... n-2 n-1
Escalonador gerencia interrupções e agendamento
Sistemas Operacionais 24
2.2 Subprocesso e Thread
Quando um processo (processo pai) cria um outro processo, o processo criado é conhecido
como subprocesso ou processo filho. O subprocesso, por sua vez, pode criar outros subprocessos.
Caso um processo deixe de existir, os subprocessos subordinados são eliminados.
A utilização de subprocessos permite dividir uma aplicação em partes que podem trabalhar de
forma concorrente.
O uso de subprocessos demanda consumo de diversos recursos do sistema. Sempre que um
novo processo é criado o sistema deve alocar recursos (contexto de hardware, contexto de software
e espaço de endereçamento) para ele, além de consumir tempo de UCP. Ainda, cada processo
possui seu próprio BCP.
Na tentativa de diminuir o tempo gasto na criação/eliminação de processos, bem como
economizar recursos do sistema, foi introduzido o conceito de thread. Em um ambiente com
múltiplos threads não é necessário haver vários processos para se implementar aplicações
concorrentes.
Threads compartilham o processador da mesma maneira que um processo. Cada thread possui
seu próprio conjunto de registradores (contexto de hardware), porém compartilha o mesmo espaço
de endereçamento com os demais threads do processo. No momento em que um thread perde a
utilização do processador, o sistema salva suas informações. Threads passam pelos mesmos estados
que um processo.
A grande diferença entre subprocessos e threads é em relação ao espaço de endereçamento.
Subprocessos possuem, cada um, espaços independentes e protegidos. Threads, por outro lado,
compartilham o mesmo espaço de endereçamento do processo, sem nenhuma proteção, permitindo
que um thread possa alterar dados de outro thread. Threads são desenvolvidos para trabalharem de
forma cooperativa, voltados para desempenhar uma tarefa em conjunto. Threads também são
conhecidas como processos leves.
A mudança de contexto entre threads em um mesmo processo exige uma alteração de um
conjunto de registradores, mas não é necessário nenhum outro trabalho, como gerenciamento de
memória, por exemplo, tornando-a mais leve que a mudança de contexto entre processos.
Quando múltiplos threads estão presentes no mesmo processo, alguns campos da tabela de
processos não são por processo, mas por thread.
Em alguns sistemas os threads são gerenciados no espaço do usuário, sem o conhecimento do
sistema operacional. É o caso do pacote P-threads (POSIX). A comutação de threads é muito mais
rápida quando é feita no espaço do usuário por não precisar fazer uma chamada ao kernel. Porém,
quando os threads são executados no espaço do usuário e um thread bloqueia, todo o processo é
bloqueado pelo kernel. Threads no nível do usuário também faz com que o tempo dedicado a
threads de diferentes processos não seja distribuído de uma forma justa.
1 processo com 3 threads3 processos, com 1 thread cada
thread processocontador de programa
Sistemas Operacionais 25
2.3 Tipos de Processos
Existem basicamente dois tipos de processos com relação ao tipo de processamento que
executam: CPU-bound e I/O-bound.
Os processos do tipo CPU-bound passam a maior parte do tempo no estado executando,
realizando poucas operações de E/S. Costumam ser encontrados em aplicações científicas.
Os processos do tipo I/O-bound passam a maior parte do tempo no estado bloqueado por
realizar um elevado número de operações de E/S. Costumam ser encontrados em aplicações
comerciais. Processos interativos também são exemplos deste tipo de processo.
2.4 Processos Cooperativos
Os processos concorrentes em um sistema operacional podem ser independentes ou
cooperativos. Um processo é um processo independente se ele não afeta e não é afetado por
nenhum outro processo do sistema. Por outro lado, qualquer processo que afetada ou é afetado por
outro processo é dito um processo cooperativo. Algumas razões para a cooperação entre processos
são:
Compartilhamento de informações → Acesso concorrente a recursos.
Aumento da velocidade de processamento → Aplicações que podem ser divididas em
subtarefas a serem executadas paralelamente.
Modularidade → Dividir as funções do sistema em processos separados.
Conveniência → Permitir a troca de dados entre processos dos usuários.
Para que possa ser implementada a cooperação entre processos é preciso existir mecanismos
que permitam aos processos comunicarem-se uns com os outros e sincronizarem suas ações.
Para ilustrar o conceito de processos cooperativos pode-se usar o exemplo do
produtor/consumidor. Um processo produtor produz informações que são consumidas por um
processo consumidor.
Para permitir a execução concorrente dos processos produtor e consumidor é preciso haver
uma área de armazenamento de itens a qual os dois processos tenham acesso. Ainda, os dois
processos devem estar sincronizados para que o consumidor não tente consumir quando não houver
item produzido.
Outro problema é que a área de armazenamento costuma ser limitada. Assim o produtor só
poderá produzir enquanto houver espaço para armazenamento de itens.
O controle de acesso a área de armazenamento pode ser feito por meio do sistema operacional
usando comunicação interprocessos ou explicitamente pelo programador com o uso de memória
compartilhada.
Utilizando memória compartilhada teríamos as seguintes definições compartilhadas pelos
processos:
const
n = ...;
type
item = ...;
var
buffer: array [0..n-1] of item;
counter, in, out: integer;
A área de armazenamento compartilhada é implementada por meio de uma fila circular. A
variável in aponta para a próxima posição livre na área de armazenamento, enquanto out aponta
para a primeira posição ocupada na área de armazenamento.
O código do processo produtor fica:
Sistemas Operacionais 26
repeat
nextp := produz_item;
while (counter = n) do (*faz nada*);
buffer[in] := nextp;
in := (in+1) mod n;
counter := counter + 1;
until false;
O código do processo consumidor fica:
repeat
while (counter = 0) do (*faz nada*);
nextc := buffer[out];
out := (out+1) mod n;
counter := counter - 1;
consome_item(nextc);
until false;
2.5 Exercícios
1) O que é um processo?
2) Pode-se afirmar que um programa executado em uma mesma máquina com sistema
operacional multitarefa levará sempre o mesmo tempo para terminar sua execução? Explique.
3) Quais os estados em que um processo pode estar? Que transições podem ocorrer entre estes
estados?
4) Para cada possível mudança de estado de um processo, cite um exemplo de evento que pode
levar a tal mudança.
5) Qual a função do bloco de controle de processos (PCB)?
6) Qual a função do escalonador de um sistema operacional?
7) O que significa mudança de contexto?
8) Que importância tem a tabela de processos para a mudança de contexto?
9) O que são threads?
10) Qual a diferença entre threads e subprocessos?
11) Como o uso de threads pode melhorar o desempenho de aplicações paralelas em ambientes
com múltiplos processadores?
12) Cite uma vantagem de se utilizar threads no lugar de processos cooperativos.
13) O que diferencia um processo CPU-bound de um processo I/O-bound?
14) Dê exemplos de aplicações CPU-bound e I/O-bound.
15) O que são processos cooperativos?
16) Cite duas razões para a cooperação entre processos.
Sistemas Operacionais 27
3 Comunicação Entre Processos
O surgimento dos sistemas multiprogramáveis tornou possível criar aplicações nas quais
diferentes partes de seu código pudessem executar de forma concorrente. Tais aplicações são
conhecidas como aplicações concorrentes.
É comum que processos de aplicações concorrentes compartilhem recursos do sistema. Não
importa quais recursos são compartilhados, os problemas decorrentes serão os mesmos. O
compartilhamento de recursos entre processos pode gerar situações indesejáveis capazes de
comprometer o sistema.
Se dois processos concorrentes trocam informações através de um buffer, um processo só
poderá gravar dados caso o buffer não esteja cheio, enquanto um processo só poderá ler dados caso
o buffer não esteja vazio. Em qualquer caso, os processos deverão aguardar até que o buffer esteja
pronto para as operações de E/S.
As considerações anteriores levam a 3 questões:
Como um processo passa informações a outro?
Como certificar que 2 processos não se interfiram?
Como realizar o sequenciamento quando um processo depende do outro?
É bastante comum que aplicações concorrentes necessitem trocar informações. Tal
comunicação pode se dar por intermédio de variáveis compartilhadas (memória compartilhada) ou
por troca de mensagens. Mas, independente do mecanismo de comunicação utilizado, é preciso que
os processos tenham condições de se manterem sincronizados.
Os mecanismos que garantem a comunicação entre processos concorrentes e o acesso a
recursos compartilhados são chamados mecanismos de sincronização. Os mesmos mecanismos se
aplicam a threads.
3.1 Condição de Corrida
Em alguns sistemas, processos que estão trabalhando em conjunto muitas vezes utilizam uma
memória comum, onde cada processo pode ler ou escrever. Este armazenamento compartilhado
pode ser feito na memória principal ou em um arquivo em disco.
Seja, por exemplo, um programa que atualize o saldo de um cliente após o lançamento de um
débito ou um crédito em um registro. O trecho do programa que faz a atualização poderia ser:
Read(Arquivo, Registro);
Readln(Valor_cred_deb);
Registro.Saldo := Registro.Saldo + Valor_cred_deb;
Write(Arquivo, Registro);
Suponha que o processo seja executado de forma concorrente por dois caixas e que o primeiro
processo leia o valor do registro. Caso o segundo processo venha a ler também o valor do mesmo
registro antes que o primeiro tenha a oportunidade de gravar sua alteração, haverá uma
inconsistência ao final das operações. Uma delas não será efetivada.
Problemas como esse são conhecidos como condição de corrida, que é quando dois ou mais
processos estão acessando dados compartilhados e o resultado final do processamento depende de
quem executa quando.
Sistemas Operacionais 28
3.2 Região Crítica
Como evitar as condições de corrida? A forma mais simples é impedir que dois ou mais
processos acessem um mesmo recurso no mesmo instante, impedindo que eles acessem o recurso
compartilhado simultaneamente. Quando um processo estiver acessando o recurso os demais
deverão esperar. A esta exclusividade de acesso dá-se o nome de exclusão mútua, uma questão
importante para o desenvolvimento de um sistema operacional.
A parte do programa que acessa a memória compartilhada é denominada seção crítica ou
região crítica (RC). Quando um processo está executando dentro de sua região crítica, nenhum
outro processo pode entrar na região crítica.
Begin
......
Entra_Regiao_Critica;
Regiao_Critica;
Sai_Regiao_Critica;
......
End.
Não é suficiente evitar que o processo seja interrompido dentro da região crítica. São quatro
as condições para uma boa solução:
1. Não pode haver mais de um processo simultaneamente dentro de suas regiões crítica.
2. Nenhuma suposição pode ser feita sobre a velocidade ou o número de UCPs.
3. Nenhum processo que execute fora de sua região crítica pode bloquear outro processo.
4. Nenhum processo deve ter que esperar eternamente para entrar em sua região crítica
(starvation).
Como pode ser observado, para garantir a implementação da exclusão mútua os processos
envolvidos devem fazer acesso aos recursos compartilhados de forma sincronizada.
3.3 Sincronização Condicional
Outra situação na qual é necessária a sincronização entre processos concorrentes é quando um
recurso compartilhado não se encontra pronto para ser utilizado. Nesse caso, o processo que deseja
acessar o recurso deverá ser colocado no estado bloqueado até o recurso ficar pronto para o
processamento.
Um exemplo clássico é a comunicação entre dois processos através de operações de gravação
e leitura em um buffer. Os processos envolvidos devem estar sincronizados de forma que um
processo não tente gravar dados em um buffer cheio ou ler de um buffer vazio.
Abaixo é mostrado, como exemplo de sincronização condicional, o programa
produtor/consumidor.
PROGRAM Produtor_Consumidor;
CONST TamBuf = (* Tamanho qualquer *); (* Tamanho do Buffer *)
TYPE Tipo_Dado = (* Tipo qualquer *); (* Tipo do dado *)
VAR Buffer : ARRAY [1..TamBuf] OF Tipo_Dado;
Dado : Tipo_Dado;
Cont : 0..TamBuf; (* Contador que controla o Buffer *)
PROCEDURE Produtor;
BEGIN
REPEAT
Produz_Dado (Dado);
WHILE (Cont = TamBuf) DO (* Nao faz nada *);
Grava_Buffer (Dado, Cont);
UNTIL False;
END;
Sistemas Operacionais 29
PROCEDURE Consumidor;
BEGIN
REPEAT
WHILE (Cont = 0) Do (* Nao faz nada *);
Le_Buffer (Dado, Cont);
Consome_Dado (Dado);
UNTIL False;
END;
BEGIN
Cont := 0;
PARBEGIN
Produtor;
Consumidor;
PAREND;
END.
Nessa solução, a tarefa de colocar e retirar os dados do buffer é realizada pelos procedimentos
Grava_Buffer e Le_Buffer. Essas duas rotinas deverão ser executadas de forma mutuamente
exclusiva.
3.4 Exclusão Mútua por Hardware
As soluções por hardware não podem ser utilizadas por qualquer programa, pois normalmente
requerem o uso de instruções privilegiadas, mas são importantes de serem estudadas porque criam
mecanismos que permitem a implementação das soluções por software.
3.4.1 Desabilitação de Interrupções
Consiste em permitir ao processo do usuário desabilitar interrupções antes de entrar na região
crítica de forma a evitar que o processo seja interrompido dentro da região crítica. Como a mudança
de contexto só pode ser realizada através de interrupções, o processo que as desabilitou terá acesso
exclusivo garantido. O processo deve habilitar novamente as interrupções ao sair da região crítica.
Não é bom que processos de usuários possam desativar interrupções. Além disso, sistemas
com mais de uma UCP só terão uma delas desativada. É um mecanismo útil apenas quando
utilizado pelo kernel.
3.4.2 Instrução TSL (Test and Set Lock)
Consiste em uma instrução especial da UCP que permite ler uma variável, armazenar seu
conteúdo em uma outra e atribuir um novo valor a essa variável. Tal instrução tem como
característica ser sempre executada sem ser interrompida. Assim, não existe a possibilidade de dois
processos estarem manipulando uma variável compartilhada ao mesmo tempo, possibilitando a
implementação da exclusão mútua.
Test_and_Set (X,Y);
A instrução possui o formato acima e quando executada o valor lógico de Y é copiado para X,
sendo atribuído à Y o valor lógico verdadeiro.
PROGRAM Programa_Test_and_Set;
VAR Bloqueio : BOOLEAN;
PROCEDURE Processo_A;
VAR Pode_A : BOOLEAN;
BEGIN
REPEAT
Pode_A := True;
WHILE (Pode_A) Do
Test_and_Set (Pode_A, Bloqueio);
Regiao_Critica_A;
Bloqueio := False;
UNTIL False;
END;
Sistemas Operacionais 30
PROCEDURE Processo_B;
VAR Pode_B : BOOLEAN;
BEGIN
REPEAT
Pode_B := True;
WHILE (Pode_B) Do
Test_and_Set (Pode_B, Bloqueio);
Regiao_Critica_B;
Bloqueio := False;
UNTIL False;
END;
BEGIN
Bloqueio := False;
PARBEGIN
Processo_A;
Processo_B;
PAREND;
END.
A solução apresentada acima, apesar de funcionar apresenta o problema de ser uma solução
com espera ativa (espera ocupada), ou seja, apesar do processo não poder continuar sua execução,
ele continua consumindo tempo de UCP desnecessariamente.
3.5 Exclusão Mútua por Software com Espera Ativa
As primeiras soluções por software que surgiram para implementar a exclusão mútua, apesar
de mais simples, apresentam o problema da espera ativa. A seguir são mostradas duas destas
soluções.
3.5.1 Alternância Estrita
Dois processos concorrem de forma alternada por uma região crítica e testam continuamente
uma variável até que seu valor seja o desejado. Assim, eles alternam a ordem de entrada na região
crítica.
Program Alternancia;
var vez: char;
procedure Processo_A;
begin
repeat
while (vez = 'B') do (* nao faz nada *);
Regiao_Critica_A;
vez := 'B';
Processamento_Longo;
until false;
end;
procedure Processo_B;
begin
repeat
while (vez = 'A') do (* nao faz nada *);
Regiao_Critica_B;
vez := 'A';
until false;
end;
begin
vez := 'A';
parbegin
Processo_A;
Processo_B;
parend;
end.
Sistemas Operacionais 31
A solução apresentada tem o problema de desperdiçar tempo de UCP (espera ativa), além de
violar as 2a e 3
a condições para uma boa solução.
3.5.2 Solução de Peterson
Peterson apresentou uma solução totalmente por software em 1981.
Program Peterson;
var
CA, CB: Boolean;
Vez: Char;
Procedure Processo_A;
Begin
Repeat
CA := True;
Vez := 'B';
While (CB and Vez='B') do (* faz nada *);
Regiao_Critica_A;
CA := False;
Processamento_A;
Until False;
End;
Procedure Processo_B;
Begin
Repeat
CB := True;
Vez := 'A';
While (CA and Vez='A') do (* faz nada *);
Regiao_Critica_B;
CA := False;
Processamento_B;
Until False;
End;
Begin
CA := False;
CB := False;
ParBegin
Processo_A;
Processo_B;
ParEnd;
End.
3.6 Exclusão Mútua por Software sem Espera Ativa
A espera ativa gasta tempo de UCP e apresenta o problema da inversão de prioridade, que
ocorre quando um processo de maior prioridade deseja entrar em sua região crítica mas não pode
porque outro processo de menor prioridade já está na região crítica. O processo de maior prioridade
não pode entrar na região crítica e o de menor prioridade recebe pouco tempo de UCP para sair da
região crítica.
Para resolver este problema é melhor utilizar mecanismos de sincronização para bloquear um
processo que deseja entrar na região crítica e não pode, evitando que a UCP seja utilizada de forma
desapropriada.
3.6.1 Semáforos
Um semáforo é uma variável inteira que conta sinais enviados a ela. Associadas aos
semáforos existem duas operações especiais: up e down. A operação down decrementa o valor do
semáforo se ele for maior que 0, senão o processo é bloqueado. A operação up incrementa o valor
do semáforo caso não hajam processos que tenham sido bloqueados pela operação down, senão um
processo é desbloqueado.
Sistemas Operacionais 32
No caso da exclusão mútua, as instruções down e up funcionam como protocolos para que um
processo possa entrar e sair de sua região crítica. O semáforo fica associado a um recurso
compartilhado, indicando quando o recurso está sendo acessado por um dos processos concorrentes.
Se seu valor for maior que 0, nenhum processo está utilizando o recurso, caso contrário, o processo
fica impedido de acessar o recurso.
Sempre que deseja entrar na sua região crítica um processo executa uma instrução down. Se o
semáforo for maior que 0 ele é decrementado de 1 e o processo que solicitou a operação pode
executar sua região crítica. Se uma instrução down é executada em um semáforo cujo valor seja 0, o
processo que solicitou a operação ficará no estado bloqueado em uma fila associada ao semáforo.
Quando o processo que está acessando o recurso sai de sua região crítica, ele executa uma
instrução up, incrementando o semáforo de 1 e liberando o acesso ao recurso. Se um ou mais
processos estiverem esperando, o sistema escolhe um processo na fila de espera e muda seu estado
para pronto.
As operações up e down são realizadas pelo sistema operacional, que deve garantir que elas
sejam executadas atomicamente.
Exemplo de utilização de semáforos para acesso à região crítica:
Program Semaforo;
Var s: semaforo;
Procedure Processo_A;
Begin
Repeat
Down(s);
Regiao_Critica_A;
Up(s);
Until False;
End;
Procedure Processo_B;
Begin
Repeat
Down(s);
Regiao_Critica_B;
Up(s);
Until False;
End;
Begin
s := 1;
ParBegin
Processo_A;
Processo_B;
ParEnd;
End.
Exemplo de sincronização condicional com semáforos (produtor/consumidor):
PROGRAM Produtor_Consumidor;
CONST TamBuf = (* Tamanho qualquer *); (* Tamanho do buffer *)
TYPE Tipo_Dado = (* Tipo qualquer *);
VAR Vazio : Semaforo; (* Quantas entradas do buffer estao vazias *)
Cheio : Semaforo; (* Quantas entradas do buffer estao ocupadas *)
Mutex : Semaforo; (* Semaforo usado para garantir a exclusao mutua *)
Buffer : ARRAY [1..TamBuf] OF Tipo_Dado;
Dado1, Dado2 : Tipo_Dado;
PROCEDURE Produtor;
BEGIN
REPEAT
Produz_Dado (Dado1);
down (Vazio);
down (Mutex);
Sistemas Operacionais 33
Grava_Dado (Dado1, Buffer);
up (Mutex);
up (Cheio);
UNTIL False;
END;
PROCEDURE Consumidor;
BEGIN
REPEAT
down (Cheio);
down (Mutex);
Le_Dado (Dado2, Buffer);
up (Mutex);
up (Vazio);
Consome_Dado (Dado2);
UNTIL False;
END;
BEGIN
Vazio := TamBuf;
Cheio := 0;
Mutex := 1;
PARBEGIN
Produtor;
Consumidor;
PAREND;
END.
Deve-se ter cuidado com o uso de semáforos. No exemplo do produtor/consumidor, se o
produtor fizer down em mutex antes de em vazio e o buffer estiver cheio, o consumidor faz down
em mutex para acessar o buffer e os dois processos bloqueiam. Semáforo devem ser usados com
cuidado.
Pode-se ocultar o mecanismo de interrupções utilizando um semáforo para cada dispositivo de
E/S.
3.6.2 Monitores
O uso de semáforos exige do programador muito cuidado, pois qualquer engano pode levar a
problemas de sincronização imprevisíveis e difíceis de reproduzir. Monitores são mecanismos de
sincronização de alto nível que tentam tornar mais fáceis o desenvolvimento e a correção de
programas concorrentes.
Um monitor é uma coleção de variáveis, procedimentos e estruturas de dados que são
agrupados em um pacote. Em um dado instante somente um processo pode estar ativo em um
monitor. Toda vez que algum processo chama um procedimento do monitor, o monitor verifica se já
existe outro processo executando qualquer procedimento do monitor. Caso exista, o processo ficará
aguardando a sua vez até que tenha permissão para executar.
Variáveis Globais
Procedimento
Procedimento
Procedimento
Procedimento
Código de Inicialização
Sistemas Operacionais 34
As variáveis globais do monitor são visíveis apenas a ele e a seus procedimentos. O bloco de
comandos do monitor é responsável por inicializar essas variáveis, sendo executado apenas urna vez
na ativação do programa onde está declarado o monitor.
Cabe ao compilador implementar as exclusões mútuas em entradas de monitor. A cláusula
synchronized da linguagem Java é um exemplo de implementação de monitores. O programador
transforma regiões críticas em procedimentos de monitor colocando todas as regiões críticas em
forma de procedimentos no monitor. Desta forma o desenvolvimento de programas concorrentes
fica mais fácil.
Para a implementação da sincronização condicional é necessário utilizar variáveis de condição
e duas instruções que operam sobre elas: wait e signal. Por exemplo, quando o produtor não pode
continuar faz wait na variável “full” e fica bloqueado. O consumidor pode então entrar na região
crítica e acordar o produtor com signal. Para não haver mais de um processo na região crítica,
signal tem que ser a última instrução executada na região crítica.
PROGRAM Produtor_Consumidor;
CONST TamBuf = (* Tamanho qualquer *); (* Tamanho do buffer *)
TYPE Tipo_Dado = (* Tipo qualquer *);
VAR Buffer : ARRAY [1..TamBuf] OF Tipo_Dado;
MONITOR Condicional;
VAR Vazio, Cheio : Condition; (* Variaveis de condicao *);
Cont : INTEGER;
PROCEDURE Produz(Dado : Tipo_Dado);
BEGIN
IF (Cont = TamBuf) THEN WAIT (Cheio);
Grava_Dado (Dado, Buffer);
Cont := Cont + 1;
IF (Cont = 1 THEN SIGNAL (Vazio);
END;
PROCEDURE Consome(var Dado : Tipo_Dado);
BEGIN
IF (Cont = 0) THEN WAIT (Vazio);
Le_Dado (Dado, Buffer);
Cont := Cont - 1;
IF (Cont = TamBuf - 1) THEN SIGNAL (Cheio);
END;
BEGIN
Cont := 0;
END; (* Termina monitor *)
PROCEDURE Produtor;
VAR
Dado : Tipo_Dado;
BEGIN
REPEAT
Produz_Dado (Dado);
Condicional.Produz (Dado);
UNTIL False;
END;
PROCEDURE Consumidor;
VAR
Dado : Tipo_Dado;
BEGIN
REPEAT
Condicional.Consome (Dado);
Consome_Dado (Dado);
UNTIL False;
END;
BEGIN
PARBEGIN
Produtor;
Consumidor;
Sistemas Operacionais 35
PAREND;
END.
3.6.3 Troca de Mensagens
A troca de mensagens é um mecanismo de comunicação e sincronização entre processos sem
a necessidade de utilização de memória compartilhada. Ela é implementada pelo sistema
operacional através das rotinas send e receive. A rotina send é responsável por enviar uma
mensagem para um processo receptor enquanto a rotina receive é responsável por receber uma
mensagem de um processo.
send (Receptor, Mensagem);
receive (Transmissor, Mensagem);
Os procedimentos send e receive, mesmo não tendo suas execuções mutuamente exclusivas,
permitem tanto a comunicação entre processos quanto a sincronização entre eles.
Um problema com a troca de mensagens é que uma mensagem de um processo P para outro
processo Q pode ser perdida em algum lugar. Existem três métodos básicos para tratamento dessa
falha:
O sistema operacional é responsável por detectar a ocorrência dessa falha e por enviar
novamente a mensagem.
O processo remetente é responsável por detectar a ocorrência dessa falha e por transmitir
novamente a mensagem.
O sistema operacional é responsável por detectar a ocorrência dessa falha e notifica o
processo remetente de que a mensagem foi perdida.
A detecção de mensagens perdidas não é sempre necessária. Alguns protocolos de rede
especificam que o envio de mensagens não é confiável, enquanto outros garantem o envio.
O método mais comum de detecção de mensagem perdida é baseado em expiração de tempo.
Quando uma mensagem é enviada, uma mensagem de confirmação de recebimento da mensagem é
enviada de volta. O sistema operacional ou um processo pode então especificar um intervalo de
tempo durante o qual ele espera pela chegada da confirmação de recebimento da mensagem. Se a
confirmação não chegar nesse período supõem-se que a mensagem se perdeu e ela é reenviada. No
entanto é possível que ela não tenha se perdido, mas simplesmente demorado mais do que o
esperado. Nesse caso, pode-se ter várias cópias de uma mesma mensagem. Deve existir um
mecanismo para fazer a distinção entre esses vários tipos de mensagens. Esse problema pode ser
resolvido numerando-se as mensagens.
Para que dois processos se comuniquem, eles devem ter uma maneira de se referir um ao
outro.
No mecanismo de comunicação direta, também conhecida como endereçamento direto, cada
processo que queira se comunicar com outro deve usar explicitamente o nome do processo receptor
ou remetente da mensagem. Nesse esquema, as primitivas send e receive são definidas:
send (P, mensagem) → Envia uma mensagem ao processo P.
receive (Q, mensagem) → Recebe uma mensagem do processo Q.
A comunicação direta só permite a comunicação entre dois processos. Seu maior problema é a
necessidade da especificação da identificação dos processos envolvidos na troca de mensagens.
O exemplo produtor/consumidor para este caso é:
Produtor Consumidor REPEAT
Produz_Dado (Dado);
SEND (Consumidor, Dado);
UNTIL False;
REPEAT
RECEIVE (Produtor, Dado);
Consome_Dado (Dado);
UNTIL False;
Sistemas Operacionais 36
Esse exemplo exibe uma simetria no endereçamento, tanto o processo remetente quanto o
destinatário precisam usar o nome do outro processo para se comunicar. Uma variante desse
esquema usa uma assimetria no endereçamento, apenas o remetente necessita usar o nome do
receptor. Assim, as primitivas send e receive são definidas:
send (P, mensagem) → Envia uma mensagem ao processo P.
receive (id, mensagem) → Recebe uma mensagem de qualquer processo e a identificação
do processo com o qual a comunicação ocorreu é armazenado na variável id.
Na comunicação indireta, também conhecida como endereçamento indireto, as mensagens
são enviadas e recebidas por intermédio de uma área compartilhada conhecida como caixa postal
(mailbox). Uma caixa postal pode ser vista abstratamente como um lugar no qual processos podem
colocar mensagens e do qual mensagens podem ser retiradas. Nesse esquema um processo pode se
comunicar com outro por intermédio de diversas caixas postais diferentes. Dois processos só podem
se comunicar se compartilharem o uso de alguma caixa postal. As primitivas send e receive são
definidas:
send(A, mensagem) → Envia uma mensagem para a caixa postal A.
receive(A, mensagem) → Recebe uma mensagem da caixa postal A.
A troca de mensagens pode ou não bloquear os processos envolvidos. Basicamente, existem
duas formas de troca de mensagens entre processos: comunicação síncrona e comunicação
assíncrona.
Na comunicação síncrona, quando um processo envia uma mensagem fica esperando até que
o processo receptor leia a mensagem, ou quando um processo tenta receber uma mensagem
permanece esperando até que o processo transmissor envie alguma mensagem. Esse tipo de
comunicação dispensa a necessidade de buffers. Esse mecanismo também é conhecido como
rendezvous.
Na comunicação assíncrona nem o receptor permanece aguardando o envio de uma
mensagem nem o transmissor o seu recebimento. Além da necessidade de buffers para armazenar as
mensagens, deve haver outros mecanismos de sincronização que permitam ao processo identificar
se uma mensagem já foi enviada ou recebida. A grande vantagem da comunicação assíncrona é o
maior paralelismo na execução dos processos.
O programa a seguir ilustra a utilização de troca de mensagens para o produtor/consumidor.
PROGRAM Produtor_Consumidor;
PROCEDURE Produtor;
VAR Msg : Tipo_Msg;
BEGIN
REPEAT
Produz_Mensagem (Msg);
SEND (Msg);
UNTIL False;
END;
PROCEDURE Consumidor;
VAR Msg : Tipo_Msg;
BEGIN
REPEAT
RECEIVE (Msg);
Consome_Mensagem (Msg);
UNTIL Falte;
END;
BEGIN
PARBEGIN
Produtor;
Consumidor;
PAREND;
Sistemas Operacionais 37
END.
3.7 Deadlock
Um processo está em deadlock quando está esperando por um evento que nunca ocorrerá.
Essa situação costuma ser consequência do compartilhamento de recursos do sistema entre vários
processos, sendo que cada processo deve ter acesso ao recurso de forma exclusiva.
Sendo A e B dois processos que necessitem dos recursos 1 e 2, suponha que A adquira o
recurso 1 e que B adquira o recurso 2. Se no futuro A precisar do recurso 2 e B precisar do recurso
1, os dois processos entrarão em espera circular, levando ao deadlock.
Para que ocorram situações de deadlock em um sistema, são necessárias pelo menos quatro
condições:
1. Cada recurso só pode estar alocado a um único processo em um determinado instante
(exclusão mútua).
2. Um processo, além dos recursos já alocados, pode estar esperando por outros recursos.
3. Um recurso não pode ser liberado de um processo só porque outros processos desejam o
mesmo recurso (não preempção).
4. Um processo pode ter de esperar por um recurso alocado a outro processo e vice-versa
(espera circular).
3.7.1 Prevenção de Deadlock
Para prevenir a ocorrência de deadlocks basta garantir que uma das quatro condições
necessárias para sua ocorrência nunca se satisfaça.
A ausência da primeira condição (exclusão mútua) certamente acaba com o problema do
deadlock. Entretanto, a falta da exclusão mútua gera inconsistências sérias no nível dos processos e
do sistema.
Na segunda condição, se for possível evitar que os processos que já possuam recursos
garantidos requisitem novos recursos, estará resolvido o problema do deadlock. Uma maneira de
implementar esse mecanismo é, sempre que um processo necessitar de recursos para executar, ele
deve requisitá-los antes de começar sua execução. Se todos os recursos necessários ao processo
estiverem disponíveis o processo poderá alocá-los e iniciar sua execução. Caso contrário, nenhum
recurso será alocado e o processo ficará no estado bloqueado. Esse mecanismo pode produzir um
grande desperdício na utilização dos recursos do sistema. Outro problema decorrente desse
mecanismo é a dificuldade de se determinar o número de recursos que um processo deverá alocar
antes da sua execução. No entanto, o mais grave nesse método é a possibilidade de um processo
sofrer starvation, ou seja, todos os recursos necessários à sua execução nunca estarem disponíveis
ao mesmo tempo.
A terceira condição pode ser evitada quando permitimos que um recurso seja retirado de um
processo no caso de outro processo necessitar do mesmo recurso. A liberação de recursos já
garantidos por um processo pode ocasionar sérios problemas, podendo até fazer o processo perder
todo o processamento até então realizado. Outro problema desse mecanismo é a possibilidade de
um processo sofrer starvation, quando o processo garante alguns recursos necessários à sua
execução e o sistema os libera em seguida.
A última maneira de evitar um deadlock é excluir a possibilidade da espera circular (quarta
condição). Uma forma de implementar esse mecanismo é forçar o processo a ter apenas um recurso
de cada vez. Se necessitar de outro recurso, deve liberar o primeiro. Outra forma de implementar tal
mecanismo é numerar os recursos e forçar que os processos possam alocar os recursos somente em
determinada ordem.
Sistemas Operacionais 38
3.7.2 Detecção do Deadlock
Em sistemas que não possuam mecanismos que previnam a ocorrência de deadlocks, é
necessário um esquema de detecção e correção do problema.
Para detectar deadlocks os sistemas operacionais devem manter estruturas de dados capazes
de identificar cada recurso do sistema, o processo que o está alocando e os processos que estão à
espera da liberação do recurso. Toda vez que um recurso é alocado ou liberado por um processo, a
estrutura deve ser atualizada.
Dependendo do tipo de sistema, o ciclo de busca por um deadlock pode variar. Em sistemas
de tempo compartilhado, o tempo de busca pode ser maior, sem comprometer o desempenho e a
confiabilidade do sistema. Sistemas de tempo real, por sua vez, devem constantemente certificar-se
da ocorrência de deadlocks, porém essa maior segurança gera mais overhead no sistema.
3.7.3 Correção do Deadlock
Uma solução bastante utilizada pela maioria dos sistemas operacionais é simplesmente
eliminar um ou mais processos envolvidos no deadlock, eliminando a espera circular.
A eliminação dos processos envolvidos no deadlock pode não ser simples, dependendo do
tipo do recurso envolvido. Se um processo estiver atualizando um arquivo ou imprimindo uma
listagem, o sistema deve garantir que esses recursos sejam liberados sem problemas. Os processos
eliminados não têm como ser recuperados, porém outros processos que estavam em deadlock
poderão prosseguir.
Uma solução menos drástica envolve a liberação de apenas alguns recursos alocados aos
processos para outros processos, até que o ciclo de espera termine. Para que essa solução seja
realmente eficiente, é necessário que o sistema possa suspender um processo, liberar seus recursos
e, após a solução do problema, retornar à execução do processo sem perder o processamento já
realizado. Esse mecanismo é conhecido como rollback e, além do overhead gerado, é muito difícil
de ser implementado por ser bastante dependente da aplicação que está sendo processada.
3.8 Problemas Clássicos de Comunicação Interprocessos
3.8.1 Problema dos Filósofos Comendo
É um problema que visa demonstrar a sincronização entre processos. Consiste em filósofos
sentados em torno de uma mesa redonda, cada um com um prato de macarrão a sua frente. O
macarrão é escorregadio e só pode ser comido se forem usados dois garfos. Os filósofos só pensam
ou comem, e para comer precisam dos dois garfos. Eles só pegam os garfos na hora de comer.
Solução errada:
procedure filosofo(i: integer);
begin
while (true)
begin
pensa;
pega_garfo(ESQ(i));
pega_garfo(DIR(i));
come;
libera_garfo(ESQ(i));
libera_garfo(DIR(i));
end;
end;
Se todos pegarem o garfo esquerdo, todos os processos ficarão bloqueados.
Se colocar as 5 declarações após “pensa” protegidas por um semáforo binário funciona, mas
somente um filósofo come por vez.
Sistemas Operacionais 39
Uma solução correta é:
Program Filosofos;
const N=5; PENSANDO=0; FOME=1; COMENDO=2;
var
estado: array [1..N] of integer;
mutex: semaphore;
S: array [1..N] of semaphore;
i: integer;
function ESQ(i:integer): integer;
begin
ESQ := ((i-1) mod N) + 1;
end;
function DIR(i:integer): integer;
begin
DIR := ((i+1) mod N) + 1;
end;
procedure teste(i: integer)
begin
if ((estado[i]=FOME) and (estado[ESQ(i)]<>COMENDO)
and (estado[DIR(i)]<>COMENDO)) then
begin
estado[i] := COMENDO;
up(S[i]);
end;
end;
procedure pega_garfos(i: integer);
begin
down(mutex);
estado[i] := FOME;
teste(i);
up(mutex);
down(s[i]);
end;
procedure larga_garfos(i: integer);
begin
down(mutex);
estado[i] := PENSANDO;
teste(ESQ(i));
teste(DIR(i));
up(mutex);
end;
procedure filosofo(i: integer);
begin
while (true)
begin
pensa;
pega_garfos(i);
come;
larga_garfos(i);
end;
end;
begin
mutex := 1;
for i:=1 to N do S[i] := 0;
for i:=1 to N do estado[i] := PENSANDO;
parbegin
for i:=1 to N do filosofo(i);
end;
end.
Sistemas Operacionais 40
3.8.2 Problema dos Leitores e dos Escritores
Visa resolver o problema de atualizar um banco de dados no qual vários processos estão
realizando consultas paralelamente. Procura-se uma solução na qual as consultas possam ser
realizadas em paralelo, ao passo que uma atualização só poderá ocorrer se o processo conseguir
acesso exclusivo ao banco de dados.
Program LeitoresEscritores;
var
mutex, db: semaphore;
rc: integer;
procedure escritor;
begin
while (true)
begin
cria_dados;
down(db);
escreve_banco_dados;
up(db);
end;
end;
procedure leitor;
begin
while (true)
begin
down(mutex);
rc := rc + 1;
if (rc=1) then down(db);
up(mutex);
le_banco_dados;
down(mutex);
rc := rc - 1;
if (rc=0) then up(db);
up(mutex);
usa_dados;
end;
end;
begin
mutex := 1;
db := 1;
rc := 0;
(* Iniciar processos leitores e escritores *)
end.
Problema: Escritor só acessa se nenhum leitor quiser acessar antes.
3.8.3 Problema do Barbeiro Adormecido
Simula uma barbearia onde vários barbeiros estão a disposição dos clientes que entram. Se
não houver cliente, o barbeiro dorme. O primeiro cliente que chega acorda um barbeiro para ter seu
cabelo cortado. O cliente que chega e encontra todos os barbeiros ocupados senta-se em uma das
cadeiras para aguardar até que um barbeiro fique livre. Se não tiver cadeira para sentar o cliente vai
embora.
Program BarbeiroAdormecido;
const CADEIRAS=5;
var
clientes, barbeiros, mutex: semaphore;
esperando: integer;
procedure barbeiro;
begin
while(true)
begin
down(clientes);
Sistemas Operacionais 41
down(mutex);
esperando := esperando - 1;
up(barbeiros);
up(mutex);
corta_cabelo;
end;
end;
procedure cliente;
begin
down(mutex);
if (esperando<CADEIRAS) then
begin
esperando := esperando + 1;
up(clientes);
up(mutex);
down(barbeiros);
cabelo_cortado;
end
else
up(mutex);
end;
begin
clientes := 0;
barbeiros := 0;
mutex := 0;
esperando := 0;
(* iniciar processos clientes e barbeiros *)
end.
3.9 Exercícios
1) O que é uma aplicação concorrente?
2) O que é um mecanismo de sincronização?
3) O que é condição de corrida? Por que ela é problemática?
4) O que é região crítica? Qual é a sua finalidade?
5) Por que o mecanismo de exclusão mútua evita a condição de corrida?
6) O que é starvation?
7) Para que serve a sincronização condicional?
8) Como a desabilitação de interrupções pode evitar a condição de corrida?
9) Por que não adianta desabilitar interrupções em sistemas com mais de uma UCP?
10) Por que não é conveniente que o sistema operacional permita que um programa de usuário
possa desabilitar interrupções?
11) O que é espera ativa? Por que ela deve ser evitada?
12) Explique de forma sucinta como é o funcionamento dos semáforos.
13) O mecanismo de semáforo pode ser utilizado para resolver o problema de starvation?
Explique.
14) Como o mecanismo de semáforo pode ser utilizado para resolver o problema da condição
de corrida?
15) Como o mecanismo de semáforo pode ser utilizado para realizar a sincronização
condicional?
16) A utilização errada de semáforos pode levar ao travamento de uma aplicação? Explique.
Sistemas Operacionais 42
17) Como é o funcionamento dos monitores como solução para a condição de corrida? Por que
signal deve ser a última instrução executada na região crítica?
18) Por que monitores são de mais fácil utilização pelos programadores do que os semáforos?
19) Para a cooperação entre processos pode ser utilizada memória compartilhada ou troca de
mensagens. Qual a diferença?
20) Explique de forma sucinta como funciona o mecanismo de troca de mensagens.
21) Na troca de mensagens, qual a diferença entre comunicação direta e comunicação indireta?
22) Na troca de mensagens, qual a diferença entre comunicação síncrona e comunicação
assíncrona?
23) No mecanismo de troca de mensagens, o que ocorre quando um processo tenta enviar uma
mensagem ao outro e o canal de comunicação não tem mais espaço para armazenar esta mensagem?
24) O que é deadlock?
25) Qual a diferença entre prevenção de deadlock e detecção do deadlock?
26) Cite uma forma de corrigir o deadlock.
Sistemas Operacionais 43
4 Gerência do Processador
A multiprogramação tem como objetivo permitir que, a todo instante, haja algum processo
sendo executado para maximizar a utilização da UCP.
O conceito que possibilitou a implementação de sistemas multiprogramáveis foi a
possibilidade da UCP ser compartilhada entre diversos processos. Portanto, deve existir um critério
para determinar qual a ordem na escolha dos processos para execução dentre os vários que
concorrem pela UCP.
O procedimento de seleção é conhecido como escalonamento (scheduling). A parte do
sistema operacional responsável pelo escalonamento é o escalonador (scheduler), as vezes
chamado de agendador. Sempre que a UCP se torna ociosa o escalonador seleciona um processo,
dentre aqueles que estão na memória prontos para serem executados, e aloca a UCP para que ele
possa ser executado.
Os principais objetivos do escalonamento são:
Manter a UCP ocupada a maior parte do tempo;
Balancear a utilização da UCP entre os processos;
Maximizar o throughput do sistema;
Oferecer tempos de resposta razoáveis para os usuários interativos.
Esses objetivos devem ser atendidos de forma que nenhum processo fique esperando
indefinidamente pela utilização do processador (starvation).
A principal função de um algoritmo de escalonamento é decidir qual dos processos prontos
deve ser alocado à UCP. Os principais critérios de escalonamento são:
Imparcialidade → Oferecer uma fatia justa da UCP a cada processo.
Utilização da UCP → É desejável que a UCP passe a maior parte de seu tempo ocupada.
Throughput (produtividade) → Representa o número de processos executados durante um
intervalo de tempo.
Tempo de processamento (turnaround) → Tempo que um processo leva desde a sua
admissão no sistema até o seu término.
Tempo de resposta → Em sistemas interativos, é o tempo desde o momento da submissão
de um pedido até a primeira resposta produzida.
Tempo de espera → Tempo que um processo fica esperando na fila de processos prontos.
Algumas destas metas são contraditórias. Sempre que se favorece alguma meta outra é
prejudicada. Outra complicação é que o comportamento dos processos é imprevisível.
O algoritmo de escalonamento não é o único responsável pelo tempo de execução de um
processo. Ele afeta somente o tempo de espera na fila de processos prontos.
Os algoritmos de escalonamento podem ser classificados como preemptivos ou
não-preemptivos. Quando o sistema pode interromper um processo durante sua execução para
colocá-lo no estado pronto e colocar outro processo no estado executando, tem-se um sistema
preemptivo. Senão tem-se um sistema não-preemptivo.
O escalonamento preemptivo permite que o sistema dê atenção imediata a processos mais
prioritários, além de proporcionar melhor tempo de resposta em sistemas de tempo compartilhado.
Outro benefício decorrente é o compartilhamento do processador de uma maneira mais uniforme.
Sistemas Operacionais 44
A troca de um processo por outro na UCP (mudança de contexto) causada pela preempção,
gera um overhead ao sistema. Para isto não se tornar crítico, o sistema deve estabelecer
corretamente os critérios de preempção.
Sistemas que usam escalonamento preemptivo têm o problema da condição de corrida, o que
não ocorre com sistemas que usam escalonamento não-preemptivo. No escalonamento não-
preemptivo, quando um processo ganha o direito de utilizar a UCP, nenhum outro processo pode
lhe tirar esse recurso.
4.1 Algoritmos de Escalonamento
4.1.1 Escalonamento First In First Out (FIFO)
Nesse escalonamento, o processo que chegar primeiro é o primeiro a ser selecionado para
execução. É necessária apenas uma fila, onde os processos que passam para o estado pronto entram
no seu final. Quando um processo ganha a UCP, ele a utiliza sem ser interrompido, caracterizando-o
como um algoritmo não-preemptivo.
O problema do escalonamento FIFO é a impossibilidade de se prever quando um processo
terá sua execução iniciada. Outro problema é a possibilidade de processos CPU-bound de menor
importância prejudicarem processos I/O-bound mais prioritários.
Este algoritmo foi inicialmente implementado em sistemas batch.
4.1.2 Escalonamento Shortest Job First (SJF)
Esse algoritmo associa a cada processo seu tempo de execução. Quando a UCP está livre, o
processo no estado pronto que precisar de menos tempo para terminar é selecionado para execução.
O escalonamento SJF beneficia processos que necessitam de pouco processamento e reduz o
tempo médio de espera em relação ao FIFO. O problema é determinar quanto tempo de UCP cada
processo necessita para terminar seu processamento. Em ambientes de produção é possível estimar
o tempo de execução, mas em ambientes de desenvolvimento é muito difícil.
É um algoritmo de escalonamento não-preemptivo e, assim como o FIFO, também foi
utilizado nos primeiros sistemas operacionais com processamento batch.
4.1.3 Escalonamento Cooperativo
Tanto o SJF quanto o FIFO não são algoritmos de escalonamento aplicáveis a sistemas de
tempo compartilhado, onde um tempo de resposta razoável deve ser garantido a usuários
interativos.
No escalonamento cooperativo, quando um processo já está em execução a um determinado
tempo, ele voluntariamente libera a UCP retornando para a fila de processos prontos.
Sua principal característica está no fato da liberação da UCP ser uma tarefa realizada
exclusivamente pelo processo em execução, que a libera para um outro processo. Não existe
nenhuma intervenção do sistema operacional na execução do processo. Isto pode ocasionar sérios
problemas na medida em que um programa pode não liberar a UCP ou um programa mal escrito
pode entrar em loop, monopolizando a UCP.
É um algoritmo de escalonamento não-preemptivo.
4.1.4 Escalonamento Circular (Round Robin)
Esse algoritmo é bem semelhante ao FIFO. Entretanto, quando um processo passa para o
estado executando, existe um tempo limite (conhecido como time-slice ou quantum) para utilização
da UCP de forma contínua. Quando esse tempo expira o processo volta ao estado pronto, dando a
vez para outro processo.
Sistemas Operacionais 45
A fila de processos no estado pronto é tratada como uma fila circular. O escalonamento é
realizado alocando a UCP para cada processo da fila no intervalo de tempo determinado pelo
quantum.
Se o quantum for muito pequeno gasta-se muito tempo de UCP com trabalho administrativo.
Se o quantum for muito grande a interatividade fica prejudicada, já que um processo que sai de
execução pode demorar muito a voltar. Em geral, o quantum varia de 10 a 100 ms.
Através do escalonamento circular, nenhum processo poderá monopolizar a UCP,
caracterizando-o como um algoritmo de escalonamento preemptivo.
Um problema com o escalonamento circular é que ele não oferece qualquer tratamento
diferenciado a processos I/O-bound. Assim processos CPU-bound terão a tendência de monopolizar
a utilização da UCP enquanto processos I/O-bound permanecem a espera.
4.1.5 Escalonamento por Prioridade
O escalonamento circular consegue melhorar a distribuição do tempo de UCP em relação aos
escalonamentos não-preemptivos, porém ainda não consegue implementar um compartilhamento
equitativo entre os diferentes tipos de processos.
Para solucionar esse problema, os processos I/O-bound devem levar alguma vantagem no
escalonamento, a fim de compensar o tempo excessivo gasto no estado bloqueado. Como alguns
processos devem ser tratados de maneira diferente dos outros, é preciso associar a cada um deles
uma prioridade de execução. Assim, processos de maior prioridade são escalonados
preferencialmente.
A preempção por prioridade é implementada mediante um relógio que interrompe o
processador periodicamente para que a rotina de escalonamento reavalie prioridades e,
possivelmente, escalone outro processo, caracterizando-o como um algoritmo de escalonamento
preemptivo.
Todos os sistemas de tempo compartilhado implementam algum esquema de prioridade. A
prioridade é uma característica do contexto de software de um processo, podendo ser estática ou
dinâmica.
Tem-se a prioridade estática quando a prioridade não é modificada durante a existência do
processo. Apesar da simplicidade de implementação, a prioridade estática pode ocasionar tempos de
resposta elevados.
Na prioridade dinâmica, a prioridade do processo pode ser ajustada de acordo com o tipo de
processamento realizado pelo processo e/ou a carga do sistema. Quando o processo sai do estado
bloqueado, recebe um acréscimo à sua prioridade. Dessa forma, os processos I/O-bound terão mais
chance de ser escalonados e compensar o tempo que passam no estado bloqueado.
Para evitar que processos com maior prioridade executem indefinidamente, a prioridade é
diminuída com o passar do tempo.
Uma outra forma de se obter prioridade dinâmica é fazer com que o quantum do processo seja
inversamente proporcional à fração do último quantum utilizado.
Embora os sistemas de prioridade dinâmica sejam mais complexos e gerem um overhead
maior, o tempo de resposta oferecido compensa.
A B C D preempção
Processo atual
Próximo processo
B C D A
Processo atual
Próximo processo
Sistemas Operacionais 46
4.1.6 Escalonamento por Múltiplas Filas
Como os processos de um sistema possuem diferentes características de processamento, é
difícil que um único mecanismo de escalonamento seja adequado a todos. Uma boa política seria
classificar os processos em função do tipo de processamento realizado e aplicar a cada grupo
mecanismos de escalonamentos distintos.
O escalonamento por múltiplas filas implementa diversas filas de processo no estado pronto,
onde cada processo é associado exclusivamente a uma delas. Cada fila possui um mecanismo
próprio de escalonamento, em função das características do processo. Nesse esquema, os processos
devem ser classificados, previamente, em função do tipo de processamento, para poderem ser
encaminhados a uma determinada fila.
Cada fila possui uma prioridade associada, que estabelece quais filas são prioritárias em
relação às outras. O sistema só pode escalonar processos de uma fila se todas as outras de prioridade
maior estiverem vazias.
Para exemplificar esse escalonamento, considere que os processos, em função de suas
características, sejam divididos em três grupos: sistema, interativo e batch. Os processos do sistema
devem ser colocados em uma fila de prioridade superior à dos outros processos, implementando um
algoritmo de escalonamento baseado em prioridades. Os processos de usuários interativos devem
estar em uma fila de prioridade intermediária, implementando, por exemplo, o escalonamento
circular. O mesmo mecanismo de escalonamento pode ser utilizado na fila de processos batch, com
a diferença de que esta fila deverá possuir uma prioridade mais baixa.
4.1.7 Escalonamento por Múltiplas Filas com Realimentação
No escalonamento por múltiplas filas os processos são previamente associados a uma
determinada fila. No caso de um processo que altere o seu comportamento no correr do tempo, esse
esquema é falho, pois o processo não poderá ser redirecionado para uma outra fila mais adequada.
O escalonamento por múltiplas filas com realimentação também implementa diversas filas,
onde cada qual tem associada uma prioridade de execução, porém os processos não permanecem
obrigatoriamente em uma mesma fila até o término do processamento.
Esse esquema permite que os processos sejam redirecionados entre as filas do sistema,
fazendo com que o sistema operacional implemente um mecanismo de ajuste dinâmico (mecanismo
adaptativo) que tem como objetivo ajustar os processos em função de seu comportamento.
Um processo, ao ser criado, entra no final da fila de mais alta prioridade. Quando um processo
em execução deixa a UCP, seja por preempção por prioridade ou por solicitação a um recurso do
sistema, ele é reescalonado dentro da mesma fila. Caso o processo esgote seu quantum, ele é
redirecionado para uma fila de menor prioridade.
O quantum em cada fila varia em função da sua prioridade. Quanto maior a prioridade da fila,
menor é o seu quantum. Assim, o quantum dos processos não é estático, variando em função da fila
na qual ele se encontra.
Essa política de escalonamento atende as necessidades dos diversos tipos de processos. No
caso de processos I/O-bound, ela oferece um bom tempo de resposta, já que esses processos têm
prioridades altas por permanecerem a maior parte do tempo nas filas de mais alta ordem. No caso de
processos CPU-bound, a tendência é de que, ao entrar na fila de mais alta prioridade, o processo
ganhe a UCP, gaste seu quantum de tempo e seja direcionado para uma fila de menor prioridade.
O maior problema deste algoritmo é que por sua complexidade ele pode gerar um grande
overhead ao sistema.
Sistemas Operacionais 47
4.1.8 Escalonamento em Sistemas de Tempo Real
Sistemas de tempo real devem ser utilizados quando existem aplicações que necessitem dar
respostas imediatas a alguns eventos do sistema.
O escalonador deve levar em conta a a importância relativa da tarefa para o sistema e tratá-la
com a prioridade adequada. Assim, o algoritmo de escalonamento por prioridade é o mais adequado
para sistemas deste tipo. Não deve haver o conceito de fatia de tempo e a prioridade de cada
processo deve ser estática.
4.2 Escalonamento com Múltiplas UCPs
O mecanismo de escalonamento para sistemas com múltiplas UCPs é bem mais complexo que
com uma única UCP. A abordagem é diferenciada para sistemas fracamente acoplados ou
fortemente acoplados.
Em sistemas fracamente acoplados, cada UCP faz seu próprio escalonamento local. O sistema
possui, além da UCP, sua memória principal, sistema operacional, algoritmo de escalonamento e
sua própria fila de processos prontos para execução.
Se várias UCPs idênticas estiverem disponíveis em um sistema fortemente acoplado, seria
possível usar uma fila de processos prontos separada para cada UCP. Entretanto uma UCP poderia
ficar ociosa, enquanto outra estivesse muito ocupada. Assim, implementa-se uma única fila de
processos prontos para todas as UCPs. Todos os processos estão presentes nesta única fila e são
escalonados no primeiro processador disponível. Como a memória é única para todas as UCPs e
todos os programas, não faz diferença em qual UCP a execução ocorrerá. Este caso em que não
importa em qual das UCPs se dará a execução do processo é conhecido como multiprocessamento
simétrico.
Nesta solução é importante que seja implementada a exclusão mútua. No caso de mais de uma
UCP tornar-se disponível em um mesmo instante, não pode haver a possibilidade de um mesmo
processo ser escalonado por duas UCPs diferentes.
Uma outra solução é designar uma UCP que executa o algoritmo de escalonamento, sendo ela
a responsável por determinar a tarefa de cada UCP do sistema.
4.3 Exercícios
1) Qual a função do escalonador de um sistema operacional?
2) No que se refere à gerência do processador, o que diferencia um sistema operacional
preemptivo de um sistema operacional não-preemptivo?
3) Por que um algoritmo de escalonamento preemptivo pode levar ao problema da condição de
corrida?
4) Por que no algoritmo de escalonamento FIFO processos CPU-bound de menor importância
podem prejudicar processos I/O-bound prioritários?
5) Como o algoritmo de escalonamento SJF reduz o tempo médio de espera em relação ao
FIFO?
6) Como funciona o escalonamento cooperativo? Ele é preemptivo? Explique.
7) Como é possível que um processo consiga utilizar a UCP sem dar a chance de outro
processo entrar em execução quando se usa escalonamento cooperativo?
8) O que é quantum para um algoritmo de escalonamento preemptivo?
9) O que diferencia o escalonamento circular (Round Robin) do escalonamento por prioridade?
Sistemas Operacionais 48
10) Para o escalonamento circular, o que é quantum?
11) Por que um valor alto para quantum prejudica a interatividade?
12) Como o sistema operacional faz para garantir que o escalonamento circular seja
preemptivo?
13) Por que processos I/O-bound podem ser prejudicados no escalonamento circular? Como o
escalonamento por prioridade resolve este problema?
14) Explique sucintamente como funciona um algoritmo de escalonamento por prioridade.
15) Qual a diferença entre prioridade estática e prioridade dinâmica para um algoritmo de
escalonamento por prioridade?
16) Como o escalonador determina o próximo processo a entrar em execução em um sistema
de tempo real?
17) Como o escalonador de tempo real trata o quantum?
18) Em um sistema com múltiplas UCPs, é possível que um processo que saiu de execução em
uma UCP, quando volte ao estado pronto, entre em execução em outra UCP? Explique.
19) O que é multiprocessamento simétrico?
Sistemas Operacionais 49
5 Gerência de Memória
Na memória principal residem todos os programas e dados que serão executados ou
referenciados pela UCP. Toda vez que for ser executado um programa residente na memória
secundária deve-se carregá-lo na memória principal.
Enquanto nos sistemas monoprogramáveis a gerência da memória não é muito complexa, nos
sistemas multiprogramáveis ela se torna crítica, uma vez que o sistema operacional deve gerenciar o
acesso dos vários processos à memória de forma concorrente.
O gerenciador de memória é a parte do sistema operacional que gerencia a hierarquia de
memória. Seu trabalho consiste em:
Controlar que parte da memória está em uso.
Alocar memória para os processos.
Gerenciar a troca entre a memória principal e o disco.
5.1 Atribuição de Endereços
Um programa é armazenado em disco como um arquivo binário executável. Para ser
executado ele deve ser colocado em memória como parte de um processo.
Os sistemas operacionais atuais permitem que os processos dos usuários sejam colocados em
qualquer parte da memória. Assim, embora o espaço de endereçamento do computador comece no
endereço zero, este espaço não precisa ser o endereço inicial de um processo. Os endereços usados
nos programas podem ser representados de maneiras diferentes. No programa fonte os endereços
costumam ser simbólicos, representados por nomes de variáveis. O compilador normalmente
associa este endereço simbólico a um endereço relativo, como uma posição a partir do início do
programa. O loader por sua vez transforma endereços relativos em endereços absolutos.
A atribuição de endereços a instruções ou dados pode ser feita:
Em tempo de compilação → A posição de memória em que o programa será armazenado
é conhecida durante a compilação. Se precisar mudar o programa deve ser recompilado.
Em tempo de carga → A atribuição final de endereços absolutos é feita durante a carga
do programa em memória.
Em tempo de execução → Quando um programa durante sua execução, pode ser
transferido de um local para outro na memória. A atribuição de endereços deve ser feita
somente durante a execução.
5.2 Carregamento Dinâmico
No carregamento dinâmico o programa é dividido em um programa principal e uma série de
rotinas que ficam armazenadas em disco. Para a execução o programa principal é carregado na
memória. Quando uma rotina é chamada, ela é carregada na memória.
A vantagem do carregamento dinâmico é que uma rotina não usada nunca é carregada na
memória.
O carregamento dinâmico não requer obrigatoriamente suporte especial do sistema
operacional, mas o sistema operacional pode fornecer chamadas de sistema para facilitar sua
implementação.
Sistemas Operacionais 50
5.2.1 Ligação Dinâmica
O conceito de ligação dinâmica é similar ao de carregamento dinâmico, só que na ligação
dinâmica é a ligação (linkedição) que é realizada em tempo de execução.
Este recurso é comumente utilizado para bibliotecas do sistema. Sua ausência obriga que
todos os programas do sistema tenham uma cópia das rotinas do sistema que utiliza, gerando um
grande desperdício tanto de espaço em disco quanto de memória. Com este mecanismo é incluído
no código executável do programa um código para ligação dinâmica, que indica onde e como
armazenar uma rotina de biblioteca.
Este recurso pode ser estendido para casos em que ocorrem mudanças no código da
biblioteca. Uma biblioteca pode ser substituída por uma nova versão e todos os programas que a
usam passarão automaticamente a usar a nova versão.
Diferentemente do carregamento dinâmico, a ligação dinâmica costuma requerer algum
suporte do sistema operacional.
5.3 Alocação Contígua Simples
Nesse esquema a memória principal é dividida em duas partes: uma para o sistema
operacional e outra para o programa do usuário. Assim o programador deve criar suas aplicações,
preocupado apenas em não ultrapassar o espaço de memória disponível.
O usuário tem controle sobre toda a memória principal, podendo acessar qualquer posição de
memória. Para proteger o sistema operacional, alguns sistemas implementam proteção através de
registradores, que delimitam as áreas do sistema operacional e do usuário. Assim, sempre que um
programa de usuário faz referência a um endereço na memória, o sistema verifica se o endereço está
nos seus limites.
A principio, os programas dos usuários estavam limitados ao tamanho da memória principal
disponível. A solução encontrada foi dividir o programa em partes, de forma que pudessem executar
independentemente uma da outra, utilizando uma mesma área de memória. Essa técnica é chamada
de overlay (sobreposição).
A definição das áreas de overlay é função do programador. O tamanho de uma área de overlay
será estabelecido a partir do tamanho do maior módulo. A técnica de overlay tem a vantagem de
permitir ao programador expandir os limites da memória principal, mas pode trazer implicações
quanto ao desempenho das aplicações pela excessiva transferência entre as memórias principal e
secundária.
5.4 Alocação Particionada
Nos sistemas monoprogramáveis o processador permanece ocioso e a memória é subutilizada,
enquanto o programa aguarda por algum evento.
Para a multiprogramação ser eficiente é necessário que vários programas tenham a
possibilidade de estarem na memória principal ao mesmo tempo.
5.4.1 Alocação Particionada Estática
Nos primeiros sistemas multiprogramáveis, a memória foi dividida em pedaços de tamanho
fixo, chamados partições. O tamanho das partições era estabelecido na fase de inicialização do
sistema. Sempre que fosse necessária a alteração do tamanho de uma partição o sistema deveria ser
reinicializado com uma nova configuração.
Os programas só podiam executar em uma das partições, mesmo se outras estivessem
disponíveis, por causa dos compiladores e montadores que geravam apenas código absoluto. A esse
tipo de alocação chamou-se alocação particionada estática absoluta.
Sistemas Operacionais 51
Com a evolução dos compiladores, linkers e loaders, a geração de código relocável foi
possível e os programas puderam ser carregados em qualquer partição. Assim foi criado um novo
tipo de organização, denominado alocação particionada estática relocável. Neste esquema o
endereço base é determinado durante o carregamento do programa.
Para manter o controle sobre quais partições estavam alocadas ou não, os sistemas possuíam
uma tabela delimitando cada partição, seu tamanho e se estava em uso ou não.
Nesse esquema de alocação de memória, a proteção baseia-se em dois registradores, que
indicam os limites inferior e superior da partição onde o programa está sendo executado.
Tanto nos sistemas de alocação absoluta quanto nos de alocação relocável, os programas
normalmente não preenchiam totalmente as partições onde eram carregados. Além disso, se um
programa for maior que qualquer partição livre, ele ficará aguardando uma que o acomode, mesmo
que existam partições adjacentes que somadas totalizem o tamanho do programa. Esse tipo de
problema é conhecido como fragmentação interna.
5.4.2 Alocação Particionada Dinâmica
A alocação particionada estática leva ao problema da fragmentação, diminuindo a capacidade
de compartilhamento de memória.
Na alocação particionada dinâmica não existe o conceito de partições de tamanho fixo. Nesse
esquema, cada processo cria uma partição com o tamanho que necessita.
A fragmentação começará a ocorrer quando os programas forem terminando e deixando
espaços na memória, não permitindo o ingresso de novos programas. Este tipo de fragmentação é
denominada fragmentação externa.
Depois de detectada a fragmentação, existem duas soluções para o problema. Na primeira, os
espaços adjacentes são reunidos, produzindo um único espaço de tamanho maior. A segunda
solução envolve a relocação de todas as partições ocupadas, eliminando todos os espaços entre elas.
Esse mecanismo de compactação, também conhecido como alocação dinâmica com
relocação, reduz o problema da fragmentação, mas a complexidade do seu algoritmo e o consumo
de recursos do sistema podem torná-lo inviável.
5.4.3 Estratégias para Escolha da Partição
Existem basicamente cinco estratégias para determinar em qual partição livre um programa
será carregado: primeiro ajuste, próximo ajuste, melhor ajuste, pior ajuste e ajuste rápido.
Memória Principal
Partição onde o programa está sendo
executado
Endereço inicial
Endereço final
Sistema Operacional
Sistema Operacional
Processo A
Processo B
Processo C
Processo D
2 MB
3 MB
4 MB
2 MB
1 MB
Sistema Operacional
12 MB
A B C D
3 MB2 MB 4 MB 2 MB
Sistemas Operacionais 52
A melhor estratégia a ser adotada depende do tamanho dos programas processados no
ambiente. Independentemente do algoritmo utilizado, o sistema deve possuir uma relação de áreas
livres com o endereço e o tamanho de cada uma.
Na representação com lista encadeada é mantida uma lista encadeada com os segmentos
alocados e com os segmentos livres. A manutenção é mais rápida se a lista for duplamente
encadeada.
Mantendo a lista classificada por endereço, quando um processo termina, a área que ele
ocupava pode se juntar a uma ou duas lacunas adjacentes formando uma só lacuna.
5.4.3.1 Algoritmo do Primeiro Ajuste (First-fit)
Esse algoritmo escolhe a primeira partição livre de tamanho suficiente para carregar o
processo. Ele costuma ser muito rápido por pesquisar o mínimo possível.
Nesse algoritmo a lista de áreas livres costuma estar ordenada por endereço.
5.4.3.2 Algoritmo do Próximo Ajuste
É semelhante ao algoritmo do primeiro ajuste, mas inicia sua procura a partir do ponto em que
parou na procura anterior.
5.4.3.3 Algoritmo do Melhor Ajuste (Best-fit)
Esse algoritmo escolhe a partição em que o processo deixa o menor espaço sem utilização.
Nesse algoritmo, se a lista de áreas livres não estiver ordenada por tamanho, o tempo de busca por
uma área desocupada pode comprometer seu desempenho.
Uma desvantagem desse método é que é alocada a partição que deixa a menor área livre,
existindo uma tendência de que a memória fique com pequenas áreas não contíguas, aumentando o
problema da fragmentação.
5.4.3.4 Algoritmo do Pior Ajuste (Worst-fit)
Esse algoritmo escolhe a partição em que o processo deixa o maior espaço sem utilização. Ao
utilizar as partições maiores, são deixados espaços livres maiores que permitem a um maior número
de programas utilizar a memória, diminuindo o problema da fragmentação. Simulações mostraram
que este algoritmo não traz bons resultados.
5.4.3.5 Algoritmo do Ajuste Rápido
Este algoritmo mantém listas separadas para alguns tamanhos mais comuns. Ele também gera
lacunas pequenas, mas é extremamente rápido na alocação de memória.
Simulações mostraram que dentre os algoritmos acima o que obteve melhores resultados foi o
algoritmo do primeiro ajuste, sendo o mais rápido e o que consome menos recursos do sistema.
A lista de lacunas pode ser mantida ordenada de formas diferentes a fim de beneficiar o
algoritmo que estiver em uso.
Um outro aspecto a ser levado em conta é que processos podem crescer. Portanto é bom
deixar lacunas entre os processos para que seja permitido o seu crescimento.
...A B C D E
PA 0 5 L 5 3 PB 8 6 PC 14 4
L 18 2 PD 20 6 PE 26 3 L 29 3
Lista encadeada
Sistemas Operacionais 53
5.5 Swapping
Mesmo com o aumento da eficiência da gerência de memória, muitas vezes um programa não
podia ser executado por falta de uma partição livre disponível. A técnica de swapping veio tentar
resolver o problema da insuficiência de memória.
Nos esquemas anteriores, um processo permanecia na memória principal até o final da sua
execução. Eles funcionam bem para sistemas em lote, mas para sistemas de tempo compartilhado
uma nova estratégia deve ser adotada, uma vez que pode não haver memória suficiente para
armazenar todos os processos.
O swapping é uma técnica aplicada à gerência de memória para programas que esperam por
memória livre para serem processados. O sistema escolhe um processo que é levado da memória
principal para o disco (swap out), retornando posteriormente para a memória principal (swap in).
Um dos problemas gerados pelo swapping é a relocação dos processos. No caso de um
processo que sai e volta muitas vezes para a memória, é necessário que a relocação seja realizada
pelo loader a cada carregamento. Essa situação torna o mecanismo ineficiente em função do tempo
gasto para o carregamento.
A melhor solução é uma implementação em hardware para permitir que a relocação seja
realizada durante a execução do programa. Esse tipo de mecanismo é denominado relocação
dinâmica.
A relocação dinâmica é realizada através de um registrador especial denominado registrador
de relocação. No momento em que o processo é carregado na memória, o registrador recebe o
endereço inicial da região de memória que o processo irá ocupar. Toda vez que ocorrer uma
referência a algum endereço, o endereço contido na instrução será somado ao conteúdo do
registrador, gerando o endereço físico. Assim um programa pode ser carregado em qualquer região
da memória.
Quando o swap cria muitas lacunas pode-se ganhar espaço movendo os processos para
realizar a compactação de memória. A compactação costuma ser evitada por tomar muito tempo da
UCP.
Se o processo precisar alocar memória e existir uma lacuna entre ele e o processo adjacente, é
alocado espaço desta lacuna. Se tal lacuna não existir o processo deve ser movido para uma lacuna
com espaço suficiente ou então deve ser realizada uma compactação de memória. Se não existe
espaço na memória nem em disco o processo deve esperar ou ser eliminado.
O conceito de swapping permitiu um maior compartilhamento da memória assim como um
maior throughput. A técnica se mostrou eficiente para sistemas com poucos usuários em ambientes
com aplicações pequenas. Seu maior problema é o elevado custo das operações de entrada/saída.
O CTSS do MIT e o OS/360 da IBM são exemplos de sistemas operacionais que
implementam tal técnica.
5.6 Memória Virtual
Memória virtual é uma técnica onde as memórias principal e secundária são combinadas,
dando ao usuário a ilusão de existir uma memória maior que a memória principal.
Até então quando o programa era muito grande para caber na memória principal empregava-
se a técnica de overlay, ficando a cargo do programador a divisão do programa em módulos. Com a
técnica de memória virtual cabe ao sistema operacional dividir o programa em partes e manter em
memória somente as partes do programa necessárias à execução do mesmo. As outras partes são
mantidas na memória secundária.
Sistemas Operacionais 54
Isto é muito útil porque verifica-se que, em muitos casos, nem todo o código do programa é
executado. Por exemplo:
Código para tratamento de condições de erro.
Tamanho da área de memória alocada por listas e tabelas que não são sempre totalmente
utilizadas.
Opções e recursos de programas que raramente são utilizados.
Mesmo quando todo o código de um programa é usado, ele não costuma ser usado
inteiramente ao mesmo tempo.
Ainda, a possibilidade de executar um programa armazenado apenas parcialmente na
memória traz benefícios como:
A área de memória disponível ao programa não fica limitada ao tamanho da memória
física disponível.
Um número maior de programas pode ser executado simultaneamente.
Uma menor quantidade de operações de E/S é necessário para carregar programas na
memória.
O conceito de memória virtual está baseado em desvincular o endereçamento feito pelo
processo dos endereços físicos da memória principal. Assim, os processos deixam de estar limitados
ao tamanho da memória física disponível.
A memória virtual também minimiza o problema da fragmentação da memória.
5.6.1 Espaço de Endereçamento Virtual
Quando se utiliza memória virtual um processo não faz referência a endereços físicos
(endereços reais), apenas a endereços virtuais. No momento da execução de uma instrução, o
endereço virtual é traduzido para um endereço físico. O mecanismo de tradução de um endereço
virtual para um endereço físico é denominado mapeamento.
O conjunto de endereços virtuais que os processos podem endereçar é chamado espaço de
endereçamento virtual, enquanto o conjunto de endereços reais é chamado espaço de
endereçamento real.
O espaço de endereçamento virtual não tem nenhuma relação direta com os endereços no
espaço real. Como os processos podem ser muito maiores que a memória física, apenas parte deles
pode estar residente na memória em um determinado instante. O sistema operacional utiliza a
memória secundária como extensão da memória principal.
Quando um usuário desenvolve uma aplicação cabe ao compilador e ao linker gerar o código
executável em função dos endereços virtuais, e ao sistema operacional cuidar dos detalhes durante a
execução.
5.6.2 Paginação
A paginação é uma técnica de gerência de memória onde o espaço de endereçamento virtual e
o espaço de endereçamento real são divididos em blocos do mesmo tamanho, chamados páginas.
Memória virtual Memória principal
Memória secundária
Sistemas Operacionais 55
As páginas no espaço virtual são denominadas páginas virtuais, enquanto as páginas no espaço real
são chamadas de páginas reais ou molduras de página (frames). Em geral as páginas variam de
512 bytes a 64 Kbytes.
Todo o mapeamento é realizado por intermédio de tabelas de páginas. Cada página virtual
possui uma entrada na tabela contendo informações de mapeamento que permitem ao sistema
localizar a página real correspondente.
Quando um processo entra no estado executando as páginas virtuais são transferidas da
memória secundária para a memória principal e colocadas em molduras de página. Sempre que o
processo fizer referência a um endereço virtual o mecanismo de mapeamento localizará na tabela de
páginas o endereço físico da moldura de página.
Nesse sistema, o endereço virtual é formado pelo número da página virtual e um
deslocamento dentro da página. O número da página virtual identifica unicamente uma página
virtual na tabela de páginas, enquanto o deslocamento funciona como índice dentro da página. O
endereço físico é calculado somando-se o endereço da moldura de página (localizada na tabela de
páginas) com o deslocamento (contido no endereço virtual).
Além da informação sobre a localização da página virtual a tabela de páginas possui outras
informações como o bit presente/ausente, que indica se uma página está ou não na memória física.
Sempre que o processo faz referência a um endereço virtual, o sistema verifica, através do bit
presente/ausente, se a página que contém o endereço referenciado está ou não na memória principal.
Caso não esteja, o sistema tem que transferir a página da memória secundária para a memória
principal. Toda vez que isso ocorre diz-se que ocorreu um falha de página (page fault).
Quando ocorre uma falha de página uma interrupção é gerada para passar o controle da UCP
ao sistema operacional. O sistema operacional então seleciona uma moldura de página (frame) para
liberar e, se necessário, a grava em disco. Por fim o sistema operacional carrega a página necessária
na moldura de página que acabou de ser liberada. A medida que um processo vai tendo suas páginas
carregadas ele tem suas molduras de página espalhadas pela memória, uma vez que elas entram
onde há espaço livre.
Cada processo de usuário vê a memória como um espaço único e contíguo, contendo apenas
esse processo. Mas na verdade o processo fica espalhado por toda a memória, que contém também
outros processos. Para que haja uma conciliação entre a memória vista pelo programa do usuário e a
xx1x24x03x
Memória virtual Memória principal01
234
01
234
5
67
89 página virtual
moldura de página
nº página virtual deslocamento
endereço do frame
Tabela de páginas
+
endereço físico
Memória principal
Sistemas Operacionais 56
memória física é necessário que seja feita uma tradução de endereços virtuais em endereços físicos,
preferencialmente sem o conhecimento do programa do usuário.
Nos sistemas atuais, a tarefa de tradução do endereço é realizada pelo hardware, juntamente
com o sistema operacional, de forma a não comprometer seu desempenho. Como a maioria das
aplicações tende a fazer referência a um número reduzido de páginas, somente uma pequena fração
da tabela de páginas é realmente necessária. Assim, foi introduzido um hardware especial para
mapear endereços virtuais em endereços físicos sem a necessidade de acesso à tabela de páginas,
chamado memória associativa ou translation lookaside buffer (TLB). Este hardware fica dentro da
unidade de gerenciamento de memória (memory management unit – MMU)
As páginas dos processos são transferidas da memória secundária para a principal apenas
quando são referenciadas. Este mecanismo é chamado paginação por demanda e é conveniente, na
medida em que leva para a memória principal apenas as páginas realmente necessárias à execução
do processo. Assim, é possível que partes do programa nunca sejam carregadas para a memória.
5.6.2.1 Tabela de páginas
A tabela de páginas tem o propósito de mapear páginas virtuais em molduras de página para
formar um endereço físico de memória, ou seja, realizar a tradução de um endereço virtual em um
endereço físico.
Dois aspectos devem ser observados quanto a tabela de páginas. O primeiro deles é que uma
tabela pode ser muito grande, consumindo parte significativa da memória. Se um sistema utilizar,
por exemplo, páginas de 4 Kbytes e 32 bits para endereçamento, sua tabela de páginas terá mais que
1 milhão de entradas. Se, por outro lado, forem utilizadas páginas muito grandes, haverá um
aumento na fragmentação interna.
O segundo aspecto é que o mapeamento deve ser feito da forma mais rápido possível, pois é
feito a cada referência à memória, ou seja, uma ou mais vezes por instrução executada.
Uma forma de diminuir o tamanho das tabelas de páginas é através do uso de tabelas de
páginas multinível, que procura evitar manter toda a tabela de páginas na memória. Neste esquema
existe uma tabela de páginas de primeiro nível que é usada para fornecer o endereço de uma
moldura de página que contém uma tabela de páginas de segundo nível, e assim por diante. Por
exemplo, suponha um sistema onde são dedicados para cada processo 12 Mbytes, sendo 4 Mbytes
para o código executável, 4 Mbytes para dados e 4 Mbytes para a pilha. Assim, tem-se para a tabela
de páginas de primeiro nível:
Entrada 0 → Tabela de páginas do código executável.
Entrada 1 → Tabela de páginas de dados.
Entrada 2 → Tabela de páginas da pilha.
Entradas 3 a 1023 → Inválidas.
Para o exemplo são necessárias apenas 4 tabelas em memória (a tabela de primeiro nível mais
3 tabelas de segundo nível). Desta forma armazena-se cerca de 4 mil entradas em memória, no lugar
das cerca de 1 milhão de entradas que seriam necessárias se não fossem utilizadas tabelas de
páginas multinível.
A figura abaixo mostra um exemplo de como é o funcionamento de uma tabela de páginas de
dois níveis e a tradução de um endereço virtual em um endereço físico:
TP1 TP2 Deslocamento
10 10 12
1024 entradas para tabela de 2º nível
32 bits de endereçamento virtual
Sistemas Operacionais 57
O formato de uma entrada na tabela de páginas varia com o sistema, mas em geral costuma
ser:
O bit presente/ausente indica se a página está na memória principal ou não. A proteção
informa que tipos de acesso são permitidos. O bit modificada informa se a página foi ou não
modificada desde a sua carga do disco. O bit referenciada é ligado sempre que a página é
referenciada. O bit desabilitar cache é usado apenas em sistemas que usam E/S mapeada em
memória, para evitar que o resultado de uma E/S seja mascarado.
A cada busca de instrução ou dados são necessários acessos extras à memória para consultar
as tabelas de páginas. É fato que a maioria dos processos tende a fazer um grande número de
referências a um pequeno número de páginas. Assim surgiu a ideia de equipar os computadores com
dispositivos de hardware para mapear os endereços sem passar pelas tabelas de páginas. Estes
dispositivos são conhecidos como Translation Lookaside Buffers (TLB) ou memória associativa,
e se encontram dentro da MMU (unidade de gerenciamento de memória).
O TLB é composto de entradas que contém informações sobre a página mapeada. Quando um
endereço virtual é passado à MMU, o hardware verifica se o número de página está no TLB. Se
estiver e não violar bits de proteção, a moldura de página será obtida diretamente do TLB, caso
contrário será gerada uma falha de TLB.
Quando o número de página não está no TLB, ele expulsa uma das entradas e a substitui pela
entrada da tabela de páginas que acabou de ser pesquisada.
Para algumas UCPs o gerenciamento e o tratamento de falhas do TLB são feitos inteiramente
pelo hardware da MMU.
Para outras UCPs a MMU simplesmente gera uma falha de TLB e passa o problema ao
sistema operacional. Neste caso, o sistema operacional deverá localizar a página, remover uma
entrada do TLB, carregar a página e reiniciar a instrução que falhou. Se o TLB for grande (64
entradas costuma ser suficiente) o gerenciamento por software será eficiente.
O ganho principal do gerenciamento do TLB por software é que a MMU fica mais simples,
sobrando mais espaço para cache.
página 0página 1
página 100
página 500
página 708
página 900
página 929
...1
500
...100
708
...929
900
..........
Tabela de páginas de
primeiro nível
Tabela de páginas de
segundo nível Memória
n1 n2 p
n1
n2
Tabela de páginas de
primeiro nível Tabela de páginas de
segundo nível
Endereço físico
Memória
p
... nº da moldura de página
presente/ausenteproteçãomodificadareferenciadadesabilitar cache
Sistemas Operacionais 58
5.6.2.2 Páginas Compartilhadas
Outra vantagem do mecanismo de paginação é a possibilidade de compartilhamento de
código, evitando que processos oriundos de um mesmo programa necessitem manter em memória
cópias de um mesmo código executável. Isto é possível de ser realizado quando o processo utiliza
código reentrante, ou seja, um código executável que não modifica a si próprio.
Para que se consiga o compartilhamento de páginas basta mapear as páginas virtuais dos
processos em uma mesma moldura de página. Deve-se ter o cuidado de contabilizar quantos
processos estão utilizando a mesma moldura de página para que ela não seja liberada de forma
indevida.
5.6.2.3 Working Set (Conjunto Funcional)
O mecanismo de memória virtual, apesar de suas vantagens, introduz um grande problema:
sempre que um processo faz referência a uma de suas páginas e ocorre uma falha de página é
exigido do sistema operacional pelo menos uma operação de E/S, que, quando possível, deve ser
evitada.
O sistema deve se preocupar em manter na memória principal um certo número de páginas
que reduza ao máximo a taxa de paginação dos processos, ao mesmo tempo que não prejudique os
demais processos que desejam ter acesso à memória.
O conceito de working set (conjunto funcional) surgiu a partir da análise da taxa de
paginação dos processos. Quando um processo começa a ser executado, percebe-se uma elevada
taxa de falhas de páginas, que se estabiliza com o decorrer de sua execução. Esse fato está ligado
diretamente a localidade (referência localizada) que é a tendência que existe em um processo de
fazer referências a posições de memória próximas. Isso significa que um processo tenderá a
concentrar suas referências em um mesmo conjunto de páginas durante determinado período de
tempo.
Se a memória for pequena para armazenar o working set, o processo poderá sofrer com a
elevada taxa de paginação (trashing), comprometendo seu desempenho.
5.6.2.4 Alocação Global x Alocação Local
A substituição de página global permite selecionar uma moldura de página para um processo
do conjunto de todas as molduras de página da memória, mesmo que esteja alocada a outro
processo. Na substituição local uma moldura de página alocada a um processo deve permanecer no
conjunto de molduras de página reservadas para uso desse processo.
Com a estratégia de substituição local a quantidade de molduras de página alocadas a um
processo não varia. Com a alocação global um processo pode sempre alocar molduras de página de
outros processos aumentando sua quantidade de molduras de páginas, desde que outros processos
não selecionem suas molduras de página.
Um problema com o algoritmo de substituição global é que um processo não pode controlar
sua própria taxa de ocorrência de páginas ausentes. O seu conjunto de páginas armazenadas na
memória dependerá também do comportamento dos outros processos, uma vez que seu
escalonamento não acontecerá, obrigatoriamente, a uma taxa constante. Por outro lado, a
substituição local pode retardar a execução de um processo, não deixando disponível a ele molduras
de página utilizadas com menos frequência.
Em geral a substituição global resulta em uma maior quantidade de processos executados por
unidade de tempo, sendo o método mais utilizado.
Sistemas Operacionais 59
5.6.2.5 Realocação de Páginas
O maior problema na gerência de memória virtual é decidir quais páginas remover. Quando o
limite do working set de um processo é alcançado e este necessita de novas molduras de página, o
sistema operacional deve escolher quais páginas devem ser liberadas.
Qualquer estratégia de realocação de páginas deve considerar se uma página foi ou não
modificada antes de liberá-la. Sempre que o sistema for liberar uma página modificada, ele antes
deverá gravá-la na memória secundária (page out), preservando seu conteúdo. O sistema mantém
um arquivo de paginação onde as páginas modificadas são armazenadas. Sempre que uma destas
páginas for novamente referenciada, ela será trazida de volta para o working set do processo (page
in).
O sistema consegue implementar esse mecanismo através do bit modificada, que existe em
cada entrada da tabela de páginas. Sempre que uma pagina é alterada, o valor do bit modificada é
marcado como 1.
A melhor estratégia de realocação de páginas seria aquela que escolhesse uma página que não
fosse referenciada num futuro próximo, porém o sistema operacional não tem como prever se uma
página será ou não utilizada novamente. A seguir serão analisadas as principais estratégias adotadas
pelos sistemas operacionais para a realocação de páginas.
Aleatória
A escolha aleatória não utiliza critério algum de seleção. Todas as páginas têm a mesma
chance de ser selecionada. Apesar de ser uma estratégia que consome poucos recursos do sistema,
raramente é utilizada por conta de sua baixa eficiência.
Not Recently Used (NRU)
Neste esquema são utilizados o bit referenciada (bit R - indica se a página foi referenciada ou
não) e o bit modificada (bit M - indica se a página foi modificada desde que foi carregada em
memória). Estes bits são marcados pelo hardware e desmarcados pelo sistema operacional.
Periodicamente o bit R é desmarcado pelo sistema operacional para determinar as páginas que
foram acessadas recentemente. Quando ocorre uma falha de página, as páginas são divididas em
quatro classes:
Classe 0 → Não referenciada, não modificada.
Classe 1 → Não referenciada, modificada.
Classe 2 → Referenciada, não modificada.
Classe 3 → Referenciada, modificada.
O algoritmo NRU remove uma página da classe não vazia com numeração mais baixa.
First In First Out (FIFO)
Nesse esquema, a página que foi utilizada primeiro será a primeira a ser escolhida para ser
liberada. Sua implementação é bastante simples, sendo necessária apenas uma fila.
Segunda Chance
É uma modificação do algoritmo FIFO para não jogar fora uma página muito utilizada. Neste
algoritmo o bit referenciada da página mais antiga é verificado. Se for 0 ela é substituída, senão o
bit será marcado como 0, a página irá para o final da fila e a próxima página será inspecionada.
Least Frequently Used (LFU)
Neste esquema a página menos referenciada (menos frequentemente utilizada) será a página
escolhida para ser substituída. Para isso é mantido um contador do número de referências feitas às
páginas, e a página que tiver o contador com o menor valor será a página escolhida. O algoritmo
mantém na memória as páginas que são bastante utilizadas. É uma boa estratégia, mas as páginas
Sistemas Operacionais 60
que entram mais recentemente no working set serão aquelas que estarão com os contadores com
menor valor. Outro problema é que é preciso manter uma lista ordenada por uso que deve ser
alterada a cada acesso à memória, gerando um grande overhead que dificulta sua utilização.
Least Recently Used (LRU)
As páginas que foram intensamente utilizadas nas últimas instruções provavelmente serão
intensamente utilizadas nas instruções seguintes. Assim, essa estratégia seleciona a página que está
a mais tempo sem ser referenciada (a página menos utilizada recentemente) para ser substituída. O
problema é que é preciso manter uma lista ordenada por uso que deve ser alterada a cada acesso à
memória, gerando um grande overhead que dificulta sua utilização.
Simulação de LRU em Software
Embora seja possível implementar o LRU, as máquinas não costumam ter o hardware
necessário. Uma solução é utilizar o algoritmo não frequentemente utilizado (not frequently used
– NFU), que usa um contador de software associado a cada página. Esse contador é iniciado com 0
e a cada interrupção do relógio o sistema operacional varre todas as páginas. O bit referenciada de
cada página é somado ao contador e zerado. Quando ocorre uma falha, a página com o contador
mais baixo é escolhida para ser substituída.
Se, ao invés do bit referenciada ser somado ao contador, o contador tiver seus bits deslocados
para a direita e o bit referenciada for somado à esquerda, obtêm-se o algoritmo da idade. Nesse
algoritmo, quando uma falha ocorre a página que tem o menor valor de contador é removida.
Os algoritmos NFU e da idade diferenciam-se do LRU por não oferecerem uma noção exata
do tempo. Se duas páginas tiverem igual histórico do bit referenciada não há como saber qual foi a
menos utilizada.
5.6.3 Segmentação
A memória estudada até o momento segue um modelo unidimensional, com endereços de 0
até um máximo.
A segmentação é uma técnica de gerência de memória onde os programas são divididos
logicamente em sub-rotinas e estruturas de dados e colocados em blocos de informações na
memória. Os blocos podem ter tamanhos diferentes e são chamados segmentos, cada um com seu
próprio espaço de endereçamento. Ainda, por constituírem espaços de endereçamento diferentes,
segmentos distintos podem crescer ou diminuir independentemente de forma dinâmica, sem afetar
os demais.
Enquanto a paginação divide o processo em partes de tamanho fixo, sem qualquer ligação
com a estrutura do programa, a segmentação permite uma relação entre a lógica do processo e sua
divisão na memória.
Os segmentos são mapeados através de tabelas de mapeamento de segmentos e os endereços
são compostos pelo número do segmento e um deslocamento (endereço dentro do segmento). O
número do segmento identifica unicamente uma entrada na tabela de segmentos, onde estão as
informações sobre o segmento na memória real. O endereço absoluto é calculado a partir do
endereço inicial do segmento mais o deslocamento.
Além do endereço do segmento na memória física, cada entrada na tabela de segmentos
possui informações sobre o tamanho do segmento, se ele está ou não na memória e sua proteção.
Na segmentação, apenas os segmentos referenciados são transferidos da memória secundária
para a memória real. Assim, para serem mais eficientes, os programas devem estar bem
modularizados.
Sistemas Operacionais 61
A figura abaixo exemplifica o uso de uma memória segmentada por um processo com cinco
segmentos.
A memória segmentada também facilita o trabalho do linkeditor, pois procedimentos em
segmentos separados sempre começam a executar no endereço 0. Assim, só é preciso compilar e
linkeditar as partes do programa do segmento alterado.
Outro aspecto facilitado pela segmentação é o do compartilhamento de código e dados entre
processos. Uma biblioteca compartilhada, por exemplo, pode ser colocada em um segmento e
compartilhada por múltiplos processos.
Ainda, segmentos diferentes podem ter proteções diferentes. Por exemplo:
Segmento de procedimento → somente execução.
Segmento de dados → leitura e escrita.
O problema da fragmentação também ocorre nesse modelo, quando as áreas livres são tão
pequenas que não acomodam nenhum segmento que necessite ser carregado. Nesse caso a
compactação pode ser utilizada para resolver a fragmentação.
5.6.4 Segmentação com Paginação
Permite a divisão lógica dos programas em segmentos que, por sua vez, são divididos em
páginas, para que não precisem ser totalmente mantidos em memória.
Assim, um endereço é formado pelo número do segmento, um número de página dentro
desse segmento e um deslocamento dentro dessa página. Através do número do segmento obtém-se
uma entrada na tabela de segmentos que contém informações da tabela de páginas do segmento.
Com o número da página obtém-se uma entrada na tabela de páginas com informações da página
nº do segmento deslocamento
endereço do segmento
Tabela de segmentos
+
endereço físico
Memória principal
Sub-rotina 1
Segmento 0
Sub-rotina 2
Segmento 1
Pilha
Segmento 3
Dados
Segmento 4
Programa principal
Segmento 2
Segmento 0
Segmento 3
Segmento 2
Segmento 4
Segmento 1
1000400400
11001000
Limite
14006300430032004700
Base
01234
1400
2400
3200
4300
4700
5700
6300
6700
Sistemas Operacionais 62
na memória física. O endereço físico é obtido somando-se a posição inicial da moldura de página
(frame) e o deslocamento.
5.6.5 Segmentação com Paginação: O Pentium Intel
O Pentium tem 16K segmentos independentes, cada um podendo conter até um bilhão de
palavras de 32 bits.
A memória virtual é constituída por duas tabelas, a LDT (Tabela Local de Descritores) e a
GDT (Tabela Global de Descritores). Cada processo tem sua própria LDT, mas a GDT é única,
compartilhada por todos os processos. A LDT descreve os segmentos próprios de cada processo,
enquanto que a GDT descreve os segmentos do sistema, incluindo o próprio sistema operacional.
Para acessar um determinado segmento deve-se carregar um seletor para este segmento em
um dos seis registradores de segmento (CS = segmento de código, DS = segmento de dados, etc.).
Cada seletor é um número de 16 bits:
1 bit para informar se o segmento é local ou global
13 bits para identificar o número da entrada
2 bits para proteção
Quando o seletor é carregado no registrador de segmento, o descritor correspondente é
buscado na LDT ou na GDT e armazenado em um dos registradores do microprograma para que
possa ser acessado rapidamente. Um descritor consiste em oito bytes, contendo informações como o
endereço base, o tamanho e outras informações do segmento.
Para converter um par (seletor, deslocamento) em um endereço físico:
O microprograma descobre qual registrador de segmento está sendo utilizado e carrega o
descritor em seus registradores. Se o segmento não existe ou não está carregado na
memória é gerada uma interrupção.
O microprograma verifica se o deslocamento está dentro do segmento. Se não estiver
gera uma interrupção.
O deslocamento é somado à base do segmento para formar o endereço linear. Se a
paginação estiver desativada o endereço linear é interpretado como um endereço físico.
Se a paginação estiver ativada, o endereço linear será interpretado como um endereço
virtual e será mapeado para o endereço físico usando uma tabela de páginas de 2 níveis.
nº do segmento nº página virtual
endereço do segmento
Tabela de segmentos
+
endereço físico
Memória principal
deslocamento
endereço do frame
Tabela de páginas
+
13 1 2
Índice Nível de privilégio0=GDT, 1=LDT
SELETOR
Endereço de Base
Limite
Outros campos
DESCRITORDESLOCAMENTO
+
Endereço linear de 32 bits
Sistemas Operacionais 63
Cada processo possui um diretório de páginas que consiste em 1024 entradas de 32 bits. Cada
entrada deste diretório aponta para uma tabela de páginas que também contém 1.024 entradas de 32
bits, que apontam para molduras de páginas.
O campo dir é usado como índice para o diretório de páginas, a fim de localizar um ponteiro
para a tabela de páginas correspondente. O campo página é usado como índice na tabela de páginas
para encontrar o endereço físico da moldura onde está a página. O campo deslocamento é
adicionado ao endereço da moldura para obtenção do endereço físico da palavra.
Para evitar repetidas referências à memória o Pentium tem uma pequena memória associativa
(TLB) que mapeia diretamente as páginas mais usadas recentemente no endereço físico de suas
molduras. Somente quando a combinação corrente não estiver no TLB é que o mecanismo do
sistema operacional será acionado e o TLB será atualizado com o resultado obtido.
5.6.6 Proteção
Em qualquer sistema multiprogramável deve existir um mecanismo que proteja o espaço de
memória de cada processo, assim como a área do sistema operacional.
No esquema de memória virtual a tradução dos endereços é realizada pelo sistema
operacional. Assim, é impossível que um processo tenha acesso a áreas de memória de outros
processos. A proteção é necessária para impedir que um processo, ao acessar uma página ou
segmento do sistema, a modifique. Mesmo as páginas e segmentos do processo podem estar
definidas com uma proteção que impeça, por exemplo, a gravação.
Em sistemas que implementam paginação e/ou segmentação, a proteção deve ser realizada em
nível de cada página/segmento na memória. Esse mecanismo é implementado utilizando-se as
tabelas de mapeamento, onde alguns bits especificam os acessos permitidos a cada uma das
páginas/segmentos.
5.6.7 Compartilhamento de Memória
Em sistemas que implementam memória virtual é bastante simples o compartilhamento de
código e dados entre processos. Para isso basta que as entradas das tabelas de páginas/segmentos
apontem para as mesmas páginas/segmentos na memória principal. Assim é possível reduzir o
número de programas na memória e aumentar o número de usuários compartilhando o mesmo
recurso.
A vantagem da segmentação em relação à paginação no aspecto de compartilhamento baseia-
se na forma em que os programas são divididos. Como as tabelas de segmentos mapeiam estruturas
lógicas, o compartilhamento de segmentos é mais simples do que o de páginas.
5.6.8 Trashing
Quando a quantidade de molduras de página alocadas a um processo decresce até um valor
inferior ao conjunto funcional, sua execução deve ser suspensa, liberando o espaço ocupado por
suas molduras de página. Caso este processo não seja suspenso e o número de páginas em uso seja
DIR PÁGINA
Diretório de páginas
DESLOCAMENTO10 10 12
DIR
Tabela de páginas
PÁGINA
Moldura de página
DESLOCAMENTO
Entrada de diretório aponta para a tabela de páginas
Entrada na tabela de páginas aponta para a moldura da página
Sistemas Operacionais 64
maior que o número de molduras de página disponíveis, ocorrerá um número excessivo de falhas de
página.
Quando o processo faz paginação excessiva, consumindo mais tempo em substituição de
páginas do que executando seu código, diz-se que está havendo trashing.
Trashing pode ser definido como sendo a excessiva transferência de páginas/segmentos entre
a memória principal e a memória secundária.
O trashing na paginação ocorre em dois níveis: no nível do próprio processo e no nível do
sistema. No nível do processo a excessiva paginação ocorre devido ao elevado número de falha de
páginas, gerado pelo processo em execução. Existem dois motivos que levam um processo a sofrer
esse tipo de trashing: o mal dimensionamento do tamanho do conjunto funcional e a não obediência
ao conceito de localidade.
No nível do sistema o trashing ocorre quando existem mais processos competindo por
memória do que espaço disponível. O sistema deve tentar administrar a memória de forma que
todos os processos sejam atendidos. O primeiro passo é a redução do tamanho dos working sets dos
processos, o que pode levar ao trashing no nível do processo. Caso a redução do working set não
seja suficiente, o sistema começa o trabalho de swapping. Se esse mecanismo for levado ao extremo
o sistema passará mais tempo fazendo swapping que executando os processos.
O trashing na segmentação também ocorre em nível de processo e em nível de sistema. No
nível do processo, a transferência de segmentos é excessiva devido à modularização extrema do
programa, não seguindo o conceito de localidade. No nível do sistema o trashing é bastante
semelhante ao da paginação com a ocorrência de swapping.
De qualquer forma, se existem mais processos para serem executados que memória real
disponível, a única solução é expandir a memória principal.
5.7 Exercícios
1) Por que a gerência de memória para sistemas multiprogramáveis é mais crítica do que para
sistemas monoprogramáveis?
2) O que vem a ser atribuição de endereços em tempo de execução?
3) Como funciona o carregamento dinâmico? Qual a sua vantagem?
4) Como funciona a ligação dinâmica?
5) Como o esquema de ligação dinâmica pode ajudar na atualização de softwares?
6) Por que o esquema de alocação contígua simples não é apropriado para a multiprogramação?
7) Como a técnica de overlay (sobreposição) permite expandir os limites da memória
principal? Qual o impacto no desempenho?
8) Por que para a multiprogramação ser eficiente é necessário que mais de um programa possa
estar carregado na memória principal simultaneamente?
9) Qual a diferença entre a alocação particionada estática e a alocação particionada dinâmica?
10) Como é feita a proteção da memória nos esquemas de alocação particionada? Por que ela é
necessária?
11) Para a gerência de memória, o que é a fragmentação? Por que ela é prejudicial?
12) Qual a diferença entre fragmentação interna e fragmentação externa?
13) O que é a técnica de swapping aplicada à gerência de memória? Qual a sua vantagem sobre
a alocação particionada?
Sistemas Operacionais 65
14) O swapping apresenta o problema da relocação. O que vem a ser relocação dinâmica? Por
que ela é necessária?
15) O que é a técnica de memória virtual sob o ponto de vista da gerência de memória?
16) Quais as vantagens de se utilizar memória virtual no lugar de swapping?
17) Qual o relacionamento entre espaço de endereçamento virtual e espaço de endereçamento
real?
18) Como a técnica de memória virtual permite que um programa ultrapasse os limites da
memória principal?
19) O que vem a ser paginação para a gerência de memória?
20) Qual a importância da tabela de páginas para a técnica de paginação aplicada à memória
virtual?
21) O que é uma falha de página quando se está utilizando memória virtual? O que deve ser
feito quando ela ocorre?
22) Como a paginação ajuda a diminuir o problema de fragmentação de memória?
23) Qual o propósito das tabelas de páginas?
24) Para que serve o bit presente/ausente nas tabelas de páginas?
25) Quando uma página carregada em memória precisar ser substituída por outra deve-se
observar o bit modificada. Por quê?
26) Considere um sistema que utilize memória virtual com paginação. Supondo que o
mapeamento das páginas virtuais em molduras de página seja realizado pela tabela de páginas
abaixo, para qual endereço físico será mapeado o endereço virtual que tem número de página 2C9 e
deslocamento 2CB (2C9:2CB)?
nº página
virtual
Endereço
moldura
.......
...
2C7
2C8
2C9
2CA
2CB
2CC
...
...
25000
3C000
8F000
BA000
45000
D8000
...
.......
.......
.......
.......
.......
.......
.......
.......
27) Por que o mapeamento de memória deve ser realizado em blocos e não sobre células
individuais?
28) Qual a função do TLB (memória associativa) para a gerência de memória?
29) Quando se usa a paginação é possível realizar o compartilhamento de páginas. O que vem
a ser isto? Explique o benefício que se obtém.
30) O que é working set (conjunto funcional)?
31) Qual a relação existente entre working set (conjunto funcional) e trashing?
32) Explique o funcionamento do algoritmo da idade, usado para escolher uma página que
deve ser retirada da memória.
33) O que é a técnica de segmentação aplicada à gerência de memória?
Sistemas Operacionais 66
34) Quando se usa segmentação o endereço é dado por um par segmento:deslocamento. Como
é o mapeamento de um endereço deste tipo em um endereço real?
35) Como a segmentação pode facilitar o compartilhamento de código entre processos
oriundos de um mesmo programa?
36) O que é trashing? Como este problema pode ser solucionado?
Sistemas Operacionais 67
6 Sistema de Arquivos
O armazenamento e a recuperação de informações são atividades essenciais para qualquer
tipo de aplicação. As principais exigências para armazenamento de informações são:
Deve ser possível armazenar uma grande quantidade de informações.
A informação deve sobreviver à finalização do processo que a utiliza.
Múltiplos processos devem ser capazes de acessar as informações concorrentemente.
É mediante a implementação de arquivos em discos ou outras mídias que o sistema
operacional estrutura e organiza estas informações. A parte responsável por essa gerência é
denominada sistema de arquivos. A manipulação de arquivos deve ocorrer de maneira uniforme,
independentemente dos diferentes dispositivos de armazenamento.
O sistema de arquivos é constituído de duas partes distintas: um conjunto de arquivos, que
armazenam dados, e uma estrutura de diretórios, que organiza e fornece informações sobre os
arquivos do sistema.
6.1 Arquivos
Computadores podem armazenar dados em diferentes meios de armazenamento. Para que um
sistema possa ser usado de forma conveniente, o sistema operacional deve oferecer uma visão
lógica e uniforme do meio de armazenamento.
Um arquivo é constituído de informações, podendo representar programas ou dados. Um
programa contém instruções compreendidas pela UCP (arquivo executável), enquanto um arquivo
de dados pode ser estruturado livremente (podem ser numéricos, alfabéticos, alfanuméricos ou
binários).
Arquivos podem ser armazenados pelo sistema operacional em diferentes dispositivos físicos
(fita magnética, disco magnético, CD-ROM, etc.). O tipo de dispositivo deve ser isolado pelo
sistema operacional de forma que exista uma independência entre os arquivos a serem manipulados
e o meio de armazenamento.
Um arquivo é identificado por meio de um nome, e as regras para os nomes de arquivos
variam de sistema para sistema. Em alguns sistemas operacionais o nome do arquivo é composto
por duas partes separadas com um ponto. A parte após o ponto é denominada extensão do arquivo e
tem como finalidade identificar seu conteúdo. As principais diferenças entre as regras para os
nomes de arquivo são:
Quantidade máxima de caracteres.
Diferenciação entre caracteres maiúsculos e minúsculos.
Uso de caracteres especiais.
Nomes com extensão tendo significado ou não.
Quando um processo cria um arquivo ele deve dar um nome a este arquivo para que quando o
processo termine o arquivo continue existindo e possa ser acessado por outros processos pelo
mesmo nome. Ao receber um nome o arquivo se torna independente do processo, do usuário e até
mesmo do sistema que o criou.
6.1.1 Organização de Arquivos
No momento da criação de um arquivo é possível definir que organização será adotada. Esta
organização pode ser uma estrutura suportada pelo sistema operacional ou definida pela própria
aplicação.
Sistemas Operacionais 68
A forma mais simples de organização de arquivos é através de uma sequência não
estruturada de bytes. Nesse tipo de organização o sistema de arquivos não impõe nenhuma
estrutura lógica para os dados (a aplicação deve definir toda a organização). A grande vantagem é a
flexibilidade para criar diferentes estruturas de dados, mas todo o controle de acesso ao arquivo é de
inteira responsabilidade da aplicação.
Uma forma estruturada para o armazenamento de arquivos é a sequência de registros de
tamanho fixo. Nela o arquivo é composto por uma série de registros com estrutura interna
característica, e as operações de leitura/gravação trabalham com registros inteiros.
Outra estrutura é a árvore de registros, uma organização que é composta por registros que não
tem necessariamente o mesmo tamanho, e cada um contém um campo com uma chave em uma
posição fixa. A árvore é ordenada pelo campo chave de forma a permitir uma busca rápida.
6.1.2 Métodos de Acesso
Os primeiros sistemas operacionais acessavam os registros de um arquivo na ordem em que
eram gravados, sendo a gravação de novos registros possível apenas no final. A esse tipo de acesso
dá-se o nome de acesso sequencial. Não é possível pular ou ler registros fora de ordem, mas é
possível retroceder registros.
Posteriormente surgiu um método de acesso mais eficientes, o acesso aleatório (acesso
direto), que permite a leitura/gravação de um registro diretamente na sua posição relativa ao início
do arquivo. No acesso aleatório não existe restrição à ordem em que os registros são lidos ou
gravados. O acesso aleatório somente é possível quando o arquivo é definido com registros de
tamanho fixo.
Em sistemas operacionais antigos o tipo de acesso ao arquivo é determinado no momento em
que o arquivo é criado. Nos sistemas operacionais modernos todos os arquivos são de acesso
aleatório, excetuando-se os casos em que o dispositivo no qual o arquivo está armazenado não
permita o acesso aleatório.
Existem ainda outros métodos de acesso menos comuns, como o acesso indexado. O acesso
indexado envolve a construção de um índice para o arquivo. Esse índice contém ponteiros para os
blocos e, para acessar uma posição de arquivo, primeiro pesquisa-se o índice e depois utiliza-se o
ponteiro para a posição desejada.
6.1.3 Tipos de Arquivos
Os sistemas operacionais costumam suportar vários tipos de arquivos. Os tipos mais comuns
são:
Arquivos regulares → Arquivos que contém informações genéricas como, por exemplo,
dados dos usuários.
Diretórios → Arquivos de sistema usados para manter a estrutura do sistema de arquivos.
Arquivos especiais de caractere → Relacionam-se com operações de E/S e costumam
modelar dispositivos seriais.
Arquivos especiais de bloco → Usados para modelar dispositivos de bloco, em especial
discos.
1 byte 1 registro A E L
F G KB C D M N O
H I J
Sequência de bytes Sequência de registros
Árvore
Sistemas Operacionais 69
Em geral arquivos regulares são classificados como arquivo texto ou arquivo binário. Um
arquivo texto (ou arquivo ASCII) é constituído por linhas de texto que podem ter tamanhos
diferentes e terminam por caracteres especiais para indicar o fim da linha. São arquivos que quando
exibidos na tela ou impressos podem ser compreendidos pelas pessoas. Podem, ainda, ser editados
com um editor de textos comum.
Arquivos binários são arquivos que não são arquivos texto. Sua listagem gera um conjunto de
caracteres incompreensíveis. Eles podem ser arquivos de usuários (com ou sem estrutura interna) ou
arquivos executáveis (com estrutura conhecida pelo sistema operacional e códigos que são
executados pela UCP).
6.1.4 Operações de Entrada/Saída
O sistema de arquivos oferece um conjunto de chamadas de sistema que permite às aplicações
realizarem operações de E/S. As chamadas de sistema de E/S têm como função oferecer uma
interface simples e uniforme entre a aplicação e os diversos tipos de dispositivos.
Na parte introdutória desta apostila foram mostradas as chamadas de sistema POSIX para
gerenciamento de arquivos, que está presente na maioria dos sistemas operacionais.
6.1.5 Atributos
Além do nome e dos dados, arquivos costumam possuir informações extras para controle
denominadas atributos. Alguns atributos variam dependendo do sistema de arquivos, mas alguns
como tamanho do arquivo, proteção, identificação do criador e data de criação, estão presentes em
quase todos os sistemas. Os principais atributos são:
Atributos relacionados com a proteção → proteção (quem e de que maneira pode
acessar), senha, criador, proprietário.
Atributos que controlam ou ativam alguma atividade específica → somente leitura,
oculto, sistema, arquivo, ASCII/binário, acesso aleatório, temporário, bloqueio.
Atributos para arquivos cujos registros podem ser pesquisados → comprimento do
registro, posição da chave, comprimento da chave.
Atributos relacionados com data/hora → tempo de criação, tempo do último acesso,
tempo da última alteração.
Atributos relacionados com o tamanho → tamanho atual, tamanho máximo.
6.2 Diretórios
A estrutura de diretórios é o modo como o sistema organiza logicamente os diversos arquivos
contidos em um disco. O diretório é um arquivo que contém uma estrutura de dados com entradas
associadas aos arquivos onde são armazenadas informações como localização física, nome,
organização e demais atributos.
Quando é solicitada a abertura de um arquivo o sistema operacional pesquisa o diretório até
encontrar uma entrada com o nome do arquivo. Quando a entrada é encontrada o sistema
operacional copia os atributos e os endereços de disco e os coloca em uma estrutura na memória, a
tabela de arquivos, que contém todos os arquivos abertos. Quando o arquivo é fechado, sua entrada
na tabela de arquivos é liberada.
A implementação mais simples de uma estrutura de diretórios é chamada de nível único, (um
nível), onde existe um único diretório contendo todos os arquivos do disco. Este modelo é bastante
limitado, já que não permite que usuários criem arquivos com o mesmo nome, o que ocasionaria um
conflito no acesso aos arquivos. Este tipo de implementação não é mais utilizado atualmente.
Em uma outra estrutura, conhecida como diretório de dois níveis, existe um diretório para os
arquivos do sistema e um diretório para cada usuário. Com esta implementação, cada usuário pode
Sistemas Operacionais 70
criar arquivos sem a preocupação de conhecer os demais arquivos do disco. Para que o sistema
possa localizar arquivos nesta estrutura existe um nível de diretório adicional denominado master
file directory, indexado pelo nome do usuário. Nele, cada entrada aponta para o diretório de cada
usuário. Para referenciar um arquivo neste tipo de estrutura é necessário especificar o diretório onde
ele se encontra e o seu nome, especificando assim seu caminho (path).
A organização dos arquivos em um único diretório não permite uma organização adequada. A
extensão para um modelo de múltiplos níveis permite que os arquivos sejam melhor organizados.
Este modelo, chamado estrutura de diretórios em árvore ou sistema de diretórios hierárquico, é
atualmente adotado pela maioria dos sistemas operacionais. Na estrutura em árvore é possível criar
quantos diretórios se deseje, podendo um diretório conter arquivos ou outros diretórios
(subdiretórios). Cada arquivo, nesta estrutura, possui um caminho (path) único que descreve todos
os diretórios desde a raiz até o diretório no qual o arquivo está, mais o nome do arquivo.
Quando o sistema de arquivos é organizado como uma árvore de diretórios, os nomes de
caminhos podem ser absolutos ou relativos. Um nome de caminho absoluto consiste no caminho
desde o diretório raiz (diretório inicial do sistema de arquivos) até o arquivo.
O nome de caminho relativo é utilizado em conjunto com o conceito de diretório de trabalho
(diretório atual) ,que é o diretório usado atualmente pelo processo e serve como base caso o nome
do caminho não inicie com o diretório raiz. Quando é utilizado um caminho relativo, o caminho até
o arquivo é buscado a partir do diretório de trabalho.
Cada processo possui seu próprio diretório de trabalho. Assim, se algum processo alterar seu
diretório de trabalho, os outros processos do usuário não serão afetados.
A maioria dos sistemas que suporta estrutura de diretórios em árvore tem duas entradas
especiais para cada diretório:
“.”→ Diretório atual.
“..”→ Diretório pai. Pode ser usada para subir na árvore de diretórios.
6.3 Alocação de Espaço em Disco
Os discos são o meio de armazenamento secundário mais comum no qual os arquivos são
armazenados. As transferências de dados entre a memória e o disco são realizadas em unidades
chamadas blocos, cada qual constituída por um ou mais setores. Dependendo do disco um setor
pode variar de 32 a 4096 bytes, sendo mais comum setores de 512 bytes.
A criação de arquivos em disco exige que o sistema operacional tenha o controle de quais
blocos no disco estão livres. Este controle é realizado através de uma estrutura de dados que
armazena informações que possibilitam ao sistema de arquivos gerenciar o disco.
A forma mais simples de implementar uma estrutura de espaços livres é através de uma tabela
denominada mapa de bits. A cada entrada da tabela é associada um bloco do disco representado por
um bit, que pode assumir valor igual a 0 (bloco livre) ou 1 (bloco alocado).
Uma segunda maneira de realizar este controle é por meio de uma lista encadeada de todos os
blocos livres do disco. Cada bloco possui uma área reservada para armazenamento do endereço do
próximo bloco, e a partir do primeiro bloco livre pode-se ter acesso aos demais de forma encadeada.
Suas principais restrições são o espaço utilizado no bloco com informação de controle e o fato do
algoritmo de busca de espaço livre sempre ter que realizar uma pesquisa sequencial na lista.
Outra solução leva em conta que blocos contíguos são geralmente alocados ou liberados
simultaneamente, enxergando o disco como um conjunto de segmentos de blocos livres. Assim é
possível manter uma tabela de blocos livres com o endereço do primeiro bloco de cada segmento e
o número de blocos livres contíguos que se seguem.
Sistemas Operacionais 71
6.3.1 Alocação Contígua
A alocação contígua consiste em armazenar um arquivo em blocos contíguos de dados no
disco. Neste tipo de alocação, o sistema localiza um arquivo através do endereço do primeiro bloco
e da sua quantidade de blocos.
O acesso a arquivos dispostos contiguamente no disco é bastante simples tanto para o acesso
sequencial quanto para o acesso aleatório. Seu principal problema é a alocação de espaço livre para
novos arquivos, já que para um arquivo ser criado com n blocos é necessário que exista uma cadeia
de n blocos dispostos sequencialmente no disco.
Pode-se enxergar o disco como um grande vetor onde os elementos podem ser considerados
segmentos com tamanhos diferentes de blocos contíguos, dispostos alternadamente entre segmentos
ocupados e segmentos livres. No momento em que o sistema operacional deseja alocar espaço para
armazenar um novo arquivo, pode existir mais de um segmento livre disponível com o tamanho
exigido. Neste caso, é necessário que alguma estratégia de alocação seja adotada para selecionar
qual o segmento na lista de blocos livres deve ser escolhido. As estratégias adotadas são as mesmas
estudadas para escolha de partição em memória, existindo também o problema da fragmentação.
Quando o disco estiver muito fragmentado para que se possa alocar espaço para a criação do
arquivo será necessário realizar a desfragmentação do disco, que consiste em mover os arquivos
para abrir espaço suficiente para o novo arquivo. A desfragmentação é um processo demorado que
deve ser realizado periodicamente.
Nem sempre é possível determinar no momento da criação de um arquivo qual o seu tamanho
máximo, podendo posteriormente existir a necessidade de extensão. A pré-alocação de espaço é
uma solução que, apesar de resolver o problema, pode fazer com que parte do espaço alocado
permaneça ocioso.
O sistema operacional IBM VM/CMS usava este tipo de alocação.
6.3.2 Alocação por Lista Encadeada
Na alocação por lista encadeada um arquivo é organizado como um conjunto de blocos
ligados logicamente, independentemente da sua localização física. Cada bloco deve possuir um
ponteiro para o bloco seguinte e assim por diante. A entrada de diretório precisa armazenar somente
o endereço do primeiro bloco.
A fragmentação do disco não ocorre na alocação encadeada já que os blocos alocados para
um determinado arquivo não precisam estar em posições contíguas. O que ocorre neste método é a
fragmentação de arquivos, que é a quebra do arquivo em diversos blocos espalhados pelo disco. A
fragmentação dos arquivos resulta no aumento do tempo de acesso, pois o processo de
leitura/gravação provoca um grande número de deslocamentos da cabeça de leitura/gravação. Para
otimizar o tempo das operações de E/S é importante que o disco seja desfragmentado
periodicamente.
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16 17 18 19
20 21 22 23 24
25 26 27 28 29
1 1 1 0 00 0 0 1 11 1 0 0 10 0 1 1 10 0 0 0 00 0 1 1 1
03121520
Bloco
03121520
Contador
Mapa de bits Lista encadeada Tabela de blocos livres
Sistemas Operacionais 72
A alocação por lista encadeada só permite o acesso sequencial aos blocos de um arquivo. Isto
constitui uma das principais desvantagens dessa técnica. Além disso é desperdiçado espaço nos
blocos com o armazenamento de ponteiros.
Outro problema da alocação por lista encadeada é referente à confiabilidade. Se o valor de um
ponteiro é corrompido se perde o encadeamento do arquivo.
6.3.3 Alocação por Lista Encadeada Utilizando Índice
É um esquema de alocação muito parecido com a alocação por lista encadeada, mas no lugar
de fazer o encadeamento utilizando um ponteiro no bloco, o encadeamento é mantido em uma
tabela.
Embora a cadeia ainda precise ser seguida para o acesso aleatório, ela é seguida por
intermédio da tabela, e não consultando bloco a bloco, e pode, ainda, ser mantida em memória,
tornando o acesso mais rápido.
A principal desvantagem é que a tabela pode ser muito grande para discos grandes. Este
problema pode ser minimizado agrupando os blocos em clusters para formar unidades maiores, mas
isto provoca um maior desperdício por causa da parte não utilizada do último cluster, que tenderá a
ser maior.
6.3.4 Nós-I (Alocação Indexada)
O método consiste em associar a cada arquivo uma tabela denominada nó-i (nó-índice ou
inode) que lista os atributos e os endereços em disco dos blocos do arquivo.
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16 17 18 19
20 21 22 23 24
25 26 27 28 29
10 06 03 18 21 28
Alocação por lista encadeada
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16 17 18 19
20 21 22 23 24
25 26 27 28 29
.. .. .. 18 ..
.. 03 .. .. ..
06 .. .. .. ..
.. .. .. 21 ..
.. 28 .. .. ..
.. .. .. -1 ..
Alocação por lista encadeada utilizando índice
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16 17 18 19
20 21 22 23 24
25 26 27 28 29
100603182128
12
nó-i
Sistemas Operacionais 73
Os primeiros endereços de disco são armazenados no próprio nó-i. Isto significa que para
arquivos pequenos toda a informação está contida no próprio nó-i e pode ser transferida para a
memória quando o arquivo é aberto. Para arquivos maiores, um dos endereços no nó-i é o endereço
de um bloco do disco chamado bloco indireto simples, que contém endereços de blocos adicionais
para o arquivo. No nó-i existe ainda o endereço de um bloco indireto duplo e de um bloco indireto
triplo. O bloco indireto duplo contém o endereço do bloco que possui uma lista de blocos indiretos
simples relativos ao arquivo, enquanto o bloco indireto triplo contém o endereço do bloco que
possui uma lista de blocos indiretos duplos.
6.4 Exemplos de Implementação de Diretórios
Quando é solicitada a abertura de um arquivo, o sistema operacional usa o nome do caminho
fornecido para localizar a entrada de diretório.
Uma questão importante é onde armazenar os atributos. Alguns sistemas armazenam
diretamente na entrada de diretório, enquanto outros armazenam no nó-i.
6.4.1 Diretórios no CP/M
O CP/M possui apenas um diretório, assim a pesquisa por um arquivo é realizada somente
neste diretório.
A entrada possui os números de blocos de disco. Se um arquivo usa mais blocos de disco que
os que se ajustam em uma entrada, são alocadas entradas adicionais.
O CP/M não informa o tamanho do arquivo em bytes. Pode-se apenas saber quantos blocos
estão alocados ao arquivo.
O campo código do usuário identifica o proprietário do arquivo. O nome de um arquivo é
composto pelo campo nome do arquivo (até 8 caracteres) concatenado com o campo tipo (até 3
caracteres, separados por um ponto. O campo grau é usado para indicar a ordem em que as entradas
do arquivo devem ser acessadas quando o arquivo for maior que 16 blocos.
6.4.2 Diretórios no MS-DOS
É um sistema de arquivos baseado em árvores de diretórios onde cada entrada de diretório
possui 32 bytes.
Atributos Bloco indireto simples
Nó-i
Bloco indireto duplo
Bloco indireto
triplo
Nome do arquivo Tipo (ext)
Código do usuárioGrau
Endereços de blocos de discoContagem de blocos
1 8 3 1 2 1 16
Sistemas Operacionais 74
O campo número do primeiro bloco é usado como índice para uma tabela que representa a
lista encadeada de blocos.
Diretórios podem conter outros diretórios, conduzindo a um sistema de arquivos hierárquico.
6.4.3 Diretórios no UNIX
Cada entrada possui apenas um nome de arquivo e o número de seu nó-i. As outras
informações sobre o arquivo estão contidas no nó-i.
Quando é solicitada a abertura de um arquivo com caminho absoluto, o sistema operacional
localiza o diretório raiz (em uma posição fixa no disco) e busca diretório a diretório até chegar ao
diretório em que se encontra o arquivo. Então o nó-i do arquivo é carregado e mantido em memória
até que o arquivo seja fechado.
Nomes com caminho relativo são pesquisados da mesma forma, exceto que iniciam a partir do
diretório de trabalho.
A entrada “.” tem o número do próprio nó-i, enquanto a entrada “..” tem o número do nó-i do
diretório pai.
6.5 Proteção de Acesso
Como os meios de armazenamento são compartilhados entre diversos usuários é importante
que mecanismos de proteção sejam implementados para garantir a proteção individual de arquivos e
diretórios.
O tipo de acesso a arquivos é implementado mediante a concessão ou não dos diferentes
acessos que podem ser realizados, como leitura, gravação, execução e eliminação. O controle de
acesso às operações realizadas com diretórios possui diferenças em relação às operações realizadas
com arquivos.
Existem diferentes mecanismos e níveis de proteção, cada qual com suas vantagens e
desvantagens.
6.5.1 Senha de Acesso
O controle de acesso por senha se resume ao usuário ter o conhecimento da senha e,
consequentemente, ter a liberação do acesso ao recurso.
Como cada recurso possui apenas uma senha, o acesso é liberado ou não na sua totalidade.
Assim, não é possível determinar quais tipos de operação podem ou não ser concedidas. Outra
desvantagem é a dificuldade de compartilhamento de arquivos, pois além do dono do arquivo todos
os demais usuários teriam que conhecer a senha de acesso.
6.5.2 Grupos de Usuários
Esse tipo de proteção tem como princípio a associação de cada usuário do sistema a um ou
mais grupos. Os grupos de usuários são organizados logicamente com o objetivo de compartilhar
recursos, sendo que os usuários que desejam compartilhar recursos entre si devem pertencer a um
mesmo grupo.
Esse mecanismo implementa três níveis de proteção: dono, grupo e todos. Na criação do
recurso o usuário especifica se o recurso deve ser acessado somente pelo seu criador, pelos usuários
do grupo ao qual ele pertence ou por todos os usuários do sistema. É necessário especificar o tipo de
acesso (leitura, escrita, execução e eliminação) para cada nível de proteção.
Nome do arquivo
Extensão Número do primeiro blocoAtributos
8 3 1 10
Reservado
2Tama-nho
2 2 4
HoraData
Sistemas Operacionais 75
6.5.3 Lista de Controle de Acesso
A Lista de Controle de Acesso (Access Control List - ACL) consiste em uma lista associada
a cada recurso, onde são especificados os usuários e os tipos de acesso permitidos. Quando um
usuário tenta acessar um recurso, o sistema operacional verifica se a lista de controle de aceso
autoriza a operação desejada.
Essa estrutura pode ser bastante extensa. Isto pode levar a um overhead se comparado com o
mecanismo de proteção por grupos de usuários por conta da pesquisa sequencial que o sistema
deverá realizar sempre que um acesso for solicitado.
Em alguns sistemas é possível encontrar tanto o mecanismo de proteção por grupos de
usuários quanto o por lista de controle de acesso, oferecendo uma maior flexibilidade ao mecanismo
de proteção de arquivos e diretórios.
6.6 Implementação de Cache
O acesso a disco é lento se comparado ao acesso à memória principal. Este é o principal fator
para as operações de E/S com discos serem um problema para o desempenho do sistema. Com o
objetivo de minimizar este problema, a maioria dos sistemas de arquivos implementa uma técnica
denominada cache. Neste esquema o sistema operacional reserva uma área na memória principal
para que se tornem disponíveis caches utilizados em operações de acesso ao disco.
Quando uma operação é realizada o sistema verifica se a informação desejada se encontra na
cache. Caso esteja disponível não é necessário acesso ao disco. Caso o bloco requisitado não se
encontre no cache, a operação de E/S é realizada e a cache é atualizada. Como existe uma limitação
no tamanho da cache, cada sistema adota uma política para substituição de blocos como, por
exemplo, a LRU.
Apesar de melhorar o desempenho do sistema, aspectos de segurança devem ser levados em
consideração. No caso de blocos de dados permanecerem por muito tempo na cache, a ocorrência
de problemas de energia pode ocasionar a perda de tarefas já realizadas e consideradas salvas em
disco. Existem duas maneiras de tratar este problema. No primeiro caso o sistema operacional
possui uma rotina que executa periodicamente atualizando em disco todos os blocos modificados da
cache. Uma segunda alternativa é realizar imediatamente uma atualização no disco sempre que um
bloco da cache for modificado (cache write-through). Quando a memória cache não atualiza o
disco imediatamente temos a chamada cache write-back. A técnica write-back implica em menor
quantidade de operações de E/S, porém o risco de perda de dados é maior. Isto não acontece nas
caches write-through em função do seu próprio funcionamento, mas o aumento considerável nas
operações de E/S tornam este método menos eficiente.
6.7 Exercícios
1) Qual o objetivo de um sistema de arquivos sob o ponto de vista do sistema operacional?
2) O que é um arquivo?
3) Por que o sistema operacional deve isolar o usuário com relação ao tipo de dispositivo em
que um arquivo foi armazenado?
4) As regras para formação do nome de um arquivo podem ser diferentes de um sistema de
arquivos para outro? Explique.
5) O que caracteriza um sistema de arquivos que armazena arquivos como uma sequência de
registros de tamanho fixo?
6) Qual a diferença entre acesso sequencial e acesso aleatório a um arquivo?
7) O que diferencia um diretório de um arquivo regular?
Sistemas Operacionais 76
8) Qual a diferença entre arquivos especias de caractere e arquivos especiais de bloco?
9) O que diferencia um arquivo texto (ASCII) de um arquivo binário?
10) O que são atributos de arquivos?
11) O que é a tabela de arquivos?
12) O que é um sistema de diretórios hierárquico?
13) Qual a diferença entre o caminho absoluto e o caminho relativo de um arquivo?
14) Explique como funciona o esquema de alocação em disco utilizando:
lista encadeada
lista encadeada utilizando índice
nó-i
15) O que é a fragmentação para o esquema de alocação contínua de espaço em disco para
arquivos?
16) Por que se torna necessário realizar a desfragmentação de disco no esquema de alocação
contínua de espaço em disco para arquivos?
17) O que é a fragmentação para o esquema de alocação de arquivos por lista encadeada?
18) Qual a principal vantagem do esquema de alocação por lista encadeada utilizando índice
sobre o esquema de alocação por lista encadeada quando se deseja acrescentar informações a um
arquivo?
19) Por que se torna necessária a utilização de clusters (agrupamento de setores) quando se
utiliza a alocação por lista encadeada utilizando índice?
20) Para que são utilizados os blocos indiretos no esquema de alocação indexada (nós-i)?
21) Onde são armazenados os atributos de arquivos nas implementações de diretórios do:
MS-DOS
UNIX
22) Por que um sistema de arquivos precisa fornecer um esquema de proteção de acesso a
arquivos e diretórios?
23) O que é uma lista de controle de acesso?
24) Em que um sistema de cache auxilia no acesso a arquivos?
25) Qual a diferença entre uma cache write-through e uma cache write-back?
26) Que tipo de problema pode ocorrer que possa leva a um sistema de arquivos se tornar
inconsistente por causa do uso de cache? Como pode ser resolvido?
Sistemas Operacionais 77
7 Gerência de Dispositivos
Os dispositivos que podem ser conectados a um computador podem variar de diversas
maneiras. Alguns exemplos das diferenças existem entre os dispositivos são:
transferência por bloco ou caractere
dados armazenados sequencialmente ou aleatoriamente
transferência síncrona ou assíncrona
acesso restrito ou compartilhado
somente leitura ou leitura e escrita
velocidade de acesso
Devido a esta grande variação, o sistema operacional precisa fornecer uma vasta gama de
funcionalidade às aplicações.
A gerência de dispositivos de entrada/saída (dispositivos de E/S) é uma das funções mais
complexas exercidas por um sistema operacional. Sua implementação é estruturada através de
camadas de software e de hardware, onde as camadas de mais baixo nível escondem das camadas
superiores as características do hardware, oferecendo uma interface simples e confiável para o
usuário e suas aplicações.
A gerência de dispositivos deve esconder das camadas superiores os diversos detalhes de cada
periférico e suas diferenças, como velocidade de operação, unidade de transferência, representação
dos dados, tipos de operações, etc. As camadas são divididas em dois grupos. O primeiro grupo
(independente do dispositivo) visualiza os diversos tipos de dispositivos do sistema de um modo
único, enquanto o segundo grupo (dependente do dispositivo) é específico para cada dispositivo.
As metas do software de entrada/saída são:
Independência do dispositivo → Processos devem poder ler/escrever de qualquer
dispositivo sem a necessidade de ser alterado.
Atribuição uniforme de nomes → O nome de um arquivo ou dispositivo deve ser simples
(uma string ou um número).
Tratamento de erros → Deve ser feito na camada mais baixa possível. Se for possível
corrigir, deve ser feito de forma que camadas superiores não sintam.
Transferências síncronas assíncronas → O sistema operacional deve fazer com que
todas as operações de E/S bloqueiem para o processo do usuário.
Dispositivos compartilhados dedicados → O sistema operacional deve tratar todos os
problemas inerentes ao acesso concorrente ao dispositivo.
7.1 O Hardware de Entrada/Saída
A comunicação de um dispositivo com o sistema computacional é feita por intermédio de
envio de sinais. Esta comunicação se dá a partir de um ponto de comunicação denominado porta. O
meio utilizado por um ou mais dispositivos para conexão com o restante do sistema computacional
Operações de E/S
Subsistema de E/S
Drivers de dispositivo
Controladores
Dispositivos
Independentedo dispositivo
Dependentedo dispositivo
Software
Hardware
Sistemas Operacionais 78
é denominado barramento. Para cada barramento é definido um protocolo que especifica um
conjunto de mensagens que podem ser enviadas através dele.
A figura abaixo mostra uma estrutura típica do barramento de um microcomputador. Ela
possui um barramento que conecta o subsistema processadormemória aos dispositivos rápidos e
um barramento de expansão que faz a comunicação com dispositivos mais lentos.
Um controlador é um conjunto de componentes eletrônicos para operação de uma porta,
barramento ou dispositivo. Para que a UCP possa enviar comandos e dados a um controlador, o
controlador faz uso de registradores para armazenar dados e sinais de controle. A comunicação da
UCP com os controladores se dá por operações de leitura e escrita nestes registradores.
Uma porta de E/S consiste tipicamente em 4 registradores:
Status → Contém bits que podem ser lidos pela UCP e fornecem informações sobre o
estado do dispositivo.
Controle → Pode ser modificado pela UCP para alterar o modo de funcionamento do
dispositivo.
Entrada de dados → A UCP obtém valores do dispositivo lendo-os a partir deste
registrador.
Saída de dados → A UCP envia valores ao dispositivo escrevendo-os neste registrador.
7.1.1 Espera em Ciclo
Supondo que sejam utilizados 2 bits para controlar a troca de dados entra a UCP e o
controlador, o controlador indica seu estado por meio de um bit ocupado no registrador de status (1
indica que o controlador está realizando alguma operação e 0 indica que o controlador está pronto
para executar o próximo comando). A UCP usa o bit processar no registrador de controle,
marcando-o para especificar que um comando deve ser processado. Assim, a comunicação entre a
UCP e o controlador segue:
1. A UCP lê repetidamente o bit ocupado até que o bit esteja desligado.
2. A UCP armazena um valor no registrador de saída de dados e liga o bit escrever no
registrador de controle.
3. A UCP liga o bit processar.
4. O controlador vê o bit processar ligado e liga o bit ocupado.
5. O controlador vê o bit escrever ligado e lê o valor armazenado no registrador de saída
de dados, gravando este valor no dispositivo.
6. O controlador desliga os bits processar e ocupado para indicar que a operação de E/S
terminou.
Controlador de terminal gráfico
Monitor
Controlador de Memória
UCP
Memória cache
MemóriaControlador
SCSI
Controlador de disco IDE
Interface do barramento de
expansão
Porta paralela
Porta serial
Teclado
Barramento
Barramento de expansão
Discos SCSI
Discos IDE
Sistemas Operacionais 79
Se o tempo de espera para o dispositivo ficar pronto for muito grande, a UCP ficará ociosa
por muito tempo enquanto poderia estar realizando outra tarefa.
7.1.2 Interrupções
O problema da espera em ciclo é o desperdício de UCP que o método gera. Para evitar este
desperdício o controlador pode fazer uso do mecanismo de interrupções para avisar a UCP sempre
que ele estiver pronto.
Ainda, o uso de interrupções possibilita à UCP responder a eventos assíncronos.
7.1.3 Acesso Direto à Memória
Para dispositivos de E/S cuja operação geralmente envolve grandes quantidades de dados, é
um desperdício usar a UCP para fazer a transferência, é melhor utilizar um processador
especializado chamado controlador de DMA (Direct Memory Access - Acesso Direto à Memória).
Para iniciar uma transferência a UCP informa ao controlador de DMA um endereço no
dispositivo de E/S, um endereço de memória, a quantidade de bytes a transferir e o sentido da
transferência. O controlador de DMA prossegue com a operação de E/S, usando diretamente o
barramento, ficando a UCP livre para executar outras operações.
Quando o controlador de DMA termina a transferência ele gera uma interrupção para avisar à
UCP.
7.2 Operações de Entrada/Saída
Sempre que um processo realiza uma operação de E/S o sistema deve tornar a tarefa o mais
simples possível para o usuário e suas aplicações. Para isso, o sistema operacional deve ser capaz de
se comunicar com qualquer dispositivo que possa ser conectado ao hardware do computador.
A maneira mais simples de um processo ter acesso a um dispositivo é através de bibliotecas
de comandos de entrada/saída, oferecidas pela maioria das linguagens de programação. Os
comandos de E/S de linguagens de alto nível independem do sistema operacional onde se está
trabalhando.
Para obter a independência de dispositivos as operações de E/S devem ser realizadas por
intermédio de chamadas de sistema (system calls). As chamadas de sistema responsáveis por essa
comunicação são rotinas presentes na camada de mais alto nível implementada pelo sistema
operacional, permitindo ao usuário realizar operações de E/S sem se preocupar com detalhes do
dispositivo que está sendo acessado. O relacionamento entre os comandos de E/S oferecidos pelas
linguagens de programação de alto nível e as chamadas de sistema é criado durante a linkedição do
programa.
Nem todo software de entrada/saída no nível do usuário pertence à biblioteca de
procedimentos. Outra categoria importante é a dos sistemas de spooling. O spool (Simultaneous
Peripheral Operation On-Line) é uma forma de se tratar com dispositivos dedicados que fazem
parte de um sistema multiprogramado. Para realizar o spool são criados processos especiais
denominados daemons e diretórios denominados diretórios de spooling.
Para evitar que um processo ganhe o uso de um dispositivo compartilhado e não o libere mais,
ou que mais de um processo ganhe acesso simultâneo a um dispositivo que deva trabalhar de forma
dedicada, o daemon é o único processo que poderá ganhar o uso deste dispositivo. Sempre que um
processo desejar escrever neste dispositivo, ele deverá gerar um arquivo no diretório de spooling e
AplicaçãoComandos
de E/SChamadas de sistema
Dispositivos
Sistemas Operacionais 80
solicitar ao daemon que faça a transferência do arquivo para o dispositivo. Exemplos de daemons
são daemon de impressão e daemon de rede.
7.3 Subsistema de Entrada/Saída
O subsistema de entrada/saída é responsável por realizar as funções que são comuns a todos
os dispositivos oferecendo uma interface uniforme, ficando os aspectos específicos de cada
periférico a cargo dos drivers de dispositivo.
Uma de suas funções é mapear o nome do dispositivo com o seu respectivo driver. As
camadas superiores conhecem apenas o nome do dispositivo e utilizam esse nome para terem acesso
ao periférico.
Cada dispositivo trabalha com unidades de informação de tamanhos diferentes. O subsistema
de E/S é responsável por criar uma unidade lógica de informação independente do dispositivo e
repassá-la aos níveis superiores.
O tratamento de erros nas operações de E/S costuma ser realizado nas camadas mais próximas
ao hardware, mas certos erros podem ser tratados e reportados de forma uniforme
independentemente do dispositivo pelo subsistema de E/S.
Alguns dispositivos podem ser compartilhados simultaneamente entre diversos usuários,
enquanto outros devem ter acesso exclusivo. O sistema operacional deve controlar os
compartilhamentos de forma organizada. O subsistema de E/S é responsável também por
implementar o mecanismo de proteção de acesso aos dispositivos.
A bufferização é outra tarefa do subsistema de E/S. Ela permite reduzir o número de
operações de E/S utilizando uma área de memória intermediária (buffer). Quando é solicitada a
leitura de um dado do disco o sistema traz para o buffer, além do dado, um bloco de dados. Na
próxima vez que uma nova leitura de um dado que pertença ao bloco for solicitada, ele já estará no
buffer, evitando uma nova operação de E/S.
Muitas das funções do subsistema de E/S estão envolvidas com o sistema de arquivos. A
implementação de caches é um exemplo desse relacionamento.
As operações de E/S podem ser classificadas de acordo com seu sincronismo. Um processo
que realiza uma operação síncrona fica bloqueado até o término da operação, enquanto um processo
que realiza uma operação assíncrona não precisa aguardar pelo término da operação e pode
continuar executando normalmente.
7.4 Drivers de Dispositivo
O driver de dispositivo (device driver), ou somente driver, tem como função a comunicação
com dispositivos de E/S em nível de hardware. Enquanto o subsistema de E/S trata de funções que
afetam a todos os dispositivos, os drivers tratam apenas dos seus aspectos particulares.
Cada driver manipula somente um tipo de dispositivo ou grupo de dispositivos semelhantes.
Os drivers têm como função receber comandos genéricos sobre acessos aos dispositivos e traduzi-
los para comandos específicos, que poderão ser executados pelos controladores.
O driver interage diretamente com as funções do controlador. Ele reconhece quantos
registradores o controlador possui, como são utilizados e quais são seus comandos. Sua função
principal é receber comandos abstratos e traduzi-los para comandos que o controlador possa
executar.
Sistemas Operacionais 81
Após a solicitação ao controlador o processo pode bloquear ou não. Um processo que foi
bloqueado será acordado por uma interrupção. O processo não será bloqueado por uma operação de
E/S somente se o dispositivo responde muito rápido a uma solicitação (um terminal, por exemplo).
Depois de responder a uma solicitação o driver fica bloqueado aguardando uma nova
solicitação, a não ser que haja uma outra solicitação enfileirada, caso em que ela passa ser atendida.
Os drivers fazem parte do núcleo do sistema operacional. Devido ao alto grau de dependência
entre os drivers e as chamadas de sistema de E/S, os fabricantes desenvolvem, para um mesmo
dispositivo, diferentes drivers (um para cada sistema operacional). Sempre que um novo dispositivo
é adicionado, o driver do dispositivo deve ser incorporado ao sistema.
7.5 Controladores
Os controladores são componentes eletrônicos (hardware) responsáveis por manipular
diretamente os dispositivos de E/S. Os drivers se comunicam com os dispositivos através dos
controladores.
O controlador costuma possuir memória e registradores próprios para executar instruções
enviadas pelo driver. Quando se tem uma arquitetura com E/S mapeada em memória o endereço dos
registradores fazem parte do espaço de endereçamento da memória (por exemplo, processadores
680x0 da Motorola). Outras arquiteturas possuem espaço de endereçamento especial de E/S, com
cada controladora recebendo uma parte deste espaço (por exemplo, processadores i386 da Intel).
Os controladores fazem uso de interrupções para informar à UCP quando estão prontos.
Máquinas Pentium, por exemplo, possuem 15 linhas de IRQ (Interrupt Request – Requisição de
Interrupção), que podem ser configuradas de forma automática (Plug and Play) ou de forma
manual. As IRQs configuradas manualmente podem ser configuradas por hardware (switches ou
jumpers) ou por software. Se a controladora for hard-wired não é possível mudar sua IRQ.
O controlador de interrupções mapeia o software de serviço da interrupção através do vetor
de interrupções.
Na maioria dos dispositivos orientados a bloco é implementada a técnica de DMA (Direct
Memory Access Acesso Direto à Memória) para transferência de dados entre o controlador e a
memória principal.
Alguns controladores implementam técnicas de cache semelhantes às implementadas pelos
sistemas de arquivos, visando melhorar o desempenho. Normalmente o controlador avisa o sistema
operacional do término de uma operação de gravação quando os dados no buffer do controlador são
gravados no disco (write-through). O controlador também pode ser configurado para avisar do
término da gravação quando os dados ainda se encontram no buffer do controlador (write-back),
oferecendo ganho de desempenho.
Um padrão muito popular para a conexão de dispositivos a um computador é o SCSI (Small
Computer Systems Interface). O SCSI define padrões de hardware e software que permitem
conectar ao computador qualquer tipo de dispositivo. Para tanto deve-se configurar o sistema
operacional com um driver SCSI e o hardware com um controlador SCSI, ao qual os periféricos são
conectados.
Chamada de sistema
Driver de disco
Controlador de disco
Ler bloco n
Ler bloco no
disco xcilindro ycabeça wsetor z
Sistemas Operacionais 82
7.6 Dispositivos de Entrada/Saída
Os dispositivos de E/S são responsáveis pela comunicação entre o computador e o mundo
externo. A transferência de dados pode ser feita através de blocos de informação ou palavra a
palavra, ambas por meio dos controladores dos dispositivos sob a supervisão da UCP.
Em função da forma com que os dados são armazenados, os dispositivos de E/S podem ser
classificados em duas categorias: dispositivos estruturados e dispositivos não estruturados.
Os dispositivos estruturados (dispositivos de bloco) caracterizam-se por armazenar
informações em blocos de tamanho fixo, possuindo cada qual um endereço. Os blocos podem ser
lidos ou gravados de forma independente. Os dispositivos estruturados classificam-se ainda em
dispositivos de acesso direto e dispositivos de acesso sequencial. Em um dispositivo de acesso
direto cada bloco pode ser acessado diretamente através de seu endereço, enquanto em um
dispositivo de acesso sequencial o dispositivo deve percorrer sequencialmente o meio à procura do
bloco.
Os dispositivos não estruturados (dispositivos de caractere) são aqueles que enviam/recebem
uma sequência de caracteres sem estar estruturada no formato de um bloco. Não é possível o acesso
ao dado após a transmissão (os dados não possuem endereço).
7.6.1 Discos de RAM
O disco de RAM é um driver utilizado para permitir que uma parte da memória seja utilizada
como um disco comum. Ele tem a vantagem de ter acesso instantâneo, sendo adequado para
programas e dados que são acessados frequentemente.
O disco de RAM é dividido em blocos, cada qual do mesmo tamanho que um bloco de disco.
Quando o driver recebe uma solicitação ele calcula onde os dados devem ser lidos/escritos na
memória e faz a transferência.
7.6.2 Disco Rígido
Um disco rígido é constituído por vários discos sobrepostos, unidos por um eixo vertical
girando a uma velocidade constante. Cada disco compõe-se de trilhas concêntricas, que são
divididas em setores. As trilhas dos diferentes discos que ocupam a mesma posição vertical formam
um cilindro. Para cada superfície de um disco existe uma cabeça de leitura/gravação. O conjunto de
cabeças é preso a um braço que se movimenta entre os vários cilindros no sentido radial.
Discos modernos têm mais setores nas trilhas exteriores que nas trilhas interiores. O
processamento destes detalhes é feito pelo hardware da própria unidade de disco.
O tempo necessário para ler/gravar um bloco de dados é função de três fatores: tempo de
busca, latência e transferência. O tempo de busca (seek) é o tempo gasto para mover o braço até o
cilindro onde o bloco se encontra. O tempo de latência é o tempo de espera até que o setor desejado
setor
trilha
eixo
Cabeça de leitura/gravação
braço
Sistemas Operacionais 83
se posicione sob a cabeça de leitura/gravação. O tempo de transferência corresponde ao tempo
necessário para ler/gravar o bloco.
O tempo total das operações de E/S pode ser extremamente demorado se comparado ao tempo
da UCP. Para a maioria dos discos magnéticos o tempo de busca é o fator de maior impacto no
acesso aos dados. Se o driver de disco processa as solicitações na ordem em que elas chegam
(FCFS – First Come First Served) não está havendo nenhuma otimização no sistema. O driver
pode manter uma tabela de solicitações indexada pelo número do cilindro para melhorar o
algoritmo.
Um algoritmo conhecido como busca mais curta primeiro (SSF – Shortest Seek First) trata a
solicitação mais próxima a seguir, reduzindo o movimento total do braço em comparação com o
FCFS. O SSF tem o problema da tendência do braço permanecer no meio do disco a maior parte do
tempo, havendo a possibilidade de solicitações longe do meio obterem serviço deficiente.
O algoritmo do elevador resolve este problema movendo o braço sempre na mesma direção
até que não haja mais solicitação nesta direção.
A utilização de cache associada com a leitura de mais dados que o solicitado também ajuda a
melhorar o desempenho.
A verificação por redundância cíclica (CRC) é gravada junto com o setor para verificação de
erros.
7.6.2.1 Arrays de Discos
A redundância pela utilização de múltiplos discos rígidos (arrays de discos) é uma forma
muito comum de se aumentar o desempenho e a segurança no armazenamento de dados. O tipo
mais comum de implementação de array de disco é o RAID (Redundant Array of Inexpensive
Drives).
O RAID pode ser implementado tanto em software quanto em hardware. A implementação
em software é mais barata, mas torna o sistema mais lento. Quando implementado em hardware o
RAID não só é mais rápido, como também pode permitir a troca de um dispositivo defeituoso sem a
necessidade de desligar o equipamento (o chamado hot swap).
O RAID pode ser implementado em 7 níveis diferentes, cada um com um objetivo específico.
O RAID nível 0 não é redundante, consequentemente não deveria se chamar “RAID”. No
nível 0, os dados são divididos pelos discos, resultando em uma maior vazão. Considerando que
nenhuma informação redundante é armazenada o desempenho é muito bom, mas uma falha de
qualquer dos discos resulta na perda dos dados. Este nível é costumeiramente chamado de striping.
O RAID nível 1 provê redundância escrevendo todos os dados em dois ou mais discos. O
desempenho do nível 1 tende a ser maior na leitura e menor na escrita quando comparado a um
sistema com um único disco, mas se qualquer dos discos falhar, nenhum dado é perdido. Este é um
bom sistema redundante, uma vez que são requeridos somente dois discos. Porém, como um disco é
Cabeça
BuscaLatência
Transferência
Sistemas Operacionais 84
usado para armazenar uma cópia dos dados, o custo por megabyte é alto. Este nível é
costumeiramente chamado de mirroring (espelhamento).
O RAID nível 2, que usa o código de Hamming para correção erros, é voltado para uso em
discos que não têm detecção de erro embutida.
O RAID nível 3 divide os dados a nível de byte por vários discos, com paridade armazenada
em um disco. A divisão a nível de byte requer suporte por hardware para uso eficiente.
O RAID nível 4 divide os dados a nível de bloco por vários discos, com paridade armazenada
em um disco. A informação de paridade permite a recuperação de falha provocada por falha em
qualquer dos discos. O desempenho do nível 4 é muito bom para leitura (semelhante ao nível 0).
Para escrita, entretanto, é necessário que a paridade seja atualizada a todo instante. Isto reduz a
velocidade para pequenas escritas randômicas; entretanto, para grandes escritas ou escritas
sequenciais, ele é bastante rápido. Como somente um disco do array armazena dados redundantes, o
custo por megabyte do nível 4 é baixo.
O RAID nível 5 é semelhante ao nível 4, mas distribui a paridade entre os discos. Isto pode
acelerar pequenas escritas em sistemas multiprocessados, já que o disco de paridade não se torna
um gargalo. Como os dados da paridade precisam ser saltados em cada disco durante a leitura, o
desempenho para leitura tende a ser menor que no nível 4. O custo por megabyte é igual ao do nível
4.
O RAID nível 6 funciona como o RAID nível 5, porém apresenta o dobro de bits para
paridade. Com isto o sistema garante a integridade dos dados mesmo com a perda de dois discos.
É possível também a composição de diferentes níveis de RAID:
RAID 0+1 → É uma combinação dos níveis 0 (striping) e 1 (mirroring), onde os dados
são divididos entre os discos para melhorar o rendimento, mas também utilizam outros
discos para duplicar as informações. Assim, é possível utilizar o bom rendimento do nível
0 com a redundância do nível 1.
RAID 1+0 → Exige ao menos 4 discos rígidos. Cada par será espelhado, garantindo
redundância, e os pares serão distribuídos, melhorando desempenho. Até metade dos
discos pode falhar simultaneamente, sem colocar o conjunto a perder, desde que não
falhem os dois discos de um espelho qualquer.
RAID 50 → É um arranjo híbrido que usa as técnicas de RAID com paridade em
conjunção com a segmentação de dados. Um arranjo RAID-50 é essencialmente um
arranjo com as informações segmentadas através de dois ou mais arranjos.
RAID 100 → É basicamente composto do RAID 10+0.
Sistemas Operacionais 85
7.7 Exercícios
1) Por que a gerência de dispositivos é implementada mediante a utilização de camadas?
2) Por que a gerência de dispositivos esconde dos processos dos usuários os detalhes de cada
periférico?
3) Para um hardware de entrada/saída defina:
porta
barramento
controlador
4) Por que a espera em ciclo não é um bom método para comunicação da UCP com um
controlador?
5) Como é a transferência de dados por DMA? Qual a sua importância para o sistema?
6) Por que a transferência de dados por DMA é preferível em relação à transferência de dados
individuais com utilização de interrupções?
7) Como um programa faz para ter acesso às operações de E/S?
8) Como se dá em um sistema de multiprogramação o acesso de processos a dispositivos
dedicados, como impressoras, que devem ser acessados com exclusividade pelos processos?
9) Para que serve a bufferização no subsistema de E/S?
10) Qual a diferença entre uma operação de E/S síncrona e uma operação de E/S assíncrona?
11) O que é um driver de dispositivo (device driver)?
12) Qual a vantagem da implementação de cache em controladores de dispositivos de E/S?
13) Qual a diferença entre dispositivos de bloco e dispositivos de caractere?
Sistemas Operacionais 86
14) Por que o processo de desfragmentação o disco torna o acesso aos arquivos mais rápido?
15) Explique como a utilização de um RAID pode tornar o sistema mais rápido e seguro.
Sistemas Operacionais 87
8 Bibliografia
Machado, F. B. & Maia, L. P., “Arquitetura de Sistemas Operacionais”, 4a ed, LTC Editora,
Rio de Janeiro, 2012.
Silberschatz, A. & Galvin, P. B., “Sistemas Operacionais – Conceitos”, 5a ed, Prentice Hall,
São Paulo, 2000.
Tanenbaum, A. S. & Woodhull, A. S., “Sistemas Operacionais – Projeto e Implementação”,
2a ed, Ed. Bookman, Porto Alegre, 2000.
Tanenbaum, A. S., “Sistemas Operacionais Modernos”, LTC Editora, Rio de Janeiro, 1995.
Tanenbaum, A. S., “Structured Computer Organization”, 3rd
ed, Prentice Hall International,
1990.
Soares, L. F. G. et alii, “Redes de Computadores – Das LANs, MANs e WANS às Redes
ATM”, 2a ed, Ed. Campus, Rio de Janeiro, 1995.
Tanenbaum, A. S., “Redes de Computadores”, 3a ed, Ed. Campus, Rio de Janeiro, 1997.