CAP: 1 – INTRODUÇÃO 1.1. Objectivos dos Sistemas Operativos
O objectivo deste texto é procurar descrever a estrutura dos SO de forma a que constituam uma ferramenta de que se conheçam não só o modo de utilização mas também os detalhes de implementação.
Para que servem os SO? Porque se tornaram indispensáveis? 2 motivos históricos:
- Transformar um conjunto diversificado de circuitos electrónicos, discos e periféricos numa máquina simples de utilizar.
- Obter o máximo rendimento do hardware através da sua utilização para o processamento de um grande conjunto de actividades.
O SO procura também apresentar ao utilizador uma interface coerente que trata, de modo uniforme, acções sobre entidades semelhantes.
Outro aspecto relevante é a garantia de fiabilidade e segurança
O SO pode pois ser considerado como uma camada de software que virtualiza o hardware de base, transformando-o numa outra máquina com primitivas próprias, normalmente muito mais próximas das acções que o utilizador vulgar pretende ver executadas pelo computador.
O segundo princípio enunciado, não é mais que o clássico princípio da optimização de custo. Na história há SO que tentaram ser atingir objectivos tão ambiciosos que se tornaram ineficientes. Os compromissos desempenho/modularidade/fiabilidade são extremamente complexos, constituindo um dos pontos mais delicados na definição da arquitectura de um SO.
1.2. Evolução Histórica
Os 1ºs sistemas informáticos (anos 50) não dispunham de SO. 1.2.1. Monitor de Controlo
A primeira aproximação foi o programa utilitário Monitor, que permitia ao utilizador carregar os seus programas em memória, editá-los e verificar a sua execução. A gestão era muito simples e consistia em atribuir a cada utilizador quotas de tempo de utilização da máquina.
Um monitor típico é constituído por rotinas utilitárias que facilitam a interacção (operação) com a máquina:
- Interpretador de uma linguagem de comendo que permite fazer executar os restantes módulos - Compilador
- Tradutor de linguagem simbólica (Assembler) - Editor de Ligações (linker)
- Carregador de programas em memória (Loader)
- Rotinas Utilitárias para o controlo de periféricos: consola; leitor de cartões; leitor/perfurador de fita de papel; bandas magnéticas.
É óbvia a ineficiência desta gestão: Durante a maior parte do tempo o processador está inactivo. 1.2.2. Tratamento em Lotes (Batch)
No tipo de gestão atrás descrito, o tempo de execução de um programa é predominantemente determinado pelas suas E/S.
Uma solução consistiu em efectuar a recolha dos dados num computador auxiliar onde eram lidos, para uma banda, os cartões dos diversos trabalhos.
Este processo dos anos 50 rapidamente evoluiu: os periféricos começara a poder executar operações autónomas e avisando o processador por meio de interrupts (grande inovação esta de notificação assíncrona do processador). Simultaneamente os dispositivos de armazenamento também evoluíram e passaram de meros sequenciais para acesso aleatório (tambores, discos). O sistema anterior permite a introdução de mecanismos de optimização na gestão da máquina, escolhendo o próximo programa a ser executado de acordo com um dado critério de prioridade. Por exemplo se nos cartões houver a indicação de duração máxima, recursos ocupados, etc., pode implementar-se uma política que beneficie os trabalhos mais curtos ou menos exigentes em recursos.
Em paralelo com estes desenvolvimentos começou a fazer sentir-se a necessidade de tornar eficiente o funcionamento dos compiladores.
1.2.3. Multiprogramação
Os mecanismos de interrupção permitem multiplexar o processador entre diversas actividade executadas concorrentemente pode ser alargado para vários programas utilizadores em memória, em vez de só um. Isso permite optimizar a utilização da unidade central.
Ex. uma operação de acesso ao disco pode ser executada em paralelo por rotinas do sistema que gerem uma interface de hardware, normalmente designada por controlador de disco. O tempo para a leitura (mecânica lenta) pode ser usado por outro programa --> vários programas simultaneamente activos.
Agora, isto só é eficaz se os diversos programas residirem na memória central, para mudança rápida de contexto. Mas como memória cara, há hierarquização de memória que permita, quando necessário, carregar novo programa e guardar na memória secundária alguns bloqueados (swapping – tem implicações profundas sobre toda a estrutura dos programas --> o código deve ser recolocável).
1.2.4. Sistemas Interactivos Sistemas transaccionais.
Em lugar de ser obrigado a esperar por um programa submetido ao tradicional sistema de lotes, o utilizador passou a desfrutar de uma máquina virtual, que lhe permite aceder ao sistema central sempre à sua disposição.
A multiplexagem entre vários utilizadores impõe a divisão do tempo disponível do processador entre os diferentes utentes.
Os sistemas interactivos obrigaram a uma profunda reformulação dos conceitos nos SO, conferindo grande importância a conceitos até aí secundários, como: sistema de ficheiros, a protecção, a linguagem de interacção com o sistema.
1.2.5. Memória Virtual
Limites levaram a evolução do hardware de gestão de endereços que permitiu um avanço significativo que consistiu na possibilidade de trabalhar sobre um espaço de endereçamento virtual de dimensões muito superiores e independente da memória física existente na máquina. O programador ficava liberto das preocupações de gestão do espaço ocupado pelas suas aplicações e, simultaneamente, o sistema podia gerir mais eficientemente a memória física disponível.
A existência de memória virtual revolucionou os mecanismos de protecção, comunicação e geração de código, constituindo uma das etapas significativas da evolução dos SO.
1.2.6. Sistemas Distribuídos
Apareceram na sequência do desenvolvimento dos mecanismos de interligação de computadores. O aumento de velocidade e elevada fiabilidade das redes locais de computadores permitiram distribuir e gerir em conjunto os recursos de diversas máquinas. Novas questões de coerência da informação e de desempenho surgiram nestes sistemas.
1.3. Tipos de Sistemas Operativos
Na gama mais barata predominam os sistemas mono-utilizadores muito simples (CPM da Digita, MS-DOS da Microsoft).
Na franja seguinte predomina o sistema UNIX, que se foi impondo como norma nos sistemas multiprogramados para equipamentos de médio porte.
Os equipamentos mais complexos possuem geralmente SO proprietários.
Todos eles pertencem a uma mesma categoria – Tempo Virtual, dado que o tempo de execução dos programas não tem relação com o tempo cronológico exterior à máquina (job-shop).
Os sistemas onde a noção do tempo é relevante são normalmente designados por Tempo Real. Têm por objectivo conseguir que o computador produza uma resposta a um acontecimento externo ao fim de um intervalo de tempo limitado e previamente especificado. Há várias gradações. Os transaccionais também se pode aqui englobar.
O SO pode ser considerado como um programa de grande complexidade, responsável pela gestão eficiente de todos os recursos da máquina, o que implica uma abordagem estruturada na sua concepção.
Uma técnica habitualmente usada é a da decomposição em camadas funcionais. Cada camada constitui um nível de abstracção que implementa uma máquina virtual com uma interface bem definida. Sobre esta máquina pode constituir-se outra que utiliza os serviços da camada precedente para implementar o próximo nível de abstracção. cada camada encapsula a implementação dos níveis inferiores fazendo que seja possível modificá-los sem afectar as camadas exteriores.
Numa descrição resumida, cada um dos níveis implementa as seguinte funções:
- Gestão de Processos: multiplexa a máquina física entre um conjunto de entidades lógicas que designaremos por processos. Cada processo pode ser visto como uma máquina virtual que executa um programa. São ainda tratados os mecanismos de baixo nível que permitem interactuar com o hardware do processador, nomeadamente as interrupções.
- Gestão de Memória: Controla a utilização da memória física. A gestão da memória virtual e todos os algoritmos associados à manipulação do espaço de endereçamento dos processos são executados neste nível.
- Comunicação e Entradas/Saídas: Os processos necessitam de comunicar para poderem gerir recursos comuns ou controlar a execução das aplicações. Na comunicação podem também ser consideradas as operações de E/S com o exterior da máquina. Apesar da implementação de E/S ser complexa, dado que interactuam com o hardware dos dispositivos, a sua estrutura interna e interface apresentam numerosas semelhanças com os restantes mecanismos de comunicação. - Sistema de Ficheiros: Podia-se considerar caso particular do anterior mas não é assim, pois nos sistemas actuais a gestão da informação na memória de massas adquiriu uma importância fundamental na estrutura da programação e utilização dos computadores. A gestão de ficheiros é responsável pela implementação eficiente de uma organização lógica que virtualiza os dispositivos de memória de massa.
- Interface Sistema: divide-se em 2 partes:
- Funções Sistema: Constituem a interface dos serviços providenciados pelas camadas internas. São agrupadas em bibliotecas de rotinas, que podem ser ligadas com os programas dos utilizadores para poderem aceder aos mecanismos sistema.
- Interpretador de Comandos: É na realidade uma aplicação fornecida com os sistema para facilitar a sua utilização. Podem encadear-se comandos, numa autêntica linguagem de controlo. Este modelo de implementação não faz referência a alguns aspectos que, por serem gerais não se enquadram em nenhum dos níveis:
- Optimização da Utilização do Sistema (Scheduling). Embora geralmente se associe à gestão eficiente do processador, relaciona-se com todos os níveis, nomeadamente a gestão de memória e E/S.
- Mecanismos de Protecção, também presentes em todos os níveis: Para não se poder realizar operações que ponham em risco o sistema ou interactuem indevidamente com outros processos. 1.5. Modelo Computacional
Olhar para o SO na perspectiva do programador, abstraindo dos detalhes de implementação dos mecanismos que o compões.
Modelo Computacional é o conjunto dos objectos do sistema operativo e as operações que os permitem manipular.
Pragmaticamente pode ser considerado como o conjunto de funções sistema que o programador dispõe para o desenvolvimento das aplicações e que lhe possibilitam estender a capacidade das linguagens de programação sequenciais.
Quando se entra em aplicações complexas é preciso utilizar as facilidades oferecidas pelos SO para obter desempenho, interactividade, paralelismo de que numerosas aplicações necessitam. Por exemplo gestão dos periféricos. outro ex. reservas de bilhetes de avião – paralelismo, com coerência dos dados --> processos envolvidos consigam comunicar e sincronizar-se.
O modelo computacional para o sistema operativo tem na programação concorrente a mesma função que o modelo proporcionado pelas linguagens de alto nível para a programação de
sequencial. Fornece aos utilizadores um quadro de referência que facilita a concepção, teste e transporte dos programas.
A necessidade de criar mecanismos para a programação sistema que se tornem independentes dos SO motivou o aparecimento de numerosas propostas de extensão das linguagens de programação.
CAP. 2 – A GESTÃO DOS PROCESSOS
O modo como se implementa o paralelismo é um dos conceitos fundamentais na compreensão dos SO.
O paralelismo não pode ser entendido restritamente pois as máquinas só têm um processador. Analogia com secretárias. O paralelismo no SO deve ser considerado de um ponto de vista macroscópico.
Os sistemas com múltiplas actividades paralelas são designados, na terminologia dos SO, por sistemas concorrentes. A concorrência advém da existência de diversos fluxos de actividade que vão disputar o acesso aos recursos do sistema, evidentemente limitados, como por ex. leitor de bandas magnéticas, memória central, disco, processador.
A multiplexagem do processador é feita internamente com critérios perfeitamente bem definidos. Contudo, a nível de utilizador este determinismo não é visível. O não determinismo no modo como é feita a multiplexagem do processador é uma característica fundamental a ter em conta na utilização do modelo computacional. Execuções diferentes do mesmo programa serão sempre diferentes.
A capacidade que o sistema tem de esconder do utilizador todos os detalhes relativos à gestão do processador permite a criação do conceito de processo como unidade abstracta, o que é de extrema importância, pois de outra forma o programador teria de considerar todos os acontecimentos existentes no sistema aquando da programação.
2.2. Processador, Processo, Programa
Processador – é o órgão material donde emana toda a actividade do sistema
Processo – é a entidade activa no sistema. Executa um conjunto de acções que são determinadas por um programa.
Programa – é uma sequência de instruções sem actividade própria.
Um processo pode, durante a sua vida, executar diversos programas e um programa ou partes de um programa podem ser partilhados por vários processos.
Um processo estabelece um ambiente de execução para o programa – máquina virtual. O processo define:
- Conjunto de operações
- Operações elementares proporcionados pelo hardware
- Operações de interacção com as outras máquinas virtuais (sincronização e comunicação com outros processos)
- Um espaço de endereçamento
A restrição do reportório de instruções e o confinamento dos processos a espaços de endereçamento limitados são a base dos mecanismos de protecção que o SO implementa.
Em cada instante o processo encontra-se numa determinada etapa de execução. Na comutação de um processo é necessário garantir que o seu estado de execução é memorizado de modo a que, mais tarde, possa ser retomada a execução no ponto onde foi interrompida. Associado a cada processo existe um vector de estado ou contexto que mantém toda a informação de que o SO precisa para o retomar.
A semelhança com máquina virtual mantém-se. O contexto é o paralelo dos habituais registos de estado que controlam a execução do processador.
No contexto é memorizada a informação relacionada com o processador e com o ambiente de software no qual o processo se executa. A informação relativa ao processador é mantida no contexto, vulgarmente designado de hardware, que depende da arquitectura da máquina e que será analisado no próximo capítulo. (TF1_2001-I-a) – quais as informações que fazem parte do contexto hardware de um processo.
O contexto de software contém informações que permitem gerir os recursos do sistema e conhecer os atribuídos a um processo:
- Identificação do processo e do utilizador - Prioridade
- Estado do processo - Periféricos utilizados - Ficheiros abertos
- Programa em execução
- Directoria actual e por omissão - Cotas de utilização de recursos
- Contabilização da utilização de recursos 2.3. Os Processos no Modelo Computacional
A criação e a eliminação de processos são funções indispensáveis no modelo computacional. Geralmente os processos organizam-se com uma hierarquia, a partir de um processo pai, o que se traduz por informações que são mantidas no contexto dos processos.
A forma da hierarquia varia de sistema para sistema. Por ex. em alguns sistemas a terminação de um processo elimina todos os seus filhos, noutros os filhos mantêm-se activos.
A função de criação de um processo pode ser utilizada para a definição de um conjunto de informações que especificam o ambiente de execução do processo e que são mantidas no seu contexto de software. A estrutura hierárquica facilita a função de criação, dado que grande parte do ambiente de execução pode ser herdada do contexto do pai.
Os parâmetros da função de criação variam muito de sistema para sistema.
Um dos parâmetros a especificar pelo utilizador é o programa a executar sob forma de um ficheiro que contém o código binário ou, nos sistemas mais simples, o endereço da posição de memória onde se encontra a primeira instrução executável.
No nosso modelo computacional didáctico:
IdProcesso := CriarProc (<Ficheiro Executável>, Prioridade) EliminarProc (IdProcesso)
São rotinas de sistema.
2.4. A Função dos Mecanismos de Sincronização
Determinadas aplicações só podem ser eficientemente implementadas quando suportadas por diversas actividades independentes.
Existem diversos motivos que justificam a necessidade de sincronizar explicitamente a actividade dos processos:
Cooperação – para executar aplicação comum. A situação mais simples é a de um processo que só pode prosseguir depois de outro ter executado determinada operação. Exemplo da reserva de bilhetes: há o processo que dialoga com o utilizador e o processo que gere a estrutura de dados. Os mecanismos de sincronização devem permitir que um processo se bloqueie à espera de um acontecimento que lhe será assinalado implicitamente quando for desbloqueado.
Competição por um recurso – ex. periférico que deve ser usado em exclusividade, ficheiro onde se pretende escrever, Gestão de Memória, visto esta ser finita. Os processo que não disponham de recursos para prosseguir têm de ser bloqueados num mecanismo de sincronização até que a condição de bloqueio deixe de ser válida.
Exclusão Mútua – A necessidade de exclusão não é tão óbvia como as anteriores tendo origem no mecanismo de multiplexagem do processador que suporta a execução dos processos. Ex. variável que pode ser acessada por vários processos, pode gerar inconsistência num processo pois foi alterada do exterior. Na programação concorrente a utilização de variáveis partilhadas implica precauções suplementares que obrigam a sincronizar os processos que as pretendem modificar. A exclusão mútua pode ser considerado um caso particular da competição por um recurso – a estrutura de dados.
2.5. Exclusão Mútua
Vamos ver com um ex. de algoritmo simples de atribuição de blocos de memória, que é suportado por uma estrutura de dados implementada por uma pilha, composta por registos que indicam o endereço inicial do bloco livre e o tamanho.
Para pedir um bloco de memória, um processo executa a rotina PedeMem que tem como parâmetro de saída o descritor do bloco. Para devolver DevolveMem. Pilha é implementada por vector de descritores indexado por variável que referencia primeiro bloco livre. Pilha e Topo são
variáveis privadas do programa Distribuidor às quais os processo utilizadores não têm acesso e que são convenientemente inicializadas no arranque do sistema.
type Descritor = record Inicio : endereço; Tamanho : integer end; var
Pilha : array [1..N] of Descritor; Topo : 0..N;
procedure PedeMem (var Desc : Descritor; var Estado : boolean); begin
if Topo <> 0 then begin
Desc := Pilha [Topo]; Topo := Topo-1; Estado := true end
else Estado := false end; {PedeMem}
procedure DevolveMem (Desc : Descritor); begin
if Topo < N then begin
Topo := Topo + 1; Pilha [Topo] := Desc end
end; {DevolveMem}
Aparentemente funciona, mas se um processo inicia a devolução de memória, mas que devido a uma interrupção lhe é retirado o processador. Consideremos ainda que o processo que se executa em seguida pretende pedir memória. Dá erro: o endereço do bloco de memória que o processo B obtém é inválido. O processo A apenas tinha incrementado a variável topo e não tina ainda actualizado o descritor que ela passou a referenciar. Erro difícil de detectar.
O erro advém de a variável Topo não estar coerente com o estado da tabela Pilha. As duas variáveis representam o estado global do Distribuidor de Memória e a sua modificação não foi feita de forma indivisível, conduzindo a que o estado da pilha seja incoerente.
Concluímos que a manipulação de estruturas de dados partilhadas deve ser realizada de forma atómica. A expressão Secção Crítica é usada para designar este conceito.
2.5.1. Soluções Algorítmicas
Com apenas 2 processos no sistema poderíamos usar uma solução algorítmica baseada numa variável de controlo que indique a situação da secção crítica (acessível ou inacessível). A variável deve ser posicionada no preâmbulo da secção crítica e colocada no estado que permita o acesso a outros processos no final.
Nota: Vamos usar uma rotina de sistema CriarProc em que é dado como parâmetro um procedimento. Os processos assim criados podem aceder Às variáveis globais declaradas no programa principal. É uma extensão aos SO comerciais que veremos no cap. 15.
O algoritmo baseia-se na existência de uma variável (Nproc) que permite a entrada de um dos processos na secção crítica, ficando o outro em ciclo até que a variável lhe permita prosseguir. Esta solução enferma de um defeito óbvio, os processos apenas poderão aceder alternadamente à secção crítica.
O programa pode ser alterado para resolver este problema. A figura seguinte mostra a solução proposta por Dekker que resolve o problema de forma elegante: o processo começa por manifestar o seu desejo de aceder à secção crítica, testando, em seguida, se o outro processo também está a tentar aceder. Caso tal não suceda, executará a secção crítica. No caso de tentativa simultânea o processo entra num ciclo controlado pela variável que exprime o desejo de entrada do processo concorrente. A decisão é efectuada pela variável ProcPrioritario que permite escolher entre os dois processos que concorrem para entrar na secção.
Se o processo rival for o privilegiado, retira o seu pedido (para permitir a entrada do outro) e fica à espera da libertação do recurso. A variável ProcPrioritario é alternada, no final do acesso, para garantir igualdade.
type
A generalização a N processos é difícil. Lamport propôs uma solução baseada na atribuição a cada processo de um número de acesso (senha) que permite sequenciá-los (Bakery Algorithm – sistemas distribuídos e centralizados).
O algoritmo baseia-se em 2 vectores. O 1º memoriza o número da senha atribuída ao processo, o segundo indica se o processo está a pretender que lhe seja atribuída uma senha. Antes de aceder à secção, o processo executa a função Max, que devolve uma senha superior a todas as já distribuídas. Devido à concorrência 2 processo poderão receber números idênticos.
Depois de atribuída a senha, o processo efectua um ciclo para determinar se o seu nº de ordem é inferior ao de todos os outros processos. O algoritmo baseia-se no varrimento da tabela de senhas tendo o cuidado de evitar comparações com os processos que estão no processo de atribuição (Escolha = true). A função Compara resolve o caso de senhas idênticas usando o nº de ordem do processo.
2.5.2. Trinco Lógico
As soluções apresentadas são complexas para o programador que apenas quer garantir a exclusão mútua. Acresce que em Pascal instruções do tipo A:=B[i+1] traduzem-se em várias instruções máquina interrompíveis individualmente.
Incorporar no modelo computacional do SO os mecanismos necessários para implementação da exclusão mútua é uma alternativa muito mais simples e eficaz. Uma solução óbvia é considerar uma variável que funcione como trinco lógico da estrutura de dados. O trinco é fechado quando se acede à estrutura e libertado quando se sai. Qualquer processo que pretenda aceder à estrutura protegida deve testar em primeiro lugar o valor do trinco e apenas prosseguir se o encontrar livre. Se estiver fechado o processo fica em ciclo até o trinco ser libertado.
O trinco (lock) é manipulado por duas primitivas: Fechar (Lock) e Abrir (Unlock) implementadas pelo SO como se segue. Tem de ser inicializado convenientemente (aberto) no arranque.
procedure
Mesmo assim há a possibilidade de a secção crítica não ser respeitada. 2.6. Semáforos
As primitivas Abrir e Fechar comportam elevado grau de ineficiência. Consideremos a hipótese de ser retirado o processador a um processo que entrou na secção crítica. Neste caso o trinco permanece aferrolhado e qualquer outro processo que pretenda aceder à secção vai manter-se a testar a variável do trinco durante todo o tempo que lhe foi atribuído para utilização do processador, apesar da libertação do trinco depender de um processo que, nesse momento, não poderá obviamente estar a usar o processador. Chama-se a isto espera activa.
Para evitar isso o processo que espera a libertação de um recurso deve ser bloqueado (novo estado em que processo pode estar), ficando memorizado no sistema a razão que motivou o bloqueio.
Até agora tínhamos Em Execução e Executável (esperando oportunidade de dispor do processador). No Bloqueado o processo é retirado da lista dos que concorrem pelo processador. O contexto é colocado numa outra lista, à espera de um determinado acontecimento.
O conceito semáforo implementa um mecanismo de sincronização sem espera activa, tornando-se assim a ferramenta de base da sincronização. Exame 2ª época-2000/2001 – 25/10/2001
Um semáforo é constituído por uma estrutura de dados composta por uma variável de controlo (normalmente inteiro: positivo indica que pode continuar execução, negativo ou nulo, o processo força o bloqueio do processo) e por uma fila de espera destinada a conter os descritores dos processos bloqueados. Exame 2ª época-2000/2001 – 25/10/2001
Bloquear um processo significa retirá-lo de execução, salvaguardar o seu contexto, marcar o seu estado como bloqueado e colocar o contexto na fila de espera (normalmente FIFO, mas também pode ser por prioridades) do semáforo. Desbloquear é tirá-lo da fila de espera do semáforo, modificar o seu estado para executável e transferi-lo para a fila dos processos executáveis. O processo continua quando o despacho o seleccionar.
O semáforo é manipulado por 2 primitivas (wait e signal / Esperar e Assinalar). O trinco é próximo do hardware o semáforo do software.
É necessário que as variáveis do semáforo sejam protegidas contra a escrita (excepto as primitivas, claro). Então os semáforos devem ser objectos de sistema com a necessária protecção. São, aliás, parte integrante do núcleo do SO que multiplexa o processador.
Para implementar secção crítica a variável deve ser inicializada a 1. Operações sobre semáforos
type
Semaforo = record Int : integer;
PRT : ApontadDescProc {apontador para descritor de processo} end;
procedure Esperar (var S:Semaforo); begin
S.Int := S.Int – 1; if S.Int < 0 then begin
< Bloquear o Processo e Incluí-lo na Fila dos Processos Bloqueados > end
end;
procedure Assinalar (var S: Semaforo); begin
S.Int := S.Int + 1; if S.Int <= 0 then begin
< Retirar um Processo e Transferi-lo para a Fila dos Processos Executáveis > end
end;
Distribuidor de Memória Implementado com Semáforos
procedure PedeMem (var Desc : Descritor; var Estado : boolean); begin
Esperar (SemExMut); if Topo <> 0 then begin
Desc := Pilha [Topo]; Topo := Topo – 1; Estado := true; end
else Estado := false; Assinalar (SemExMut) end; {PedeMem}
procedure DevolveMem (Desc : Descritor); begin
Esperar (SemExMut); Topo := Topo + 1; Pilha [Topo] := Desc; Assinalar (SemExMut) end; {DevolveMem}
2.7. Gestão de Recursos
A exclusão mútua é um caso particular de competição para um recurso único. O Distribuidor de memória fornece-nos outro exemplo de competição que resulta do nº de blocos de memória ser finito. Também se pode usar semáforo, para garantir que quando a pilha se encontre vazia o processo seja bloqueado até haver um bloco livre. A inicialização da variável permite controlar o nº de vezes que o procedimento Esperar pode ser chamado (sem Assinalar nenhum) sem conduzir a bloqueios:
Distribuidor de Memória – Semáforo para controlo dos blocos de memória disponíveis procedure PedeMem (var Desc : Descritor);
begin Esperar (Memoria); Esperar (SemExMut); Desc := Pilha[Topo]; Topo := Topo-1; Assinalar (SemExMut) end;
procedure DevolveMem (Desc : Descritor); begin
Esperar (SemExMut); Topo := Topo+1; Pilha [Topo] := Desc; Assinalar (SemExMut); Assinalar (Memoria); end;
{Inicialização dos Semáforos} SemExMut := CriarSemaforo (1); Memoria := Criar (Nmax);
A variável, em cada momento indica o nº de blocos livre. Memoria = Valor Inicial + N DevolveMem – N PedeMem
É importante perceber que a ordem das operações Esperar (Memória) e Esperar (SemExMut) é a única possível dado que a ordem inversa poderia conduzir a bloqueio permanente. Daí o grande cuidado que é preciso para programar com semáforos independentes. Neste caso o sistema não tem maneia de saber que o bloqueio do processo no semáforo Memória elimina a necessidade de manter a secção crítica fechada.
2.8. Cooperação Entre Processos
Na sua forma mais simples consiste em bloquear um processo Pi até que um processo Pj lhe assinale que uma determinada acção ou acontecimento se produziu. Para sincronizar os 2 processos é necessário dispor de um mecanismo independente da velocidade de execução que permita a um processo activo:
- Bloquear-se à espera de um sinal a ser emitido por outro processo - Activar um processo bloqueado
2.8.1. Sincronização Directa
A acção directa sobre o estado de um processo é o mecanismo mais simples de sincronização. Primitivas (Suspender, Acordar). Há pois uma nova fila de espera onde o processo pode ser colocado. Na directa há que conhecer o identificador do processo sobre o qual se pretende actuar. Este processo não pode ser geral por duas razões: 1- Não se pode dar aos programas do utilizador (sempre suspeitos) a possibilidade de interferir com utilizadores ou mesmo com o SO. Uma restrição habitual é só permitir aos processos relacionados hierarquicamente (normalmente dom mesmo utilizador). 2- é de ordem lógica. na definição do algoritmo de um processo é possível saber que este irá interactuar com outro, mas desconhecer a sua identidade que apenas será definida durante a execução. Ex. marcação de bilhetes...
É pouco flexível mas por vezes necessário. Ex. processo sistema para agrupar blocos de memória. Só pode ser executada directamente pois não é possível prever onde colocar, no código dos processos, os pontos de sincronização.
2.8.2. Sincronização Indirecta
Tem a vantagem de garantir a igualdade de tratamento a todos os processos com os mesmos privilégios. Tem 2 categorias:
- Acontecimento Memorizado – o sinal enviado por um processo é memorizado no caso de não existir um processo bloqueado à sua espera.
- Acontecimento não memorizado – o sinal só é tido em conta se existir um processo bloqueado à sua espera.
Há casos em que há interesse ter o sinal memorizado durante apenas algum tempo (sistemas de tempo real)
A sincronização indirecta exprime-se com base em semáforos através do conceito de semáforo privado (são normalmente abstracção que o programador deve garantir as propriedades) a um processo (se for o único que executa a primitiva Esperar), podendo os outro notificá-lo por Assinalar. Deve ser inicializado a zero. Memoriza o nº de vezes que o acontecimento se produziu (Assinalares).
2.9. Sincronização no Modelo Computacional
O modelo computacional tem de ser completado com os mecanismos de sincronização entre processos. Normalmente os sistemas oferecem primitivas para sincronização directa:
Suspender (IdProc) Acordar (IdProc)
Por vezes pode-se usar temporização: Adormecer (IntervaloTempo)
Todos os sistemas dispõem de algum mecanismo, mesmo rudimentar, para implementar a sincronização indirecta entre processos. Os mais simples consiste em variáveis binárias de sincronização que, no estado falso, bloqueiam os processos que executam Esperar e, quando tomam o valor verdadeiro, autorizam a passagem de todos os processos.
dado que os semáforos dão para implementar qualquer modelo de sincronização vamos usá-los como mecanismo fundamental de sincronização no nosso modelo computacional.
Primitivas:
IdSemaforo := CriarSemaforo (ValorInicial) EliminarSemaforo (IdSemaforo) Esperar (IdSemaforo)
Assinalar (IdSemaforo)
2.10. Exemplos de Programação Concorrente 2.10.1. Sistema de Controlo
2.10.2. Leitores e Escritores (de uma estrutura de dados)
Considera-se que existem dois conjuntos de processos: os que lêem e os que escrevem/actualizam uma estrutura de dados partilhada. Quando um processo pretende escrever na estrutura de dados tem de aceder em exclusividade. Os processos Leitores que não modificam a informação podem aceder concorrentemente. Quando um Escritor termina a sua actividade, todos os Leitores, eventualmente bloqueados, devem ser acordados para poderem prosseguir em paralelo.
O ponto mais delicado é a necessidade de libertar todos os processos Leitores bloqueados quando lhe é atribuída a possibilidade de acederem à estrutura de dados. A programação da rotina IniciaLeitura reflecte este problema sendo interessante analisar a utilização dos semáforos nesta rotina.
É de destacar ainda a necessidade de proteger com uma secção crítica a modificação das variáveis que definem o estado em que o sistema se encontra. É importante realçar que a secção crítica tem de ser libertada antes de bloquear o processo no semáforo Leitores. esquecer a libertação conduziria à paragem de todo o sistema.
var
NLeitores, LeitoresEspera, EscritoresEspera : integer; EmEscrita : boolean;
Mutex, Leitores, Escritores : semaforo; procedure IniciaLeitura;
begin
Esperar (MutEx) /*secção crítica para estado do sistema if EmEscrita or EscritoresEspera > 0 then
begin
LeitoresEspera := LeitoresEspera + 1;
Assinalar (Mutex); /*liberta secção crítica depois de actualizar estado do sistema
Esperar (Leitores); /*Bloqueia-se pois há processos em escrita ou à espera para escrever. Avança quando houver um
Assinalar(Leitores)
Esperar (Mutex); /* Depois de ler vai actualizar estado do sistema
LeitoresEspera := LeitoresEspera – 1;
if LeitoresEspera > 0 then Assinalar (Leitores); /* se houver mais à espera para ler vai assinalar que acabou de ler, para entrar outro
end;
NLeitores := NLeitores + 1;
Assinalar (Mutex); /* liberta secção crítica depois de aactualizar estado do sistema end; {IniciaLeitura)
procedure AcabaLeitura; begin
Esperar (Mutex); /* acabou de ler e vai actualizar estado do sistema
NLeitores := NLeitores – 1;
if EscritoresEspera > 0 and NLeitores = 0 then /* Se há escritores à espera e não há leitores a ler...
Assinalar (Mutex); /* Liberta secção crítica end; {AcabaLeitura}
procedure IniciaEscrita begin
Esperar (Mutex); /* Bloqueia-se na secção crítica
if NLeitores > 0 or EmEscrita then /* Se houver leitores ou escritores em actividade...
begin
EscritoresEspera := EscritoresEspera + 1; /* fica à espera
Assinalar (Mutex); /*liberta secção crítica
Esperar (Escritores); /* bloqueia-se pois há escritores ou leitores em actividade
Esperar (Mutex); /* tenta aceder à secção crítica, depois de ter sido desbloqueado por Assinalar (Escritores)
EscritoresEspera := EscritoresEspera – 1; end;
EmEscrita := true; /* Depois de ter actualizado a secção escreve
Assinalar (Mutex); /* liberta secção crítica end; {IniciaEscrita}
procedure AcabaEscrita; begin
Esperar (Mutex); /* tenta aceder à secção crítica depois de ter escrito tudo
EmEscrita := false; /* Como já acabou de escrever actualiza a secção com essa informação
if LeitoresEspera > 0 then Assinalar (Leitores) /* Se há Leitores à espera desbloqueia-os
else if EscritoresEspera > 0 then Assinalar (Escritores); /* Se há escritores à espera desbloqueia-os
Assinalar (Mutex); /*Liberta secção crítica
end; {AcabaEscrita}
2.11. Interblocagem
Apesar dos semáforos existem ainda alguns problemas:
- Imobilização definitiva de um recurso – um processo que execute Esperar (SemExMut) sem fazer em seguida Asssinalar (SemExMut) devido a erro de programação impede a utilização do recurso pelos restantes processos.
- Interblocagem – Ser SemA e SemB forem dois semáforos de exclusão mútua e se dois processos executarem o encadeamento de operações descrito na figura (pg.67) atinge-se uma situação em que nenhum dos dois poderá prosseguir, ficando ambos bloqueados e sem qualquer hipótese de saírem dessa situação.
Processo 1 – Esperar(semA) – Expira quantum
Processo 2 – Esperar (Sem(B) – Esperar (SemA) – Bloqueado Processo 1 – Esperar(SemB) - Bloqueado
O problema da interblocagem é complexo. Para a tentar evitar foram propostas algumas soluções preventivas:
- Garantir que os recursos do sistema são todos adquiridos pela mesma ordem (não pode ser geral pois há programadores independentes)
- Requisitar todos os recursos que o processo necessita no início da sua execução (degradação de desempenho)
- Quando a aquisição de um recurso não é possível, libertar todos os recursos detidos (só recurso preemptíveis. ex. ficheiro parcialmente escrito não pode ser libertado a meio).
A prevenção é o melhor mas penaliza desempenho. Há outras propostas com mecanismo de alto nível (cap. 16 e 17).
CAP. 3 – O NÚCLEO DO SISTEMA OPERATIVO 3.1. Arquitectura Típica de Um Processador
Apesar da complexidade de um computador, é relativamente fácil identificar os principais elementos da arquitectura. Todas as máquinas consideradas neste texto baseiam-se em evoluções do modelo de Von Neumann que há 3 décadas vem sendo utilizado na definição da maioria dos computadores comerciais. O modelo pressupõe uma memória onde são armazenados os programas e respectivos dados. O CPU e os controladores que executam algoritmos de comunicação com os dispositivos físicos. Mais os buses.
3.1.1. Unidade Central de Processamento Tem 3 subunidades:
- Unidade Aritmética e Lógica ou Unidade Operativa - Unidade de Controlo
- Unidade de Gestão de Memória
Unidade Aritmética e Lógica (ALU – Arithmetic and Logic Unit)
para além dos circuitos lógicos que implementam as operações, tem associado um banco de registos endereçados explicita ou implicitamente nas instruções. É vulgar um segundo CI encarregar-se das operações com vírgula flutuante (coprocessamento). As instruções destinadas ao coprocessador têm um código de operação específico que permite a sua identificação. Quando o coprocessador reconhece as instruções que lhe são destinadas, toma conta do bus para aceder aos operandos e efectua a operação. Os registos podem conter apenas variáveis especializadas como Acumulador, Registo de estado, auxiliar, contador de programa (86/286) ou podem ser de utilização indiferenciada (VAX e M68000). Esta última solução apesar de complicar a estrutura interna permite optimização de código.
Recentemente, assistiu-se a uma evolução significativa na forma como os registos são utilizados na arquitectura dos processadores. Analisando-se como a linguagens de alto nível funcionam, chegou-se à conclusão de que se podiam obter ganhos significativos de desempenho através da utilização de bancos de registos de grande dimensão, internos ao processador, cuja optimização os compiladores se encarregam pois conhecem a sua dimensão. O número de acessos à memória reduz-se consideravelmente.
Para acomodar um extenso banco de registos, a complexidade dos modos de endereçamento (indexado, relativo, etc.) foi reduzida. O nome desta arquitectura é RISC.
Do ponto de vista do SO, o conjunto de registos corresponde ao contexto de execução hardware do programa que se executa no computador. Ao mudar a actividade em curso, é necessário salvaguardar os valores de todos os registos para restaurar o contexto de hardware quando se pretenda prosseguir com a actividade interrompida. (TF1_2001-I-a) – quais as informações que fazem parte do contexto hardware de um processo.
A Unidade de Controlo
A principal função é descodificar as instruções e traduzi-las em microcomandos que vão actuar sobre a unidade operativa e as linhas de sinalização que controlam o bus.
Pode ser implementada de várias formas, sendo a mais comum a microprogramação.
Uma outra capacidade é a de restringir a execução de instruções privilegiadas e especiais (ao utilizador), como as de manipulação de interrupções ou registos especiais. Isto levou à definição de 2 modos de execução: modo sistema e modo utilizador.
Algumas máquinas apresentam um esquema um pouco mais complexo, considerando vários níveis de protecção para permitir um melhor escalonamento dos mecanismos de protecção. No VAX e no Intel 286 existem 4 níveis, que, ligados ao mecanismo de gestão de memória, permitem implementar um sistema sofisticado de protecção.
Tem ainda como função a gestão de interrupções. Unidade de Gestão de Memória
MMU: não existe nos sistemas mais simples mas é indispensável nos sistemas de endereçamento virtual. Funcionalidade:
- Possibilidade de recolocar código ou dados na memória sem alterar o programa - Detecção de endereços inválidos
- Protecção de blocos de memória de acordo com o modo de execução do processador.
Faz parte integrante do processador, apesar de ser normalmente implementada por circuitos ou cartas independentes. O acesso à memória faz-se passando sempre por esta unidade que transforma o endereço virtual enviado pela ALU num endereço físico que será enviado para a memória central.
3.1.2. As Interrupções
Permitem desviar assincronamente a execução de um programa para outro. Do ponto de vista do SO são indispensáveis para a implementação da concorrência em que se baseia todo o funcionamento do sistema.
Para analisar o tratamento de interrupções podemos considerar os seguintes pontos: - Controlo de aceitação das Interrupções
- Salvaguarda do contexto
- Selecção da rotina de tratamento da interrupção. Aceitação das Interrupções
Baseia-se em 2 mecanismos.
O 1º é automático e inibe as interrupções depois de uma delas ter sido aceite. O 2º é explicitamente controlado por programação através de uma máscara global.
É vantajoso, durante a execução de uma rotina referente a um acontecimento de pequena prioridade, esta poder ser interrompida --> conjunto de máscaras associadas aos vários níveis de interrupção.
Em alguns sistemas este controlo é executado pelo SO, mas na maioria das arquitecturas é pelo hardware. Registo memoriza o nível actual das interrupções (IPL – Interrupt Priority Level), posto num nível e mascarando os outros.
Salvaguarda do Contexto
Devido à interrupção, tenho de guardar: - Contador de Programa
- Registo de Estado do Processador - Registos Gerais
Pode ficar a cargo do preâmbulo da rotina de interrupção ou ser parcialmente feita pelo hardware antes de lhe transferir o controlo.
Selecção de Rotina de Interrupção
Os primeiros processadores só tinham uma linha de interrupção e depois através de um teste programado viam de onde ela vinha.
Para evitar este teste foram incluídos na arquitectura das interrupções mecanismos de vectorização. O controlador do periférico envia um vector (8bits no 286 --< tabela identifica posição de memória de início da rotina) que permite ao hardware agulhar para a rotina respectiva (ex 286 – NMI: falha de alimentação, inicialização/reset, etc; e INT). Também podem ser provocadas por instruções.
3.1.3. A Memória
É um dos recursos críticos no desempenho global do sistema. Hierarquizando:
- memória cache – memória de reduzido tempo de acesso onde são mantidas as variáveis ou as instruções recentemente acedidas;
- memória central – memória do processador implementada com tecnologia de semicindutor; - memória secundária – discos magnéticos.
O sistema deverá gerir esta hierarquia de memória de forma a obter o melhor compromisso entre a ocupação das memórias rápidas e as necessárias transferências para disco.
3.1.4. Bus
A memória, a UCP e os controladores de periféricos encontram-se interligados através de um bus, que é composto por 3 buses funcionalmente especializados:
- Bus de endereços que interliga o CPU e a unidade de gestão de memória - Bus de controlo
3.1.5. Periféricos
Nas máquinas actuais o SO não se ocupa da gestão de baixo nível dos periféricos. Placas com capacidade autónoma de processamento ou CIs executam os protocolos físicos de gestão dos periféricos, oferecendo uma interface virtual ao SO.
Os controladores mais vulgares são: - Controlador de disco
- Controlador de linhas assíncronas (ligações a terminais interactivos) - Controlador de protocolos síncronos (ex: X25)
- Controlador de portos paralelos
- Controlador de redes locais de computadores (ex: Ethernet)
Mecanismos de Acesso Directo à Memória (DMA) permitem optimizar o débito de transferência da informação entre periféricos de alta velocidade e a memória. Cada canal de acesso directo À memória é composto por um conjunto de registos que são programados antes do início da transferência e especificam o endereço de/para onde se pretende ler/escrever os dados, o número de octetos a transferir, sentido da transferência, etc. Depois de iniciada, decorre de forma autónoma ao processador.
Um periférico importante é o gerador de temporizações. 3.2. Estrutura do Núcleo
O SO é um programa ou conjunto de programas que implementa as diversas funções de suporte ao modelo computacional. Como é complexo temos de estruturar com base em níveis de abstracção para facilidade de implementação e manutenção.
Cada camada encapsula a implementação dos conceitos que lhe correspondem, oferecendo uma interface que permite a sua utilização pelos restantes níveis --> sucessivas máquinas virtuais. É habitual que as diversas funções das camadas mais internas não sejam acessíveis aos utilizadores ou que as funções internas do núcleo não respeitem a decomposição modular que o modelo de camadas sugere.
Geralmente diversas camadas são agrupadas numa entidade chamada núcleo do SO. As restantes são consideradas como serviços sistema e são executadas fora do núcleo. A protecção e o tempo de execução são 2 importantes factores para essa definição.
É frequente agrupar no núcleo do SO todas as funções que necessitem executar-se no nível mais privilegiado do processador ( que exigem manipulação de interrupções, acesso a registos especiais, interacção com periféricos; ou modificam informação em que se baseia todo o funcionamento do sistema – tabelas de processos, tabelas de memória).
Para que o modelo de multiprogramação seja eficaz é importante que o núcleo não monopolize a utilização do processador. A conjunção destes 2 critérios implica que algumas funções associadas às camadas mais internas da estrutura de implementação seja executadas fora do núcleo, como serviços sistema.
Como é difícil rigor, geralmente, as funções que têm a ver com gestão de processos, gestão de memória e entradas/saídas são integradas no núcleo.
3.3. Gestão de Processos
É a camada mais interna da estrutura hierárquica de implementação dos SO. As funções executadas por esse nível são fundamentais para o funcionamento de todo o sistema e são aquelas a que está associado o maior nível de protecção.
É vulgar dividi-la em 3 unidades funcionais: - Gestão das Interrupções;
- Multiplexagem do Processador – efectuada conjuntamente por 2 entidades funcionais (com execução em níveis diferentes do modelo):
- Gestão do Processador – Escalonamento - Despacho;
- Funções de Sincronização.
A rotina de despacho tem por função determinar o próximo processo a executar-se e efectuar a comutação do contexto;
O escalonamento implementa a política global de gestão que optimiza a utilização de todos os recursos da máquina (processador, memória, periféricos, etc.). Exame1ªépoca2001-2002 Grupo III
3.3.1. Gestão das Interrupções
Encontra-se no cerne da actividade do sistema. São o mecanismo que permite a comunicação assíncrona entre o processador e o sistema. Sem elas os programas apenas poderiam testar sistematicamente o hardware.
O tratamento das interrupções é uma das zonas do SDO mais directamente dependentes da arquitectura do processador.
É importante distinguir Interrupções e Excepções que têm consideráveis diferenças a nível dos mecanismos que as controlam.
As interrupções são globais --> núcleo toma conta, as excepções relacionam-se apenas com o processo que as provoca --> tratadas no seu contexto.
O programador de aplicações raramente terá de interactuar com a gestão de interrupções. Ao contrário, o controlo de excepções é normalmente da responsabilidade sua.
3.3.2. Representação dos Processos
São representados por um contexto que memoriza todas as informações necessárias à sua execução. Esse contexto, ao longo da sua execução, irá fazer parte de inúmeras listas do núcleo. É vulgar considerar o contexto constituído por 2 partes:
- Hardware (todos os registos do processador): - Acumulador(es)
- Registos de Uso Geral - Contador de Programa - Apontador de Pilha
- Registo de Estado do Processador
(TF1_2001-I-a) – quais as informações que fazem parte do contexto hardware de um processo.
- Software:
- Identificação do processo e do utilizador - Prioridade
- Estado do Processo - Periféricos utilizados - Ficheiros abertos - Programa em execução - Directoria actual / por defeito - Quotas de utilização dos recursos
- Contabilização da utilização dos recursos. Alguma desta informação é permanente outra é volátil.
O contexto é inicializado quando o processo é criado com informações fornecidas pelo utilizador na chamada à rotina de criação do processo ou retiradas do contexto utilizador do processo pai. Os contextos dos processos activos são mantidos na Lista dos Processos Executáveis que procura reflectir o algoritmo de selecção do despacho.
3.3.3. Despacho
A rotina de despacho é chamada sempre que o processo actual deva ser comutado ou exista a possibilidade de ser comutado. dada a frequência de utilização do despacho, o tempo de execução da rotina é crítico.
A comutação de processos é a base da pseudoconcorrência e o seu funcionamento apenas precisa de gestão cuidada das interrupções.
Normalmente quando se dá uma interrupção os processadores guardam o PC e o registo de estado. Quando há a RTI prossegue do endereço onde se tinha dado a interrupção.
Para introduzir a hipótese de comutação temos de modificar o final da rotina de interrupção por forma a chamar o despacho antes de efectuar o retorno. Este verifica se há necessidade de comutação e se sim, guarda o contexto, no descritor do processo e coloca nos registos do
processador o estado do processo seleccionado pelo algoritmo de escalonamento. Finalmente troca o PC e Rflags para relançar um processo. Depois desta troca é que vem o RTI.
3.3.4. Gestão do Processador – Escalonamento
O processador é o recurso crítico do sistema pelo que o escalonamento tenta optimizar a sua utilização, mantendo-o tanto quanto possível ocupado. Mas tem de ser tudo visto em conjunto. Por ex. de nada serve colocar sucessivamente processos em execução se isso originar muitos acessos ao disco (gestão de memória).
Teoricamente seria interessante chamar o escalonamento cada vez que um recurso é atribuído ou libertado, mas isso é um grande peso para o sistema reportar todos os acontecimentos. É pois preciso encontrar um equilíbrio, definindo os dados e acontecimentos decisivos.
Nos sistemas mais evoluídos, o sistema atribui prioridades aos processos e modifica-se de acordo com o seu comportamento no sistema, procurando privilegiar os processos que melhor se adaptam aos critérios globais de gestão, que podem ser vários (tratamento por lotes, interactivos, tempo real, tempo partilhado, etc.) TF1_2001-I-c)
No escalonamento podemos considerar 3 tipos de objectivos diferentes:
- Procurar equilibrar a carga do sistema por forma a dar um serviço relativamente uniforme aos utilizadores interactivos (Tempo Partilhado) Exame1ªépoca2001-02-GrupoIII
- Dotar o sistema de grande reactividade às condições externas (Tempo Real Relaxado) - Conseguir que o sistema execute determinadas acções em intervalos de tempo prédeterminados (Tempo Real Restrito) TF1_2001-I-b)
Tempo de Execução Partilhado
Para equitatividade a maioria dos sistemas apenas permite que o processo em execução utilize o processador durante um certo intervalo de tempo (time-slice / quantum). Eventualmente o despacho pode seleccionar o mesmo se for o mais prioritário.
Solução Round Robin é muito fácil mas tem o evidente problema de facilmente conduzir a tempos de resposta elevados em situações de carga e não permite reagir a prioritários.
Multilista
Uma evolução é haver várias listas de acordo com o tipo de processo em execução. Para sistemas mistos de tempo partilhado e lotes. Aos últimos aplicar-se-ia uma política baseada na menor duração do trabalho e aos outros, circular.
Uma evolução possível é considerar que os processos podem ser transferidos entre várias listas de acordo com o modo como utilizam o processador. A lista mais prioritária corresponde aos processos que utilizam pouco tempo de processador (típico dos interactivos - I/O bound).
A gestão multilista tem muitas variantes. A solução mais simples (retirar sempre da lista mais prioritária) pode conduzir a starvation em carga elevada.
Prioridades Dinâmicas
É uma evolução. O ajuste de prioridade de um processo reflecte o seu comportamento. O critério de alteração baseia-se na utilização dos recursos críticos, principalmente do processador.
O peso da mudança de contexto é importante, pelo que uma optimização possível é aumentar o quantum.
na maioria dos sistemas o quantum é fixo. Preempção
Preempção designa a acção de retirar o processador a um processo em execução devido à existência de outro mais prioritário. É indispensável nos sistemas de tempo-real. A preempção tem, naturalmente, custos, devido ao peso das mudanças de contexto, pelo que alguns sistemas limitam o seu uso, naõ deixando um processo ser retirado sem decorrer um determinado tempo. Tempo Real
Nestes sistemas as prioridades dos processos são geralmente fixas dado que estão associados a acontecimentos, que são notificados através dos mecanismos de sincronização.
3.4. Implementação da Sincronização 3.4.1. Secções Críticas
Inibição das Interrupções
A solução mais simples para implementar a exclusão mútua é inibir as interrupções durante o posicionamento das variáveis partilhadas e respectivo teste, para que não seja possível a mudança de contexto. Mas isso tem desvantagens: ineficiente gestão da máquina resultante da paragem de todas as acções externas susceptíveis de fazerem evoluir o estado do sistema. Por ex. não se pode fazer esperar os discos por causa de sincronização entre utilizadores.
Assim, a inibição de interrupções só poderá ser usada quando a secção crítica for muito pequena ou o processador possui sistema de interrupções hierarquizado.
Implementação dos Trincos
Uma variável do tipo trinco só é implementável se a sequência de teste e posicionamento for indivisível. Uma implementação possível consiste na inibição das interrupções durante a execução das operações. Contudo, esta solução não só coloca problemas ao escalonamento dos processos como não funciona em arquitecturas multiprocessador. Uma solução mais geral implica a utilização de mecanismos de hardware que garantam a possibilidade de efectuar, de forma atómica, o teste e modificação de uma posição de memória. Na maioria das máquinas existem instruções específicas Test and Set: Como se trata de uma única instrução não pode ser interrompida.
function TestAndSet (var Trinco: boolean): boolean; begin
TestAndSet := Trinco; Trinco := true;
end; Exame1ªÉpoca2001-02 – Grupo II a) e b) Exame2ªÉpoca2000-01 – Grupo III c) procedure Fechar (var Trinco: boolean);
begin
while TestAndSet (Trinco) do; end;
Exclusão Mútua em Arquitecturas Multiprocessador
Test and Set não é suficiente. A sequência de teste e posicionamento envolve leitura e escrita, o que para o controlador de bus (de todos os processadores) é sempre visto como 2 sequências independentes.
Nesta arquitectura temos portanto de descer ao nível de gestão do bus para garantir a atomicidade das operações elementares de sincronização. Teste e modificação num único ciclo de acesso à memória. No M68000 a instrução TAS; no I8086 o prefixo lock torna uma instrução indivisível. A rotina fechar é conhecida por spin-lock
fechar proc near mov a1, 1
ciclo: lock xchg a1, trinco or a1, a1
jnz ciclo ret fechar endp 3.4.2. Semáforos
A cada semáforo está associado uma variável que representa o seu valor e um apontador que indica o início da lista. Esta estrutura de dados deverá ser mantida nas tabelas centrais do sistema.
No contexto do processo deve existir um apontador que permite referenciar outros contextos. A manipulação do apontador permite colocar logicamente o contexto na lista correspondente ao seu estado.
As rotinas Assinalar e Esperar têm de ser sequências indivisíveis de acções. Basta pensar em Esperar, decrementa variável, ainda não fez o teste e é interrompido. Outro processo efectua
Esperar e fica logo bloqueado. O primeiro encontra semáforo com valor negativo e bloqueia também.
A manipulação da estrutura de dados do semáforo deve ser incluída numa secção crítica. Mas como semáforos são objecto do núcleo, a secção crítica pode implementar-se com base na inibição de interupções, com as restrições já apontadas.
É interessante analisar o funcionamento dos semáforos em conjunção com uma política de escalonamento preemptível porque existe interdependência entre ambos.
Vamos supor que um semáforo de exclusão mútua com uma lista de espera gerida no habitual FIFO e dois processos concorrentes P1 < P2 que pretendem aceder à mesma secção crítica. Consideremos que P1 efectua primeiro a primitiva Esperar iniciando a execução do código da secção crítica. Quando P2 executar Esperar teremos a seguinte situação:
- P1 a executar a secção crítica - P2 bloqueado no semáforo
Quando P1 libertar o semáforo vai desbloquear P2 que passa para a lista dos processos executáveis. Nesta situação, como considerámos P2 era mais prioritário, vai executar-se imediatamente, passando P1 do estado de Em Execução para Executável. Se a relação de prioridades fosse inversa, logicamente P1 continuaria a sua execução e P2 apenas passaria para Executável. De notar que, mesmo que existisse P3 com maior prioridade que os 2 outros, não poderá aceder à secção crítica pois a variável do semáforo permaneceu a zero.
/* Programação de um semáforo procedure Esperar (S:Semáforo) begin
Fechar (Trinco);
S.Int := S.Int – 1; if S.Int < 0 begin
< bloquear processo e incluí-lo na fila de espera > end;
Abrir (Trinco); end;
procedure Assinalar (S:Semáforo) begin
Fechar (Trinco);
S.Int := S.Int + 1; if S.Int <= 0 then begin
< Retirar um processo da fila de espera transferi-lo para a fila dos executáveis > end;
Abrir (Trinco); end;
3.5. Implementação das Funções Sistema
O sistema implementa mecanismos que, a vários níveis, protegem o acesso às suas estruturas de dados. Primeiramente nenhuma operação do sistema operativo poderá ser executada sem ser através da invocação das funções sistema fornecidas com o SO.
De realçar que função sistema engloba a função propriamente dita que se executa no núcleo e a rotina de interface que é ligada (linked) com o código do utilizador. Esta é responsável pela validação de parâmetros, sua formatação e colocação nas estruturas adequadas e finalmente desencadear a interrupção. No retorno efectua o inverso e trata as indicações de erros.
A protecção das funções sistema implica que a chamada se efectue através de pontos de entrada específicos. Na maioria dos sistemas são utilizadas instruções especiais (trapas) que causam uma interrupção. A agulhagem para a rotina de sistema é efectuada pela rotina de tratamento da interrupção com base num identificador da função.
Para além de assegurar a segurança este processo tem a vantagem do código das funções sistema ser partilhado por todos os programas.
Um segundo mecanismo de protecção aplica-se aos parâmetros das funções sistema. Um utilizador não pode especificar como parâmetro entidades do sistema ou zonas de memória que não lhe pertençam.
CAP. 4 - MECANISMOS DE GESTÃO DE MEMÓRIA 4.1. Introdução
A exposição está dividida em 2 partes:
- Os mecanismos de gestão de memória, determinam a organização da memória do computador, ou seja, se o endereçamento é real ou virtual, se é segmentada ou paginada, ou qual o tamanho das páginas ou segmentos. Estes mecanismos são em geral executados pelo hardware de gestão de memória do processador, devidamente programado pelo SO.
- Os algoritmos de gestão de memória, são da exclusiva responsabilidade do SO. Eles determinam que decisões devem ser tomadas, usando os mecanismos de baixo nível para as levar a cabo.
4.1.1. Processo e Espaço de Endereçamento
Já foi dito que um programa executa as suas instruções num determinado espaço de memória associado ao processo. Designa-se por espaço de endereçamento de um processo o conjunto de posições de memória que um programa executado por esse processo pode referenciar. O espaço de endereçamento está associado ao processo. Se houver uma falha, o hardware provoca uma excepção que é tratada pelo SO, causando normalmente a terminação do processo em curso.
4.1.2. Hierarquia de Memória
Do ponto de vista do SO a memória divide-se em:
- Memória Primária (Física ou Central) – pode ainda ser decomposta em central e cache, geralmente geridas pelo próprio hardware; É volátil.
- Memória Secundária (ou de Disco); é persistente.
Os tempos de acesso às memórias variam normalmente na razão inversa do seu custo. Por esta razão, a dimensão da memória secundária é tipicamente uma ou mais ordens de grandeza superior.
4.1.3. Endereços Reais e Virtuais
Os primeiros computadores suportavam apenas endereçamento real, no qual os endereços gerados pelo programa tinham uma relação directa com os endereços da memória física do computador. Não havia qualquer transformação pelo hardware. Um endereço real refere-se sempre à memória física, nunca à secundária.
Desvantagens do método:
- A dimensão dos programas é limitada pela dimensão da memória física
- Um programa só pode funcionar nos endereços físicos para onde foi escrito, não podendo ser executado numa outra máquina com um mapa de memória diferente.
- A multiprogramação fica bastante dificultada, pois não é possível executar simultaneamente 2 programas que tivessem sido preparados para correr nos mesmos endereços físicos.
Para fazer face a estes problemas, numa máquina com endereçamento virtual, os endereços gerados pelo programa são convertidos pelo processador e em tempo de execução em endereços físicos.
É função do hardware de gestão de memória e dos SO fazer a correspondência. Se a palavra estiver em memória secundária, a unidade de gestão de memória avisa o SO para este carregar a palavra em memória física.
4.2. Endereçamento Real
4.2.1. Sistemas Monoprogramados (MS/DOS, Macintosh) Funcionamento
O principal inconveniente é o tamanho dos programas ser limitado.
Para ultrapassar este problema desenvolveu-se a técnica da sobreposição (overlay): O programa é dividido numa parte residente, que está sempre em memória, e em overlays, que são módulos independentes uns dos outros e que são carregados em memória a pedido do programa. Ex. Programa principal, overlay de inicialização, de simulação, de escrita de resultados. A comunicação é feita por variáveis situadas na zona que permanece residente, que correspondem a variáveis globais do programa numa linguagem de alto nível.
Este método é simples, até para o SO, mas tem a desvantagem de ter de ser o programador a indicar explicitamente quando deve ser carregado novo overlay, o que torna a programação mais difícil. É ainda difícil de aplicar a certos programas e pode tornar-se lento.
É óbvio que as limitações físicas se mantêm: a soma da parte residente + overlay não pode exceder a memória física.
Protecção
Em geral o SO fica localizado no extremo inferior da memória. Uma vez que só existe um utilizador, o problema da protecção pode ser ignorado. Se um programa perder o controlo e corromper o SO, ter-se-à de reinicializar o a máquina e carregar novamente o SO.
Máquinas mais sofisticadas têm um registo limite indicando o endereço mínimo a que um programa pode aceder. Se tenta --> excepção (modo utilizador e modo sistema, já).
4.2.2. Sistemas Multiprogramados com Partições Fixas Funcionamento
Para permitir multiprogramação é necessário que vários programas possam coexistir em memória física. A foram mais simples é dividir a memória em partições, carregando-se um programa em cada. Quando o programa da partição 1 se bloquear numa operação de E/S salvaguarda-se o contexto do processador e passa-se a executar o programa da partição 2, e assim sucessivamente. O grau de multiprogramação depende do nº de partições
Inicialmente os programas eram compilados para uma determinada partição, o que é uma má gestão. Para resolver o problema surgiram os programas recolocáveis: os compiladores não fazem referência a endereços físicos, mas geram tabelas que o carregador em memória (loader) converte. Assim a decisão da partição onde colocar é feita pelo operador de sistema na altura. Alguns compiladores têm registo base que é carregado com endereço físico do início da partição, que serve de base a indexação pelo hardware na altura de execução. A isto também se chama endereçamento baseado, que já é uma aproximação do virtual pois também conta com o hardware. No entanto continuam relacionados com os físicos a menos de uma constante. Resolve o problema da recolocação mas deixa outros em aberto.
Dimensão dos Programas
A técnica dos overlays pode continuar a ser usada dentro das partições, sendo sempre limitada pela maior partição. Á noite pode juntar-se tudo numa partição.
Fragmentação
É do tipo fragmentação interna (dentro do bloco/partição). Protecção
A existência de vários utilizadores implica que cada um não aceda a outra partição que não a sua. Há um par de registos que é carregado com o endereço máximo e mínimo da partição actual, que o hardware depois controla.
4.2.3. Sistemas Multiprogramados com Partições Variáveis Funcionamento
O problema da fragmentação anteriormente referido pode ser resolvido mudando dinamicamente o número e dimensão das partições sem parar o sistema.
Fragmentação
Quando um programa termina e liberta a sua partição, 3 situações podem ocorrer:
O novo programa é exactamente do tamanho do bloco livre (pouco provável); Se for maior, não se poderá executar; se for menor pode e deixa zona menor livre.
Com o tempo gera-se fragmentos de memória espalhados pela memória (fragmentação externa); logo, de tempos a tempos tem de haver recompactação.
Dimensão dos Programas