material adicional

Propaganda
Capítulo
DOZE
Software Básico
1 2 . 1 Introdução
Emprega-se comumente a palavra software para designar o conjunto de programas que são
utilizados com um sistema de hardware para facilitar seu uso por programadores e
operadores do sistema. Entretanto, esta utilização do termo exclui programas de aplicações,
incluindo apenas programas que fornecem funções gerais independentes dos detalhes de uma
aplicação (software do sistema ou software básico). Na prática, o que o usuário enxerga
facilmente deste grupo (embora ele seja mais extenso) são as linguagens que ele encontra
disponíveis para programação, como Basic, Fortran, Cobol, Pascal, Logo, C, Delphi,
Visual C, C++, Java, etc, e programas escritos para resolver problemas específicos ou que
realizam funções especiais como planilhas de cálculo, editores de texto, jogos eletrônicos,
entre outros, que fazem parte do software de aplicação.
Os programas que são escritos em linguagens de programação de alto nível (emprega-se esta
denominação como distinção a linguagens mais próximas da máquina) precisam ser
convertidos em programas de máquina. O processo desta conversão é executado por um
elemento de software denominado de processador de linguagem. Os programas escritos
em alto nível tendem a ser independentes da estrutura da máquina na qual serão executados,
mas os programas de baixo nível não. Por isso, esta conversão é dependente da máquina
sobre a qual vai ser executado o programa.
Os processadores de linguagem são programas longos, que ocupam espaço significativo na
memória do computador. Por isso, freqüentemente ficam armazenados (residem) em um
dispositivo de entrada/saída, como um disco, por exemplo. Este programa será “chamado” e
copiado para a memória quando o programador quiser fazer uso dele. O uso de um
processador de linguagem significa que o programador vai executá-lo, tendo como entrada
de dados o programa escrito em linguagem de alto nível. Como saída, será obtida uma
representação do programa em forma diretamente executável pela máquina ou uma forma
mais próxima desta do que a representação anterior (em alto nível).
A escrita de um programa consiste na especificação, direta ou indireta, de uma seqüência de
instruções de máquina para o computador. As instruções de máquina formam um padrão
binário com o qual é difícil de trabalhar do ponto de vista humano e complexo para entender.
É mais fácil de escrever programas empregando símbolos mais familiares, orientados ao
usuário, tais como os caracteres alfa-numéricos e suas combinações. Como conseqüência
desta facilidade, torna-se necessário traduzir esta representação simbólica de programas em
elementos binários que possam ser reconhecidos pelo hardware.
1 2 . 2 Linguagens de programação
Um programa é uma lista de instruções ou comandos que determina ao computador a
execução de uma tarefa de processamento de dados. Há diversas linguagens de
programação, através das quais pode-se escrever programas para o computador; mas o
computador só pode executá-los quando eles estão representados internamente na forma
binária, organizado e codificado de forma a resultar na geração de sinais de controle da UCP.
12-1
Portanto, os programas que não estão escritos na forma binária necessitam ser traduzidos
antes da sua execução. Os programas podem se incluir em uma das seguintes categorias:
• código binário: sequência de instruções e operandos em binário; lista a
representação exata das instruções como elas aparecem na memória;
• código octal ou hexadecimal: representação, equivalente ao código binário, em
representação octal ou hexadecimal;
• código simbólico: o usuário emprega símbolos tais como letras, números e
caracteres especiais, para representar o código das instruções, composto por parte de
operação, endereço e outras. Cada instrução simbólica pode ser traduzida em uma
instrução binária equivalente, o que é feito por um programa montador (assembler).
Por esta razão, este programa simbólico é referido como programa em linguagem
de montagem ou assembler.
• linguagens de programação em alto nível: são linguagens especiais
desenvolvidas para facilitar a especificação de procedimentos usados na solução de
problemas, não havendo preocupação com o comportamento do hardware que suporta
estas operações. Portanto, elas empregam símbolos e formatos orientados a
problemas, de acordo com o raciocínio comumente empregado na solução destes.
Assim, cada comando precisa ser traduzido em uma seqüência de instruções binárias,
antes que o programa possa ser executado em um computador. O programa que traduz
um programa escrito em linguagem de alto-nível para binário é chamado de
compilador.
O termo linguagens de programação refere-se em geral ao estudo da estrutura das diversas
linguagens de programação de alto nível, sendo realizado de forma independente dos
dispositivos computacionais de do seu hardware. Este não é o enfoque para o pessoal de
arquitetura, que o utiliza com outra conotação incluindo linguagens a nível de máquina.
A seguir será exemplificada esta relação existente entre as diversas linguagens de
programação. Esta relação não é estabelecida diretamente a nível de comandos, mas tenta
deixar claro o nível crescente de dificuldades de programação e depuração de código, à
medida que de vai do alto para o baixo nível de programação.
1 2 . 3 Exemplo com NEANDER
Problema: Realizar a multiplicação de dois números inteiros positivos, a e b (a≥0, b≥0),
utilizando o método das somas sucessivas, ou seja, somar a consigo mesmo b vezes.
Descrição em português estruturado:
Sejam a,b,r três números inteiros positivos.
Então fazer:
1. Zerar r
2. Enquanto b for maior que zero, somar a em r e decrementar b de uma unidade
3. Fim. O resultado está em r
Descrição em pseudo-linguagem de alto nível
Begin Program
Variable a,b,r: type positive-integer
r:=0
while b>0
do r:=r+a
b:=b-1
12-2
end while
End Program
Descrição em pseudo-linguagem, sem uso de while
Begin Program
Variable a,b,r: type positive-integer
Label laço
r:=0
laço:
if b>0
then r:=r+a
b:=b-1
goto laço
end if
End Program
Descrição em pseudo-linguagem, somente com uso de if-goto
Begin Program
Variable a,b,r: type positive-integer
Label laço, fim
r:=0
laço:
if b=0 goto fim
r:=r+a
b:=b-1
goto laço
fim:
End Program
Descrição em pseudo-assembler
fim:
ORG 0
LDA Zero
STA r
LDA b
JZ fim
LDA r
ADD a
STA r
LDA b
ADD m1
STA b
JMP laço
HLT
a
b
r
zero
m1
DEF BYTE
DEF BYTE
DEF BYTE
DEF BYTE=0
DEF BYTE=-1
laço:
% define endereço de início do programa
% r:=0
% if b=0 goto fim
% r:=r+a
% b:=b-1
% goto laço (poderia ser JMP laço+1: otimização !)
% define variável a
% define variável b
% define variável r
% define variável zero e inicializa com 0
% define variável m1 e inicializa com menos um
Descrição na linguagem do NEANDER (sem labels)
Endereço
0
2
4
Instrução
LDA 26
STA 25
LDA 24
% r:=0
% if b=0 goto fim
12-3
6
8
10
12
14
16
18
20
22
23
24
25
26
27
JN 22
LDA 25
ADD 23
STA 25
LDA 24
ADD 27
STA 24
JMP 4
HLT
0
0
0
0
255
% r:=r+a
% b:=b-1
% goto laço
% define variável a
% define variável b
% define variável r
% define variável zero e inicializa com 0
% define variável m1 e inicializa com menos um
Descrição na linguagem do NEANDER (em decimal)
Endereço
0
2
4
6
8
10
12
14
16
18
20
22
23
24
25
26
27
Instrução
32 26
16 25
32 24
160 22
32 25
48 23
16 25
32 24
48 27
16 24
128 4
240
0
0
0
0
255
% r:=0
% if b=0 goto fim
% r:=r+a
% b:=b-1
% goto laço
% define variável a
% define variável b
% define variável r
% define variável zero e inicializa com 0
% define variável m1 e inicializa com menos um
1 2 . 4 Programas a nível de sistema
A produção de programas do sistema é complexa, e requer extenso conhecimento e prática
em computação. A existência destes programas representa vantagens e conveniência aos
programadores de aplicações e aos usuários de computadores em geral. O software do
sistema pode ser dividido em várias categorias (algumas citadas anteriormente):
• processadores de linguagens, que convertem programas para a linguagem de máquina
a partir de linguagens orientadas a usuários ou a aplicações;
• biblioteca de programas, que fornecem rotinas padrão para o programador de
aplicações;
• programas carregadores (ou “loaders”), para facilitar a carga dos diversos programas
na memória do computador;
• programas utilitários, para facilitar a comunicação entre os componentes do
computador e entre o computador e o usuário;
• programas de diagnóstico, que facilitam a manutenção do computador;
• um sistema operacional, que supervisiona os demais programas e controla sua
execução.
12-4
Assemblers
Nos primórdios da computação, o programador de computadores tinha a sua disposição uma
máquina que, através do hardware, executava certas funções básicas. A programação do
computador consistia em escrever uma série de uns e zeros (linguagem de máquina),
colocar esta série na memória, pressionar um botão. Com isto o computador iniciava a
execução destas instruções.
É extremamente difícil, entretanto, escrever e ler programas em linguagem de máquina. Na
procura por um método mais conveniente, desenvolveram-se os processadores de
linguagem, ou seja, programas que traduzem um programa fonte escrito pelo usuário
em um programa objeto que pode ser entendido pelo computador. Um montador ou
assembler é um programa do sistema que traduz um programa escrito em linguagem
assembler para um programa equivalente descrito em linguagem binária (da máquina).
Tipicamente, em uma linguagem assembler utilizam-se mnemônicos (símbolos) para cada
instrução de máquina, e a principal função do montador é traduzir cada um destes símbolos
no código binário equivalente.
Compiladores
Com o aumento da complexidade dos programas, mesmo o uso de linguagem assembler não
fornece o grau de abstração necessário para uma boa compreensão do programa. Assim,
desenvolveram-se as linguagens de alto nível, onde um único comando substitui dezenas de
instruções assembler. Um compilador é um programa do sistema que traduz um programa
em linguagem de alto-nível para a linguagem de máquina. Uma linguagem de alto nível é
suficientemente abstrata para ser independente do hardware (ou quase independente diversas características da arquitetura, como por exemplo a representação dos números, são
refletidos nas linguagens). Para poder utilizar uma linguagem de alto nível, o programador
deve conhecer sua sintaxe (forma) e sua semântica (significado).
As linguagens de alto nível estão em constante evolução, refletindo as mudanças que
ocorrem nas metodologias de programação. Dentre as primeiras linguagens destacam-se
FORTRAN, COBOL, ALGOL e PL/I. Entre as linguagens mais utilizadas atualmente podem
ser citadas C e PASCAL. Dois outros processadores de linguagens largamente usados são os
montadores de macros (ou macro-assemblers) e os interpretadores.
Macros
Uma macro é uma pseudo-instrução que define um grupo de instruções de máquina. Uma
pseudo-instrução é uma instrução que existe para definir condições especiais para o
montador; não resulta em código propriamente dito, mas provoca transformações no código
original. Um montador de macros traduz programas escritos em linguagem assembler
com facilidades de macros, o que significa usar nomes simbólicos para representar
seqüências de instruções. Cada vez que o programa encontra esta macro, substitui-a pela
seqüências de instruções.
Assim, por exemplo, poderia ser definida uma macro para o NEANDER que realizasse
subtrações. Tal macro (convenientemente denominada SUB), conteria as instruções
necessárias para realizar uma subtração (inversão do subtraendo, soma de uma unidade e
soma com o minuendo), como ilustrado na definição da macro a seguir:
MACRO SUB end
STA aux
LDA end
NOT
ADD um
ADD aux
END MACRO
% realiza AC <– AC – MEM(end)
% salva Acumulador em MEM(aux)
% note-se que end é um parâmetro da macro
% MEM(um) contém a constante UM
12-5
Todas as ocorrências posteriores do símbolo SUB seriam substituídas pela sequência acima,
uma vez para cada símbolo. As macros não devem ser confundidas com as subrotinas.
Subrotinas são conjuntos de instruções que podem ser utilizadas diversas vezes por um
programa; assim, ações repetitivas não precisam ser escritas repetidamente e nem geram
trechos repetidos de código em um programa. Cada vez que uma rotina é utilizada, é
executado um desvio do programa principal (é o que chama a subrotina) para o endereço
inicial onde ela se encontra carregada; após sua execução, é executado um novo desvio que
causa o retorno para o programa principal. Uma subrotina para subtração seria por exemplo
o seguinte trecho de programa, armazenado a partir do endereço xx :
xx
STA aux
LDA end
NOT
ADD um
ADD aux
RET
% salva Acumulador em MEM(aux)
% note-se que end é um parâmetro da subrotina
% MEM(um) contém a constante UM
A chamada desta função seria feita através de uma instrução de chamada para subrotina
(CALL xx) ou de desvio para subrotina (JSR xx). Note-se que na chamada deve ser
fornecido o endereço de memória onde a subrotina foi armazenada. Tanto a instrução de
chamada (CALL ou JSR) como a instrução de retorno (RET) devem existir na arquitetura do
computador. O NEANDER, por exemplo, não possui estas facilidades.
Interpretadores
Um interpretador também é um processador de linguagem que traduz cada comando de
um programa escrito em linguagem de alto nível, executando-o imediatamente. Assim, o
interpretador trabalha alternando ações de tradução e execução. O compilador atua
exclusivamente na tradução do programa fonte e não na execução, o que os diferencia.
Assim o interpretador responde mais rapidamente a modificações no programa fonte, o que é
bastante útil em um ambiente de desenvolvimento. Na execução rotineira de programas ele se
torna mais lento, uma vez que refaz a tradução de cada comando a cada nova execução. O
exemplo mais conhecido de interpretador é o utilizado na linguagem BASIC.
Bibliotecas
Bibliotecas de programas existem para simplificar tarefas repetitivas de programação.
Assim, rotinas muito utilizadas são padronizadas, catalogadas e tornadas acessíveis aos
usuários. Isto implica no estabelecimento de convenções. Nas aplicações científicas,
encontra-se neste campo a programação de funções matemáticas tais como raiz quadrada,
funções exponenciais, inversão de matrizes, etc Para processamento de dados comercial,
encontram-se funções de organização de arquivos como ordenação e busca ou procura.
Muitas outras estão disponíveis ou podem ser programadas de acordo com o interesse dos
usuários.
Carregadores
Carregadores são programas do sistema que inserem ou posicionam outros programas na
memória e preparam-nos para a execução. Podem ser do tipo absoluto, que carregam o
programa a partir de um endereço especificado pelo usuário, ou do tipo relocável, que não
permitem a intervenção do usuário, posicionando o programa de forma eficiente, de acordo
com as disponibilidades de espaço na memória.
Em um esquema simples, o montador assembler armazena sua saída em uma memória
secundária (disco, por exemplo). A função do carregador seria simplesmente transferir o
código desta memória secundária para a memória principal e transferir o controle para ele
(colocar no PC para o endereço da primeira instrução do programa).
12-6
O uso de generalizado de bibliotecas, subrotinas e macros, entretanto, faz com que um
carregador tenha mais tarefas a realizar. Basicamente, um carregador desempenha quatro
tarefas:
•
Obter e reservar espaço de memória para o programa e suas subrotinas (Alocação).
•
Resolver os endereços simbólicos (labels) entre o programa e as bibliotecas utilizadas
(Ligação ou linking ).
•
Ajustar todas as referências a endereços, de forma que elas correspondam aos
endereços fisicamente utilizados, ou seja, aos endereços da porção da memória onde
o programa foi carregado (Relocação).
•
Colocar fisicamente as instruções e os dados na memória (Carga).
O período de execução de um programa é denominado de tempo de execução. O período
de tradução de uma programa fonte é denominado de tempo de compilação ou tempo de
montagem. O tempo que se refere ao período de carga e preparação de um programa para
execução é chamado de tempo de carga.
Um programa carregador especial é o bootstrap loader, cuja função é inicializar as
atividades no computador, quando ele é ligado. O início da operação do computador não
ocorre apenas com a ligação da energia, mas com a execução de um programa de
inicialização. Este programa pode existir residente na memória ou pode ser carregado a cada
início de operação.
Utilitários
Programas utilitários correspondem a uma coleção de rotinas freqüentemente
empregadas que o programador pode usar para facilitar o seu trabalho de desenvolver tarefas
específicas e consequentemente seu trabalho de programação. Assim como no grupo
anterior, estão catalogados e disponíveis aos usuários do sistema. Como exemplos, pode-se
citar: editores de texto, ferramentas de depuração de programas (“memory dump”, “trace”,
“debuggers”, etc), rotinas de entrada e saída.
Programas de diagnóstico têm por objetivo exercitar certas partes do hardware do
sistema a fim de verificar situações de mau-funcionamento ou para testar a funcionalidade
destas unidades. Assim, ele fornece sinais ao hardware e coleta as respostas
correspondentes, comparando-as com as informações esperadas (resultados corretos obtidos
a partir da especificação da máquina, por exemplo). Estes programas auxiliam o pessoal que
trabalha em manutenção. Também podem ser rodados preventivamente para antecipar a
ocorrência de problemas.
Sistema Operacional
Um sistema operacional é uma coleção de programas que controlam a operação do
computador com o propósito de obter um desempenho eficiente. Consiste basicamente em
programas de controle permanentemente residentes na memória, que supervisionam todos
demais programas executados no computador. Em geral, sua eficiência é dependente dos
recursos de hardware existentes para suportá-lo, uma vez que ele faz uso intensivo destes
recursos. Sistemas operacionais rudimentares, instalados em geral em máquinas pequenas,
são referidos pelos termos: programa monitor, supervisor ou executivo. Os sistemas
operacionais serão estudados com maiores detalhes na próxima unidade.
1 2 . 5 Interface entre hardware e software
Uma questão essencial a nível da arquitetura e organização de computadores é o
estabelecimento da interface entre o hardware e o software. É possível conhecer-se vários
12-7
aspectos de software, sem que se tenha familiaridade com o detalhamento do hardware
associado; da mesma forma, é possível projetar-se partes de hardware sem conhecer suas
capacidades a nível do software. Entretanto, os projetistas da arquitetura de computadores
precisam conhecer hardware e software, pois estas áreas influenciam-se mutuamente.
Uma das definições estabelecidas a nível desta interface é a escolha das funções que serão
implementadas no hardware e quais utilizarão o software. Reflexos destas definições serão
sentidas na maior ou menor facilidade de programação e na velocidade de execução (de
obtenção de respostas) destas funções. Adicionalmente, podem existir estruturas de
arquitetura para apoiar funções do sistema operacional ou mecanismos que auxiliem na
detecção de erros de programação.
Existem três níveis básicos de implementação de funções de máquina no computador:
• por hardware, fixas pelo projeto lógico dos circuitos;
• por microcódigo, que consiste em um nível intermediário entre o hardware e o
software, e que pode conceitualmente ser enxergado como um conjunto de programas
escritos em baixíssimo nível (especificação direta de sinais de controle) que executam
diretamente sobre o hardware;
• por software, especificadas em uma linguagem de baixo nível.
Funções complexas podem ser obtidas a partir da composição de outras dos níveis
inferiores.
O nível de problemas quando da definição da interface varia bastante em complexidade.
Assim como é necessário definir detalhes de implementação de instruções, também é
necessário determinar a arquitetura global da máquina. Há, por exemplo, arquiteturas
dirigidas a determinadas linguagens, que utilizam combinações implementadas em hardware
e firmware para estender a arquitetura de uma máquina na direção das formas utilizadas pelas
linguagens de programação.
Assim, grande parte das arquiteturas atuais são influenciadas por conceitos de estruturas de
programação encontradas em linguagens de programação de alto nível e por funções comuns
aos sistemas operacionais. Entretanto, existem divergências sobre quanto deve pesar esta
influência e se ela deve ser mais motivada pelas características convenientes às linguagens ou
aos sistemas operacionais. Esta é uma questão que resta a resolver.
1 2 . 6 Sistemas operacionais
Na primeira geração de computadores, quando a programação era feita quase que
exclusivamente que através de chaves e lâmpadas, os sistemas operacionais eram
praticamente inexistentes. A complexidade de tal programação, entretanto, motivou o
desenvolvimento de uma grande série de programas, analisados na seção anterior. Um
quadro típico da década de 60, quando o sistema de entrada e saída era baseado em cartões
perfurados, é descrito a seguir:
• o programador perfurava o seu programa fonte (por exemplo, na linguagem
FORTRAN) em um conjunto de cartões.
• o programador pegava os cartões do compilador FORTRAN, marcados de verde para
serem visualmente distinguidos dos demais, os colocava na leitora de cartões e
carregava o compilador para a memória.
• a seguir, com o compilador na memória, o programador colocava o programa fonte
(em cartões incolores) na leitora. O compilador lia estes cartões e, caso não existissem
erros, perfurava uma série de cartões (vermelhos) com o código objeto gerado.
12-8
• terminada a tarefa do compilador, o programador colocava o carregador (em cartões
rosa) na leitora e carregava-o para a memória.
• com o carregador na memória, o programador colocava agora seu programa objeto na
leitora, seguido dos cartões correspondentes às bibliotecas utilizadas (em diversas
cores específicas)
• o carregador lia todos estes cartões, preparava o programa na memória e transferia o
controle do computador para este programa.
• o programador colocava na leitora os cartões contendo os dados a serem manipulados
pelo programa, que lia estes cartões e gerava relatórios em uma impressora.
• existindo erros na programação, todo o processo tinha que se refeito (e um dos
pesadelos da época era a queda dos cartões no chão, embaralhando-os).
Este sistema de cartões multicoloridos, embora facilitasse em muito o uso do computador,
ainda era muito insatisfatório. O programador tinha que comandar manualmente todo o
processo, e grande parte do tempo do computador era desperdiçado lendo e perfurando
cartões. Conforme a demanda por tempo de processamento, memória, dispositivos de
entrada e saída e quantidade de dados aumentava, a gerência eficiente destes recursos tornouse crítica. Todos os recursos de computação eram na época muito caros e valiosos, e tinham
de ser eficientemente aproveitados para não gerar desperdícios.
Foi nesta época que os sistemas operacionais foram desenvolvidos e refinados, sendo os
precursores dos sistemas utilizados hoje em dia. Um dos mais utilizados na época era o
sistema operacional baseado em lotes (batch operating system ), que permitia que um certo
número de tarefas (jobs ) fossem colocadas juntas na leitora de cartões. Assim, por exemplo,
o exemplo descrito acima poderia ser realizado pelo seguinte conjunto de cartões:
//EXAMPLE
//STEP1 EXEC
100
9100
/*
//STEP2
/*
//STEP3
JOB
DONOVAN,
FORTRAN, NOPUNCH
READ 9100,N
DO 100 I = 1,N
I2 = I * I
I3 = I * I * I
PRINT 9100, I, I2, I3
FORMAT (3I10)
END
T168,1,100,0
EXEC LOAD
EXEC OBJECT
10
/*
O primeiro cartão define um processo que o usuário de nome Donovan deseja realizar; o
segundo cartão indica que os cartões seguintes devem ser processados pelo compilador
FORTRAN (até o cartão de fim de arquivo, marcado com /*). O passo seguinte é a chamada
do carregador, sem nenhum outro cartão especial. O último passo comanda a execução do
programa objeto recém gerado, e fornece os dados a serem lidos (no caso o número 10).
Todos os cartões são interpretados pelo sistema operacional, que inclui todos os programas
do sistema mencionados anteriormente, além de outros que supervisionam e controlam as
operações de todos os programas no computador.
O sistema operacional realiza as seguintes funções:
• aloca espaço de memória e carrega programas para a execução;
12-9
• fornece serviços para a obtenção de dados de entrada e para a produção de dados de
saída;
• fornece recuperação automática para diversos tipos de erros, como erro na leitura de
um dispositivo de entrada, ou erro de “overflow”.
Nos sistemas operacionais simples, a memória totalmente alocada a um único programa.
Assim, se este programa não utiliza toda a memória, parte deste recurso não é utilizado. Nos
tempos atuais isto não é considerado um problema sério, mas até a década de 70 a memória
era um recurso extremamente caro e que não poderia ficar ocioso. Para usar a memória na
sua integridade, desenvolveram-se os sistemas operacionais multiprogramados.
Em sistemas multiprogramados, dois ou mais programas são carregados na memória
(em áreas diferentes, naturalmente) e o computador os executa simultaneamente. Havendo
apenas uma UCP, o sistema só pode processar, em cada instante, tarefas relativas a um
usuário. Mas o sistema operacional atende a todos com uma rotatividade intensa, de tal forma
que ele parece estar executando todos em paralelo.
Portanto, nestes sistemas, muitos programas podem residir simultaneamente no computador.
O sistema operacional aloca os diversos recursos computacionais para os programas
selecionados e mantém as atividades de chaveamento dos recursos durante o funcionamento
do sistema. Exemplificando, enquanto um programa está sendo executado na UCP, um
outro programa pode estar recebendo dados de um dispositivo periférico (fita magnética, por
exemplo), e um terceiro pode estar imprimindo dados.
Em tais sistemas um dos principais problemas é a gerência eficiente de memória. Com a
constante alocação e liberação de memória, podem surgir áreas de memória muito pequenas
para conterem um programa. O surgimento destes “buracos” na memória é conhecido como
fragmentação. A fragmentação tem sido minimizada por diversas técnicas. Uma delas
consiste em rearranjar os endereços do programas (realocação dinâmica), movendo-os de
lugar e assim reunindo todas as porções não utilizadas em uma única região contínua de
memória.
Outra técnica é a de paginação: por este método, os programas são sub-divididos em porções
iguais ou páginas, e a memória é dividida em porções denominadas blocos. Na hora da
carga do programa, as páginas são carregadas nos blocos. Em um sistema de paginação
simples todas as páginas do programa são carregadas na memória. Já em um sistema mais
sofisticado de paginação por demanda, um programa pode ser executado sem que todas
as suas páginas estejam na memória. As páginas são carregadas conforme elas são
necessárias, isto é, quando elas são referenciadas (demandadas).
Em sistemas onde há mais do que uma UCP, há a possibilidade real de que sejam executadas
duas ou mais instruções ao mesmo tempo: neste caso, tem-se multiprocessamento, ou
seja, cada processador pode executar um programa diferente em paralelo (ao mesmo tempo)
aos demais.
Existem sistemas nos quais o tempo do processador é partilhado entre diversos usuários: são
os sistemas de time-sharing (ou “de divisão de tempo”). Nestes, muitos usuários
comunicam-se com o sistema computacional através de terminais remotos. O sistema
operacional aloca a cada um, ou seja, a cada “job”, um período de tempo (“time-slice”) com
base em considerações de prioridade. O “job” é uma unidade de trabalho especificado
aplicado na execução de uma tarefa de processamento de dados. Assim, durante estes
períodos de tempo, o computador faz com que o computador processe um “job” até que
ocorra uma das seguintes condições:
• o “job” é completado;
• um erro é detectado;
• ocorre solicitação ou necessidade de uma entrada/saída;
• o período de tempo termina.
12-10
Então o processador é designado para o “job” de mais alta prioridade. Nos dois primeiros
casos, o “job” deve ser removido da memória. Nos dois últimos, o “job” é suspenso
temporariamente.
O sistema operacional contribui para o usos mais eficiente dos recursos de hardware pelo
gerenciamento dos recursos de memória. Por exemplo, se um programa não pode ser
acomodado inteiramente na memória devido ao seu tamanho, o sistema operacional pode
dividi-lo em partes denominadas de páginas ou segmentos, transferindo-os gradualmente
da memória secundária para a principal.
O efeito do sistema operacional na gerenciamento do sistema computacional visa melhorar
sua eficiência, a qual é avaliada pelo “throughput”. “Throughput” é a quantidade de
processamento que o sistema realiza com sucesso durante um intervalo de tempo
especificado. Pode servir como medida de avaliação tanto para hardware como para o
software. A contribuição do sistema operacional neste sentido é resultante da eficiência das
facilidades existentes no seu código.
1 2 . 7 Funções básicas dos sistemas operacionais
Um sistema operacional é um conjunto de programas que permite ao computador controlar
os recursos, executar programas e gerenciar dados. Inclui todos os programas do sistema
mencionados anteriormente, além de outros que supervisionam e controlam as operações de
todos os programas no computador. Do ponto de vista do usuário, a função do sistema
operacional é auxiliá-lo na mecânica de resolução de problemas.
O sistema operacional tem três encargos principais: gerenciar a execução de programas e
ações realizadas na memória, gerenciar o armazenamento de arquivos e gerenciar as
atividades de E/S. Para tanto, ele detém três funções básicas, que são:
• controlar os recursos do sistema de computação;
• executar os programas do computador;
• gerenciar dados.
Estas funções são executadas sem que o programador precise estar instruindo o sistema
operacional a fazê-lo, a cada momento. Na verdade, o sistema operacional já existe como
uma série de programas que fazem parte da máquina e que permitem uma operação
razoavelmente confortável para o programador de aplicações. Os programas de controle
minimizam a intervenção do operador de tal forma que as operações do computador fluem de
forma suave e contínua. O programa mais importante no sistema é o supervisor, cuja maior
parte reside na memória (está sempre lá). Ele controla o sistema operacional inteiro e chama
outros programas do sistema operacional (do disco), quando necessário, para que eles
permaneçam na memória enquanto forem executados. Após, eles retornam para o disco, para
que se tenha uso eficiente do espaço de memória.
O sistema operacional aumenta a eficiência de duas maneiras:
• ele atua como promotor da cooperação entre os usuários do sistema, ajudando-os a
fazerem o melhor uso possível dos recursos computacionais de forma que todos tirem
proveito.
• ele chama tradutores e outros programas para que se encarreguem de tarefas comuns
(usuais). Isto libera os programadores de aplicações de tarefas rotineiras e repetitivas.
A fim de cumprir as funções acima listadas, o sistema operacional executa atividades tais
como:
• operações de seqüenciamento e escalonamento de “jobs” (alocação de espaço de
memória e carga de programas para a execução), e controle de tráfego: o sistema recebe
12-11
os diferentes “jobs”, e com base em suas características e necessidades decide sobre
sua execução (prioridades, tempo de execução, recursos disponíveis, etc). Quando é
possível executar-se entrada/saída simultaneamente à execução de um programa, todas
estas funções são escalonadas pelo controlador de tráfego.
• programação de entrada e saída: executa diretamente as ações relacionadas às operações
de entrada e saída, quando o canal de entrada e saída tem seu conjunto próprio de
instruções especializadas, necessitando apenas de instruções simples do usuário
referentes a estas operações.
• auto-proteção (contra o usuário) e proteção do usuário com relação aos demais: oferece
proteção ao usuário, evitando que seus programas, bases de dados ou arquivos sejam
modificados por ações maliciosas ou acidentais. Igualmente, o próprio sistema
operacional deve assegurar sua auto-inviolabilidade.
• gerenciamento de armazenamento secundário: é o controle do uso de discos, fitas e
outros meios de armazenamento secundário para os programas e dados do usuário,
sendo uma tarefa realizada pelo sistema operacional, transparente do ponto de vista do
usuário.
• manipulação de erros: o sistema operacional deve realizar ações específicas com relação
aos diversos tipos de erros que podem ser causados durante a operação e uso da
máquina, que podem variar desde o envio de aviso ao usuário até a correção ou
modificação de parâmetros para poder prosseguir na operação.
Estas funções precisam ser compatibilizadas com as estruturas de hardware, de modo que se
obtenha o melhor compromisso em objetivos conflitantes tais como eficiência, ciclos rápidos
de execução, e conveniência do usuário. Para tanto são usadas diversas técnicas de
implementação; algumas são comentadas ao longo deste material.
1 2 . 8 Processos e escalonamento
Um processo pode ser visto como uma seqüência ou conjunto de operações que realizam
uma tarefa computacional, como por exemplo um processo de leitura, impressão, execução,
etc. Um processo computacional pode ser seqüencial com um conjunto de operações
ordenadas em tempo ou concorrente com operações paralelas. O conceito de processos é
importante para sistemas operacionais porque a realização de cada processo pode representar
a execução de uma tarefa isolada, complementar ou concorrente escalonados pelo sistema
operacional.
Processos seqüenciais são caracterizados por uma ordenação da execução de suas tarefas no
tempo, portanto facilitando a administração de um conjunto de tarefas por um sistema
operacional. Em processos concorrentes é possível ter duas ou mais operações em paralelo.
Os processos concorrentes existem por causa da competição no uso dos recursos de um
computador como, por exemplo, usos da memória, UCP, leitoras, etc, resultando em
escalonamento de operações para minimizar qualquer conflito entre processos como também
reduzir o tempo de execução. Isto é feito normalmente através de processos como, por
exemplo, leitura pelos canais, enquanto a UCP executa outros processos. A operação
spooling é baseada sobre o princípio de sobreposição de processos com o auxílio de canais
ou dispositivos especiais. Em geral, o compartilhamento dos recursos de um computador em
tempo e espaço necessita de um módulo de escalonamento associado aos sistemas
operacionais multiprogramáveis.
Existem duas formas principais de escalonamento: escalonamento sem e com preempção. O
escalonamento sem preempção assume que um processo já em posse de um recurso (UCP,
leitora, impressora, etc) não é interrompido até o final da execução do processo. Para
implementar tal política, são usados modelos primitivos na decisão de “enfileiramento” dos
12-12
processos, tais como: primeiro a chegar, primeiro a ser servido, ou processos com mínimo
tempo de execução.
Com preempção, um processo pode ser interrompido em execução para transferir controle de
um recurso para outro processo ou atender às necessidades do sistema. Este tipo de
escalonamento é muito empregado em sistemas de multiprogramação onde existe
compartilhamento de espaço e tempo por todos os processos. Em sistemas de
multiprogramação, os modelos para determinar a política de escalonamento podem ser
bastante complexos. A interrupção, bastante usada em sistemas multiprogramáveis, tem a
função de preempção de um processo momentaneamente em posse de um recurso de um
recurso, tal como UCP, dispositivos, etc.
1 2 . 9 Carga do sistema (inicialização da máquina)
Denomina-se de carga do sistema, à operação que tem por objetivo colocar o computador em
condições de funcionamento. Consiste, fundamentalmente, em carregá-lo com rotinas
essenciais ao atendimento dos diversos programas de aplicação que lhe serão posteriormente
submetidos. Esta operação é comumente denominada de bootstrap, referindo-se
informalmente ao procedimento como “dar o boot” na máquina.
Esta operação envolve: a colocação das primeiras instruções na memória a partir de um
comando específico: nas máquinas modernas, este comando já vem embutido na inicialização
da máquina; nas antigas, era disparado por uma tecla ou botão especial, ou estas instruções
eram carregadas manualmente na memória da máquina. Este programa dispara a leitura (de
um disquete, do disco, de uma memória de armazenamento permanente, por exemplo) de um
programa carregador que então é posicionado na memória; a partir deste, novos programas
ou rotinas são sucessivamente carregados ou posicionados na memória, para executar as
ações subseqüentes. Esta carga completa é denominada de boot inicial do sistema. O
computador não tem a capacidade de reter a informação que está na memória principal
quando é cortada a energia elétrica: assim, esta operação é repetida a cada nova ligação da
máquina.
1 2 .1 0 Multiprogramação
Assim, como visto na unidade anterior, a multiprogramação se refere à existência de mais
do que um programa em diferentes partes da memória principal ao mesmo tempo. Seu
principal objetivo é a eficiência computacional. Uma técnica relacionada é a multi-tarefas, ou
a existência de diversas tarefas que são parte do mesmo “job” e podem ser executadas
simultaneamente.
Em sistemas multiprogramados, dois ou mais programas são carregados em diferentes
áreas da memória, na expectativa de que eles vão gerar uma quantidade significativa de
trabalho para o computador. Se este trabalho gerado exceder a capacidade do computador, o
resultado é que a máquina (em princípio) não deverá ficar parada, esperando novas tarefas.
Assim, o programa supervisor executa o trabalho de alocação das diferentes unidades pela
manutenção de listas de passos que estão prontos para execução em cada uma das unidades.
Quando uma unidade completa um passo, o supervisor pode consultar a sua lista para um
novo trabalho. No caso das unidades de entrada/saída, a conclusão de uma passo é indicado
por uma interrupção, que sinaliza à UCP para parar temporariamente o programa atual e para
alocar mais trabalho à unidade de E/S, se houver. No caso da CPU, a conclusão é indicada
quando o programa na UCP ou chega ao final do job ou requisita buffers de E/S que ainda
não estão disponíveis – isto é – se uma entrada foi requisitada, e o sistema ainda não
preencheu o buffer, ou se uma saída foi requisitada, o sistema ainda não esvaziou os buffers
anteriores de tal forma que haja lugar para os novos.
Neste gerenciamento de execução de jobs, há problemas envolvidos tais como:
12-13
• quando ocorre a interrupção nas atividades de um job, visando transferir o
processamento para outro, é necessário assegurar a guarda de valores contidos nos
registradores e memória, para posteriormente poder retomar as atividades;
• determinação de qual(is) job(s) deve(m) ser carregado(s) na memória quando há
diversos aguardando na fila de execução, e qual tarefa deve ser alocada a uma unidade
em particular, quando há diversas tarefas que aguardam por programas que já estão na
memória. A solução emprega algoritmos de escalonamento e critérios de prioridades;
• custo de memória, já que há necessidade de maior espaço de memória do que em
sistemas onde somente um job pode ser rodado em cada momento;
• ainda outros problemas dizem respeito a questões tais como: alocação de memória,
relocação e proteção.
A fim de racionalizar o compartilhamento pa UCP (processador) entre os diversos jobs
(processos) utiliza-se um scheduler, ou seja, um elemento de software que realiza duas
funções básicas:
1. selecionar um processo entre os prontos para executar (ready) para ser o próximo a
ganhar o controle da UCP.
2. determinar a fatia máxima de tempo de processador (time-slice) que o processo pode
utilizar antes de ir novamente para a fila dos processos prontos para executar.
A Figura 12.1 ilustra as transições de estado que um processo pode sofrer. Os processos que
reúnem todas as condições necessárias para serem executados estão no estado “pronto”
(ready, em inglês). Quando um dos processos deste grupo ganha o controle do processador,
ele transiciona para o estado “executando” (running). Ele sairá deste estado ou quando a sua
fatia de tempo terminar (neste caso ele retorna para o grupo pronto) ou quando necessitar de
um evento externo à UCP (neste caso vai para o grupo bloqueado). Um processo em estado
“bloqueado” (blocked) permanece neste estado até que o evento esperado ocorra (como o
término de uma operação de E/S, a execução de uma determinada operação por outro
processo, etc). Quando o evento ocorre, o processo volta ao grupo “pronto”.
Processo selecionado
Pronto
Executando
Processo precisa esperar
pelo término de um evento
(por exemplo, E/S)
Processo interrompido
(fim da fatia de tempo)
Bloqueado
O evento esperado pelo processo ocorreu
Figura 12.1 - Estados de um processo
A figura não ilustra nem o “nascimento” nem a “morte” de processos. Quando um processo é
inicializado, ele vai primeiramente para a fila dos processos “prontos”, concorrendo com os
demais ao uso da UCP. Quando um processo encerra sua execução, ele simplesmente não
retorna ao grupo “pronto”.
12-14
Diversos problemas podem ocorrer no escalonamento dos processos, principalmente
relacionados à sincronização entre processos. Um deste problemas é o da “corrida” (race),
quando a sincronização é tão crítica que diversas ordens de escalonamento podem produzir
diferentes computações (resultados diferentes). Outro problema é o do “deadlock”, quando
diversos processos se bloqueiam mutuamente, e de tal forma que cada processo fica
esperando por eventos que deveriam ser gerados pelos outros processos bloqueados. Estes
problemas podem ser resolvidos de duas maneiras básicas distintas: por cooperação ou por
comunicação. Na comunicação, os processos trocam mensagens entre si, de forma a se
sincronizarem e impedir o surgimento de problemas. Na cooperação, existem recursos
críticos, que devem ser utilizados única e integralmente por um processo antes de passarem
para outros processos. Estes recursos de “exclusão mútua” normalmente tem seu acesso
controlado por variáveis do tipo “semáforo”. O detalhamento exato dos problemas acima,
assim como as metodologias e soluções possíveis serão analisados em outras disciplinas.
1 2 .1 1 Multiprocessamento
O multiprocessamento se refere aos sistemas onde há duas ou mais UCPs em um único
sistema computacional; assim, há a possibilidade real de que sejam executadas duas ou mais
instruções ao mesmo tempo. Estas UCPs estão conectadas à mesma memória, de tal forma
que elas podem estar executando partes do mesmo ou de diferentes programas. O uso de
múltiplas UCPs visa incrementar a capacidade de processamento do sistema, freqüentemente
avaliado em mips (millions of instructions per second).
Ainda há os sistemas nos quais várias UCPs, cada uma com a sua própria memória, podem
ser interligadas através de canais, cada uma assemelhando-se a um dispositivo de E/S do
ponto de vista dos demais computadores. Isto não é multiprocessamento: é uma sistema
multicomputador, também denominado de rede de computadores. Nestes sistemas, há
distribuição dos jobs aos computadores que estão com tempo de processamento disponível
nas modalidades requisitadas, o que é caracterizado como distribuição de carga.
1 2 .1 2 Exemplos de sistemas operacionais
Os sistemas operacionais vêm normalmente incorporados às máquinas (computadores),
sendo cobrados à parte ou não. Atualmente, há tendência no uso de sistemas operacionais
genéricos, isto é, sistemas operacionais cuja natureza permite que ele opere com sistemas de
computadores de diferentes fabricantes. Estes sistemas tem sido freqüentemente criados por
companhias de software e não por fabricantes de máquinas. Exemplos destes casos são o
UNIX e o MS-DOS.
O UNIX foi desenvolvido em 1971 por Ken Thompson e Denis Ritchie na AT&T Bell
Laboratories para uso nos minicomputadores DEC da Bell. Os projetistas surpreenderam-se
com a aceitação no mercado externo à Bell. Como razões para o fato, apontam: o uso
disseminado do software por faculdades e universidades, cujos usuários repassaram o uso
posteriormente às indústrias; outra razão foi a enorme redução no preço por cópia, em 1981,
sendo estas comercializadas por US$ 40. Este é um sistema operacional multi-usuário, com
divisão de tempo (time-sharing) que, embora inicialmente implementado para
minicomputadores, atualmente roda em máquinas de grande porte e em alguns
microcomputadores. Entre os problemas relacionados ao UNIX, apontam-se: sua dificuldade
de utilização (do ponto de vista do usuário, não é “user-friendly”, embora esta característica
tenha sido melhorada pelo uso de menus tradicionais); além disto, faltam ao UNIX algumas
características de segurança sofisticadas.
Para computadores pessoais, o sistema operacional mais popular é o MS-DOS (Microsoft
Disk Operating System; Bill Gates, Microsoft). Este sistema foi escolhido pela IBM para
incorporá-lo aos seus computadores pessoais, em detrimento do CP/M (Gary Kildall, Digital
Research) que já se encontrava em uso, na época. Neste sistema, os programas são
executados pela emissão de um comando, ou seja, um nome que chama o programa
12-15
desejado. Os comandos internos do DOS são colocados na memória do computador quando
o usuário liga-o. Os demais, que residem em disco, são chamados de comandos externos do
DOS.
De forma geral, o software escrito para rodar em um sistema operacional, não rodam em
outro. Assim, os projetistas de software tentam maximizar as vendas de seu produto
escrevendo programas para os sistemas operacionais mais utilizados. Alguns sistemas
fornecem figuras e/ou designações simplificadas aos invés de comandos ou símbolos
simples como o PROMPT (>) do DOS. O efeito destas figuras é o de apresentar um aspecto
mais “amigável” para o usuário, existindo tal como uma “roupagem” ou shell ao redor do
sistema operacional. Assim, eles criam um ambiente confortável para o usuário, que não
precisa estar memorizando comandos específicos.
1 2 .1 3 Redes de computadores
Computadores podem ser interligados entre si, de forma a compartilhar recursos (memória,
periféricos, UCP e informação). Se os computadores estão próximos entre si (na mesma sala
ou no mesmo prédio), a rede é denominada de rede local ou LAN (Local Area Network). Se
por outro lado os computadores estão geograficamente distantes, tem-se uma WAN (Wide
Area Network).
Ao nível de hardware, a comunicação é realizada através de circuitos especiais, que
transformam os dados a serem transmitidos em sinais elétricos. Como meio físico utilizam-se
desde pares telefônicos até cabos coaxiais, fibras óticas, enlaces de microondas e até satélites
de comunicação. Ao nível de software desenvolveram-se diversos métodos de comunicação
(protocolos), que definem como esta comunicação é realizada e comos os dados
intercambiados devem ser interpretados. Frente a sua complexidade, redes modernas são
projetadas de forma altamente modularizada. A maioria das redes se encontra organizada em
uma série de camadas hierárquicas, onde cada camada utiliza os serviços definidos da
camada inferior e fornece uma outra série de serviços, de mais alto nível, para a camada
superior. A International Standards Organization (ISO) criou o modelo de referência OSI
(Open Systems Interconection), que define sete destas camadas em cada máquina:
1. Físico: define as características dos equipamentos e os requisitos para a realização
das ligações.
2. Enlace (ou ligação): define os meios e os procedimentos para a transmissão de
blocos de informação e controle dos possíveis erros que possam ocorrer.
3. Rede: define o intercâmbio de informação dentro da rede. Trata do agrupamento da
informação em pacotes, do endereçamento dentro da rede e da detecção e correção
de erros.
4. Transporte: trata da transferência de mensagens, do agrupamento e decomposição
dos pacotes de dados. Outra atribuição é a otimização do uso da rede, selecionando
as conexões adequadas.
5. Sessão: controla as operações realizadas sobre os dados, a fim de assegurar sua
integridade com respeito ao uso compartilhado dos mesmos. Neste nível agrupamse as mensagens relacionadas entre si (estabelecendo uma sessão).
6. Apresentação: trata da organização das entradas e saídas, definindo os formatos
necessários aos terminais, aos arquivos e aos dados, a fim de que possam ser
utilizados pela sessão e pela aplicação do usuário.
7. Aplicação: consiste no controle e supervisão dos processamentos dos usuários que
se intercomunicam.
12-16
Do ponto de vista do sistema operacional, tem-se um recurso a mais a ser gerenciado: a rede.
Serviços que não estão disponíveis no computador local podem estar acessíveis via rede e,
para utilizá-los de forma adequada, o sistema operacional deve ter conhecimento deles. Temse assim os sistemas operacionais distribuídos, onde os recursos a serem gerenciados
não estão concentrados em um único computador, mas sim espalhados ao longo da rede.
O processador deixa de ser uma entidade única no sistema. Além do tradicional aspecto do
escalonamento de processos (qual processo será executado pelo processador), surge a
questão do escalonamento de processadores (qual dos processadores disponíveis irá executar
determinado processo).Uma outra consequência da multiplicidade de processadores é a
possibilidade de continuidade do processamento após a ocorrência de falhas em um
determinado processador. Como provavelmente ainda haverá réplicas desse recurso na rede,
os processos afetados pela falha podem continuar após migrarem para um processador não
falho.
Tais vantagens, entretanto, são pouco exploradas atualmente. Esse fato decorre da pouca
utilização de sistemas operacionais distribuídos nesse tipo de ambiente. É mais frequente o
emprego de sistemas operacionais centralizados tradicionais, que não foram projetados
especificamente para ambientes distribuídos, com extensões para suportar algumas operações
remotas básicas (como transferência de arquivos e execução remota de processos, por
exemplo).
Por um sistema distribuído se entende atualmente um sistema que consiste de múltiplos
processadores que não compartilham memória primária e se comunicam por mensagens
através de uma rede de comunicação. Programas distribuídos podem contar com quatro tipos
de processos: clientes, servidores, filtros e pares. Clientes e servidores interagem usualmente
de forma síncrona: um cliente envia uma mensagem de requisição de serviço a um servidor
local ou remoto; o serviço é executado pelo servidor, que retorna os resultados ao cliente
(que espera bloqueado). Um servidor pode ser projetado para atender múltiplas requisições
concorrentemente. Processos filtro recebem, processam e enviam adiante os dados obtidos.
Processos pares interagem com mensagens para cooperar no atendimento a requisições.
Nos dias de hoje os ambientes computacionais da maioria das organizações apresentam um
realidade heterogênea. Esta heterogeneidade, que pode ser definida com a diversidade
existente em sistemas de computação, manifesta-se especialmente sob os seguintes aspectos:
• arquitetura da máquina: processadores, memória e dispositivos de E/S. As arquiteturas
atuais diferem em uma série de aspectos, dos quais o principal é a família do elemento
processador utilizado no equipamento. Por família entenda-se um conjunto de
elementos processadores com certo grau de compatibilidade entre si. Exemplos são os
microprocessadores Intel 80x86 e os Motorola 680x0. Uma parcela das estações de
trabalho existentes atualmente utiliza processadores RISC (Reduced Instruction Set
Computer), embora as linhas de processadores de cada fabricante sejam incompatíveis
entre si. Há ainda outros tipos de processadores, tal como os Transputers, utilizados
em máquinas paralelas.
• sistemas operacionais: chamadas do sistema, interface com o usuário (interpretador de
comandos e ambientes de janelas) e mecanismos de comunicação entre processos;
• redes de comunicação: protocolos de comunicação e interfaces de rede;
• linguagens de programação e compiladores: diferenças entre implementações de uma
mesma linguagem e uso de diferentes linguagens.
Um sistema operacional de rede pode ser definido como a soma das camadas de software
escritas em cada máquina (host, ou hospedeiro) para propiciar comunicação e
compartilhamento de recursos entre a máquina local e diferentes máquinas remotas. Tanto o
computador local como os equipamentos remotos que compõe a rede são entidades
autônomas.
12-17
Download