fsOperativosResumo_Livro

Share Embed Donate


Short Description

Download fsOperativosResumo_Livro...

Description

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. 1.4. Arquitectura do Sistema Operativo

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 (, 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. type

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 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) if EmEscrita or EscritoresEspera > 0 then begin LeitoresEspera := LeitoresEspera + 1; Assinalar (Mutex); Esperar (Leitores);

Esperar (Mutex);

/*secção crítica para estado do sistema

/*liberta secção crítica depois de actualizar estado do sistema /*Bloqueia-se pois há processos em escrita ou à espera para escrever. Avança quando houver um Assinalar(Leitores) /* 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); NLeitores := NLeitores – 1; if EscritoresEspera > 0 and NLeitores = 0 then Assinalar (Escritores);

/* acabou de ler e vai actualizar estado do sistema /* Se há escritores à espera e não há leitores a ler... /* Desbloqueia Escritores

Assinalar (Mutex); end; {AcabaLeitura} procedure IniciaEscrita begin Esperar (Mutex); if NLeitores > 0 or EmEscrita then

/* Liberta secção crítica

/* Bloqueia-se na secção crítica /* 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; Assinalar (Mutex); end; {IniciaEscrita}

/* Depois de ter actualizado a secção escreve /* liberta secção crítica

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 desbloqueiaos 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 dados bidireccional

- 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 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. O espaço de endereçamento do SO encontra-se protegido contra acessos do modo utilizador.

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 Continua limitada pela memória física mas não é necessário parar o sistema.

Protecção É igual à anterior 4.3. Endereçamento Virtual O principal objectivo é dissociar os endereços gerados pelo programa dos físicos, acabando, para o programador, com a limitação do tamanho dos programas. O mecanismo de tradução é efectuado pelo hardware (para ser rápida), mais propriamente pela gestão de memória do processador. As outras componentes do processador (unidade de controlo, ULA) só manipulam endereços virtuais. Uma tabela de correspondência ocupava muita memória. Por isso, a memória é dividida logicamente em blocos contíguos, existindo para cada bloco uma entrada na tabela de traduções. Um endereço passa a ter a forma (bloco, deslocamento) Os 2 métodos mais importantes de dividir a memória em blocos são: paginação (blocos com o mesmo tamanho) e segmentação (blocos com tamanhos diferentes). 4.3.1. Segmentação Objectivos Divisão dos programas em segmentos lógicos que reflictam a sua subdivisão funcional (ex. rotina). A ideia base é tentar suportar na gestão de memória as abstracções comuns das linguagens, nomeadamente: - Carregamento em Memória – O segmento é a unidade mínima a carregar em memória. Considera-se que todas as palavras dentro de um segmento têm a mesma probabilidade de ser acedidas. - Protecção – É feita em termos de blocos lógicos. - Eficiência – O princípio da localidade de referência diz-nos que, se acedermos a um endereço de um segmento lógico, com grande probabilidade os próximos acessos serão a endereços situados no mesmo segmento. Mecanismo de Tradução de Endereços Um endereço tem a forma (segmento, deslocamento) Existe uma tabela de segmentos composta pelo descritor de cada segmento. Cada descritor contém o endereço físico do segmento (de base), a sua dimensão, informação respeitante à protecção e à utilização do segmento. 2 registos da unidade de gestão (registo base da tabela de segmentos – BTS) e registo limite da tabela de segmentos – LTS) contêm respectivamente o endereço real de início da tabela e a sua dimensão. Quando o programa gera um endereço, o número do segmento é comparado com o LTS. Se for inferior, é somado com o BTS obtendo-se a entrada na tabela de segmentos correspondente a esse segmento. Ás vezes, para maior rapidez, usam-se, na soma, apenas alguns bits – segmentos físicos começam na fronteira de 2 n. De seguida os bits de protecção são testados (ler, escrever, executar, acesso legal). Por fim, é verificado se o deslocamento pretendido é inferior ou igual à dimensão do segmento. Se o endereço for válido, o deslocamento é somado ao endereço físico do início, obtendo-se o endereço físico pretendido. Se algum teste falhar, o endereço é inválido, o hardware interrompe a tradução e gera uma excepção. É tudo feito pelo hardware. O SO só programa os vários registos, preenche a tabela de segmentos e trata excepções. Existe uma tabela de segmentos por processo. Quando se dá uma mudança de contexto, os registos base e limite têm de ser carregados com o endereço e dimensão da tabela de segmentos do novo processo. A dimensão máxima de um segmento está associada à arquitectura da máquina. ex. Intel 80286 é 64Kbytes. Optimização do Mecanismo de Tradução de Endereços A tabela de segmentos está em memória física. Se o hardware tiver de lhe aceder cada vez que o programa gera um endereço, o custo de acesso a uma posição de memória duplica, o que é inaceitável, pois a velocidade do computador está em grande parte limitada pelos acessos à memória.

Em arquitecturas segmentadas a solução habitual é guardar em registos, de acesso rápido, as entradas da tabela de segmentos correspondentes aos segmentos em utilização. O 80286 tem 4 registos para segmentos: código, dados, pilha e um extra. A operação demorada, que é ler o descritor da tabela de segmentos, só é efectuada da primeira vez que o segmento for acedido – tipo cache. Fragmentação A segmentação gera fragmentação externa. Uma maneira de a resolver é copiar todos os segmentos para um dos extremos da memória, como no caso das partições variáveis. Outra solução é usar a memória secundária. o que é incrtemental. Em ambas o processamento é interrompido. Protecção A unidade de protecção é o segmento e está especificada no descritor, e é feita a vários níveis: - Como processos diferentes têm tabelas de segmentos diferentes, os espaços de endereçamento virtual são distintos, não havendo forma de um processo poder referenciar zonas de memória de outro. - O deslocamento dentro do segmento é verificado comparando-o com a dimensão presente no descritor. - O tipo de acesso ao segmento é verificado. Partilha de Memória Entre Processos Basta ter na tabela nas várias tabelas de segmentos uma entrada contendo o mesmo endereço físico e dimensão. 4.3.2. Paginação Objectivos Oferecer ao programador um espaço de endereçamento (virtual) linear, em geral bastante maior que a dimensão da memória física da máquina. A ideia subjacente é fazer com que o programador não precise de se preocupar com a gestão de memória quando escreve um programa. Mecanismo de Tradução de Endereços A memória é dividida em blocos todos do mesmo tamanho, chamados páginas. Um endereço tem a forma (página, deslocamento) Existe uma tabela de páginas compostas pelo descritor de cada página. Cada descritor contém o endereço físico da página, um bit de presença e informação respeitante à protecção e à utilização da página. Dois registos de hardware de tradução de endereços, chamados Base da Tabela de Páginas (BTP) e Limite da Tabela de Páginas (LTP), contêm respectivamente o endereço real de início da tabela de páginas e dimensão desta. Quando o programa gera um endereço, o nº da página é comparado com o registo LTP. Se for inferior, é somado com o BTP, obtendo-se a entrada na tabela de páginas correspondente a esta página. De seguida testam-se os bits... se o endereço for válido, o deslocamento é somado ao endereço físico do início da página, obtendo-se o endereço físico pretendido. A soma pode ser só concatenação de bits... também pode haver excepção para o SO. Existe uma ou mais tabelas de páginas por processo. Quando se dá uma mudança os vários registos de base e limite têm de ser carregados com o endereço e dimensão das tabelas de páginas do novo processo. A dimensão das páginas está definida na arquitectura das máquinas não podendo nunca ser alterada. O espaço virtual típico é de 4Gbytes (32 bits), a dimensão das páginas varia de 512 bytes (VAX) a 4 a 8 Kbytes (SUN). Tabela de Tradução de Endereços (TLB) A tabela de páginas reside em memória física. Como na segmentação o mecanismo obriga a acesso suplementar À memória, tornando indispensável a existência de uma tabela interna à unidade de gestão de memória que guarde as PTE das últimas páginas acedidas. Agora não é

possível registos pois nunca é possível saber se a palavra a ser acedida está na página ou não (não há divisão lógica). A solução comum consiste em guardar numa memória associativa de acesso muito rápido chamada TLB (Translation Lookaside Buffer) os descritores das últimas páginas acedidas. è feito em paralelo com o acesso à tabela de páginas. Se for encontrado o endereço na TLB é interrompido o acesso à tabela de páginas. Se não, é posto na TLB em FIFO. Os RISC usam TLB gerida por software. A dimensão destas tabela sé pequena (64, 128 entradas) pois o seu custo é elevado (90 a 95% de eficácia). Falta de Página A PTE tem um bit de presença (bit P) que indica se a página está ou não em memória primária. Se o bit estiver a zero, o hardware de gestão gera uma excepção que interrompe a instrução a meio. Diz-se então que houve uma falta de página. O SO analisa a causa da excepção, determina que foi uma falta de página e inicia o processamento adequado. Em 1º lugar terá que alocar uma página livre em memória. Se é uma página nova basta preenchê-la com zeros; se existe em memória secundária, é necessário lê-la do disco. Quando o acesso ao disco terminar, o SO preenche a PTE com o endereço da página, põe o bit P a 1 e coloca o processo na fila dos executáveis. As excepções geradas pela UGM são diferentes pois interrompem uma instrução ao meio e o processador tem depois de retomá-la a meio. Até porque uma instrução pode ter uma parte numa página e outra noutra. As instruções têm pois de ser recomeçáveis. A diferença importante é que só as instruções que carregam um novo segmento podem originar uma falta de segmento. Só estas têm de ser recomeçáveis, o que simplifica o hardware do processador. Fragmentação Há fragmentação interna na última página de cada bloco (código, dados, pilha). Quanto maiores forem as páginas maior é o desperdício. Protecção Faz-se da mesma forma que na segmentada: processos distintos têm tabelas de páginas diferentes e cada página contém os modos de acesso permitidos (leitura, escrita, execução). No entanto, a granularidade de protecção é a página --> implica mais complicado e deselegante que a segmentada para proteger bloco lógico. Partilha de Memória Entre Processos Basta ter nas tabelas de páginas dos processos um conjunto de PTE indicando o mesmo endereço físico. Dimensão das Páginas É opção importante. Páginas pequenas têm a vantagem de diminuir a fragmentação interna mas aumentam o número de faltas de páginas mantidas pelo SO. 4.3.3. Memória Segmentada/Paginada Segmentação segue aproximação lógica e é elegante. Paginação segue aproximação física e é eficiente. A arquitectura segmentada/paginada procura obter as vantagens de uma e de outra. O endereço tem a forma (segmento, página, deslocamento) O mecanismo de tradução de endereços pode ser visto na pg. 115 A protecção e partilha de segmentos é feita como na segmentação pura. O 80386 tem uma arquitectura segmentada/paginada. Um endereço virtual tem 48 bits, os 16 mais significativos determinam o segmento, cuja dimensão máxima é de 2 32 octetos. cada segmento é subdividido em páginas de 4 Koctetos

CAP. 5 – ALGORITMOS DE GESTÃO DE MEMÓRIA 5.1. Introdução Estes algoritmos são usados para decidir onde se deve colocar um bloco de programa dada a memória livre, quando transferir uma página ou segmento de memória secundária para primária e vice-versa e quando não existe mais memória livre que bloco retirar de memória para lá colocar outro com maior prioridade. Dependem do SO. Existem 3 tipos de decisões que o SO tem de tomar: - Alocação: onde colocar um bloco de memória - Transferência: quando transferir um bloco de memória secundária para primária e vice-versa - Substituição: quando não existe mais memória livre, qual o bloco a retirar de memória para satisfazer o pedido. A complexidade dos algoritmos tem a ver com a arquitectura de memória do computador. 5.2. Alocação (colocação) de Memória São representados por duas rotinas: endereço = AlocMem (dimensão) LibMem (endereço) A 1ª rotina devolve o endereço de um bloco de memória livre da dimensão igual ou maior que o pedido. O SO tem de utilizar estes algoritmos quando: - Na criação e terminação de processos - Por extensão do espaço de endereçamento. 5.2.1. Alocação de Páginas Como as páginas são todas do mesmo tamanho (uniformidade da memória), o algoritmo é trivial: aloca-se qualquer página que esteja livre. As páginas livres são mantidas numa lista, geralmente gerida em FIFO. 5.2.2. Alocação de Segmentos É mais complexa pois é preciso encontrar um segmento com dimensão suficiente. Entre os blocos livres maiores que o pedido, tem que se decidir qual deles provocará menor fragmentação externa. Mas um algoritmo muito complicado pode comprometer o desempenho, por demasiado lento. A estrutura de dados manipulada pelas rotinas de alocação de segmentos é composta por uma lista de blocos de memória livres. Cada registo contém o endereço do bloco livre e a sua dimensão. A lista está ordenada por endereços físicos de forma a se poder agrupar blocos contíguos na libertação. Certos algoritmos ordenam também por dimensão do bloco. Habitualmente os blocos têm granularidade mínima, do valor de uma potência de 2. Embora provoque fragmentação interna, existem algumas vantagens: - Evita que se gerem fragmentos muito pequenos, que seriam de difícil aproveitamento e iriam aumentar o comprimento da lista, tornando a pesquisa lenta. - Os blocos devolvidos ficam naturalmente alinhados numa fronteira de palavra. - Os registos da lista de blocos livres precisam de menos bits. Os diversos algoritmos que iremos analisar diferem na política de escolha do bloco a devolver: Best-Fit Pesquisa a lista à procura do bloco cuja dimensão seja a mais próxima (por excesso) da dimensão pedida. Deixa livre, normalmente, fragmento pequeno de difícil reutilização. A lista é ordenada por dimensão crescente de blocos. Em média é preciso percorrer metade da lista para encontrar o bloco certo, e depois é preciso reintroduzir o fragmento na posição adequada da lista. Worst-Fit Pesquisa a lista à procura do maior bloco livre. A ideia é o fragmento ainda servir. Tem a desvantagem de esgotar primeiro os blocos maiores, o que pode fazer com alguns pedidos subsequentes não possam ser atendidos.

A lista é ordenada por ordem decrescente de dimensão dos blocos, pelo que o maior é sempre o primeiro. Também é preciso reintroduzir fragmentos. First-Fit Pesquisa a lista por ordem crescente (ou decrescente) de endereços e escolhe o primeiro bloco livre com dimensão suficiente. Procura optimizar o tempo de alocação diminuindo o número de registos a percorrer na lista. Gera um grupo de pequenos blocos no início. É mais rápido que os anteriores, embora gere maior fragmentação. Não há necessidade de manter lista ordenada por dimensão. Não tem de se reintroduzir fragmentos. Next-Fit É um first-fit modificado, em que a pesquisa do bloco livre começa no ponto onde terminou o anterior, evitando-se a pesquisa dos fragmentos pequenos no início das lista. Tem a desvantagem de espalhar fragmentos pela memória toda. Buddy Organiza a memória livre em blocos de dimensão bn, b e n nº naturais. Normalmente b =2, o que origina o buddy binário. Para satisfazer um pedido de dimensão D, percorre-se a lista à procura de um bloco de dimensão R tal que 2k-1 < R < 2k. Se tal bloco não for encontrado, a lista é percorrida à procura de um bloco de dimensão 2k+i , i>0, que será dividido em duas partes iguais, de dimensão 2k+i-1, chamados buddies. Um destes buddies será ainda subdividido quantas vezes as necessárias até se obter um bloco de dimensão 2k. Se possível, na libertação o bloco é recombinado com o seu buddy, sendo a associação entre buddies repetida até se obter um bloco com a maior dimensão possível. Este método mais elaborado permite um bom equilíbrio entre o tempo de procura e a fragmentação interna e externa. 5.3. Algoritmos de Transferência Determinam quando um bloco de memória deve ser transferido entre memória secundária e central. Existem 3 alturas em que a transferência pode ser feita: - A pedido (on request): o programa ou o SO determinam explicitamente quando se deve carregar o bloco em memória; - Por necessidade (on demand): o bloco é acedido e gera-se uma falta de página ou segmento, sendo necessário carregá-lo em memória - Por antecipação: o bloco é carregado em memória pelo SO, pois este considera fortemente provável que ele venha a ser acedido nos próximos instantes. 5.3.1. Transferência de Segmentos (Swapping) Para o processo se executar tem de te todos (4) os segmentos carregados em memória. Quando um novo processo é criado, é possível que não haja espaço em memória para o conter. Nesse caso é necessário transferir um ou mais segmentos de outros processos par o disco. Geralmente escolhe-se um processo e transferem-se todos os seus segmentos, pois é inútil ter apenas alguns em memória. Nesta altura diz-se que o processo foi transferido para disco (swapped out). Um segmento é sempre transferido como um todo. Este mecanismo denomina-se transferência de segmentos (swapping) e permite, de uma forma simples e sem hardware sofisticado, oferecer um ambiente de multiprogramação. As transferências descritas são feitas a pedido. Um processo é transferido para memória quando está executável mas swapped out e o SO considera que o deve carregar para executar em breve. A passagem para executável pode acontecer mesmo quando estava swapped out, porque o sistema guarda o seu descritor residente em memória. Nunca há falta de segmento, o que permite algoritmos muito simples (Unix sobre PDP-11) Em máquinas mais complexas, que suportem faltas de segmento, é possível mais sofisticação, em que certos segmentos de um programa só são carregados por necessidade. Tem interesse se o processo é constituído por vários segmentos. Tem a vantagem de só carregar um segmento em memória quando ele é mesmo necessário.

Os segmentos que não cabem de momento em memória são colocados numa zona separada de disco, chamada área de transferência (swap area). Aloca-se com na memória. Quando o segemento é transferido para disco, guarda-se no seu descritor da tabela de segmentos o bloco de disco onde começa o segmento na área de transferência. Em memória segmentada não se utiliza, geralmente, técnicas de carregamento por antecipação. Mas pode optimizar-se guardando os mais utilizados na swap area em blocos contíguos. 5.3.2. Transferência de Páginas O mecanismo normal de transferência de páginas é por necessidade (on demand). Quando a página se encontra no disco, a PTE contém, em vez do endereço real da página, o bloco de disco onde a página se encontra. Quando ocorre falta de página, o SO aloca uma página, retirando-a da lista de páginas livres, preenche a PTE com o endereço físico da página e inicia a leitura do disco. Depois posiciona o bit P a 1 e coloca o processo na lista dos executáveis. No inverso determina o bloco do disco... Tipicamente, cada processo tem uma tabela de páginas para o código e outra para os dados e pilha. Existe ainda a do SO. As páginas de código e dados inicializados em tempo de compilação podem ser lidas directamente do ficheiro. Quando o SO põe o programa em execução, percorre a tabela de páginas do processo, coloca em cada PTE o bit P a 0 e escreve o nº de bloco dessa página no ficheiro. O código não é lido duma vez, vão-se gerando faltas de página. Nestes caso é possível aplicar políticas de antecipação, que consistem em ler em avanço algumas páginas. Tem 2 vantagens: diminui-se o nº de faltas de página e optimizam-se os acessos ao disco. Pode é ler-se páginas não necessárias. O sistema mantém uma zona de disco onde escreve as páginas, chamada área de paginação. O bit P da PTE é colocado a 0 e no lugar do endereço físico de início da página coloca-se o bloco de disco onde a página está guardada. Tem de se ver se já há em disco (ex. código não preciso reescrever). A escrita de páginas em memória secundária é feita em grupo. As páginas modificadas são guardadas numa fila até uma certa dimensão. 5.4. Algoritmos de Substituição 5.4.1. Substituição de Páginas Os algoritmos de substituição são particularmente importantes na memória paginada. Quando não há páginas livres, terá de se retirar uma página a um processo para satisfazer o pedido. O SO mantém 2 listas de páginas: a lista de páginas livres e a lista de páginas livres mas modificadas. As primeiras podem ser usadas a qualquer momento; as segundas têm de ser escritas em disco antes de serem reutilizadas. Se o nº de páginas livres ficar abaixo de um mínimo, é acordado o processo paginador, que irá libertar páginas, utilizando um dos algoritmos que a seguir veremos, até que o nº suba acima de um valor máximo. Periodicamente a lista de páginas modificadas é escrita em disco e as páginas desta lista passam para a lista de páginas livres. Um programa tende a concentrar as referências à memória em determinadas zonas durante certos períodos – Princípio da localidade de referência – boa indicação para futuro próximo, para não retirarmos páginas que vão logo ser precisas. Os métodos que vamos ver seguem todos este raciocínio. Todos eles mantêm uma história dos últimos acessos à memória feitos pelo programa, como forma de decidir qual a página que, em princípio, será menos provável de vir a ser acedida nos instantes seguintes. Menos Usada Recentemente (LRU) A que não seja acedida há mais tempo. No entanto, a sua aplicação rigorosa implica que se determine exactamente a página que não é acedida há mais tempo. Uma possibilidade de o fazer seria registar na PTE uma marca de tempo (timestamp) cada vez que a página fosse acedida, procurando depois a que tivesse a marca mais antiga. Teria de ser efectuado pelo hardware de gestão de memória – complexo e dispendioso. Há variantes mais simples que dividem as páginas em grupos etários. O Unix V mantém a idade em 3 bits na PTE e inicializados a 0.

Se o bit R estiver a 0 o processo paginador, acordado regularmente, incrementa-o; se R estiver a 1 o contador e o bit R são postos a zero. Quando o contador chegar a um nº máximo a página é marcada como inválida (bit P a 0) e posta na lista das páginas livres ou modificadas. Não Usada Recentemente (NRU) É simplificação do LRU. O processo paginador percorre regularmente as tabelas de páginas, analisa os bits R e M e volta a colocar o bit R a 0. As páginas dividem-se em 4 grupos: - Grupo 0 (R= 0, M=0) – Não referenciada, não modificada - Grupo 1 (R=0, M=1) – Não referenciada, modificada - Grupo 2 (R=1, M=0) – Referenciada, não modificada - Grupo 3 (R=1, M=1) – Referenciada e modificada O algoritmo escolhe uma página qualquer do grupo mais baixo que não esteja vazio. 2 bits de hardware apenas. É possível realizar o NRU em máquinas que não possuam o bit R (ver pg. 127) FIFO Escolhe a página que esteja há mais tempo em memória. A grande desvantagem é que a página mais antiga é, possivelmente, muito acedida. A vantagem é a simplicidade do algoritmo. Em geral, nenhum SO usa FIFO puro, mas o VMS e outras Unix usam FIFO modificado, com 3 listas (ocupadas, livres e livres mas modificadas). Todas as listas são geridas em FIFO. O paginador em vez de percorrer as listas limita-se a retirar as de que necessita da lista de páginas ocupadas. Desta forma as mais antigas vão para as livres ou modificadas (cache). Espaços de Trabalho Os processos devem roubar páginas uns aos outros ou só a si mesmos? Se paginar contra si próprio, qual deve ser o nº de páginas a manter em memória? Na década de 70 Denning desenvolveu a teoria dos espaços de trabalho (working sets). Define-se o working set de um processo num determinado intervalo de tempo como sendo o nº de páginas acedidas pelo processo nesse intervalo. É interessante verificar que para tempos razoáveis (20, 50 ms) o working set de um processo se mantém constante. Se estiverem em memória menos páginas que o ws há constantes falhas de página. Se vários tiverem este comportamento há colapso (thrashing) do sistema. Ex. no VMS um processo só é colocado em memória central se existir um nº mínimo de páginas livres (ws mínimo), que depois pode crescer até um valor máximo após o que começa a paginar contra si mesmo. Se o nº descer abaixo do ws mínimo (devido a um prioritário, por exemplo) o proceso é swapped out. 5.4.2. Substituição de Segmentos A decisão não pode ser tomada pelos mesmos critérios usados para a paginação, pois o segmento ideal a substituir pode ser demasiado pequeno. Geralmente usam-se 3 critérios: - Estado e Prioridade do processo: processos adormecidos e pouco prioritários são os primeiros candidatos a ir para disco. - Tempo de permanência do processo em memória: os mais recentes não devem sair logo - Dimensão do processo 5.5. Análise Comparativa da Segmentação e Paginação 5.5.1. Segmentação Vantagens: - Adapta-se à estrutura lógica do programa - Permite a realização de sistemas simples sobre hardware simples - permite realizar eficientemente as operações que agem sobre um segmento inteiro Desvantagens: - O programador tem de ter sempre algum conhecimento dos segmentos subjacentes - Os algoritmos tornam-se bastante complicados em sistemas mais sofisticados

- O tempo de transferência de segmentos entre memória central e disco torna-se incomportável para segmentos muito grandes - A dimensão máxima dos segmentos é limitada. As características que tornam, a segmentação vantajosa para sistemas simples (PCs), transformam-se em desvantagens insuperáveis para sistemas mais complexos. 5.5.2. Paginação Vantagens: - O programador não tem que se preocupar com a gestão da memória - Os algoritmos de alocação, transferência e substituição são mais simples e eficientes - O tempo de leitura de uma página de disco é razoavelmente pequeno - a dimensão dos programas é virtualmente ilimitada Desvantagens: - O hardware é mais complexo, não permitindo realização de máquinas de baixo custo (por enquanto) - Operações sobre segmentos lógicos são mais complexas e menos elegantes - O tratamento de faltas de páginas representa uma sobrecarga ao processamento. O aspecto da visibilidade pelo programador da organização de memória é de importância fundamental. Nas máquinas de arquitectura segmentada, o programador tem várias formas de gerir a memória: - O código fica num segmento, os dados e a pilha noutro. É o modelo mais usado. ... Um outro aspecto muito importante é o desempenho. A paginação segue uma aproximação física, de acordo com o que for mais eficiente executar. A segmentação segue uma aproximação lógica, que nem sempre é possível executar com eficiência.

CAP. 6 – COMUNICAÇÃO ENTRE PROCESSOS 6.1. Modelo de Comunicação Os mecanismos de sincronização descritos no cap. 2 permitem controlar a execução de um processo em função do estado das variáveis de sincronização. Os mecanismos de comunicação permitem generalizar as interacções entre processos, associando a sincronização e a transferência de informação. A comunicação pode sempre reduzir-se a uma interacção entre um produtor e um consumidor de informação (mensagem – interpretada segundo um determinado código ou protocolo). Do ponto de vista do sistema, a mensagem é normalmente considerada como uma sequência de octetos não sendo importante a sua estrutura interna (protocolos). A transferência é suportada por um canal, que tem de oferecer fiabilidade, sendo, normalmente, mecanismo interno do SO. 6.1.1. Identificadores do Canal de Comunicação Nas primitivas de transferência de informação é necessário identificar o canal, que define o par produtor/consumidor, podendo ser explicitado de forma directa ou indirecta. Na directa, a cada processo é implicitamente associado um canal. O identificador atribuído pelo sistema na sua criação é utilizado para o identificar nas primitivas de comunicação. Os canais são implicitamente criados e geridos pelo SO. Enviar (IdProcCons, Mensagem) Receber (IdProcProd, ApontMensagem) Esta solução coloca alguns problemas, pois obriga a indicar explicitamente no código o processo correspondente, tornado difícil a implementação de estruturas genéricas, por ex., com vários receptores sobre o mesmo canal. A indirecta melhora isso. Os canais são explicitamente criados. Enviar (IdCanal, Mensagem) Receber(IdCanal, ApontMensagem) Um processo pode possuir vários canais, com significados específicos (especializados). Reciprocamente, um canal pode ser partilhado por vários processos. Os canais devem ter identificadores, para os processos os designarem. a gestão dos identificadores é com o SO (espaço de nomes). Como qualquer outro objecto do sistema, tem de haver mecanismos de protecção dos canais que garantam a sua boa utilização. Quando um canal é criado fica a pertencer a um processo com poderes para determinar quais os processos que se lhe poderão associar e eliminar o canal. Devido à semelhança há SO que usam a estrutura de directórios do sistema de ficheiros para manter os identificadores dos canais. 6.1.2. Armazenamento das Mensagens O canal pode conter apenas uma mensagem ou ter capacidade para várias. Se não tem capacidade de memorização, o produtor tem de ficar bloqueado até que o receptor execute primitiva de recepção. A capacidade permite optimizar a estrutura das aplicações e tem a ver com a dimensão e número de tampões do núcleo. 6.1.3. Estrutura das Mensagens A comunicação pode fazer-se através de mensagens individualizadas ou de uma interface em que os processos escrevem/lêem sequências de caracteres (byte stream). O primeiro tipo de interface é o mais comum corresponde ao conceito habitual de um sistema de comunicações por mensagens. Contudo o segundo é vulgar nos sistemas de ficheiros e na interface das E/S. Normalmente são oferecidos os 2 e o 2º pode ser implementado através do primeiro com controlo do SO por algoritmos. 6.1.4. Sincronização A primitiva de envio de mensagens pode ter diversas semânticas: - Assíncrona – O cliente envia o pedido e continua a execução - Síncrona – o cliente fica bloqueado até que o servidor leia a mensagem Cliente/Servidor – o cliente fica bloqueado até que o servidor envie uma mensagem de resposta. A assíncrona é a mais vulgar.

Caso não haja memorização tem de haver sincronização estrita (síncrona por rendez-vous). A última não é geralmente oferecida pelos SO comuns, sendo vulgar nos sistemas distribuídos. 6.1.5. Implementação da Transferência de Dados A implementação do canal é um aspecto de grande importância e relaciona-se com os mecanismos que o SO deverá fornecer para permitir a transferência da informação física. Numa aproximação simplista podemos pensar que bastava os processo terem acesso a uma zona de memória comum, mas há os mecanismos de protecção que não deixam um processo aceder a uma zona de memória fora do seu espaço de endereçamento. Torna-se portanto necessário um suporte adicional dos mecanismos de gestão da memória ou funções de transferência internas ao núcleo com privilégios. Ambas as soluções são possíveis. A primeira solução corresponde à possibilidade de o mecanismo de gestão de memória criar zonas de memória partilhada, mapeadas nos 2 espaços de endereçamento. A maioria tem para o utilizador manipular explicitamente. A segunda é uma chamada a uma função do sistema. É a mais vulgar, usando o espaço do núcleo para a transferência. A implementação do canal assume importância vital nos sistemas distribuídos, por protocolos. 6.2. Relações Entre Processo Produtor e Processo Consumidor 6.2.1. Mestre / Escravo A estrutura mestre/escravo pressupõe que o processo receptor tem a sua acção totalmente controlada por um processo mestre. - A actividade do escravo é totalmente controlada pelo mestre - O mestre não tem de pedir permissão para utilizar o escravo mas não lhe pode atibuir nova tarefa antes que este tenha terminado a anterior. A sincronização é, portanto, estrita entre as operações de envio e recepção de informação. - O canal que define a associação entre o mestre e o escravo é fixo É o modelo mais simples (de implementação), mas a sua rigidez limita a sua utilização. ex: Prod – ProcEscrita 6.2.2. Correio O correio baseia-se na possibilidade de transferência assíncrona de informação sob a forma de mensagens. ProcEscrita – Gere Imp GereImp não trabalha par um único processo, mas para o conjunto de processos no sistema. A informação transmitida tem de ficar armazenada no canal de comunicação, até que GereImp possa recebê-la. Assim GereImp não é escravo de um processo que quer imprimir mas sim de um processo autónomo do sistema, que oferece um serviço aos restantes. O processo cliente não tem de ficar bloqueado à espera que o serviço solicitado na mensagem seja executado. As características do correio podem resumir-se: - O emissor não tem de pedir permissão para enviar mensagem. Pode, contudo, ficar bloqueado se a capacidade de memorização do canal tiver sido excedida. - O emissor não tem qualquer controlo sobre o ou os receptores - O canal deve memorizar as mensagens durante o intervalo de tempo que decorre entre a sua produção e os eu consumo. O canal é vulgarmente chamado caixa de correio. 6.2.3. Diálogo O diálogo é um modelo de comunicação que se pode considerar como apresentando características comuns aos 2 anteriores. Como o 1º também estabelece um canal permanente entre os 2 processos. Contudo é criado de forma dinâmica. A associação durará o tempo da interacção. Por razões de eficácia quando a interacção é longa. Ex. gestor de bandas magnéticas. Inicialmente o cliente tem de pedir ao gestor das unidades de banda, através de uma caixa de correio, que lhe seja atribuída uma unidade. Há analogia dos 2 últimos modelos com as duas interfaces de comunicação em redes de dados (pacotes-datagram e ligação virtual).

Nos centralizados a interacção do tipo diálogo é menos vulgar que a correio, que aparece contudo nos sockets Unix, por exemplo. Resumo das características: - Existe um canal fixo entre os processos, mas a sua criação é dinâmica - Um dos processos deve requisitar o estabelecimento da ligação - A associação entre cliente e servidor é temporária. 6.2.4. Difusão Pode ser considerada um caso especial dos anteriores mas é de capital importância nos sistemas distribuídos. Pretende-se enviar a mesma informação a um conjunto de processos. Distingue-se do correio porque, apesar de neste modelo também poderem existir vários receptores, cada mensagem é apenas lida por um processo. Exemplo de utilização é selecção de um processo interlocutor. Ex. vários processos gestores de impressoras, um utilizador poderá querer saber se algum controla uma a laser por ex. Quando se quer aumentar a fiabilidade a difusão também pode ser importante. ex. 2 sistemas de ficheiros com informação replicada. Pode implementar-se sobe um modelo de correio, obrigando o emissor a repetir a mensagem um nº de vezes..., mas isso é custoso. Existe crescente interesse nos sistemas distribuídos. 6.3. Comunicação no Modelo Computacional 6.3.1. A Memória Partilhada É o mais simples para implementação de um canal de comunicação entre 2 processos. A zona de memória deve fazer parte do espaço de endereçamento dos 2 processos. Nos sistemas de memória paginada e segmentada, como vimos, esta facilidade é normalmente oferecida. A nível do modelo computacional, a utilização baseia-se numa primitiva que permite criar uma região de memória partilhada e atribuir-lhe um nome lógico. A primitiva devolve o apontador no qual pode ser baseada a estrutura de dados de comunicação. Da posse do nome lógico, um outro processo pode requisitar o uso da região. A primitiva de associação devolve um apontador, que corresponde à mesma zona. Endereço = CriarRegião (Nome, Tamanho) Endereço = AssociarRegião (Nome) EliminarRegião (Endereço) Uma zona pode implementar a difusão. Para implementar qualquer dos outros modelos temos de recorrer aos mecanismos de sincronização. O mestre/escravo é “simples”: Mestre: - produzir informação - assinalar ao escravo - outras actividades - esperar resposta do escravo Escravo: - esperar sinal do mestre - processar a informação - enviar resposta - assinalar fim da operação Recorrendo a 2 semáforos privados, para garantir a sequência das acções: 6.3.2. Caixas de Correio Implica estrutura mais complexa. Para simplificar a vida dos programadores, os sistemas oferecem directamente objectos do tipo caixa de correio. Nas implementações mais vulgares, as caixas têm capacidade de memorização. Assim, o produtor só se bloqueia se caixa estiver cheia e o receptor bloqueia-se sempre que a caixa estiver vazia. A implementação mais vulgar das caixas de correio baseia-se na criação de um objecto controlado pelo sistema que efectua a sincronização e faz as transferências de informação.

Apesar de constituírem um mecanismo normalmente oferecido pelo sistema operativo, podemos facilmente programar uma caixa de correio com recurso a uma zona de memória partilhada e aos tradicionais mecanismos de sincronização. fig. 6.9. pg. 148 Normalmente a mensagem é especificada através de um apontador e de um parâmetro que indica o respectivo tamanho ou utiliza-se um caracter terminador para a delimitar. A implementação segue a sincronização mais habitual: o produtor bloqueia-se quando caixa cheia (semáforo Cheio) e o consumidor sempre que não existam mensagens (semáforo Vazio). No nosso modelo computacional vamos considerar a semântica mais vulgar (envio assíncrono e cap. de armazenamento), as primitivas são: IdCC = CriarCCorreio (Nome, Parâmetros) IdCC = AssociarCCorreio (Nome) EliminarCCorreio (IdCC) O nome da caixa deverá ser mantido nas tabelas do sistema de forma a permitir que outro processo se lhe associe. A fig. 6.10. (pg.150) ilustra a programação de um modelo cliente/servidor, usando caixas de correio. 6.3.3. Ligações Virtuais O modelo computacional de suporte ao diálogo necessita de primitivas que permitam estabelecer uma ligação virtual entre os processos. O processo servidor tem de declarar a sua disponibilidade de aceitar ligações registando o nome do canal e colocando-se à espera de pedidos de ligação. IdCanalServidor = CriarCanal (Nome) IdCanal = AceitarLigação (IdCanalServidor) A associação ao canal retorna o identificador do canal que fica dedicado ao par de processos. De forma recíproca, a primitiva AceitarLigação devolve o identificador do canal que ficou associado à comunicação. Do lado dos clientes o estabelecimento das ligações é mais simples, resumindo-se À primitiva: IdCanal = PedirLigação (Nome) Ver fig. 6.12. pg. 153 – Programação do Modelo de Diálogo

CAP. 7 – AS ENTRADAS / SAÍDAS 7.1. O Modelo das Entradas/Saídas A forma mais habitual de interacção de um programa com o ambiente que o rodeia é através das Entradas/Saídas. Uma operação de E/S pode ser visualizada como o estabelecimento de um canal com um periférico através do qual o processo recebe ou envia informação que pode ser gerada, armazenada ou afixada pelo periférico físico. Esta descrição esconde a grande complexidade, principalmente devido à grande diversidade de periféricos. As operações efectuadas pelos periféricos são muito variadas e até as suas características físicas da interacção. EX: - Unidade de Transferência de Informação (caracter, cadeias de caracteres, blocos de tamanho fixo) - Velocidade de Transferência – de alguns caracteres por segundo até 100 milhões de bits nas redes de fibra óptica - Representação dos Dados – Ascii, Ebcdic Esta diversidade foi responsável pelo facto das E/S serem a área pior estruturada dos SO iniciais (agregado de rotinas totalmente dependente do periférico físico). Hoje em dia os controladores encarregam-se de interactuar com os dispositivos físicos, apresentando uma interface mais clara com níveis de abstracção. Outra dificuldade do tratamento das E/S advém da necessidade de os processos acederem directamente ao hardware dos controladores e manipularem as interrupções que lhe estão associadas, devido aos mecanismos de protecção (utilizador) pelo que terão de ser feitos pelo núcleo ou processos com privilégios especiais. 7.1.1. Objectivos do Modelo de E/S A independência entre o modo como as operações de E/S são especificadas nos programas e a forma como são implementadas pelos periféricos físicos, começou a tornar-se importante em sistemas complexos e que pretendiam aceder a um conjunto de periféricos indiscriminadamente. A independência em relação aos periféricos permitia uma gestão mais eficiente dos recursos. Com a generalização dos sistemas de tempo partilhado isso tornou-se ainda mais importante (ex: visualizar antes de enviar para impressora). A noção que os periféricos deviam ser virtualizados tornou-se geral. É de salientar que independência na utilização de um periférico tem 2 vertentes: uma relacionada com a designação do periférico e outra com a uniformização das operações efectuadas. A designação é um problema semelhante ao da identificação directa ou indirecta dos canais. A uniformização de operações é mais complexa. Se bem que existam operações genéricas como ler ou escrever, os periféricos possuem funções de inicialização, controlo e leitura do estado que lhes são próprias. Contudo, apesar da diversidade é possível construir soluções baseadas em rotinas com parâmetros genéricos. Há grandes semelhanças com os mecanismos de comunicação, que até pode permitir a integração num modelo comum. Apesar disso, a redirecção das E/S só adquiriu uso generalizado no sistema Unix. 7.1.2. Elementos do Modelo O modelo de E/S procura garantir uma uniformidade de interacção e solucionar os problemas de protecção. A interacção com os periféricos é decomposta em 2 níveis: um próximo do utilizador, que apresenta uma interface de alto nível independente dos periféricos, e outro interno ao sistema, especializado conforme o tipo de dispositivo. Vamos basear o modelo em 3 entidades: - Periféricos Virtuais - Funções de E/S – um dos objectivos do modelo é reduzir e uniformizar o nº de operações diferentes. Podemos levar ao limite de uma operação apenas: uma rotina chamada EfectuaES, teria um conjunto de parâmetros que definem a operação e o periférico virtual. - Gestor de Periférico – elemento do modelo que efectua a interacção real com o periférico (device driver ou device handler). Assume frequentemente a forma de um processo independente. Pequeno ex. para ver como se articulam as diferentes entidades do modelo: A função de E/S é uma rotina sistema que inicia a operação de E/S enviando para o gestor de periférico a informação necessária para que este a possa executar. Normalmente há uma

mensagem normalizada (I/O Request Block – IORB). Os dados podem ser transferidos para tampões do sistema ou mantidos no espaço de endereçamento do processo até a operação se concluir. Podemos ainda considerar que o periférico é partilhado por diversos utilizadores. Então gestor tem uma caixa de correio para os IORBs. O gestor está normalmente bloqueado. O gestor poderia ficar a testar se a operação já tinha terminado, mas isso é pouco eficiente. O sistema é notificado que a operação terminou, através de uma interrupção. A rotina de int. efectua um processamento mínimo (não convém int. muito tempo bloqueadas) e assinala ao gestor o fim da operação. O esqueleto do modelo E/S está na fig. 7.1. (pg. 158). 7.2. O Modelo Computacional das E/S O periférico virtual pode ser visto como um canal de comunicação que é explicitamente estabelecido e sobre o qual são trocadas informação entre o processo utilizador e o gestor de periférico que na outra extremidade interactua com o periférico. Temos de analisar como na comunicação, como um processo se associa a um periférico e a sincronização implícita nas operações. 7.2.1. Associação ao Periférico Virtual Traduz-se pelo estabelecimento de um canal de comunicação entre o processo e o gestor de periférico. IdCanal = AbrirCanal (Nome) FecharCanal (IdCanal) Por exemplo, no Unix, os canais de E/S são logicamente integrados no sistema de ficheiros, pelo que são identificados como os ficheiros. Identificador é normalmente um inteiro; é local ao processo. 7.2.2. Função de E/S A rotina de E/S deve começar por validar os parâmetros independentes do periférico. Depois, a rotina prepara um IORB contendo os campos necessários à execução da operação de acordo com os parâmetros fornecidos na chamada. EfectuaES (IdCanal, Operação, EndereçoDados, Tamanho, Semáforo) A execução do gestor é iniciada transmitindo-lhe o IORB pelo canal criado. Há 2 modos de implementação do gestor que correspondem a 2 modos de o activar. Dependendo do tipo de sincronização associado à operação, o programa pode continuar ou bloquear-se no semáforo. Na interacção com os periféricos é vulgar considerar 2 tipos de interface para trx. de dados: - Sequência de caracteres de tamanho variável (bytestream) – memória de massa - Blocos de tamanho fixo – interactivos/de comunicação Sincronização O problema é idêntico ao do modelo de comunicação. Há um ganho significativo de desempenho se o processo apenas efectuar a transferência para uma estrutura de dados tampão interna ao núcleo, prosseguindo imediatamente sem esperar que os dados sejam efectivamente escritos no dispositivo físico. Isto na escrita. A leitura é naturalmente bloqueante. No cap. 9 veremos leituras assíncronas, para eliminar limitações de progaramação de alguns algoritmos. Funções de E/S Nas Linguagens de Programação As linguagens de alto nível incorporam no seu modelo computacional rotinas que permitem ao utilizador programar a interacção com os periféricos sem ter de se preocupar com problemas de sincronização, formatação, gestão de tampões, etc. Mas esta inclusão impõe um funcionamento rígido não permitindo, por vezes, programar as formas de interacção desejadas. Um exemplo típico é o read em Pascal que não lê uma cadeia de caracteres do terminal quando o programador o indica, só na linha completa, o que impede o controlo por setas. O C usa bibliotecas.

Leitura de um terminal Alfanumérico canal := AbrirCanal (‘TTY’); Sem := CriarSemáforo (0); while not Fim do begin EfectuaES (Canal, LER, Car, 1, Sem); Esperar (Sem); case Car do CR : ; LF : ; ESC : ; otherwise end; end; Entrada de um terminal numa linguagem de programação while not Fim do begin read (Car); case Car do CR : ; LF : ; ESC : ; otherwise ; end; end; 7.3. Gestores de Periférico 7.3.1. Funcionalidade A funcionalidade do gestor pode ser esquematizada de forma simples (fig. 7.4., pg. 162), apesar da sua implementação ser, normalmente, muito complexa. O gestor deve receber o IORB, validar os parâmetros dependentes do periférico e descodificar a função a executar pelo periférico. Esta informação deve permitir construir a sequência de comandos a enviar ao controlador. Pode ainda ter necessidade de transferir dados do espaço de endereçamento do utilizador para tampões próprios. Quando todas as tarefas de inicialização estão terminadas, a interrupção associada ao controlador deve ser desinibida e a operação de E/S desencadeada. Esta inicialização traduz-se muitas vezes por efectuar a 1ª operação que provoque uma interrupção. Ex. trx. série: o gestor escreve o 1º caracter e a rotina de int. encarrega-se de trx. o resto. A rotina de int. deve executar conjunto reduzido de operações, para não inibir int. muito tempo. Depois de realizar operações imprescindíveis, assinala ao gestor o final da operação para que este conclua o processamento. Quando a op. termina, o gestor avisa o processo da sua conclusão. Numa interface síncrona, esta acção corresponde a executar Assinalar sobre o semáforo (trx. no IORB). O DOS é bom exemplo. a interacção com os gestores de periféricos é feita por um bloco de controlo (request header – 13 campos fixos + área variável). Os gestores são carregados na inicialização do sistema (os que estiverem na lista do Config.Sys). 7.3.2. Processo Independente (grande flexibilidade) ou Função Sistema A prioridade do gestor deve ser elevada e os seus privilégios ultrapassar as protecções. A gestão de memória necessita de ter em conta as características especiais do processo, dado que este não pode ser retirado de memória quando espera informação de um periférico. Depois de indicado ao controlador de DMA o endereço para onde deve transferir os dados, este espaço de memória física não deve ser atribuído a outro processo.

Há sacrifício do desempenho, devido a comutação dos processos. Por isso alguns sistemas implementam-nos directamente no interior do núcleo. Nesta implementação a rotina de E/S, em vez de enviar uma mensagem ao processo gestor de periférico, chama uma rotina sistema que agulha para os procedimentos que implementam o gestor (fig. 7.5. pg.164). A rotina só retorna quando a E/S se conclui. Tal poderá ocorrer imediatamente, se a operação apenas exigir manipulação de dados do núcleo, ou depender da execução da operação pelo periférico. Neste caso a rotina do gestor inicializa do mesmo modo o controlador e indica ao gestor de processos que pode continuar com outro processo até que a rotina de interrupção lhe indique que a operação do periférico terminou. A vantagem desta solução é a maior simplicidade e redução de operações, dado que evita a comutação do processo. Contudo, a solução com um processo independente permite melhor isolamento das entidades que interactuam com a E/S. 7.3.3. Programação do Gestor de Periférico É de grande complexidade, situando-se aí todos os problemas de interacção com os controladores e com as restantes níveis do sistema. A própria eficiência das E/S depende muito do gestor. O esquema base decompõe-se em: - Descodificador de comandos (simples) - Rotinas de interacção com o controlador de hardware (simples) - Armazenamento temporário dos dados - Interacção com a memória virtual - A rotina de interrupção. Armazenamento Temporário dos Dados Nos periféricos de armazenamento de dados os gestores são escravos dos processo utilizadores, nos de comunicação não: têm de estar sempre prontos a receber dados sem aviso prévio. Têm de ter tampões, de tamanho geralmente variável. O dimensionamento e gestão dos tampões é uma das partes delicadas da programação de um gestor de periférico. A utilização de tampões alternados ou tampões circulares são as 2 técnicas mais vulgares. A primeira consiste simplesmente na utilização de 2 tampões comutando de um para outro quando um se encontra preenchido. Tem limitações óbvias a situações de débito pequeno ou controlado. A 2ª é melhor para recepção de informação de tamanho variável. O dimensionamento obedece a uma análise estatística do tráfego previsível. A maioria dos protocolos permite parar quando cheio. A regulação do fluxo pode ser tratada pelo gestor mas é normalmente relegada para software de nível mais elevado. ex. do 1º caso são o controlo do fluxo de terminais assíncronos (teclado), e do 2º gestor de rede local Ethernet, que ignora pacotes depois de cheio cabendo a software de camadas superiores recuperar a situação. Interacção Com a Gestão de Memória É fundamental que as páginas envolvidas na transferência de informação não sejam retiradas ao processo depois de se iniciar uma operação de E/S. Para tal o gestor deve assinalar à gestão de memória que determinadas páginas devem ficar fixas (wired) durante a operação de E/S. Rotina de Interrupção Tem de ser programada com relativo cuidado porque qualquer erro é fatal para funcionamento global do sistema. A sua estrutura típica é mais ou menos rígida: - Salvaguarda do contexto hardware que poderá ser modificado pela rotina e que não foi automaticamente salvaguardado pelo processador - Bloquear as interrupções que possam interferir com o funcionamento da rotina - Determinar a causa da interrupção - Executar as operações necessárias - assinalar ao gestor o respectivo acontecimento - repor a situação das interrupções - restaurar o contexto

É interessante realçar que na arquitectura do 80286 já foram incluídos no hardware mecanismos dedicados à automatização do arranque dos gestores de periférico. Estas tarefas especiais (interrupt tasks) bloqueiam-se à espera de interrupções e o hardware efectua automaticamente a comutação. 7.4. Partilha de Periféricos Alguns periféricos são utilizados exclusivamente por um processo. Por ex. unidade de banda magnética. Outros são partilhados de forma indirecta, como os discos. Há ainda outros que apesar de obrigarem a utilização exclusiva não necessita de ser atribuídos a um processo, como por ex. as impressoras. Neste caso a solução é efectuar a E/S de forma indirecta. Em vez da rotina de E/S interactuar directamente com o gestor da impressora, comunica com um processo servidor que controla o gestor. O processo servidor interactua com os restantes processos utilizando os mecanismos de comunicação habituais e implementa gestão própria com prioridades, etc. Esta partilha indirecta chama-se spooling e usa-se nas impressoras e plotters.

CAP. 8 – SISTEMA DE FICHEIROS 8.1. Introdução volátil vs. persistente Neste capítulo vamos abordar os aspectos mais importantes dos sistemas de ficheiros. A exposição está dividida em 3 partes: - Modelo Computacional: princípios gerais da organização do SF e primitivas de acesso - Estrutura Interna do Sistema de Ficheiros: organização da informação em disco - Controlo de Dispositivos de Memória Secundária: características físicas dos dispositivos, optimização dos tempos de leitura e escrita e sua relação com a organização dos blocos em disco e com a utilização das primitivas de acesso a ficheiros. 8.2. Modelo Computacional 8.2.1. Entidades Fundamentais Um ficheiro é uma colecção de dados persistentes relacionados, identificados por um nome. Podemos decompor um ficheiro em 3 componentes: - Nome: identifica o ficheiro perante o utilizador - Descritor de Ficheiro: estrutura de dados em memória secundária com informação sobre o ficheiro (dimensão, datas de criação, modificação e acesso, dono, autorizações de acesso) - Informação: dados guardados em disco Os nomes dos ficheiros são catalogados em directórios, que estabelecem a associação entre o nome e o descritor do ficheiro. O directório pode conter os descritores dos ficheiros ou apenas os identificadores desses descritores. Neste caso, nomes diferentes podem corresponder ao mesmo descritor e, consequentemente, ao mesmo ficheiro. Um sistema de ficheiros é um conjunto de ficheiros, directórios, descritores de ficheiros e estruturas de dados auxiliares, autónomos em termos de administração e suporte físico. Em geral cada SF reside num único disco. SF é também o módulo do SO responsável pelo acesso e organização dos ficheiros. Por questões de desempenho, o acesso aos ficheiros é feito em 3 etapas: - Abertura do ficheiro, dado o nome: o sistema pesquisa o directório, copia o seu descritor para memória e guarda-o numa entrada da tabela de ficheiros abertos, cuja referência, o identificador de ficheiro aberto, é devolvida ao utilizador. - Leituras e escritas, dado o identificador de ficheiro aberto: este permite obter rapidamente a cópia do descritor do ficheiro em memória, onde está toda a informação necessária para aceder aos dados - Fecho do ficheiro: operação necessária para libertar a entrada na tabela de ficheiros abertos e actualizar o descritor de ficheiro em disco, caso tenha sido modificado. 8.2.2. Organização dos Nomes dos Ficheiros Hierarquia de Nomes A organização dos nomes é de extrema importância para o utilizador pois é a face visível externamente. A forma mais simples de organizar os nomes é dar um nome a cada ficheiro, que se guardam num directório único (primeiros computadores em tratamento por lotes). Contudo levanta o problema da colisão de nomes. Uma primeira alteração foi atribuir um directório a cada utilizador. No entanto, utilizadores com muitos ficheiros não os podia organizar de forma lógica. O Multics foi mo primeiro a propor organização hierárquica na forma de árvore invertida: Começase pela raiz, os ficheiros são sempre folhas. O nome de um ficheiro, também chamado caminho de acesso (pathname), é uma cadeia de caracteres que permite localizar o ficheiro na árvore. Nomes Absolutos e Nomes Relativos O caminho de acesso desde a raiz é chamado nome absoluto. Em Unix, por ex. /a/b/c/d No entanto, ter de escrever sempre o nome completo é fastidioso e pouco flexível (ex. referência a ficheiro em programa, não podia depois mudar de directório). Por este motivo o SF suporta a noção de directório corrente, podendo o ficheiro ser referenciado pelo nome relativo. Uniformidade dos Nomes

O Unix tem um espaço de nomes uniforme. Ficheiros, directórios, dispositivos são referenciados usando exactamente a mesma sintaxe. Um dispositivo contendo um SF pode ser montado (primitivas mount e unmount) em qualquer directório. Os dispositivos são acedidos por intermédio de ficheiros especiais agrupados no directório /dev Esta uniformidade é um dos pontos fortes do Unix, pois permite uma grande flexibilidade e coerência de utilização na redirecção das E/S. O MS/DOS tem um espaço uniforme para os ficheiros e directórios mas os dispositivos têm de ser referenciados explicitamente. ex: a:\b\c\d ou c:\a\b\c\d O VMS não tem uniformidade de nomes: um ficheiro é composto por 3 partes: nome de dispositivo, cadeia de directórios e nome do ficheiro. Ex: a:[b.c]d Nomes e Extensões É habitual os nomes terem extensões (são convenções) que dão alguma informação sobre o conteúdo dos ficheiros. Alguns SO, como o Unix, não atribuem qualquer significado às extensões e aos nomes, pelo que não há restrições. No VMS a extensão é obrigatória. 8.2.3. Tipos de Ficheiros O tipo está relacionado com o seu conteúdo e forma de acesso. Um ficheiro é composto por um conjunto de registos de determinado tipo. O método de acesso pode ser sequencial (+ simples – bandas magnéticas), directo ou por chave. Os ficheiros de acesso directo são idênticos aos sequenciais mas pode posicionar-se em qualquer registo sem ter de percorrer os anteriores. Não se pode é inserir registos entre outros 2. Discos magnéticos. O método por chave permite inserir registos entre 2, pois a organização interna destes ficheiros é em árvore (ISAM – Indexed Sequential Access Method) – computadores de grande porte. O tipo de registos guardados num ficheiro pode ser de dimensão fixa ou variável. No VMS por ex. um ficheiro de texto produzido pelo editor é constituído por registos de comprimento variável, contendo cada registo uma linha. Os resultantes de compilações são de dimensão fixa. Um caso particular muito importante são os registos tipo caracter pois com eles pode-se construir qualquer outro tipo. A interpretação do conteúdo dos ficheiros é da responsabilidade dos programas que lidam com eles (ex. editor de texto sabe que caracter carriage return (13 em Ascii) significa fim de linha. O Unix suporta apenas este tipo de ficheiros. 8.2.4. Protecção Assume no SF uma importância fundamental, pois os ficheiros constituem as entidades mais valiosas num computador e o acesso a pessoas não autorizadas significa catástrofe. Grande parte dos mecanismos de protecção que falámos (semáforos, caixas de correio) estão relacionados, por vezes, mesmo baseadas na protecção do SF. O mecanismo de base é o seguinte: quando um ficheiro é criado, o SO guarda o número de utilizador que o criou e os direitos de acesso no descritor do ficheiro. As primitivas do SF acedem ao descritor e verificam se o utilizador que as efectua tem os direitos de acesso necessários. Os identificadores (utilizador e dono) são geridos pelo SO.A verificação é feita na cópia, abertura e remoção, pesquisa no directório, pelo que na leitura e escrita já não é necessário. Na criação de processos também é feito teste. Existem várias formas de especificar os direitos de acesso: o mais intuitivo é associar a cada ficheiro uma lista de acesso, especificando para cada utilizador, as operações permitidas (Multics). Uma simplificação usada no Unix e no VMS consiste em agrupar utilizadores em grupos e especificar apenas a protecção para cada grupo. Há 3 categorias de utilizadores: dono do ficheiro (owner), os utilizadores pertencentes ao mesmo grupo (group) e os restantes (world). É menos potente mas eficiente pois apenas precisa de uns bits. 8.2.5. Sistema de Ficheiros e Entradas/Saídas Todos os SO procuram oferecer o acesso transparente aos dispositivos, de forma a que os programas funcionem em periféricos diferentes sem ser preciso alterá-los. Podemos considerar que um ficheiro é um dispositivo virtual, sendo as primitivas de acesso as mesmas que

especificámos para as E/S. No entanto o aceso a ficheiros é mais frequente e complexo, sendo necessário dispor de primitivas especiais (ex. apagar). Existem 2 formas de considerar o problema: - O SF situa-se num nível acima das E/S implementado estas os mecanismos básicos de acesso aos periféricos utilizados pelo SF para aceder ao disco. O SF usa “serviços” do E/S. VMS - As E/S estão ao mesmo nível que o SF. Os ficheiros são vistos como dispositivos virtuais e são acedidos com o mesmo tipo de funções (Unix). As primitivas E/S são mais complicadas que as que demos no cap. anterior mas tem a vantagem de servir também para o SF. Esta opção é mais transparente. 8.2.6. Primitivas do Sistema de Ficheiros A maioria dos programas utiliza os mecanismos das linguagens de programação (instruções ou bibliotecas próprias que implementam modelo computacional próprio) para aceder ao SF. Não utilizam directamente as do núcleo mas elas existem. Abertura, Criação e Fecho de Ficheiros Fd := Abrir (Nome, Modo) Fd := Criar (Nome, Protecção) Fechar (Fd) O modo é leitura, escrita ou ambos. O SO verifica se o utilizador tem permissão para o modo especificado. Operações Sobre Ficheiros Abertos Ler (Fd, Tampão, NumOctetos) Escrever (Fd, Tampão, NumOctetos) Posicionart (Fd, Posição) As 2 primeiras operam sobre o descritor de ficheiro, anteriormente obtido com Abrir ou Criar. Os mais simples (sequenciais) para os quais o SO mantém a posição actual numa variável chamada apontador de ficheiro (file pointer). Os outros há posicionar para acesso a qualquer registo. Operações Complexas Sobre Ficheiros Copiar (Origem, Destin) Mover (Origem, Destino) Apagar (Nome) LerAtributos (Nome, Tampão) EscreverAtributos (Nome, Atributos) Mover apenas se lhe muda o nome. Um ficheiro quando é criado é criado com um conjunto de atributos: - Protecção - Identificação do dono - Dimensão - Data de criação, última leitura e última escrita - Tipo do Ficheiro Operações Sobre Directórios ListaDir (Nome, Tampão) MudaDir (Nome) CriarDir (Nome, Protecção) Mesmo no Unix existe CriaDir. Não tem ListaDir mas sim um utilitário que abre o ficheiro directório e lê o seu conteúdo. No VMS a 1ª chamada devolve identificador; as seguintes devolvem as entradas seguintes do directório. Certos sistemas, como o Unix tem um número reduzido de chamadas sistema, sobre as quais é possível construir utilitários. 8.3. Estrutura Interna do Sistema de Ficheiros

O identificador de ficheiro aberto é usado pelo SO para detectar se a operação se destina a um ficheiro ou a um dispositivo. Se for uma E/S é chamado o gestor de periférico, se for acesso a ficheiro é chamada a gestão de ficheiros. O diferente tratamento deve-se a: - A organização do sistema de ficheiros é, geralmente, muito complexa - O tipo de dispositivos usados é diferente (E/S – disp. tipo caracter; disp. mem. sec. são tipo bloco - O sistema de ficheiros necessita de optimizações específicas, pois o nº de acessos grande implica que desempenho é vital. A este nível iremos considerar o disco como um vector de blocos, que podem ser lidos ou escritos dado o seu número de bloco. 8.3.1. Dispositivos Lógicos Um dispositivo físico (disco, disquete, etc.) é habitualmente subdividido em segmentos contíguos chamados dispositivos lógicos, partições ou volumes. O SO gere o acesso aos dispositivos lógicos. A tradução do nº de bloco lógico em físico é feita pelo gestor do periférico por soma do nº de bloco lógico com o bloco físico do início da partição. Cada partição contém um conjunto de informação totalmente independente das outras. 8.3.2. Directórios Conceptualmente, um directório é apenas uma tabela que associa nomes a descritores de ficheiros. Em certos SO o descritor está dentro do directório, noutros este contém um apontador interno para o descritor (onde está o nº dos blocos onde estão os dados). Neste caso é possível haver entradas diferentes (no mesmo ou noutro directório) que apontem para o mesmo descritor, o que significa que o ficheiro pode ser acedido por vários caminhos --> flexibilidade. 8.3.3. Ficheiros Um ficheiro é representado pelo seu descritor, que contém toda a informação, execpto o seu nome e os dados propriamente ditos. A informação é lida e escrita em sectores (habitualmente de 512 ou 1024 octetos). A gestão do disco é feita em blocos de dimensão múltipla dos sectores. Em máquinas de memória paginada, cada bloco é geralmente da dimensão das páginas. A unidade de alocação de zonas de disco é múltipla dos blocos – segmento (extent). Os segmentos pertencentes ao ficheiro são mantidos no seu descritor. A política de alocação tenta manter os ficheiros contíguos em disco. Os blocos livres são mantidos numa estrutura de dados (simples lista ligada ou dividindo por cilindros) em memória secundária. Tudo é mantido no chamado superbloco (replicado noutros blocos por segurança). 8.4. Controlo de Dispositivos de Memória Secundária Configuração física e técnicas de optimização dos acessos. Cada operação de leitura ou escrita é composta pelas seguintes fases: - A gestão de ficheiros coloca o pedido de E/S na fila de pedidos pendentes para o dispositivo - O gestor de periférico obtém um pedido da fila, programa o controlador do dispositivo para iniciar a operação e espera pela interrupção que indica o seu fim - Quando a interrupção ocorre, o gestor do periférico devolve o pedido ao processo que o requereu e, se houver mais pedidos pendentes, repete. Os controladores de dispositivos de memória secundária são actualmente muito sofisticados, residindo numa carta ligada ao processador central por um bus específico (ex. SCSI). Uma vez programado para executar fá-lo autonomamente libertando o processador (leitura e escrita de sectores, formatação, configuração de pistas, etc.). 8.4.1. Tipos de Dispositivos Discos Magnéticos Os mais usados actualmente. Composto por um conjunto de pratos sobrepostos. Em cada prato a informação é escrita em pistas concêntricas. Cada pista é formada por um nº de sectores. Um cilindro é composto pelo conjunto das pistas com o mesmo raio.

As cabeças deslocam-se segundo o raio do dos pratos, accionadas por um braço único. Capacidade de uma pista = Nº de sectores X Nº de octetos por sector Capacidade de um cilindro = Nº de pistas por cilindro X Capacidade de uma pista Capacidade de um disco = Nº de cilindros X capacidade de um cilindro O tempo de leitura ou escrita de um sector é composto por: - Tempo de posicionamento (seek time): das cabeças no cilindro. Está relacionado com o movimento mecânico das cabeças e é lento; depende do tempo de arranque (constante) e do nº de cilindros percorridos. A distância média é 1/3 do nº de cilindros, sendo este o valor usado para calcular o tempo médio de posicionamento. - Tempo de Latência: Tempo de espera pelo sector. Para rotação a 3600 rpm, o valor máximo é 16,7 ms. O valor médio é metade. - Tempo de Transferência: de um sector. dado pelo tempo de uma revolução completa dividido pelo nº de sectores por pista. A grandeza mais utilizada para caracterizar a velocidade de um disco é o tempo médio de acesso (soma dos 3 médios). Disquetes São semelhantes a um disco só com um prato. A sua velocidade de acesso é muito menor, bem como o custo. Discos Ópticos Têm pista em espiral. Actualmente (1989) são tipo WORM. baixo custo. Têm maior capacidade e menor velocidade que os magnéticos (160ms) Discos Magneto-Ópticos Nova tecnologia. A informação é escrita com uma combinação de polarização magnética e raio laser, sendo a leitura óptica. A vantagem é que podem ser apagados explicitamente, não suportando reescrita simples para apagar. 60 a 150 ms com leituras mais rápidas 3 vezes que escritas. Bandas Magnéticas Sequencial. 9 pistas longitudinais (octeto mais bit de paridade) em cada fatia (frame). as fatias são agrupadas em blocos separados por gaps. 2400 pés São caracterizadas pelos seguintes parâmetros: - Densidade: nº de bits por polegada (800, 1600, 6150 ou até 30000) - Velocidade: nº de polegadas por segundo (30 a 200) - Dimensão dos Gaps: 0,3 a 0,75 polegadas O factor de blocagem (escolha da dimensão dos blocos é muito importante para bom aproveitamento pois os gaps são grandes. Usadas para backups. 8.4.2. Optimização dos Acessos a Disco Constituem o suporte habitual do sistema de ficheiros. - O tempo de acesso a disco é sempre muito maior que o tempo de acesso a memória central, pelo que o sistema de ficheiros justifica algoritmo complexo para evitar acessos a disco. - O tempo de posicionamento é o factor determinante, pelo que a sua minimização é crucial. - Devem organizar-se os sectores de forma a ler o maior nº possível por revolução. 8.4.3. cache de Blocos de Disco As linguagens de programação (rotinas) realizam sempre o acesso bloco a bloco, pelo que os pedidos devem ser resolvidos no bloco em cache, quando este fica totalmente preenchido é escrito em disco e lido o bloco seguinte. O Unix optimiza no SO mantendo em disco uma cache com os últimos blocos lidos. Periodicamente (30s) os blocos modificados são actualizados em disco. Na cache estão também o directórios que contêm os utilitários de sistema. Em contrapartida a cache implica mais uma cópia e não garante que a escrita seja mesmo feita quando pedida, o que pode ser mau para sistemas sem falhas (bases de dados)

8.4.4. Optimização do Tempo de Posicionamento Há 2 níveis: organização da informação em disco e forma a concentrar dados logicamente relacionados e optimização dos movimentos das cabeças. O segundo nível consiste em ordenar os pedidos de acesso segundo a ordem mais favorável: - Ordem de Chegada: É simples e justo mas não optimiza operações mecânicas - Menor Deslocamento: o primeiro a ser servido é o que está mais próximo do actual. Aumenta o desempenho mas pode tornar-se injusto para os cilindros das extremidades. - Elevador: é semelhante ao anterior mas aplica-se só aos pedidos situados no sentido actual de deslocamento. Visita menos os cilindros das periferias mas a discriminação não é grande, sendo um dos métodos mais usados pelos SO reais. - Elevador Circular: É variante do anterior. Os pedidos aqui só são servidos num sentido, o outro serve apenas para movimentar as cabeças. Elimina a discriminação do anterior. O ideal com cargas baixas é o elevador e com cargas elevadas é o elevador circular, comportando-se este último bem em todas as situações de carga. 8.4.5. Optimização do Tempo de Latência O factor que se procura optimizar é o número de sectores da mesma pista que se consegue ler durante uma única rotação do disco. Acessos ao disco são em bloco mas devido a ter de interromper sector a sector e o disco continuar a rodar só se lê um sector por cada revolução caso estejam em sequência. A técnica mais usada é ordenar os sectores duma pista segundo um factor de entrelaçamento (interleave). Actualmente a técnica mais usada pelos controladores é ler uma pista completa para uma cache (64KB) sendo desnecessário factor de entrelaçamento.

CAP. 9 – ACONTECIMENTOS ASSÍNCRONOS E EXCEPÇÕES 9.1. Acontecimentos Assíncronos No modelo que temos vindo a considerar, um processo pode sincronizar-se com acontecimentos exteriores, quer provenientes das E/S quer da comunicação através de mecanismos de sincronização. De um modo geral, quando um processo pretende conhecer que um dado acontecimento se produziu, bloqueia-se num semáforo, cujo posicionamento corresponderá à notificação do acontecimento esperado. Este método tem limitação óbvia: o processo apenas pode esperar um acontecimento de cada vez e não múltiplos ou raros. O tratamento das temporizações ilustra a limitação. Supondo que processo quer fazer uma acção ao fim de um certo tempo. Adormecer não é solução pois o processo quer continuar a executar-se e a temporização funcionar como alarme (timeout). Ex. interacção cliente/servidor, cliente quer continuar e continuar à espera da resposta por um período. Outro ex. é consulta a base de dados por utilizadores e outros processos. Temos pois um problema clássico em programação sistema: decidir em qual dos dois possíveis pontos de sincronização se deve o processo bloquear. - Teste Periódico: Em vez de efectuar leituras bloqueantes, o processo testa apenas se existe informação na caixa de correio (espera activa) - Utilizar 2 Processos: Um efectua a leitura do terminal outro o tratamento das caixas de correio. Evita a espera activa e degradação do desempenho mas coloca outro problema: os processos não são independentes e precisam comunicar. É de eliminar as caixas de correio pois colocaria outra vez o problema dos dois pontos de sincronização entre os quais não se pode estabelecer prioridade. Há mecanismos próprios. A situação anterior coloca-se sempre que as acções que condicionam a execução de parte do programa são assíncronas não sendo possível o seu encadeamento de forma determinista. Necessitamos, portanto de um mecanismo sistema que permita lançar a execução concorrente de rotinas no interior do espaço de endereçamento de um processo. São vulgarmente consideradas duas extensões ao modelo computacional para tratamento dos acontecimentos assíncronos. - Permitir a criação de actividades independentes no interior de um processo. - Associar a determinados acontecimentos rotinas específicas que se executam de forma assíncrona, quando o acontecimento de produz. 9.2. Modelo Multitarefa Os sistemas multitarefa podem criar, no contexto de um processo, um ambiente de programação concorrente. Light Weight Processes ou Threads. O modelo multitarefa pode ser encarado como uma reencarnação , no contexto de um processo, do modelo computacional. O modelo dedica a cada acontecimento uma tarefa. O seu interesse extravasa a resolução do problema dos acontecimentos assíncronos. As tarefas tornam-se uma ferramenta de programação que permite explorar o paralelismo dos algoritmos e fornece mecanismos para encapsular o tratamento de acções independentes no interior de um programa. Existem 2 implementações diferentes dos ambientes multitarefa, dependendo da forma como é efectuado o escalonamento. A gestão de tarefas pode ser implementada nível do núcleo ou totalmente no espaço de endereçamento. A 1ª solução o núcleo efectua a multiplexagem do processador; na 2ª a comutação entre tarefas tem de ser explicitamente programada pelo programador ou em bibliotecas que efectuam a comutação quando uma das tarefas desencadeia uma operação bloqueante (ex. um E/S). As primeiras são tarefas reais a segundas corotinas. SO distribuídos implementam um modelo multitarefa, que flexibiliza a programação. As características prinicipais são: - As tarefas são processos simplificados, cuja criação não envolve a execução de funções sistema complexas - As tarefas são criadas no contexto do processo partilhando o mesmo espaço de endereçamento - As tarefas são da responsabilidade interna do processo, pelo que o sistema não as gere com o mesmo grau de complexidade dos processos (protecção, escalonamento e gestão de memória) - Os mecanismos de sincronização internos permitem implementar algoritmos de cooperação e gestão de recursos entre as tarefas. Primitivas

IdTarefa = CriarTarefa (Procedimento) EliminarTarefa (IdTarefa) fig. 9.3. – pg.197 – Programação num sistema Multitarefa (interacção com terminal e recepção de mensagens de outros utilizadores). As tarefas RecebeMensagem e TrataComando executam-se concorrentemente com o fluxo inicial que constitui o corpo do programa. O programador terá de definir a forma como as tarefas se cincronizam e como gerem as variáveis comuns. 9.3. Rotinas Assíncronas Constituem um mecanismo menos ambicioso mas fornecem solução eficiente. A ideia é associar a um acontecimento uma rotina que se executará assincronamente quando o acontecimento se produzir. Os acontecimentos podem ser definidos pelo sistema ou pelo programador (cap. 11 e 13) RotinaAssíncrona (Acontecimento, Procedimento) O mecanismo de lançamento da rotina é complexo. Apesar de transparente para o utilizador, o núcleo tem de efectuar operações delicadas no contexto do processo. Na fig. 9.5 está a programação do problema anterior segundo esta nova forma. Este modelo assemelha-se ao tratamento das interrupções. Contudo a semelhança é puramente funcional. As rotinas de interrupção não permitem a execução de código do utilizador, são um dos mecanismos mais internos do sistema. Segue-se programação em modelo multitarefa e rotinas assíncronas do controlador de tarefas apresentado no cap. 2 9.4. Excepções São violações do modelo computacional que obrigam a recuperar o estado da máquina virtual, quer através de procedimentos especificados pelo programador quer por acção prevista por defeito no sistema, normalmente a terminação do processo. Embora sejam também acontecimentos não previstos são diferentes dos assíncronos pois não são provocados por elementos externos, que podem ser outros processos, gestores de periféricos ou serviços como as temporizações; são provocadas pelo programa quando executa uma acção não prevista no modelo computacional. As causas mais vulgares podem agrupar-se em: - Acesso ilegal à memória: endereço inválido, tentativa de violação da protecção - Instrução ilícita - Aritméticas: overflow, underflow, divisão por zero - Emulação - Teste: para debuggers - Provocadas pela execução de funções sistema É importante distinguir 3 conceitos semelhantes mas que têm consideráveis diferenças a nível dos mecanismos que os controlam: - Interrupções globais a toda a máquina: É o núcleo do sistema que assume o controlo - Excepções sistema relacionadas apenas com o processo que as provoca, devendo, portanto, ser tratadas no seu contexto. - Excepções no algoritmo do programa do utilizador Nesta secção apenas nos preocupamos com as sistema. O sistema tem um tratamento por omissão associado às excepções que, geralmente, faz terminar o processo. Sendo isso indesejável na maioria das aplicações (excepto emulação e teste). Nos SO tradicionais as excepções foram tratadas de forma não muito cuidada nem uniforme. Alguns sistemas introduziram a noção de tratamento de excepções (exception handler) definindo a forma como se podia associar uma rotina a um acontecimento. A sua modificação constitui a capacidade de tratamento de excepções por parte do utilizador, mas a sua programação é delicada, porque terá de interactuar com a pilha do processo ou com os registos do processador para recuperar a situação de erro. No Unix a associação é feita da mesma forma que a de um acontecimento assíncrono a um tratamento.

Vamos considerar que um programa podia associar uma rotina através de uma função Excepção. Por ex. divisão por zero. Neste caso devíamos ter um acontecimento detectável pelo sistema: divisão por zero (DivZero) de modo a poder efectuar a seguinte chamada: Excepção(DivZero, TrataDivZero) Isto ganha importância no âmbito dos sistemas distribuídos, sendo que poucos SO dão facilidades de programação destas rotinas de tratamento.

CAP. 10 – NÚCLEO PARA SISTEMAS INTEGRADOS 10.1 Os Sistemas Integrados O 1º objectivo deste cap. é a apresentação de um pequeno núcleo de SO, disponível comercialmente, que implementa um modelo computacional semelhante ao descrito anteriormente. Uma 2ª razão é a importância deste tipo de sistemas no desenvolvimento de aplicações baseadas no microprocessador. Embedded Systems – Sistemas Integrados. Ex. controlo de máquina ferramenta, central de comutação telefónica, controlo de avião. O desenvolvimento das aplicações pode ser feito com base no controlo directo das interrupções, tentando responder às solicitações exteriores, ou utilizar um SO que virtualize a máquina física. A 1ª origina programação complexa, tempos elevados de testes e dificuldades de manutenção. A 2ª fornece ao programador um conjunto de abstracções que permite ignorar detalhes do hardware, basear-se no modelo computacional documentado, melhorar segurança e fiabilidade, usar ferramentas de teste e afinação e aumentar portabilidade. Há vários fornecedores de núcleos (camadas inferiores apenas) multitarefa, sendo que todos apresentam os mecanismos básicos de gestão de processos, sincronização, gestão de memória e comunicação. Fornecem também mecanismo para uma gestão em tempo real pois o espectro das aplicações é mais exigente a este nível. Por outro lado depois de desenvolvido o sistema só responde a um conjunto de situações típicas. A tónica na simplicidade para caberem em memórias de 8 a 12koctetos (EPROM). O desenvolvimento das aplicações é feito também de forma diferente dos SO tradicionais, dado que normalmente não são capazes de suportar a interacção com utilizadores. As aplicações são portanto desenvolvidas em sistemas hospedeiros utilizando compiladores cruzados. As bibliotecas de interface do sistema são ligadas com o código das aplicações e o conjunto é carregado no sistema alvo para teste e afinação. Vamos ver o RMX-EX 86 da Intel 10.2. Modelo Computacional do Núcleo RMX/EX As funções essenciais asseguradas por este pequeno núcleo são: - Gestão da concorrência - Sincronização entre tarefas - Comunicação entre tarefas - Tratamento das interrupções Os objectos sistema manipulados pelo modelo computacional são os clássicos: - Tarefas - Segmentos - Semáforos - Regiões - Mailboxes, uma extensão dos semáforos com inclusão de mecanismos para transferência de mensagens - Queues, caixas de correio com interface do tipo sequência de caracteres. Quando da criação de um objecto é-lhe atribuído identificador (token – 16 bits), que lhe permite ser identificado nas funções sitema 10.3. Tarefas Segue política preemptiva e não implementa quantum. A tarefa só abandona o processador quando se bloqueia, auto-suspende ou outra mais prioritária se torna executável. Isto está de acordo com a filosofia tempo-real, para além de simplificar o núcleo. Contudo esta não existência de gestão em tempo partilhadodesvirtua algumas das premissas do modelo computacional concorrente, obrigando a análise profunda dos algoritmos para que uma tarefa não monopolize o processador. Por outro lado simplifica a sincronização entre tarefas pois permite ao programador contrlo determinista da comutação. 10.3.1. Estados das Tarefas Execução (running) Executável (ready) bloqueada (asleep) suspensa (suspended)

asleep-suspended 10.3.2. Prioridades São fixas, o que é habitual nos sistemas de tempo-real. É preciso muito cuidado na sua atribuição pois uma mais prioritária, se não se bloquear pode monopolizar. Varia entre 0 8mais alta) e 255. 10.3.3. Protecção Como o 8086 não tem qualquer mecanismo de protecção hardware, não há distinção entre modo sistema e utilizador, o que obriga a que as aplicações tenham fiabilidade muito elevada, para não escreverem em posições de memória do sistema. Cabe também ao programador definir as interacções entre as tarefas. 10.3.4. Criação e Terminação das Tarefas A simplicidade do núcleo é patente aqui. O RMX/EX pressupõe que o código das tarefas já existe em memória, necessitando apenas de um apontador para a 1ª instrução. IdTarefa = CREATE_TASDK (Prioridade, EndInício, SegDados, SegPilha, TamPilha, Flags, PtrCodRot) PtrCodRet é um apontador para uma palavra onde é assinalado o sucesso o suceso ou insucesso da função através de um código de erro (genérico para todas as primitivas do RMX/EX) DELETE_TASK (IdTarefa, PtrCodRot) 10.4. Gestão de Memória É segmentada, embora não pura, dispondo apenas de registos que definem a base dos segmentos de código, dados e pilha correntes, para além de um segmento genérico (extra). Não existe portanto uma tabela de segmentos associados a cada tarefa com definido no cap.4. A gestão é pois de grande simplicidade, limitando-se a gerir uma região de memória que pode ser alocada sob a forma de segmentos pelas tarefas. Têm dimensão entre 16 e 64Koctetos sendo geridos em bloco. A zona de memória onde os segmentos se localizam é definida na geração do sistema, controlando o núcleo a sua aquisição e libertação. IdSegmento = CREATE_SEGMENT (Tamanho, PtrCodRet) DELETE_SEGMENT (IdSegemento, PtrCodRet) 10.5. Sincronização 10.5.1. Suspensão A mais simples de sincronização é a suspensão a directa. Como não há mecanismos de protecção, qualquer tarefa pode ser suspensa desde que se conheça o token (se for 0 é uma auto-suspensão) SUSPEND_TASK (IdTarefa, CodPtrRet) RESUME_TASK (IdTarefa, CodPtrRet) Podem ser suspensa várias vezes, mantendo grau de supensão SLEEP (LimTempo, CodPtrRet) Se LimTempo = 0 é só colocada no fim da lista. 10.5.2. Semáforos A diferença mais significativa em relação aos semáforos dados no cap. 2 é a possibilidade de enviar ou receber um número qualquer de unidades. Foram pensados para gestão de recursos e cooperação entre processos, existindo um outro mecanismo para a implementação da exclusão mútua. Assinalar: SEND_UNITS (IdSemaforo, Unidades, PtrCodRet) Esperar: UniRest = RECEIVE_UNITS (IdSemaforo, Unidades, LimTempo, PtrCodRet) Se a tarefa não consegue obter o nº de unidades especificado, fica bloqueada. Se não existirem unidades para servir o primeiro pedido, os pedidos seguintes não serão atendidos, mesmo que existam unidades suficientes para o atender. A saída por temporização permite desencadear alarmes, quando determinados acontecimentos não são assinalados no intervalo de tempo. IdSemaforo = CREATE_SEMAPHORE (ValorInicial, ValorMax, Flags, PtrCodRet)

DELETE_SEMAPHORE (IdSemaforo, PtrCodRet) Flags determina a forma de gestão da fila de espera: FIFO ou baseada na prioridade das tarefas. 10.5.3. Regiões Para exclusão mútua no acesso a variáveis partilhadas. É mais simples que o semáforo. Uma primitiva permite adquirir o direito de aceder à região em exclusão mútua, existindo a recíproca libertação IdRegião = CREATE_REGION (Flags, PtrCodRet) DELETE_REGION (IdRegião, PtrCodRet) 3 funções permitem efectuar o controlo da região. para além das funções óbvias, bloquear e libertar o acesso a uma região, existe a primitiva ACCEPT que apenas adquire o direito à região quando ela está livre, evitando portanto o bloqueio da tarefa. RECEIVE_CONTROL (IdRegião, PtrCodRet) SEND_CONTROL (PtrCodRet) ACCEPT_CONTROL (IdRegião, PtrCodRet) O núcleo mantém a lista das regiões que uma dada tarefa controla. Quando a tarefa executa SEND_CONTROL, é a última região que é libertada. No caso de a tarefa ser eliminada, são libertadas todas as regiões que detinha. Minimiza interblocagens, em relação aos semáforos, além de semântica mais simples. Fig. 10.2 – Distribuidor de Memória em C do RMX/EX 10.6 Comunicação Duas tarefas que tenham acesso ao mesmo segmento podem utilizá-lo para transferir informação, sincronizando-se, se for necessário, por intermédio de regiões ou semáforos. 10.6.1 Semáforos com Mensagens (Mailboxes) – modelo de correio A comunicação é tratada como uma extensão dos semáforos. Basicamente altera-se o significado da rotina assinalar para que esta passe não só a sinalizar um acontecimento mas também enviar uma mensagem. Reciprocamente, quando um processo executar Esperar, receberá uma mensagem. Assinalar (IdSemaforo, Mensagem) Esperar (IdSemaforo, ApontTampao) À estrutura de dados do semáforo deve ser associada uma segunda fila onde são memorizadas as mensagens. É evidente que a fila de processos estará vazia quando a de mensagens contém elementos e vice-versa. Há um problema delicado que se prende com a forma como as mensagens são armazenadas durante a sua permanência na fila do semáforo. A solução podia basear-se numa cópia, idêntica à implementação de uma caixa de correio, ou na memorização na fila do semáforo de apontadores para a mensagem. A 2ª é mais simples, originando porém uma gestão de memória mais complexa pois os dados a transferir deverão passar do espaço de endereçamento do processo original para o de destino. Como este é desconhecido (será a tarefa que fizer Esperar) não se pode efectuar a cópia imediatamente. O RMX7EX soluciona o problema utilizando a infra-estrutura de gestão de memória. Quando uma tarefa pretende enviar uma mensagem, adquire um segmento e escreve nele. Na primitiva Assinalar é enviado o identificador do segmento contendo a mensagem, o que implicitamente efectua a transferência. A tarefa receptora receberá o identificador do segmento contendo a mensagem, podendo copiá-lo ou utilizá-lo directamente. SEND_MESSAGE (Ccorreio, Seg, Resposta, PtrCodRet) Seg = RECEIVE_MESSAGE (Ccorreio, LimTempo, PtrResposta, PtrCodRet) 1 – O Processo P1 cria um segmento, o sistema devolve o identificador 2 - Escreve a mensagem 3 – Faz SEND_MESSAGE para uma mailbox 4 – O Processo P2 recebe a mensagem constituída pelo identificador do segmento 5 – O processo P2 acede aos dados da mensagem 10.6.2. Filas de Caracteres

As mailboxes apenas permitem a transferência de segmentos entre tarefas. Para determinados tipos de comunicação, esta estrutura é demasiado pesada. Ex. terminal de caracteres para editor de linha. Há várias possibilidades: - Por cada caracter o processo gestor do terminal adquire um segmento... - Os dois processos utilizam um segmento comum com os mecanismos de sincronização necessários para garantir a coerência da estrutura de dados. Ambas não são atraentes pois implicam diversas chamadas ao sistema para transferir um só caracter. O mais comum é só transferir os caracteres quando já se chegou a um determinado nº ou ao fim dum intervalo de tempo, embora diminua capacidade de interacção. O EX criou um novo objecto designado por queue que implementa uma caixa de correio com interface do tipo sequência de caracteres. NOctLivres = SEND_QUEUE (Fila, PtrMens, Cont, LimTempo, PtrCodret) NOctetos = RECEIVE_QUEUE (Fila, Tampão, Cont, limTempo, PtrCodRet) PtrMens – apontador para os dados a transferir Tampão – apontador para a estrutura de dados que receberá os octetos lidos NOctetos indica o nº de octetos ainda existentes na fila e permite optimizar o algoritmo da tarefa produtora, de forma a evitar bloquear-se numa fila cheia. Na primitiva de envio, atarefa pode bloquear-se se a fila se encontra cheia, daí o LimTempo. Fila = CREATE_QUEUE (DimOctetos, PtrCodRet) DELETE_QUEUE (Fila, PtrCodRet) 10.7. Entradas / Saídas O tratamento das interrupções, nestes sistemas integrados, tem particular importância, pois a aplicação tem de controlar eficientemente o hardware. O sistema dispõe de 2 mecanismos base: - Rotinas de Interrupção - Tarefas de Interrupção No 2º caso apenas o processamento imediato (não todo) tem de ser feito na rotina de interrupção. A vantagem é libertar o mecanismo de interrupção. SET_INTERRUPT (Nível, Flag, RotinaInterrupção, SegDados, PtrCodRet) Flag indica se a tarefa fica associada ao tratamento da interrupção 10.7.1. Rotina de Interrupção Tem de efectuar a salvaguarda e restauro dos registos e o processamento necessário ao tratamento da interrupção. Durante a sua execução as interrupções estão inibidas. Um ponto importante relaciona-se com a zona de dados acedida pela rotina durante a sua execução. Quando começa o segmento de dados é o da tarefa interrompida. É pois necessário definir um novo segmento. Tal pode ser feito de forma fixa no código da rotina, modificando explicitamente o registo do segmento de dados, ou manter a programação da rotina genérica definindo o segmento de dados quando é efectuada a associação à interrupção – parâmetro SegDados de SET_INTERRUPT. A mudança é efectuada pela chamada à função ENTER_INTERRUPT que comuta os segmentos. 10.7.2. Tarefa de Interrupção São criadas como as outras tarefas. Contudo quando executa SET_INTERRUPT com a flag diferente de 0, a sua prioridade é modificada de acordo com uma tabela predefinida que correlaciona os níveis de interrupção com as prioridades das tarefas. Tem de efectuar WAIT_INTERRUPT para desinibir interrupts, enquanto nas rotinas de interrupção isso acontece logo que SET_INTERRUPT. mas as interrupções de mais alta prioridade podem continuar a ser atendidas. Uma interrupção é assinalada à tarefa com SIGNAL_INTERRUPT . Em flag especifica-se o nº máximo de signals que se admitem sem tratamento. EXIT_INTERRUPT assinala ao hardware o fina da rotina e desmascara interrupts. 10.8. Organização Interna do Núcleo 10.8.1. Estruturas de Dados

Objectos representados por descritores que memorizam características e estado. Existem 2 tipos de descritor: - Com 16 octetos (semáforos, regiões, mailboxes) - Com 32 octetos (tarefas e queues) A memória para os descritores é alocada estaticamente na geração dos sistema (interna ao núcleo e são 2 pilhas de apontadores) 10.8.2. Despacho Baseia-se na lista de tarefas no estado executável organizada por ordem descendente de prioridade. A sua função é comutar a pilha de tarefas dado que os registos foram salvaguardados no âmbito de uma rotina de interrupção que precede sempre uma comutação. 10.8.3. Sincronização Como o sistema mantém em listas todos os objectos, as listas de semáforos, regiões, mailboxes e queues são geridas de forma idêntica na criação e eliminação. Uma lista duplamente ligada referencia os descritores das tarefas suspensas; as asleep ou bloqueadas (semáforo, mailbox ou queue) com limite de tempo são mantidas noutra lista. No descritor de tarefas existe um campo para manter a temporização (relativa). O descritor do semáforo é uma variável que memoriza o nº de unidades actuais, o nº máximo e 2 apontadores, um para o início da fila de tarefas e outro para a última tarefa, de forma a implementar facilmente uma fila de espera. As regiões só têm variável para controlar o estado, mas no descritor da tarefa devem ser referenciadas as regiões que a tarefa controla, para as eliminar todas quando a tarefa é eliminada. As mailboxes têm uma estrutura com mais uma lista. As queues são mais complexas na sua estrutura. Têm associado um tampão circular para conter os caracteres com os respectivos índices e apontadores para as 2 listas de processos correspondentes às tarefas produtoras e consumidoras.

CAP. 11 – MODELO COMPUTACIONAL DO UNIX 11.1. A Evolução do Unix O objectivo inicial era desenvolver um sistema de tempo partilhado simples e eficiente para minicomputadores. Mas não tem mecanismos para tempo real e a implementação de controladores de periférico necessita de grande conhecimento do modo como o núcleo está implementado. A história explica o aparente excesso de mecanismos. Bell -> Multics (protecção, memória virtual, sistema de ficheiros) --> Unix --> PDP-11 (gestão de memória e tratamento de excepções) – linguagem C --> portabilidade. 11.2. Conceitos Fundamentais O Unix baseia-se num conjunto relativamente reduzido de conceitos que permitem grande uniformidade na sua utilização. Do ponto de vista do utilizador: - Processos: elementos activos com estrutura interna simplificada, o que permite grande uniformidade na sua utilização - O Sistema de Ficheiros: hierárquico simples mas coerente. Constitui o suporte para a comunicação entre processos e para as E/S - Interpretador de Comandos: Flexível para o utilizador (shell). Facilmente extensível e tem a possibilidade de redireccionar as E/S Mecanismos genéricos (em todos) --> mecanismos de sincronização e comunicação do sistema V --> mecanismos de comunicação do 4.2. BSD 11.3. Processos Segue o modelo geral de concorrência. Um processo executa uma imagem (ambiente de execução do processo), que do ponto de vista do utilizador: - Texto (código do programa) - Dados do utilizador - Pilha do utilizador (User Stack) A zona de dados privada do processo é o seu segmento de dados. O processo possui ainda um segmento reservado para a sua pilha de activação (stack pointer). O núcleo mantém um contexto interno por cada processo, onde se define o seu ambiente de execução no SO. Associado a um processo existe ainda um conjunto de variáveis que se relaciona com o ambiente de execução do interpretador de comandos (definem directório de base do utilizador, onde procurar executáveis, editor, etc.), no seu segmento de dados. Identificadores É um valor inteiro - pid Integram-se em grupos, para associar todos os que têm um antecessor comum e que podem receber acontecimentos assíncronos e excepções.. O grupo é identificado pelo pid do processo mais antigo. Protecção no Acesso aos Recursos É definida hierarquicamente em 3 categorias: - Dono (owner): utilizador que normalmente criou o recurso - Grupo (group): conjunto de utilizadores com afinidades de trabalho que justificam direitos semelhantes - Restantes Utilizadores (world) Um processo tem associados dois identificadores que definem o seu número de utilizador User Identifier (UID) e o seu nº de grupo (Group Identification) GID. Quando um utilizador se identifica os valores do UID e GID são obtidos do ficheiro de identificação (etc/password) que contém um registo descrevendo a informação necessária para validar a senha e estabelecer o ambiente de execução do processo. É importante não confundir o GID com o identificador de grupo mencionado anteriormente. O primeiro é um mecanismo de protecção integrado na gestão administrativa do sistema e da

responsabilidade do gestor do sistema, enquanto o segundo é um mecanismo interno do núcleo de que veremos a utilização na secção 11.4. Privilégios Os privilégios detidos por um processo são definidos de forma estática e associados à classe a que o processo pertence. No Unix há (só) 2 classes (é uma das partes mais discutíveis do Unix): - Utilizador - Superutilizador 11.3.2. Criação de um Processo São criados explicitamente pela primitiva fork. É diferente dos outros SO pois a primitiva fork cria um novo processo absolutamente idêntico ao processo pai. O filho herad todo o contexto (núcleo e utilizador) e continua a executar o mesmo código na instrução a seguir ao fork. Tem identificação própria e o tempo de utilização do processador é reiniciada. Esta implementação permite criar um processo de forma muito simples, sendo apenas necessário copiar do processo pai o contexto sistema e os segmentos de dados e pilha. A criação de processo é muito eficaz e esta característica é amplamente utilizada. Ex. shell. Para além de eficiente permite transmitir implicitamente para o filho o ambiente de execução do pai (ficheiros abertos, directório corrente, dispositivos de E/S, prioridade, etc) IdProcesso = fork () Para o proceso pai a primitiva fork retorna o identificador do novo processo, enquanto para o filho retorna 0. Pode ainda retornar –1 (habitual no Unix para erros na execução de funções sistema) quando o nº de processos permitidos é excedido. main() { int pid; pid = fork(); if (pid==0) { /* código do processo filho*/ } else { /* código do processo pai */ } /* instruções seguintes */ } Criação de um Subprocesso – Fork main () { int pid; pid = fork (); if (pid ==0) { execl (“/bin/who”, “who”, 0); /* controlo devia ter sido transferido para o novo programa */ printf (“Erro no execl\n”); exit (-1); } else { /* algoritmo do processo pai */ } } Criação de um Subprocesso – Exec É vulgar contudo, pretender-se que o processo filho execute um programa diferente. Usa-se então a primitiva exec , que modifica o segmento de dados e de texto do processo, mantendo-se o

contexto núcleo inalterado. Assim o novo tem o mesmo GID e UID. Isto permite passar ficheiros abertos de pai para filho, o que é largamente utilizado na comunicação. int execl (NomeFicheiro, arg0, arg1,...,argn,0) char *NomeFicheiro, *arg0, *arg1,...,argn; int execv (NomeFicheiro, argv) char *NomeFicheiro, argv) char *argv[]; Uma variante da primitiva exec (execle, execve) permite definir os valores das variáveis da shell. É possível a modificação dos códigos de protecção associados a um processo, mas só por um processo superutilizador. Existe contudo um mecanismo associado ao carregamento de um novo programa que permite efectuar esta modificação: No cabeçalho do ficheiro executável é possível indicar ao sistema que o UID e GID do processo devem ser alterados para os do dono do ficheiro executável. É evidente que apenas processos superutilizador podem modificar os cabeçalhos. daqui sai que passa ahaver o Effective User-Id e o real User-Id esta facilidade permite, por exemplo, a execução de comandos que necessitam de privilégios acrescidos. O subprocesso lançado pelo shell pode assumir temporariamente os privilégios de superutilizador mantendo o sistema inalterados os códigos de protecção do processo utilizador. O comando login utiliza estas facilidades. No ficheiro executável é indicado que pertence ao superutilizador. Quando se conclui o processo de identificação, é chamada a função setuid para modificar os códigos de protecção para os que estão associados ao utilizador no ficheiro de identificação. 11.3.3. Terminação de um processo A função sistema exit permite a um processo terminar normalmente a sua execução. O processo pai pode bloquear-se à espera da terminação do filho, através da primitiva wait, que retorna o identificador do processo que terminou. main () { int pid, estado; pid = fork (); if (pid == 0) { /* algoritmo do processo filho */ exit (0); } else { /* o processo pai bloqueia-se à espera da terminação do filho */ pid = wait (&estado); } } Sincronização entre processo Pai e Filho 11.4. Acontecimentos Assíncronos – Signals O tratamento de excepções e de acontecimentos assíncronos tem no Unix especial importância, pois extrapola funções e é mecanismo de controlo de execução dos processos. Pode ser assinalados a um processo em execução através de um signal, que pode ter origens muito diversas: SIGHUP SIGINT SIGQUIT ... 11.4.1. Tratamento dos Signals Quando um signal é enviado a um processo, a acção provocada por omissão conduz à terminação do processo, dando alguns signals origem a um core. Mas também podems er associadas a rotinas de tratamento. signal (IdentificadorSignal, RotinaTratamento)

CAP. 11 MODELO COMPUTACIONAL DO UNIX 11.1. A Evolução do Unix O objectivo inicial do Unix era construir um SO de tempo partilhado simples e eficiente para minicomputadores. o que levou ao seu desenvolvimento foi essa simplicidade aliada à grande divulgação nos meios universitários, para além de aparecerem versões para muitas máquinas. Isto vai tornar, provavelmente, o Unix o SO mais vulgarizado nos PCs e estações de trabalho de engenharia. Embora, nas versões mais recentes, a simplicidade não se tenha mantido, continua a ser uma das melhores alternativas quando se pretende assegurar continuidade das aplicações, independentemente do hardware em que se executam. Não é panaceia para todas as aplicações. Por ex. não dispõe de mecanismos para tempo real e a implementação de controladores de periférico exige grande conhecimento do SO. a história explica o aparente excesso de mecanismos do SO. Cientistas do Lab. Bell, que já tinham participado no desenvolvimento do Multics, criam o Unix. Este sistema teve logo grande influência conceptual na evolução dos SO (memória virtual, protecção, sistema de ficheiros), mas a ambição tornou-o num sistema gigantesco e complexo. quando a Bell desiste, alguns cientistas saiem e criam versão simples em e no PDP-7 e depois no PDP-11. A linguagem C foi um importante desenvolvimento paralelo e garantiu-lhe portabilidade e clareza. Como a ATT não estava em condições de comercializar, várias companhias criaram as suas versões. Depois a ATT decidiu suportar o sistema e a versão System V constitui uma referência base que tende a ser imposta como norma. É essa que vamos estudar. 11.2. Conceitos Fundamentais. O Unix baseia-se num conjunto relativamente reduzido de conceitos que permitem grande uniformidade na sua utilização. Do ponto de vista do utilizador, as versões iniciais estruturavamse em torno de 3 entidades: - Os processos – elementos activos numa estrutura interna simplificada -> bom desempenho - O sistema de ficheiros – sistema de arquivo hierárquico simples mas coerente. Constituiu o suporte para comunicação entre processos e para as E/S - Interpretador de comandos – Interface flexível com o utilizador. É facilmente extensível e tem a possibilidade de redireccionar as E/S Vamos estudar: - Mecanismos genéricos de todas as versões - Mecanismos de sincronização e comunicação do V - Mecanismos de comunicação do BSD 4.2 11.3. Processos 11.3.1. Características Gerais Segue o modelo geral de concorrência descrito nos cap. anteriores. Um processo executa uma imagem, que na terminologia Unix designa o ambiente de execução do processo. Do ponto de vista do utilizador, a imagem é formada por: - Texto (código do programa) – código reentrante - Dados do utilizador – zona privada de dados + zona reservada para a pilha de activação - Pilha do utilizador As variáveis do ambiente do shell são mantidas a nível de utilizador no seu segmento de dados, podendo ser modificado livremente, ao contrário das variáveis referentes ao contexto interno do processo que são memorizadas no núcleo. Identificadores O processo é identificado no sistema pelo seu pid (nº inteiro) atribuído na sua criação. Os processos integram-se em grupos (cada um é identificado pelo pid do processo mais antigo hierarquicamente), ligados, cada, a um antecessor e podem receber acontecimentos assíncronos e excepções. Protecção No Acesso Aos Recursos Para um recurso, a protecção é definida hierarquicamente em 3 categorias:

- Dono (owner): utilizador que normalmente criou o recurso - Grupo (group): conjunto de utilizadores com afinidades de trabalho que justificam direitos semelhantes. - Restantes Utilizadores (world) Um processo tem associados 2 identificadores que definem o seu número de utilizador (UID) e de grupo (GID). Quando um utilizador se identifica, os valores de UID e GID são obtidos do ficheiro de identificação (etc/password) que contém um registo descrevendo a informação necessária para validar a senha e para estabelecer o ambiente de execução do processo. É importante não confundir o GID com o identificador de grupo mencionado anteriormente. O primeiro é um mecanismo de protecção integrado na gestão administrativa e da responsabilidade do gestor de sistema, enquanto o segundo é o mecanismo interno do núcleo de que veremos a utilização na secção 11.4. Privilégios Os privilégios detidos por um processo são definidos de forma estática e associados à classe a que o processo pertence. Em Unix há apenas 2 classes: utilizador e superutilizador. Para os processos em modo utilizador o sistema valida todos os acessos aos recursos através dos respectivos códigos de protecção, não podendo os processos modificar os valores de UID e GID. O superutilizador não só pode ultrapassar as protecções estabelecidas, como utilizar alguns mecanismos do núcleo vedados aos utilizadores normais. A existência de apenas 2 classe é uma das partes mais discutíveis do Unix, sendo parcialmente responsável por alguns problemas de segurança atribuídos ao sistema. 11.3.2. Criação de um Processo Os processos são criados explicitamente através da primitiva fork, sendo que é criado, com esta primitiva, um novo processo absolutamente idêntico ao pai e herdando todo o contexto (núcleo e utilizador) e continua a executar o mesmo código na instrução seguinte ao fork. A criação de processos é, assim, muito simples e transfere implicitamente o ambiente de execução do pai (ficheiros abertos, directório corrente, dispositivos E/S, prioridade, etc.). É óbvio que o novo processo recebe uma nova identificação e o tempo de utilização é reinicializado. IdProcesso = fork() Para o processo pai a primitiva fork retorna o identificador do novo processo enquanto o valor retornado para o processo filho é sempre 0. –1 quando há erro de sistema. É vulgar, contudo, pretender-se que o processo filho execute um programa diferente, usando-se então a primitiva exec, que modifica o segmento de dados e texto mas não o contexto núcleo (UID e GID), o que permite a passagem implícita dos ficheiros abertos para o processo filho, o que é muito usado nos mecanismos de comunicação. int execl (NomeFicheiro, arg0, arg1, ... , argn, 0) char *NomeFicheiro, *arg0, *arg1, ..., *argn; ou int execv (NomeFicheiro, argv) char *NomeFicheiro char *argv[]; Há variante exec (execle, execve) que define ambiente shell (que é destruído com exec). A modificação dos códigos de protecção associados a um processo só pode ser feita por um processo superutilizador. Existe, contudo, um mecanismo associado ao carregamento de um novo programa que permite efectuar esta modificação. No cabeçalho do ficheiro executável é possível indicar ao sistema que o UID e GID do processo devem ser alterados para os do dono do ficheiro executável. Assim, o processo assume outra identidade no que toca ao controle de acesso aos recursos. É evidente que apenas os processo superutilizador pode alterar cabeçalhos. Há assim o Effective User-Id e o Real User-Id. 11.3.3. Terminação de um Processo A função sistema exit. O processo pai pode bloquear-se à espera da terminação do processo filho, através da primitiva wait. -----> Sincronização entre Processo Pai e Filho main() {

int pid, estado; pid = fork(); if (pid == 0) { /*Algoritmo do processo filho */ exit(0); } else { /* O processo pai bloqueia-se à espera da terminação do processo filho */ pid = wait (&estado); }} -----> Lançamento de um Subprocesso #include main() { int pid, pid_term, estado; pid = fork(); if (pid == 0) { printf (“Sou o processo filho\n”); execl (“bin/who”, “who”, 0); printf (“erro no exec\n”); exit(-1); } else { printf (“Criado o processo %d\n”, pid); pid_term = wait (&estado); printf (“Terminou o processo %d com o estado %i\n”, pid_term, estado); }} 11.4. Acontecimentos Assíncronos – Signals O tratamento das excepções e de acontecimentos assíncronos tem, em Unix, particular importância, extrapolando a função habitual e tornando-se num mecanismo de controlo da execução de processos. Podem ser assinalados a um processo em execução através da activação de um signal. Os acontecimentos susceptíveis de serem assinalados por um signal têm origens muito diversas. Na tabela 11.1 (pg. 234) apresentam-se alguns dos mais utilizados. Alguns estão relacionados com interrupções provocadas pelo hardware (instrução ilegal, violação da protecção no endereçamento). Outros são excepções (divisão por zero, nº errado de argumentos na chamada a uma função sistema); há ainda outros relacionados com a interacção com os terminais: tecla del, break. Os signals estão referenciados num ficheiro de texto que indicam o nome lógico e um valor numérico. 11.4.1. Tratamento dos Signals Quando um signal é enviado a um processo, a acção provocada por omissão conduz à terminação do processo, dando alguns origem ao core. Também podem ser associados a rotinas. signal (IdentificadorSignal, RotinaTratamento); ----->Tratamento de um Signal #include #include ApanhaCTRLC() { char ch; printf(“\nQuer terminar execução (s/n) ? “); ch = getchar(); if (ch == ‘s’) exit(); else { printf(“Vamos continuar\n”); signal(SIGINT, ApanhaCTRLC); }} main () { signal (SIGINT, ApanhaCTRLC); printf(“Associou-se um tratamento ao SIGINT\n”); for(;;) sleep(10); }

Um signal também pode ser ignorado (SIG_IGN) ou, depois de ter sido associado a uma rotina, ser colocado na situação de omissão (SIG_DFL). A rotina signal devolve como parâmetro de retorno o tratamento associado anteriormente à excepção. No sistema V a associação é só para um tratamento, voltando depois à de default/omissão, podendo, claro, ser associada novamente. No período que medeia entre o lançamento da rotina de tratamento e a nova associação, a recepção de um signal fará terminar o processo. tal aconselharia a que a associação fosse a 1ª instrução da rotina, mas isso não é isento de problemas, pois o processo pode receber uma série de signals o que fará esgotar a pilha conduzindo à sua terminação. No BSD é proibido inibindo a recepção de signals durante o tratamento. O mecanismo de signals é de extrema importância para o funcionamento de todo o sistema, dado que diversas situações de bloqueio nos mecanismos de sincronização podem ser interrompidas quando o processo recebe um signal. 11.4.2. Envio Explícito de Signals ( de um processo a outro) – aspecto muito interessante, pois extrapola o mecanismo de excepções. A primitiva é kill (pid, sig) ex: desencadear a leitura da caixa de correio. Há uma particularidade interessante, que se pode ver neste exemplo: Quando o processo bloqueado num semáforo, num mecanismo de comunicação ou na leitura de um ficheiro recebe um signal, é automaticamente desbloqueado. Esta implementação faz com que a função sistema causadora do bloqueio retorne antecipadamente. O utilizador é avisado através de um código de erro que indica que a função não foi executada. O interesse é (como no exemplo) libertar o processo do bloqueio na leitura de caracteres no interior da rotina LeComando. Isto é pouco elegante pois obriga a testar e reinicializar todas as primitivas susceptíveis de serem interrompidas assincronamente. ---->Acontecimentos Assíncronos (b) void ProduzMensagem (buf) char *buf; { strcpy (buf, “Mensagem de Teste”); } cliente (pid_servidor) int pid_servidor: { int CCServ; if (CCServ = msget (SERVIDOR, 0)) < 0) erro(“msgget Servidor”); msg.ident = CLIENTE; ProduzMensagem (msg.texto); msg.mtype = 1; /*Envio mensagem e signal*/ msgsnd (CCServ, &msg, sizeof(msg), 0); kill (pid_servidor, SIGUSR1); } main() { int pid_servidor = getpid(); CCServ = msgget (SERVIDOR, 0777|IPC_CREAT); signal (SIGUSR1, LeCaixaCorreio); //Lê caixa de correio quando o Servidor lhe manda o SIGUSR1 if (fork() == 0) { cliente (pid_servidor); } else { LeComando(); /* vai ser interrompido por signal (está bloqueado aqui: semáforo, leitura de ficheiro, comunicação) msgctl (CCServ, IPC_RMID, (struct msqid_ds *) 0); } No BSD faz-se com que o sistema ao saber que uma chamada sistema foi interrompida a reinicialize, quando possível automaticamente. O signal SIGKILL não pode ser ignorado ou tratado pelo processo destinatário e conduz sempre à respectiva terminação.

A protecção contra o uso indevido dos signals é feita com base na identificação do utilizador a que pertence o processo. Um processo só pode enviar signals aos processos que possuem o mesmo UID real ou efectivo (excepto os superutilizador). Se o pid especificado na rotina kill for 0, o signal é enviado a todos os processos que pertencem ao mesmo grupo (difusão de signals), o que é útil p. ex. para enviar a todos os processos que implementam uma aplicação. Este método é usado pelo gestor de terminal para enviar os signals SIGINT e SIGQUIT. A compreensão da manipulação dos signals é um exercício interessante. ----> Código Simplificado da Função System subrotina que pode ser utilizada dentro de um programa em C para lançar um subprocesso bloqueando-se à espera da sua terminação. #include int system(argv) char *argv[]; { int pid, status; void (*del)(), (*quit)(); del = signal (SIGINT, SIG_IGN); quit = signal (SIGQUIT, SIG_IGN); switch (pid=fork()) { case 0 : signal (SIGINT, del); signal(SIGQUIT, quit); execl (“/bin/sh”, “sh”, “-c”, argv, 0); exit(-1); case –1: status = ~0; break; default : while (wait(&status) != pid); } signal (SIGINT, del); signal (SIGQUIT, quit); return status; } Um processo pode bloquear-se à espera de um signal, através da função pause e pode requerer que lhe seja enviado um signal do tipo SIGALARM: alarm(Número de Segundos) Há na biblioteca uma função para adormecer um processo durante uns segundos e que utiliza as primitivas pause e alarm: sleep (Número de segundos) 11.5. Espaço de Endereçamento dos Processos Em Unix um processo executa-se no interior de segmentos (no sistema V é regiões). A detecção de um endereço inválido gera uma excepção que se traduz no signal SIGSEGV, o qual, a não ser tratado, conduz à terminação do processo e à geração do ficheiro com a imagem de memória. 11.5.1. Modificação do Espaço de Endereçamento - Aumento da pilha (comum e automática) - Alocação dinâmica de dados (malloc) - Modificação do código (tem a ver com o exec) Quando a região de dados foi toda utilizada, é chamada a função sistema brk que aumenta a região. 11.6. O Sistema de Ficheiros A abstracção ficheiro engloba também a interacção com os periféricos. Os ficheiros normais são muito simples porque constituídos por uma sequência de octetos sem qualquer estrutura. Qualquer mecanismo que considere tipos mais complexos (numéricos, estruturas) tem de ser implementado por bibliotecas ou rotinas especiais sobre esta abstracção fornecida pelo sistema. 11.6.1. Identificadores O SF é organizado como uma árvore com uma única raiz (root – representada por /). Todos os nós da árvore não terminais são ficheiros directórios. Nas folhas estão os ficheiros habituais, directórios ou relacionados com as E/S.

A associação entre o ficheiro e o seu nome funciona em Unix de modo diferente. Um ficheiro pode ser referenciado a partir de vários directórios através do estabelecimento de ligações, designadas links em terminologia Unix. Um ficheiro só é apagado quando não existe nenhum link para ele. 11.6.2. Utilização dos Ficheiros Do ponto de vista do modelo computacional, um ficheiro pode ser considerado como um canal a que o programa se associa através de uma primitiva de abertura (open), que devolve um file descriptor (local ao processo) para um descritor na Tabela de Ficheiros Abertos do utilizador. a primitiva é muito simples: Fich = open(Caminho de Acesso, ModoUtilização) Para criar: Fich = creat (Nome, ModoProtecção) Para ler e escrever: NLidos = read (Fich, Tampão, Ncaracteres) NEscritos = write (Fich, Tampão, Ncaracteres) Dada a necessidade de optimizar a gestão dos discos, existe uma memória tampão (cache) onde os dados são colocados antes de serem transferidos para disco. O programador utiliza conjunto de rotinas de biblioteca stdio, para maior eficiência (páginas). ----->Cópia de um Ficheiro ... 11.6.3. Ficheiros Especiais Os ficheiros que representam os periféricos são de um tipo especial que permite o sistema agulhar para o respectivo gestor de periférico os pedidos das aplicações. Uniformidade. 11.7. Comunicação Entre Processos – Pipes Os pipes foram o mecanismo inicial para intercomunicação entre processos no Unix e constituiu um dos conceitos unificadores na estrutura do interpretador de comandos. Um pipe pode ser visualizado como um canal (byte stream) ligando 2 processos e permitindo um fluxo de informação unidireccional. Funcionalmente é idêntico a uma caixa de correio em que as mensagens são sequências de octetos de qualquer dimensão. Para o programador, os pipes têm uma interface absolutamente idêntica à dos ficheiros. É criado com a chamada à função sistema pipe, que devolve 2 descritores, um representando a extremidade de escrita e outro a de leitura P[0]. int P[] status = pipe (P) A sincronização é a habitual num mecanismo de comunicação. Um processo é bloqueado quando escreve para um pipe já cheio. No caso da leitura, quando o pipe está vazio, o processo leitor é bloqueado. -----> Utilização de um Pipe #include main () { char mensagem[] = “Mensagem de teste”; char tampão [1024]; int fd[2]; pipe(fd); for(;;) { write (fd[1], mensagem, sizeof(mensagem)); read (fd[0], tampão, sizeof(mensagem); printf(“Leitura do pipe: %s\n”, tampãp); printf(“Quer terminar (s/n)”); if (getchar() == ‘s’) exit();}} Um pipe pode ser utilizado como um ficheiro ou em substituição do periférico de entrada ou saída do programa. Esta última hipótese constitui um dos interesses maiores dos pipes, permitindo um mecanismo fácil de redirecção das E/S entre processos. A redirecção (saída de um ficheiro ser entrada de outro) é obtida de forma muito simples aproveitando a função dup que duplica um descritor de ficheiro colocando-o na primeira posição livre na tabela.

A fig. 11.12 ilustra a forma de efectuar a redirecção da entrada para um processo filho. O processo cria um pipe antes de criar o filho, para o qual passam, como se sabe, todos os descritores. O processo filho fecha o seu descritor de entrada e duplica o descritor de leitura do pipe (posição 0, livre). 11.7.1. Pipes com Nome Como são locais, um processo não pode tentar abrir um que tenha sido criado por outro (sem relação hierárquica), o que limitava a programação de estruturas de comunicação. Então criaramse os pipes com nome, utilizando-se o serviço de nomes do SF, pelo que se comporta como um ficheiro. Usa-se a função mknod para criar pipes com nome ou Fifos (terminologia V). ----> Cliente/Servidor com Named Pipes (a) ... Os pipes são um mecanismo interessante e inovador que permite a interligação entre os mecanismos de E/S, a comunicação entre processos e o SF. A maior limitação advém de, pelo facto de ser baseado no SF, restringe a funcionalidade (unidireccionalidade, interface byte stream) e o desempenho. No V e no BSD foram introduzidos outros mecanismos de comunicação para ultrapassar estas limitações. 11.8. Sincronização e Comunicação no Sistema V O sistema passou a dispor de semáforos, memória partilhada e caixas de correio, embora com a preocupação de uniformização das diversas semânticas: são todos criados por uma primitiva get que lhes atribui um identificador e uma primitiva de controlo ctl que permite obter informações e parametrizar o funcionamento dos objectos. Todos os objectos de comunicação são identificados por um valor inteiro que corresponde a um índice para as tabelas já referidas. 11.8.1. Semáforos Os semáforos em Unix não são são tratados individualmente mas em grupo, ao qual é associado um identificador global no sistema (Chave – dado pelo utilizador). IdSem = semget(Chave, NumSems, Semflg) Semflg é um conjunto de definições do modo de utilização do semáforo, permite, por exemplo, especificar se o conjunto de semáforos deve ser criado (IPC_CREAT), se se pretende utilizar a um já existente, se o seu uso é exclusivo do processo (IPC_EXCL), etc. e permite também a especificação da protecção, como nos ficheiros. A função retorna o identificador sistema do conjunto de semáforos que será utilizado nas primitivas de manipulação. A primitiva semop permite executar as funções Esperar e Assinalar semop (Semid, Sops, Nsops) Sops é um apontador para uma tabela de estruturas que define as operações e Nsops o nº de estruturas na tabela. A tabela é constituída por registos que identificam o semáforo e a operação a executar: struct Sembuff { short SemNum; short SemOp; short SemFlg; } Em resumo, semop executa sobre o semáforo SemNum do grupo definido por SemId a operação SemOp, tendo em atenção as condições indicadas por Semflg. A operação executada sobre o semáforo depende do valor do parâmetro SemOp, existindo diversos tipos de condição de bloqueio e libertação: SemOp = 0 – a função testa o valor da variável do semáforo. Se esta for zero, o processo continua; caso contrário é bloqueado, esperando que o valor do semáforo seja zero. SemOp < 0 - SemOp é adicionado ao valor do semáforo. Se o resultado for zero, liberta os processos à espoera da condição zero. Se a variável do semáforo for menor em valor absoluto que o valor de SemOp, o processo é bloqueado, esperando que o semáforo tenha um valor que lhe permita prosseguir. SemOp > 0 - SemOp é adicionado à variável do semáforo e os processos bloqueados que vejam satisfeitos os seus pedidos são desbloqueados. Corresponde a Assinalar, com possibilidade de enviar um nº qualquer de unidades. Reciprocamente a operação com um nº negativo corresponde a Esperar, indicando o nº de recursos que se pretende obter. A utilização de SemOp=0 corresponde à utilização de variável binária de sincronização.

Não esquecer que um processo bloqueado num semáforo também pode ser desbloqueado por um signal. A complexidade dos semáforos (em grupos) tem a ver com a tentativa de resolver situações de interblocagem. Caso o processo fique bloqueado numa das operações, todas as anteriores são anuladas para que o conjunto dos semáforos fique coerente. Quando o processo é desbloqueado a operação é reinicializada para todo o grupo. Ex: caso de um processo que tem de entrar em 2 secções críticas protegidas por semáforos: struct Sembuf EspDupla [2]; /* Inicialização da estrutura que define a operação */ EspDupla[0].SemNum = 0; EspDupla[0].SemOp = -1; EspDupla[1].SemNum = 1; EspDupla[1].SemOp = -1; /*Operação sobre os 2 semáforos */ semop (semid, EspDupla, 2); 11.8.2. Comunicação por Memória Partilhada No V, os processos podem declarar regiões de memória partilhada como pertencentes ao seu espaço de endereçamento. A primitiva shmget cria uma região de determinado tamanho, à qual fica associada uma Chave fornecida pelo utilizador: IdRegPart = smhget (Chave, Tamanho, Flag) Depois de adquirida deve ser mapeada: EndVirtual = smht (IdRegPart, End, Flags) EndVirtual é atribuído pelo núcleo se não puder ser End dado pelo utilizador, que normalmente deixa em branco A primitiva shmdt(End) retira a região do espaço de endereçamento do processo. ----> Comunicação Por Memória Partilhada /*Produtor*/ #include #include #include #include #define CHAVEMEM 10 main () { int shmid, *shmp, i; if ((shmid = shmget (CHAVEMEM, 1024, 0777|IPC_CREAT)) < 0) erro(“shmget”); printf (“Criou uma região com a chave %d\n”, CHAVEMEM); if ((shmp = (int*) shmat (shmid, (char ) 0, 0)) == (int) –1) erro(“shmat”); for (i=0; i Cliente/Servidor com Caixa de Correio /* Cliente */ #include #include #include #define TAMMSG 100

#define SERVIDOR 10 #define CLIENTE 11 struct msgTeste { long mtype; long ident; char texto[TAMMSG]; } msg; main () { int CCCliente, CCServ; if ((CCCliente = msgget (CLIENTE, 0777!IPC_CREAT)) < 0) erro (“msgget CLIENTE”); if ((CCServ = msgget (SERVIDOR, 0)) < 0 erro (“msgget SERVIDOR”); msg.ident = CLIENTE; msg.mtype = 1; ProduzMensagem (msg.texto); /*Envia pedido e recebe resposta */ ... 11.9 Comunicação Distribuída – Sockets A versão Berkeley introduziu várias alterações na estrutura do sistema. Destacamos os sockets, que correspondem a uma proposta inovadora na comunicação entre processos e que certamente irá ser mantida nas futuras versões do Unix resultantes da fusão do sistema V e BSD 4.3 Os objectivos são: - Transparência: a comunicação entre processos não devia ficar dependente da localização dos processos numa única máquina. - Compatibilidade: Pretendeu-se que o novo mecanismo se inserisse no mecanismo clássico de comunicação e de E/S do Unix. Desta forma os sockets apresentam uma interface baseada nos descritores de ficheiros como os restantes mecanismos. Podem ser considerados uma evolução dos pipes, mas são bidireccionais. São criados num dado domínio que especifica os protocolos utilizados e as convenções de identificação. Os domínios mais interessantes são: domínio Unix (corresponde ao seu uso como caixas de correio numa máquina) e domínio Internet, que visa a comunicação entre máquinas diferentes e usa os protocolos Darpa. Os sockets têm tipos que estabelecem a semântica do funcionamento da comunicação. algumas das propriedades que podem ser definidas pelos tipos são: - Garantia da sequencialidade; - Mensagens não duplicadas; - Fiabilidade da comunicação; - Preservação das fronteiras das mensagens. Os tipos mais significativos são: - Stream – Comunicação bidireccional, fiável e sequencial. Interface semelhante aos pipes ou aos ficheiros, propondo modelo de comunicação diálogo, como explicado no cap.6 - Datagram – Comunicação bidireccional, sem garantia de sequencialidade, fiabilidade nem eliminação de mensagens duplicadas. São mais eficazes e sobre redes locais é adequado a numerosas aplicações. Criação de um Socket: IdSocket = socket (domínio, Tipo, Protocolo) O estabelecimento do circuito virtual (stream), permitindo o diálogo entre os processos cliente e servidor, é feito por primitivas específicas. O sockete é criado sem nome sendo preciso uma primitiva bind para se lhe associar uma designação: bind (IdSocket, EndSocket, TamanhoEnd) No domínio Unix, os nomes são caminhos (ex: /dev/sockexemplo). No Internet o identificador corresponde a uma estrutura mais complexa, composta por endereço Internet e nº de porto de comunicação.

Sockets Com Ligação No stream, depois da criação o servidor atribui um nome ao socket para que os clientes possam pedir uma ligação. Porém, antes de aceitar a ligação, o servidor tem de indicar a sua disponibilidade para receber chamadas. A primitiva listen indica que o servidor está disponível e especifica o nº máximo de pedidos pendentes. listen (IdSocket, DimensãoFila) Do lado do cliente a criação do socket é semelhante: connect (IdSocket, EndSocket, tamanhoEnd) O estabelecimento da ligação só se verifica quando o servidor executa accept. Quando a ligação é estabelecida o sistema cria um novo socket no servidor que ficará associado ao novo canal de comunicação: Nsd = accept (IdSocket, EndSocket, TamanhoEnd) O processo servidor poderá criar um subprocesso para tratar este pedido, mantendo-se a recepção de novos pedidos de ligação no socket inicial. Depois da ligação estabelecida, os processo podem utiliza as primitivas write e read ou chamadas específicas send e recv. -----> Cliente / Servidor Com Sockets /* Servidor */ #include #include #include #include #define MAXBUF 1024 #define NOME “/tmp/Servico” char buf[MAXBUF]; main () { int sd, ns, dim_serv, dim_cli; struct sockaddr_un end_cli, end_serv; if (( sd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) erro (“socket”); bzero ((char *) &end_serv, sizeof(end_serv)); end_serv.sun_family = AF_UNIX; strcpy (end_serv.sun_path, NOME); dim_serv = strlen (end_serv.sun.path) + sizeof(end_serv.sun_family); unlink (NOME); if (bind(sd, (struct sockaddr *) &end_serv, dim_serv) < 0) erro (“bind”); if (liste (sd, 1) aumenta 1 o nº de utilizadores que lhe podem aceder. As outras são copiadas. - Incrementar o nº de utilizadores dos ficheiros abertos do pai - Criar um registo de activação na pilha do núcleo do novo processo. Colocar no estado executável. - Retornar o valor do pid do novo para o pai. 12.2.4. Terminação de um Processo (função exit) Também pode terminar quando recebe signal. exit efectua: - Fecha todos os ficheiros - Liberta o directório corrente - Liberta as regiões de memória - Escreve num registo de contabilidade a utilização do processador, memória, E/S. - Envia signal death of child ao pai Não retira completamente o processo do sistema. Todos os recursos são libertados à excepção da estrutura proc (estado zombie), o que permite ao pai encontrar informação sobre o filho com wait. A eliminação de um processo pai não faz terminar os filhos. 12.2.5. Execução de Outro Programa (função exec) Depois de verificar que o ficheiro existe e é executável, o núcleo efectua: - Copia os parâmetros da chamada a exec da pilha do utilizador para o núcleo, pois o contexto utilizador irá ser destruído. - Liberta as regiões de dados e pilha ocupados pelo processo e eventualmente a região de texto se nenhum outro processo estiver a usar - Aloca novas regiões de memória - Carrega o ficheiro de código executável - Copia os parâmetros para a pilha utilizador - Cria um registo na pilha núcleo. Como referimos, os códigos de protecção podem ser alterados na execução da função exec. 12.2.6. Gestão de Processos As prioridades em modo utilizador indicam a importância relativa do processo e o modo como este tem utilizado o recurso principal do sistema, o processador. Ao contrário, as prioridades em modo núcleo só se encontra definida quando o processo está bloqueado e não depende da utilização do processador mas do tipo de recursos que o processo detém quando é bloqueado; são sempre maiores que as de modo utilizador. Hierarquia: Modo Núcleo Não pode receber signals Swapper E/S Disco Espera Tampão da cache Espera inode Podem receber signals Entrada terminal Saída terminal Terminação filho Modo utilizador Nível 0 ... Nível N Modificação Dinâmica das Prioridades em Modo Utilizador São actualizadas periodicamente. O algoritmo de actualização visa diminuir a prioridade dos processos que utilizaram recentemente o processador durante mais tempo. Gestão de prioridades como estrutura multilista circular.

O sistema tem um relógio interno com um período com valores entre 20 e 200 ms. Ao fim de cada 1s é executado o algoritmo de escalonamento e efectuado o cálculo das prioridades dos processos em modo utilizador. Para o cálculo é usada a informação na tabela proc sobre a utilização do tempo de processador: Prioridade = TempoProcessador + Prioridade Base No sistema V a contabilização do tempo de processador não é uma mera adição, tentando-se amortecer a influência de usos remotos por decaimento. TempoProcessador=TempoProcesaador/2 Prioridade = TempoProcessador/2 + Prioridade Base (ver tabela 12.1: Actualização das prioridades em modo utilizador) – pg. 272 Existe ainda um parâmetro que influencia o cálculo da prioridade, que é o nice. O valor de nice é mantido na tabela proc e é adicionado à fórmula anterior. O valor é definido através da função sistema nice e assim o superutilizador pode dar-lhe valores negativos para aumentar a prioridade do processo. Os utilizadores apenas podem diminuir a prioridade sendo portanto nices (simpáticos) para com os outros. Despacho O processo em execução perde o direito a continuar a executar-se em 3 situações: - Quando se bloqueia no interior de uma função sistema - Quando termina - Quando retorna a modo utilizador e existe um processo mais prioritário executável (mecanismo de preempção). Como consequência, um processo em modo núcleo nunca pode ser retirado de execução, o que é determinante para a impossibilidade de usar Unix em sistemas de tempo real. 12.3. Sincronização Interna Um processo pode passar a um estado bloqueado no decorrer de uma chamada sistema. As razões de bloqueio são múltiplas. A maioria tem a ver com as E/S mas também com os mecanismos de sincronização em modo utilizador (semáforos, mailboxes). A sincronização do núcleo é conseguida através do bloqueio dos processos em acontecimentos – events, que são representados por endereços (existe tabela na estrutura de dados do núcleo, com cada entrada associada a um acontecimento).

CAP. 13 – Modelo Computacional do VMS PRIMITIVAS 1 - Criação e Terminação de Processos SYS$CREPRC ([Pidadr,] Image [, Input] [, Output] [, Error] [, Prvadr] [, Quota] [, Prcnam] [, BasPri] [, Uic] [, Mbxunt] [, Stsflg]) Image – nome do ficheiro onde se encontra o código executável do processo Pidadr – endereço da palavra que irá receber o identificador que o sistema associa ao processo quando criar a sua estrutura interna Prcnam – nome (cadeia de caracteres) especificado pelo utilizador que permitirá a identificação lógica do processo Input, Output, Error – identificadores dos canais de entrada/saída e de notificação de erros durante a execução dos processos Mbxunt – caixa de correio onde o processo pai poderá receber a informação de terminação do processo filho Baspri – nível mínimo de prioridade do processo Prvadr – endereço de uma palavra de 64 bits que define os privilégios do processo Quota – definição das quotas de utilização dos recursos do sistema Uic – Se for zero, indica que se trata de um subprocesso e o sistema atribuí-lhe o valor de UIC do pai; se for diferente de zero, trata-se de um detached process. SYS$DCEXH – permite definir rotina que será invocada antes da terminação real do processo SYS$EXIT – autoterminação habitual SYS$FORCEX e SYS$DELPRC – permitem eliminar outro processo desde que executadas por um processo que possua os privilégios necessários para fazê-lo. A segunda é normalmente utilizada quando se pretende que não seja executado o procedimento de terminação (ou quando se sabe que este não existe) 2 – Acontecimentos Assíncronos (AST) SYS$TIMER – lança um temporizador ex: SYS$SETIMR (0, IntervaloBin, TicTac, 0); Num dos parâmetros é indicado o modo como se pretende tratar o acontecimento gerado pelo expirar da temporização. Neste caso indicou a rotina TicTac que é o AST que deve ser executado quando acabar a temporização/acontecimento em que bloqueou. SYS$BINTIM (&Tempo, IntervaloBin) – Converte para o formato interno do tempo. -> 3 – Sincronização Indirecta (event-flags) O posicionamento das event-flgs é feito através de duas rotinas: SYS$CLREF – indica a condição de bloqueio (posiciona) ex: SYS$CLREF (Efn96) SYS$SETEF – assinala o acontecimento que a event-flag controla, mas antes tem de ser posicionada com SYS$CLREF ex: SYS$SETEF (Efn96) SYS$WAITFR – bloqueia o processo quando a event-flag não está posicionada. (Quando recebe o assinala, posiciona a flag, logo há que posicioná-la a seguir pelo SYS$CLREF) ex: SYS$WAITFR(Efn96) SYS$ASCEF – cria um grupo de event-flags ou para se lhe associar. Atribui um nome ao grupo e especifica os identificadores locais (grupo 2 ou 3) a que fica associado. ex: Estado = SYS$ASCEF (Efn96, &EfnGrupo3, 0, 0) SS$_WASCLR – testa se a event-flag já está posicionada. _ é not? Primitivas para sincronização múltipla sobre event-flags: SYS$WFLAND (efn, mascara) – bloqueia o processo até que a todas as event-flags do grupo referenciado por efn e pertencentes ao subconjunto definido por máscara (palavra de 32 bits) estejam posicionadas

SYS$WFLOR (efn, mascara) – reciprocamente, bloqueia o processo até que a primeira das eventflags seja posicionada 4 – Comunicação Por Memória Partilhada SYS$CRMPSC ([inadr] [, retadr] [, acmode] [, flags] [, gsdnam] [, iden] [, relpag] [, chan] [, pagcnt] [, vbn] [, prot] [, pfc]) inadr e retadr – vectores que contêm o endereço inicial e o endereço final da região. inadr é fornecido pelo utilizador e retadr é o endereço virtual onde o sistema localizou a região flags – definem a função a executar O tipo de secção que corresponde a uma zona partilhada de memória designa-se por global section: SEC$M_GBL e SEC$M_PAGFIL – são funções que devem fazer sempre parte da selecção gsdnam – define o nome da secção global que permitirá referenciá-las nas primitivas de associação. A opção mais vulgar é a zona de memória corresponder a uma extensão do espaço de armazenamento: SEC$M_EXPREG – especifica que a região a alocar deverá ser colocada na primeira posição livre do mapa de endereçamento ( a opção por omissão é utilizar os endereços especificados em inadr). pagcnt – quando EXPREG é especificado, especifica o tamanho e inadr deve especificar se se pretende mapear no espaço de memória P0 ou P1. SYS$MGBLSC ( inadr [, retadr] [, acmode] [, flags], gsdnam [, iden] [, repag]) - Associação a uma secção global. SYS$DELTA – Retira do espaço de armazenamento as páginas previamente alocadas. ex: SYS$DELTA ( EndRegiao, 0, 0), com EndRegiao = gsdnam 5 - Entradas/Saídas (canal – abstracção) Um canal deve ser associado ao processo antes de ser utilizado: SYS$ASSIGN – permite efectuar a associação a um canal, cujo nome é fornecido, devolvendo um identificador do sistema, que será utilizado nas primitivas de escrita e leitura. SYS$QIO ([efn,] chan, func [, iosb] [, astadr] [, astprm] [, p1] [, p2] [, p3] [, p4] [, p5] [, p6]) chan – identificador do canal func – função a executar

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF