Sis to Per 2001

March 14, 2019 | Author: Diego Lima de Oliveira | Category: Kernel (Operating System), Operating System, Distributed Computing, Computer Network, Computer Data Storage
Share Embed Donate


Short Description

Download Sis to Per 2001...

Description

Universidade Estadual Paulista Instituto de Biociências, Letras e Ciências Exatas Departamento de Ciência da Computação e Estatística

Apostila de Projeto de Sistemas Operacionais

Prof. Dr. Norian Marranghello Outubro de 2001 [email protected] Rua Cirstóvão Colombo, 2265 – São José do Rio Preto, SP C.E.P.15054-000; Tel: +17 221 2215; Fax: +17 221 2203

Índice  Índice......................................................  Índice............................................................................. .............................................. .............................................. ................................. .......... 2 Sistemas Distribuídos Distribuídos ........................................... .................................................................. .............................................. ......................................... .................. 4 Sistemas Centralizados..................................................................................................... Centralizados................................................................................................................. ............55 Sistema em Rede .......................................................................... ...........................................................................................................................7 .................................................7 Sistemas Distribuídos .................................................................. ...................................................................................................................8 .................................................8 Sistemas Autônomos Autônomos Cooperativos Cooperativos ..................................................................... .............................................................................................9 ........................9 Algoritmos Distribuídos.............................................................................................................10

 Projeto de Sistema Operacional .............................................. ..................................................................... ........................................... .................... 10

Eficiência ..................................................... ........................................................... ................................ 10 Flexibilidade .......................................................... ........................................................... ...................... 11 Consistência ........................................................... ........................................................... ...................... 12 Robustez ...................................................... ........................................................... ................................ 12 Transparência...................................................................................... Transparência........................... ........................................................... .................................................... 13

Serviços .............................................. ..................................................................... .............................................. .............................................. ................................... ............ 14  Modelos de Arquiteturas Arquiteturas .............................................. ..................................................................... .............................................. ............................... ........ 16  Comunicação ............................................ ................................................................... ............................................... ............................................... ........................... .... 18 O modelo de referência da ISO .................................................................................................19 O modelo TCP/IP do DoD..........................................................................................................21

 Aspectos de projeto ........................................... .................................................................. .............................................. ........................................... .................... 23  Modelo Cliente/Servidor............................... Cliente/Servidor...................................................... .............................................. .............................................. ......................... 25 Serviços de Tempo ............................................ ................................................................... .............................................. ........................................... .................... 27   Ramificações no Fluxo de Controle de Processos Processos ............................................ ............................................................ ................ 30  Mecanismos de Linguagem para Sincronização........................................................... Sincronização............................................................... .... 33 Comunicação entre Processos.......................................................................................... Processos.................................................................................................... ..........36 36 Passagem de Mensagens ............................................................ ........................................................... .. 37 Chamadas a Procedimentos Remotos ........................................................... .......................................... 40

Transações ............................................. .................................................................... .............................................. .............................................. ............................... ........ 44 Protocolo de aceitação em duas fases........................................................................................44 Coordenação Distribuída...........................................................................................................45 Por disputa ................................................... .............................................................................................................. ........................................................... ................................ 45 Controlada.................................................... ........................................................... ................................ 46

 Algoritmos de Eleição................................................... Eleição.......................................................................... .............................................. ............................... ........ 48  Acordo Distribuído Distribuído ........................................... .................................................................. .............................................. ........................................... .................... 49  Falhas Bizantinas...................... Bizantinas ............................................. .............................................. .............................................. ........................................... .................... 49 Impossibilidade de Consenso.....................................................................................................51 Consenso Distribuído Aleatório................................................................................................. Aleatório.................................................................................................51 51

 Escalonamento de Processos................................ Processos....................................................... .............................................. ....................................... ................ 53 Modelos........................................................................................................................................53

2

Escalonamento Estático.................................................................................................... Estático.............................................................................................................. ..........54 54 Modelo de Precedência...............................................................................................................55 Modelo de Comunicação............................................................................................................57 Custo de Computação............................ Computação....................................................................................................... ..................................................................................... ..........57 57 Custo de Comunicação...............................................................................................................58 Escalonamento Dinâmico...........................................................................................................58 Algoritmos Genéticos ...................................................... ........................................................... ............ 60

Compartilhamento de Memórias Distribuídas ........................................................................61 Arquiteturas NUMA ........................................................ ........................................................... ............ 63

Alocação, Migração e Cópia de dados ......................................................................................65 Modelos de Consistência de Memória.......................................................................................66 Sistemas de Memória Cache em Multiprocessadores.............................................................. Multiprocessadores..............................................................68 68 Estruturas de Memórias Cache.................................................................................................69 Protocolos de Coerência.............................................................................................................70 Algoritmos de Gerenciamento de MCDs..................................................................................71 Localização de Dados ....................................................................................................... ................................................................................................................. ..........72 72 Sistemas de Arquivos Distribuídos.................................................................................. Distribuídos............................................................................................ ..........73 73 Implementação de Sistemas Sistemas de Arquivos Distribuídos Distribuídos ...........................................................74 Compartilhamento de Arquivos................................................................................................75 Réplicas de Arquivos e Dados....................................................................................................77 Operações de Leitura .................................................................................................................78 Operações de Escrita..................................................................................................................78

 Bibliografia ............................................ ................................................................... .............................................. .............................................. ............................... ........ 79

3

Sistemas Distribuídos Segundo Tanenbaum (tradução, página 261), um sistema distribuído é aquele que roda em um conjunto de máquinas sem memória compartilhada, máquinas essas que mesmo assim aparecem como único computador para seus usuários. Por outro lado, Chow e Johnson [1 - página 27] definem um sistema operacional (SO) distribuído como uma integração de serviços, apresentando uma visão transparente de um sistema com vários computadores, com controle e recursos distribuídos. Este curso tratará de sistemas operacionais distribuídos, cujo desenvolvimento tem sido motivado pela: - crescente necessidade de compartilhar recursos e informações; - redução rápida do custo de estações de trabalho; - proliferação do uso de redes; e - maturação das tecnologias de software Inicialmente será feita uma breve revisão de alguns conceitos importantes sobre sistemas operacionais. A seguir, serão mostradas as diferenças conceituais de quatro categorias de sistemas operacionais. Posteriormente, diversos aspectos de projeto de Sistemas operacionais distribuídos serão analisados individualmente. Um sistema operacional é um conjunto de programas que fazem a interface entre o hardware da máquina e seus usuários, com o objetivo de tornar seu uso mais eficiente, fácil e seguro. O hardware funciona à base de sinais elétricos, os quais são trazidos para uma simbologia binária de modo a facilitar o trabalho dos projetistas dos circuitos lógicos. Esta linguagem de máquinas é usada para escrever alguns programas, que executam certas funções básicas, as quais tornam mais eficiente a manipulação do hardware. Tais funções são utilizadas pelos próprios projetistas de sistemas operacionais para criar outras funções, um pouco mais complexas, que além de facilitar e tornar mais eficiente o trabalho de seus usuários, garantem que o acesso destes ao sistema não comprometa sua segurança, nem a dos demais usuários do sistema. As funções tratadas pelo sistema operacional são o escalonamento dos processos nos processadores, a coordenação da interação entre tais processos, o gerenciamento dos recursos do sistema, o controle de acesso a esses recursos (portanto sua proteção), a recuperação de erros e a interface com os usuários do sistema. Para suprir tais funções o sistema operacional fornece dois tipos de serviços, quais sejam, serviços de núcleo e os de sistema. Os serviços de núcleo correspondem às funções essenciais, dependentes da arquitetura da máquina. Os serviços do sistema correspondem às funções de alto nível percebidas pelos programas de aplicação. Essas funções do sistema operacional, por um lado, visam ajudar os administradores do sistema a gerenciarem os seus recursos, por outro lado, visam apresentar aos usuários uma máquina estendida mais fácil de lidar. A ênfase dos sistemas operacionais tradicionais era no aspecto administrativo, ou seja, em gerir os meios necessários ao uso da máquina. Já, os sistemas operacionais modernos enfatizam seus objetivos, isto é, visam fornecer facilidades para sua utilização. Os sistemas operacionais modernos podem ser classificados de acordo com seu grau de acoplamento em: centralizados, distribuídos, em rede e autônomos. Para classificá-los levam-se em consideração as três características seguintes: 4

-

interoperabilidade: que é a capacidade do sistema facilitar a troca de informação entre componentes heterogêneos; transparência: que é a capacidade do sistema fornecer uma visão lógica única ao usuário, independente do sistema físico disponível; e autonomia: que é a capacidade dos elementos do sistema processarem tarefas independentemente uns dos outros, ao mesmo tempo que cooperam entre si para a solução de um problema.

Sistemas Centralizados Os sistemas operacionais tradicionais, utilizados tanto em arquiteturas com vários processadores como em monoprocessadas, são centralizados. Estes sistemas são fortemente acoplados e a comunicação entre seus processos é feita pelo compartilhamento de memória ou por interrupções. Nas fases iniciais, o projeto desses sistemas visavam o controle das interfaces com os usuários e do sistema de entrada e saída, através de interrupções e de um conjunto de acionadores de dispositivos. Frente à necessidade de aumentar a eficiência do sistema de entrada e saída surgiram os conceitos de entrada e saída virtual e de spooling. Este correspondendo ao armazenamento temporário, em disco, de operações de entrada e saída, permitindo assim a minimização das diferenças de velocidades de processamento entre a UCP e os periféricos; e aquele correspondendo à abstração dos dispositivos de entrada e saída de modo que o sistema operacional possa apressálos como se fossem arquivos, deixando a tarefa “conversar” diretamente com os dispositivos para os respectivos acionadores. Com o passar do tempo a necessidade de tratar programas grandes deu origem ao conceito de memória virtual, o que permite o tratamento de programas, que utilizem espaço de endereçamento maior do que o disponível na memória real, através das técnicas de paginação e segmentação. A contínua necessidade de otimizar o uso dos sistemas deu origem a vários outros conceitos, que possibilitaram o acesso de diversos usuários simultaneamente. Tais conceitos incluem a multi-programação, o compartilhamento de tempo, o necessário escalonamento de processos, o controle de concorrência, o controle de acesso aos arquivos do sistema e a proteção aos arquivos compartilhados. Associado ao acesso de vários usuários está a necessidade de tratar diversas tarefas o que originou uma série de técnicas para viabilizar seu processamento concorrente, incluindo a sincronização de processos, a recuperação do sistema após bloqueios fatais e a comunicação entre os processos concorrentes. O sistema operacional é um sistema grande, podendo chegar a milhões de linhas de código. Este programa deve ser estruturado em módulos divididos vertical e horizontalmente. Cada módulo pode ser entendido como um conjunto de instruções necessárias para executar determinado serviço do sistema. Os módulos são agrupados (horizontalmente) para formar componentes funcionais. Por outro lado, eles são agrupados (verticalmente) em várias camadas, estrutura na qual só é permitida a interação entre módulos de camadas adjacentes. Todos os recursos do sistema inclusive arquivos e processos, podem ser abstraídos como objetos de dados sendo sua representação física “escondida” por uma estrutura de dados abstrata. O modelo de orientação a objetos possibilita uma uniformidade dos mecanismos de acesso e proteção de módulos do sistema. Devido, a esta uniformidade sua alteração é mais fácil, o que melhora a portabilidade do sistema. Contudo, os sistemas operacionais tradicionais são do tipo monolítico, nos quais não há particionamento do núcleo, fora a modularidade normal do código. Nos sistema monolíticos os serviços do sistema e do núcleo fazem parte de um mesmo programa. Todavia, em sistemas operacionais modernos a preocupação com a eficiência e a portabilidade é maior. O núcleo deve ser mínimo, restrito apenas a serviços dependentes do hardware ou àqueles cujo oferecimento fora do núcleo seja muito caro ou difícil. Dentre tais serviços estão os ligados ao 5

tratamento de interrupções, ao acionamento de dispositivos e algumas primitivas básicas para a sincronização e a comunicação entre processos. Módulos estes que, por serem dependentes do hardware, não podem ser transportados de uma máquina a outra sem serem recodificados. Serviços que tipicamente são incluídos em um núcleo mínimo são: - mecanismos para a comunicação entre processos; - algumas funções básicas para o gerenciamento de memória; - algumas primitivas para o gerenciamento e o escalonamento de processos em baixo nível; e - primitivas para o tratamento de E/S em baixo nível. As funções de um sistema operacional podem ser divididas em quatro categorias, conforme os recursos que gerenciam; são elas: processos e processadores, memória, entrada e saída, e dados ou arquivos. As funções para o gerenciamento de processos e processadores tem por finalidade básica definir que processos devem ser executados em cada processador do sistema e quando a execução deve iniciar. Elas são chamadas, respectivamente, de alocação e escalonamento. Definir quais processos devem ser executados, quando os recursos do sistema estiverem disponíveis e/ou certas condições forem atingidas, tem por objetivos minimizar o tempo de resposta dos processos e maximizar a vazão do sistema. Esta definição pode ser feita de forma estática levando-se em consideração as relações de precedência entre os processos, que indicam como os processos devem ser sincronizados. A definição pode também ser feita de forma dinâmica considerando-se as relações de dependência entre processos, que indicam as interações entre elas. Para permitir a coexistência de vários usuários e diversas tarefas no sistema, outras funções são necessárias. Há as que viabilizam a multiprogramação, responsáveis pela multiplexação espacial da memória onde residem os processos. Há as que viabilizam o compartilhamento do tempo, responsáveis pela multiplexação temporal do processador onde os processos são executados. A interação entre os processos necessita ainda de mecanismos para as suas comunicação e sincronização, o que pode ser obtido através da exclusão mútua, por exemplo. A exclusão mútua pode ser obtida fazendo-se chamadas ao sistema e acionando operações sobre variáveis especiais, chamadas semáforos, capazes de bloquear processos interativos, permitindo coordenar seu funcionamento. Uma forma muito usual de tratar a comunicação entre processos é por meio do compartilhamento de áreas de memória. Todavia, processos são geralmente assíncronos e, via de regra, não compartilham espaço de endereçamento. Consequentemente, a alternativa natural é a troca de mensagens, o que pressupõe algum tipo de sincronização. As funções para o gerenciamento de entrada e saída geralmente são fornecidas pelo sistema operacional via uma interface genérica, deixando para os fabricantes de periféricos o projeto dos acionadores e controladores desses dispositivos. Portanto, o sistema operacional enxerga dispositivos virtuais (arquivos lógicos) sobre os quais ele pode fazer operações apenas de escrita, como no caso das impressoras, apenas de leitura, como no caso dos teclados, ou de leitura e escrita, como no caso dos discos. As funções para o gerenciamento de memória respondem pela alocação e desalocação de memória para mapear o espaço lógico de endereçamento dos programas na memória física. Para tanto é criado um espaço de endereçamento virtual o qual permite que o tamanho do programa, mais seus dados e a pilha, seja maior que a memória física disponível. Isto é conseguido pela divisão do espaço de endereçamento lógico em páginas ou segmentos e o mapeamento destas entidades (páginas/segmentos) em blocos da memória física. Este mecanismo serve também para resolver discrepâncias entre memória em disco (geralmente de grande capacidade, mas lenta) e a principal (usualmente de capacidade reduzida, mas rápida). Como o compartilhamento de memória é uma 6

solução relativamente fácil para sincronização entre processos, muitos algoritmos foram desenvolvidos para este fim. Para aproveitar tais algoritmos vários autores procuram simular a existência de uma memória compartilhada em sistemas distribuídos, o que levanta questões como a consistência dos dados compartilhados e o desempenho do sistema assim estruturado. As funções para o gerenciamento de arquivos têm dois enfoques fundamentais, quais sejam, o acesso e o compartilhamento. De maneira genérica qualquer computação pode ser vista como processos operando sobre arquivos. Estes são entidades lógicas que agrupam dados e devem ser estruturadas e implementadas em algum dispositivo antes de serem manipuladas. As funções para manipulação de arquivos devem implementar mecanismos que garantam o acesso aos dados contidos no arquivo apenas a usuários autorizados protegendo-os contra uso indevido. Mecanismos para disciplinar o acesso garantem a integridade dos dados contidos nos arquivos são necessários para a sua segurança. Mecanismos de sincronização são necessários para controlar o acesso correspondente ao conteúdo dos arquivos, permitindo o compartilhamento dos respectivos dados.

Sistema em Rede É um multicomputador fracamente acoplado no qual não existe qualquer tipo de controle direto de uma máquina sobre as outras e no qual a comunicação entre as outras máquinas é bem mais lenta que dentro de uma dada máquina. O objetivo principal de sistemas em rede é possibilitar o compartilhamento de seus recursos. Para tanto, as máquinas que compõem o sistema trocam informações via um canal de comunicação, que as interconecta. A principal característica explorada nestes sistemas é a sua interoperabilidade, para o que são necessários alguns mecanismos de suporte, tais como: protocolos de comunicação devidamente padronizados e interfaces comuns para sistemas de arquivos e bases de dados. Nos sistemas em rede a troca de informações é dividida em níveis, por exemplo: no nível de hardware trocam-se sinais físicos via a rede de comunicação, no de sistema operacional tem-se os serviços de transporte de dados que trocam grupos de bits entre si, e no nível dos usuários são trocadas as mensagens através de processos de alto nível que implementam a comunicação fim-a-fim orientada a aplicações. Os principais modelos em camada, que serão estudados mais adiante, são o RM-OSI da ISO e o TCP/IP do DoD. Na origem dos sistemas operacionais em rede temos o conceito de sistemas abertos, que é um sistema preparado para se comunicar com qualquer outro sistema aberto utilizando regras padrão que governam o formato, o conteúdo e o significado das mensagens enviadas ou recebidas. Neste sentido, os sistemas operacionais em rede são extensões dos sistemas centralizados que facilitam o compartilhamento de recursos implementando serviços de transporte para interfacear os processos aplicativos e a rede de comunicação. A maioria dos sistemas operacionais em rede usam interfaces de aplicação de alto nível, tais como soquetes ou chamadas a procedimentos remotos, para que o serviço de transporte possa sustentar a comunicação entre sistemas em domínios de rede diversos. Algumas classes de aplicações de rede são: - login remoto: é a capacidade do sistema permitir que um usuário use uma máquina como terminal de outra conectada à rede, para poder utilizar o processador e outros recursos associados à máquina hospedeira. Para poder fornecer este serviço a máquina hospedeira deve ser capaz de compreender pacotes de dados vindos do teclado da máquina remota e de enviar pacotes de dados compreensíveis para o seu monitor. Esta comunicação requer um protocolo de redes, sendo o mais comum o telnet. - transferência de arquivos: é a capacidade do sistema transferir dados estruturados na forma de arquivos, juntamente com seus atributos, entre máquinas do sistema. Como parte da operação de transferência de arquivos há a necessidade do intercâmbio e da 7

-

-

-

validação de informações, tais como: atributos do arquivo, formato e fluxo dos dados e, controle de acesso ao arquivo. Este serviço é suportado por protocolos de transferência de arquivos, como o ftp. mensagens: é a capacidade do sistema transferir mensagens, incluindo documentos e arquivos, sem a necessidade de estabelecer uma conexão em tempo real. Ao contrário da transferência de arquivos, as mensagens são empacotadas e somente umas poucas informações de controle são consideradas. Neste caso há serviços para atender transações comerciais em formatos específicos entre computadores com sistemas operacionais diversos (EDI - eletronic data interchange) e para troca de mensagens entre usuários de rede (correio eletrônico) que se servem de padrões como o X.400 do CCITT e o SMTP do DoD. pesquisas em redes: é um serviço que permite a busca e a apresentação de documentos distribuídos entre máquinas participantes da rede. O modelo de varredura mais comum é o cliente/servidor, no qual um programa cliente busca objetos em sistemas servidores de arquivos remotos. O programa cliente mais popular é o netscape. O sistema mais usado é o www (world wide web) que é um modelo de dados para a ligação de documentos hipermídia usando apontadores (URL - universal resource locators). A comunicação principal com o servidor se dá através do hyper text transport protocol (http); em algumas ocasiões são usados também o telnet e o ftp. Os documentos são tipicamente escritos usando-se a hyper text markup language (html). Execução remota: é a capacidade do sistema enviar mensagens para pedir a execução de programas em máquinas remotas. A execução remota se dá, em geral, através da interpretação de scripts e não pela compilação de programas, devido a dependência da arquitetura da máquina. Este serviço costuma ser restrito a aplicações nas quais a segurança do sistema não seja comprometida pela violação de proteções. Aplicações deste serviço incluem o uso de linguagens intermediárias para a transmissão de imagens compactadas. Talvez uma aplicação mais popular atualmente seja JAVA, que é uma linguagem orientada a objetos, derivada da C++, incorporando protocolos tais como o http e o ftp. Ela produz instruções compactas e independentes de máquinas, os applets, que são objetos que tanto podem conter URL (para apontarem/ativarem outros applets) como podem ser apontados por URL.

Sistemas Distribuídos Os sistemas distribuídos representam um passo além daqueles em rede, pois, além de possibilitarem o compartilhamento de recursos eles viabilizam a coordenação de atividades distribuídas. Os recursos físicos costumam ser distribuídos geograficamente dentro das empresas, entre outras razões por questões como eficiência no uso de certos recursos e segurança do sistema como um todo. Por outro lado, os recursos lógicos estão, por sua natureza, dispersos na empresa, pois representam as informações que as pessoas possuem. A função de um sistema operacional distribuído é gerenciar as atividades e os recursos distribuídos, possibilitando um processamento descentralizado e melhorando o desempenho do sistema. A principal diferença de um sistema distribuído para um sistema em rede é sua característica de transparência, isto é, enquanto num sistema em rede o usuário deve manipular operações remotas explicitamente, o usuário de um sistema distribuído tem a impressão de estar trabalhando em um sistema centralizado de bom desempenho. A transparência pode ser notada em vários aspectos, alguns dos quais são exemplificados a seguir.

8

-

transparência de concorrência é quando os usuários não têm consciência do compartilhamento de tempo com outros; transparência de localização de programas é quando o mapeamento do programa em memórias e processadores é escondido do usuário; transparência de acesso a arquivos é quando os arquivos podem ser armazenados em qualquer lugar do sistema e por ele movido sempre que conveniente sendo o acesso ao arquivo feito via um caminho lógico para o usuário; e transparência de desempenho é quando os processos podem ser executados em qualquer processador e movidos dentro do sistema de acordo com suas necessidades, sem que o usuário perceba diferença de desempenho significativa.

Para atingir estes e outros aspectos, um sistema digital deve incluir mecanismos para tratar da coordenação das atividades de processos distribuídos, gerenciando adequadamente o uso dos recursos dispersos pelo sistema, através da implementação de algoritmos distribuídos, como será visto mais adiante no curso.

Sistemas Autônomos Cooperativos Estes sistemas mantêm as noções de transparência e interoperabilidade existentes nos sistemas distribuídos, mas para permitir a existência de processos autônomos capazes de exportar e solicitar serviços, interagindo independentemente, abolem a necessidade de passar a impressão da existência de um único usuário no sistema. Então, grupos de atividades podem resultar em serviços de nível mais elevado, através de composição de serviços mais simples. Desta forma, pode-se formar qualquer sistema de software integrando vários serviços por meio de certas estruturas pré-estabelecidas. Um exemplo interessante de um sistema autônomo cooperativo pode ser emprestado da robótica evolutiva. Neste caso, um conjunto de robôs usam técnicas de algoritmos genéticos para interagirem e criarem soluções para problemas propostos. Os processos devem ser distribuídos pelos robôs, autônomos e assíncronos. Três classes de serviços são implementadas em cada robô do sistema, quais sejam: auto-avaliação, seleção e reprodução. Auto avaliação consiste em métricas para medida de desempenho do algoritmo executado no respectivo robô. Estas métricas devem ser baseadas em parâmetros que não dependem de agentes ou observadores externos. Por exemplo, deve medir a energia gasta para executar certa tarefa, o que pode ser materializado pelo tempo necessário ou pelo número de operações necessárias. A seleção visa reconhecer indivíduos mais bem ajustados, para executar a tarefa em questão, os quais fornecerão genes (isto é, assumirão o papel de pais) para os menos ajustados. Estes, por sua vez, receberão os genes e se modificarão gerando novos entes (isto é, filhos). No processo de reprodução não há reestruturação física dos robôs, mas somente a troca de código do programa de controle. Para colocar em prática estes procedimentos cada um dos robôs determina o seu nível virtual de energia, ou seja, seu desempenho na execução da tarefa a ele atribuída. Então irradia sua informação genética em uma freqüência proporcional ao seu nível de energia. Esta informação genética contém um gene escolhido aleatoriamente do seu genoma e que passou por um processo de mutação. Os demais robôs podem ou não aceitar o gene irradiado. Os que aceitarem sobrescrevem seu gene correspondente com o gene recebido, caso contrário o gene recebido é simplesmente descartado. A receptividade de cada robô do sistema é inversamente proporcional ao seu nível de energia.

9

Robôs com nível de energia maior (melhor desempenho) tendem a disseminar mais seus genes e resistem mais a tentativas de reprodução dos outros. Todavia, este processo é probabilístico e não garante que robôs mais ajustados transfiram todos os seus genes para os menos ajustados.

 Algoritmos Distribuídos O principal objetivo do desenvolvimento de algoritmos distribuídos é fornecer mecanismos que possibilitem contornar a falta de informação global sobre o estado de um sistema distribuído. Cada processador de um sistema distribuído possui o seu relógio interno, que não é sincronizado com os relógios dos demais processadores, causando problemas de interação entre eles. Mesmo que se use um relógio global, centralizado, a propagação desta informação para os processadores do sistema está sujeita a atrasos diversos. Portanto, a tarefa de ordenar os eventos no sistema não é trivial. Além dos atrasos na comunicação, há falhas que podem tornar certas fontes de informação inexistentes, ou não confiáveis. O sistema necessita de algoritmos capazes de identificar e contornar todos os tipos de falhas, evitando situações que levem à inoperância do sistema ou, o que é mais grave, à sua operação defectiva. A arquitetura do sistema influi na implementação desses algoritmos. A topologia da rede (se completa, regular, fixa ou irregular, etc.) e o tipo de comunicação (se ponto-a-ponto ou multiponto) influem na sua implementação. Do ponto de vista do software a reprodução de cópias dos dados introduz o problema da coerência e da consistência dessas cópias, que também influi no algoritmo distribuído.

Projeto de Sistema Operacional Ao projetar um sistema operacional distribuído deseja-se que a execução de aplicativos no sistema resultante tenha um retorno melhor do que em sistemas monoprocessados centralizados, que o sistema distribuído seja capaz de se adaptar a variações tecnológicas e a necessidades de seus usuários, que seu comportamento seja, uniforme e previsível, que ele seja, confiável, tolerante a falhas e devidamente protegido contra ações nocivas, e que os detalhes de sua implementação sejam escondidos do usuário final. Desta forma, pode-se dizer que os projetistas de sistemas operacionais distribuídos têm cinco objetivos principais, como explicados a seguir.  Eficiência

A resposta de um sistema operacional distribuído, ao executar um aplicativo, deve ser melhor que a resposta de um sistema centralizado, ou não seria razoável investir em um sistema mais caro e complexo, cujo desempenho fosse inferior. O desempenho de um sistema operacional distribuído pode ser medido por diversos parâmetros, tais como: tempo de resposta do sistema, sua vazão (o número de trabalhos executados por unidade de tempo), a taxa de uso do sistema (seus processadores) e de seus recursos. No caso de sistemas distribuídos a questão da eficiência é mais complexa que nos sistemas centralizados, em especial, devido aos atrasos na comunicação. Em sistemas distribuídos geograficamente o tempo para a troca de mensagens pode superar o limiar dos segundos. Entretanto, mesmo em sistemas fisicamente próximos, não é possível eliminar os atrasos na propagação dos dados. Estes atrasos podem ser agravados por eventuais sobrecargas dos protocolos de comunicação e pela distribuição de carga no sistema. 10

O tempo gasto na propagação dos dados (na troca de mensagens) depende fortemente do protocolo de comunicação utilizado, motivo pelo qual ele deve ser bem projetado, com base em primitivas de comunicação (tanto da linguagem quanto do sistema operacional) eficientes. O balanceamento de carga deve ser cuidadosamente calculado para se evitar gargalos e congestionamentos no sistema de comunicação. Sempre que possível deve-se cuidar para estruturar os processos distribuídos para que haja uma sobreposição de cômputos e comunicação no nível das aplicações visando melhorar a eficiência do sistema. A granulosidade das tarefas concorre para tal balanceamento. Processos com muitas tarefas e processamento interno curto, ditos de granulosidade fina, requerem muita comunicação. Processos com tarefas relativamente grandes, que requerem bastante processamento interno, são ditos de granulosidade grossa, e requerem pouca comunicação. Portanto, quanto mais fina a granulosidade dos processos, menores as tarefas. Com tarefas menores torna-se mais fácil a distribuição de carga entre os processadores. Quanto mais bem distribuída a carga de processamento, melhor a taxa de uso do sistema e de seus recursos. Desta forma, pode-se dizer que quanto mais fina a granulosidade dos processos, melhor o desempenho do sistema. Paradoxalmente, quanto mais processos distribuídos no sistema, maior o número de mensagens trocadas entre eles. Quanto maior o fluxo de mensagens, maior o tempo gasto com comunicação no sistema. Conseqüentemente, menor a vazão e maior o tempo que o sistema requer para produzir as respostas desejadas. Por conseguinte podese inferir que quanto mais fina a granulosidade dos processos, pior o desempenho do sistema. O resultado disto é que a granulosidade dos processos deve ser ajustada de forma a tornar o sistema o mais eficiente possível. Outra questão que afeta a eficiência de um sistema operacional distribuído é sua tolerância a falhas. É desejável que um sistema operacional distribuído mantenha todos os processos em execução, ainda que algum(s) de seus componentes falhem. Para tanto, quando uma tarefa é atribuída a um processador ele deve comunicar isto a outro(s) processador(es) do sistema para que, caso o processador principal falhe, o outro possa continuar a execução da tarefa. Entretanto, isto envolve comunicação sem utilidade para a computação, o que reduz o desempenho geral do sistema. Flexibilidade

Um sistema operacional distribuído deve ser capaz de evoluir e migar, para se adaptar a variações tecnológicas. Deve ser possível adicionar serviços inexistentes e remover serviços obsoletos, preferencialmente sem a necessidade de recompilar e reinstalar o sistema, ou seja, o sistema deve ser modular e escalonável. Conforme mudam as plataformas de trabalho (o hardware) é desejável que se possa migrar o sistema a um baixo custo, isto é, re-escrevendo e recompilando o menor número possível de rotinas. Para atingir esta portabilidade o núcleo do sistema deve ser o menor possível. Além disto, o sistema deve ter interfaces comuns para suas bases de dados e seu sistema de arquivos, bem como deve fazer uso de protocolos de comunicação padronizados, resultando em um sistema capaz de administrar um conjunto heterogêneo de máquinas. As quatro propriedades recém discutidas, quais sejam, escalabilidade, modularidade, portabilidade e interoperabilidade são de crucial importância para garantir a capacidade de adaptação do sistema. Apesar dos sistemas operacionais com núcleo monolítico reinarem absolutos, suas características não favorecem ao requisito de flexibilidade. Estes sistemas são compostos por um sistema operacional centralizado, ao qual, são acrescidas funções para operação em rede e integrados serviços para operação remota. Os principais exemplos são os sistemas operacionais distribuídos derivados do UNIX. Por isto, a maioria dos sistemas operacionais distribuídos projetados desde o início, sem utilizar partes de sistemas pré-existentes, adota o modelo de micronúcleo, já que este, por não fazer quase 11

nada, é mais flexível. Todos os serviços prestados pelo núcleo de um sistema operacional micronúcleo está lá por ser muito difícil ou muito caro prestá-lo fora do núcleo. Os sistemas micronúcleo fornecem, fundamentalmente, quatro categorias de serviços, quais sejam: - mecanismos de comunicação de processos - mínimo de funções para gerência de memória - mínimo de funções para gerência de processos e escalonamento; e - funções de entrada e saída de baixo nível. As demais funções do sistema são fornecidas fora do núcleo, através de troca de mensagens com este. Entre estas funções encontram-se aquelas para gerência completa de processos, as de manipulação de chamadas ao sistema, e os sistemas de arquivos e de diretórios. Como praticamente todos os serviços são fornecidos a partir de processos clientes em nível de usuário, a alteração ou a ampliação de serviços não implica a parada do sistema para a carga de um novo núcleo. Foi mostrado que mesmo a aparente vantagem que teriam os sistemas monolíticos, qual seja, a de apresentar um melhor desempenho por não trabalhar a base de trocas de mensagens, não existe. Consistência

A operação de um sistema operacional distribuído envolve várias máquinas, muitas vezes com características heterogêneas, o que torna a interação entre os módulos do sistema por vezes, bastante complexa. Do ponto de vista do usuário, um sistema consistente deve permitir um uso uniforme e ter um comportamento previsível, apesar dessas possíveis características heterogêneas. Além disto, para garantir uma boa eficiência no uso do sistema, freqüentemente é necessário particionar os dados e fazer várias cópias deles. Isto requer mecanismos que viabilizem o controle de uso das diversas cópias, de modo a mantê-las sempre idênticas. A inexistência de informação sobre o estado global do sistema, bem como a ausência de um sincronismo geral, requer mecanismos especiais para o controle de concorrência, de forma a manter a integridade do sistema. Outra possível fonte de inconsistência no sistema é a possibilidade de falhas de seus componentes. O principal problema, neste caso, é a dificuldade, quando não a impossibilidade, de identificação da falha. Os três problemas mais comuns são: defeito em uma máquina, comprometimento de uma conexão e perda de uma mensagem. Exemplo (do Silberschatz): Sejam duas máquinas A e B, diretamente conectadas. Periodicamente elas enviam, uma à outra, uma mensagem x, indicando que estão ativas. Se, após um certo período, A não recebe a mensagem x de B, ela sabe que ocorreu algo, mas não sabe o que. Se enviar uma mensagem y para B, pela mesma conexão, e não receber resposta, continuará sem saber qual o erro. (se receber, provavelmente a mensagem x anterior havia se perdido). Se enviar a mensagem y por uma conexão alternativa e não receber resposta, novamente não tem como saber o que ocorreu. (se receber, provavelmente a conexão usual está com problemas). De qualquer forma são necessários procedimentos específicos para, adequadamente, reconhecer a falha, tratar os processos de forma a manter o sistema consistente e posteriormente recuperar o estado do sistema anterior à ocorrência da falha.  Robustez

Para ser robusto um sistema operacional distribuído deve estar disponível a maior parte do tempo, sempre apresentando dados confiáveis. Na teoria, uma das grandes vantagens de um sistema operacional distribuído é sua maior disponibilidade, geralmente resultante da redundância de seus componentes. Se um processador se mantém operante em 98% do tempo, um sistema com 4

12

processadores idênticos a este teria uma probabilidade de falhas de (0,02) 4 = 24 x (10-2)4 = 1,6 x 10-7 ~ 0,00002%, o que corresponde a uma disponibilidade em 99,99998% do tempo. Em outras palavras, se o monoprocessador falha uma hora para cada 50 horas de funcionamento, o quadriprocessador falharia uma hora para cada cinco mil horas de funcionamento, ou seja, estaria cem vezes mais disponível. Evidentemente, estes números são fictícios, apenas ilustrativos, para se ter uma idéia da possibilidade de uma maior disponibilidade de sistemas distribuídos, em relação a um monoprocessador. Esta maior disponibilidade só é conseguida às custas de redundância, como  já foi dito, o que, por sua vez, aumenta o risco de inconsistências no sistema. Falhas em canais de comunicação, em nós de processamento e perdas de mensagens são freqüentes em um sistema operacional distribuído. A robustez do sistema do ponto de vista de tolerância a falhas (sua confiabilidade) está vinculada à sua capacidade de reinicialização, em um estado no qual sua integridade seja preservada e seu desempenho seja pouco degradado. Se uma máquina falha, o sistema deve: - tirar esta máquina de operação; - transferir as tarefas que eram realizadas por ela para outra máquina; - reconfigurar o sistema sem a máquina falha; e - re-integrar a máquina ao sistema, após seu reparo. Tudo sem que os usuários percebam mais que uma, possivelmente pequena, variação no desempenho geral do sistema. Mesmo sem falhas explícitas, um sistema robusto deve poder tratar situações especiais e erros, tais como, mudanças na sua topologia, longos atrasos em mensagens ou a impossibilidade de localizar um servidor. Em outras palavras, deve ser capaz de controlar o acesso aos elementos do sistema. Finalmente, a robustez de um sistema também diz respeito aos mecanismos de proteção existentes, isto é, deve poder garantir a segurança das informações nele contidas, resguardando sua integridade, bem como a confidencialidade dos dados dos usuários do sistema. Transparência

Segundo Leslie Lamport: “um sistema distribuído é aquele no qual eu não posso fazer meu trabalho, pois, uma máquina que eu não conheço, não sei onde está e sobre a qual nunca ouvi falar, encontra-se fora do ar”. Para evitar este tipo de reação, os projetistas de sistemas operacionais distribuídos enganam a todos, fazendo com que um conjunto de máquinas seja visto pelos usuários como se fosse um sistema único, de tempo compartilhado. Neste contexto, pode-se dizer que transparência de um sistema operacional distribuído é sua capacidade de, por assim dizer, esconder dos usuários detalhes da implementação do sistema, em particular aqueles mais complexos (dependentes da máquina), na medida do possível apresentando aos usuários um modelo lógico da máquina como eles gostariam de operar e não como ela é realmente. Portanto, através do uso do conceito de transparência deve-se apresentar ao usuário o sistema mais simples possível sem, contudo, comprometer sua eficiência. A idéia de propiciar ao usuário um sistema lógico mais amistoso que o sistema físico, minimizando a necessidade dos usuários tomarem ciência do sistema físico, é análoga aos conceitos de virtualidade em sistemas convencionais e de abstração em linguagens de programação. Na página 30 do Chow & Johnson há definições de 10 aspectos de transparência, transcritos a seguir: 13

-

-

-

 Access transparency refers to the ability to access both local and remote system objects in a uniform way. The physical separation of system objects is concealed f rom the user.  Location transparency means that users have no awareness of object locations. Objects are mapped and referred to by logical names. This is sometimes called name transparency.  Migration transparency is an added property of location transparency where an object  is not only referred to by its logical name but can also be moved to a different physical location without changing the name. Migration transparency is also called location independence. Concurrency transparency allows the sharing of objects without interference. It is similar to the time-sharing concept in a broader sense.  Replication transparency exhibits consistency of multiple instances (or partitioning) of    files and data. It is closely related to currency transparency but is treated separately since files and data are special objects. Parallelism transparency permits parallel activities without users knowing how. Where, and when these activities are carried out by the systems. The parallelism might not be explicitly specified by the users. Failure transparency provides fault tolerance such that failures in the system can be transformed into graceful system performance degradation rather than disruptions, and  damage to the users is minimized. Performance transparency attempts to achieve a consistent and predictable (not  necessary equal) performance level even with changes of the system structure or load  distribution. In addition, the users should not experience excessive delays or variations in remote operations. Size transparency is related to modularity and scalability. It allows incremental growth of a system without the user’s awareness. The size of the system has no effect on the user’s perception of the system.   Revision transparency refers to the vertical growth of system as opposed to the horizontal growth in system size. Software revisions of the system are not visible to users.

Objetivos do sistema Eficiência operacional distribuído Transparências

Concorrência Paralelismo Desempenho

Flexibilidade Acesso Localização Migração Tamanho Revisão

Consistência

Robustez

Acesso Replicação Desempenho

Falha Replicação Tamanho Revisão

Serviços Um sistema operacional é um provedor de serviços. Serviços Primitivos: serviços fundamentais, os quais devem ser implementados no núcleo. - comunicação: primitivas send/receive (passagem de mensagens) - sincronização: semântica de comunicação ou servidor de sincronização. - servidor de processos: criação, eliminação e rastreamento de processos, alocando recursos tais como memória e tempo. Os servidores de processos interagem via primitivas de sincronização e comunicação para garantir a transparência do sistema.

14

Serviços de Sistema: serviços que podem ser implementados em qualquer lugar do sistema, mas que ainda desempenham funções básica da operação do sistema. - Servidor de nomes: mapeamento de nomes lógicos em endereços físicos. Relacionandoos com usuários, processos e máquinas. - Servidor de diretórios: relacionado com arquivos e portas de comunicação. - Servidor de rede: traduz e seleciona caminhos e faz roteamento da informação. - Servidor de tempo: relógios lógicos ou físicos, necessário para ordenação dos eventos. - Servidores de arquivos e de impressão. Serviços de Valor Agregado: são serviços não essenciais na implementação de um sistema distribuído, mas que são úteis no suporte a aplicações distribuídas. - servidor de grupos - servidor de web

15

Modelos de Arquiteturas De forma abstrata os modelos de arquitetura de sistemas distribuídos podem ser definidos com base em três entidades que são fundamentais para o seu funcionamento, quais sejam: - Arquivos, que são todos os conjuntos de dados que podem sofrer manipulação dentro do sistema. - Processos, que são todos programas em execução, os quais são responsáveis pela mudança de estado do sistema. - Portas, que são os caminhos disponíveis para a comunicação de dados entre os diversos processos do sistema. Os arquivos e os processos residem em estações de trabalho e trocam informações através de uma rede de comunicação. A forma como as estações de trabalho e a rede de comunicação são estruturadas define a arquitetura do sistema distribuído. Todavia, o usuário não se preocupa com o sistema físico propriamente dito, salvo com seu custo, com a aparência de alguns de seus componentes e com o desempenho geral do sistema, ainda que este nem sempre seja determinado pelo seu hardware . De qualquer forma, tem-se processos no sistema que utilizam serviços fornecidos por outros processos. Dependendo de especificidades geográficas ou características de desempenho necessárias, algumas estações de trabalho do sistema podem ser dedicadas à prestação de certos serviços especiais. As demais estações podem servir preferencialmente para processamento local, como um computador de uso exclusivo para determinado usuário, e ocasionalmente interagir, via malha de comunicação, com as demais estações que compõem o sistema distribuído. Há também o caso de estações sem disco, que servem apenas como terminais de servidores remotos e que dependem em tudo dos serviços prestados pela rede, inclusive para inicialização ( boot ). Este modelo é chamado de estação-servidor (wokstation-server ) e pode ser esquematizado como apresentado na Figura 1.

Estações

Portas

de

E s t a ç õ e s S e r v i d o r

Servidores

Figura 1 – Modelo Estação-Servidor O caso do modelo estação-servidor apresenta uma série de vantagens, como a possibilidade de realizar ao menos parte do processamento localmente. Desta forma, o volume de comunicação gerado no sistema é relativamente pequeno e o dimensionamento da malha de comunicação pode ser relaxado. Conseqüentemente, o custo do sistema é reduzido neste quesito. Por outro lado, o uso 16

de processamento localizado faz com que a maioria das estações estejam ociosas ao longo do tempo. Uma solução para este problema é concentrar todo o poder de processamento do sistema e deixar os usuários apenas com terminais, cuja inteligência se restrinja ao hardware / software necessários ao funcionamento eficiente do terminal de vídeo e da interface com a rede de comunicação. Neste caso, todo o restante do processamento é fornecido por um conjunto de processadores com base na demanda real dos usuários do sistema. Este modelo é dito poço de processadores ( processor-pool) e pode ser esquematizado como mostrado na Figura 2. Terminais

Portas

de

Servidores

Poço de processadore

Figura 2 – Modelo Poço de Processadores

Entretanto, se por um lado a utilização de um reservatório com processadores, no qual é realizado todo o processamento do sistema, otimiza o uso dos processadores, por outro lado, aumenta consideravelmente a sobrecarga na malha de comunicação. Resultados gerais mais adequados talvez possam ser obtidos por intermédio de um modelo híbrido, no qual há estações de trabalho com capacidade de processamento local as quais, formam um reservatório de processamento distribuído geograficamente. Os processos sendo executados em qualquer dessas estações podem migrar para outras estações que estejam ociosas ou com carga de trabalho menor. Esta migração deve ser controlada por alguma estratégia de compartilhamento de carga pré-definida para o sistema.

17

Comunicação Como o desempenho de um sistema distribuído está diretamente ligado à possibilidade de migração dos dados, sua rede de comunicação tem papel muito importante. Esta rede engloba elementos físicos utilizados na interconexão dos equipamentos e protocolos para disciplinar a comunicação entre os processadores. A Comunicação pode ser feita ponto-a-ponto, quando se estabelece uma ligação direta entre um par de processadores, ou multi-ponto, quando um conjunto de processadores é interligado através de um barramento ou chaves eletrônicas. Os barramentos são mais baratos, mas permitem apenas o compartilhamento no tempo. Já, as chaves, embora formem um sistema mais caro e complexo, possibilitam não somente a multiplexação no tempo como também no espaço. As redes de comunicação locais são restritas ao âmbito de empresas e geralmente têm um raio de abrangência de alguns poucos quilômetros. Estas redes normalmente utilizam a interconexão por algum tipo de barramento e fundamentam sua comunicação em protocolos, como o padrão 802.X do IEEE, como é o caso das redes Ethernet, token bus, token ring, FDDI (  fiber distributed data interface) e DQDB (distributed queue dual buses). Por outro lado, as redes dos serviços públicos de comunicação utilizam a ligação por chaves de barramentos cruzados ou de múltiplos estágios. Estas redes, incluem o ISDN ( integrated services digital network ), o SMDS (switched multimegabit data service) e o ATM ( asynchronous transfer  mode). O roteamento que no caso anterior é feito pela análise das mensagens que circulam no barramento, neste caso é feito pela programação das chaves que compõem a rede. A comunicação em sistemas distribuídos se dá em camadas, buscando propiciar a comunicação entre entidades de um mesmo nível ( peer-to-peer ). Por exemplo, usuários do sistema precisam trocar mensagens para se comunicar e devem ter a impressão de estarem falando diretamente um com o outro. Entretanto, como a distância não permite eles fazem uso do sistema. Para tanto, sem que o usuário perceba, o serviço do sistema que o atende passa a mensagem desejada para uma máquina hospedeira, a qual estabelecerá um canal de comunicação com uma máquina remota, servidora do interlocutor, a qual, por sua vez, providenciará para que a mensagem chegue ao seu destino. Contudo, as máquinas também não são capazes de se comunicarem a distância. Para que isto ocorra elas devem utilizar portas de comunicação, as quais encapsularão as mensagens das máquinas servidoras e estabelecerão uma comunicação ordenada através do sistema físico de comunicação. Neste rápido exemplo introdutório pode-se notar que foram estabelecidos três níveis (ou camadas) de comunicação, quais sejam: o de troca de mensagens entre os usuários; o de comunicação entre as respectivas máquinas hospedeiras; e o de coordenação pelas portas do sistema de comunicação. Mundo real Sistemas de comunicação computacional Exemplo de aplicações

Telefonemas Orientando a conexão

Cartas Sem conexão

Transferência de arquivos

Nível de rede Nível de hardware

circuito virtual Chaveamento de circuito

Divulgação do estado do sistema Datagramas Chaveamento de pacotes

As estruturas em camadas referidas no parágrafo anterior são chamadas de protocolos de comunicação. Protocolos de comunicação são conjuntos de regras que regulam a troca de mensagens, para prover um fluxo de informação ordenado e confiável, entre os processos comunicantes. Nos protocolos, camadas inferiores fornecem serviços usados para comunicação

18

entre camadas imediatamente superiores. Os principais protocolos são, como foi dito anteriormente, o RM-OSI da ISO e o TPC/IP do DoD. A seguir eles são descritos mais detalhadamente.

O modelo de referência da ISO Em 1977, sentindo a necessidade de padronizar a conexão de sistemas computacionais heterogêneos, a   International Standards Organization (ISO), através de seu comitê técnico de processamento de dados (TC97), criou um subcomitê (SC16), para estudar o problema. No ano seguinte, o SC16 apresentou uma proposta, aceita pelo TC97 em 1979. Esta proposta foi chamada de reference model for open system interconnection (RM-ISO) e foi aprovado como padrão ISO7498 em maio de 1983. Em setembro do ano anterior, o RM-OSI foi adotado como recomendação X.200 pelo CCITT. Este modelo é dividido em sete camadas, contemplando diversos níveis de abstração de um sistema distribuído, desde o nível físico até o de aplicações voltadas para o usuário. A seguir, apresenta-se uma descrição sucinta das sete camadas que compõem o RM-OSI. A camada mais baixa do RM-OSI é a física. Ela é a responsável pela interface entre os nós da rede e o meio físico de transmissão. Portanto, nela existe a preocupação com a transferência de bits (unidade de dados desta camada) diretamente através de uma conexão física. Desta forma, há um interesse especial na definição das características elétricas, mecânicas e funcionais necessárias para a transferência adequada de informações. Algumas características elétricas consideradas são a amplitude, o período e a freqüência dos sinais, o esquema de modulação e o modo de operação usados. Dentre as características mecânicas estão o número de pinos usados nos conectores, o tamanho dos conectores e de seus pinos, bem como a distribuição destes naqueles. Já as características funcionais têm a ver com os procedimentos necessários para o estabelecimento e o encerramento de conexões, por exemplo. Resumindo: Na camada física a preocupação recai sobre as características elétricas, mecânicas e funcionais necessárias para ativar, manter e desativar conexões físicas para a transmissão de bits entre nós da rede. A camada seguinte é a de enlace de dados. Uma vez que a camada física trata apenas da transmissão de seqüências de bits, a camada de enlace é a primeira do RM-OSI a dar alguma atenção ao significado de tais bits. Nesta camada, a unidade de dados é o quadro. Os quadros são compostos por conjuntos de bits, normalmente uns poucos milhares. A camada de enlace é a principal responsável pela transmissão confiável dos quadros. Para tanto, ela deve ser capaz de detectar e possivelmente corrigir erros que aconteçam eventualmente, o que é viabilizado pela inclusão de alguns bits de controle nas extremidades dos quadros, os quais permitem a identificação de seus limites e o tratamento dos erros. Como existe a possibilidade de perda de quadros durante a transferência, esta camada deve oferecer mecanismos que permitam o reconhecimento dos quadros corretos e a retransmissão dos incorretos e dos faltantes. Neste caso, como é possível a recepção de quadros duplicados, a camada de enlace deve estar apta a identificar esta situação. Em resumo: A camada de enlace de dados deve oferecer um serviço de transferência confiável de dados entre os nós da rede, suportada pela camada física subjacente. A camada de rede é a terceira no RM-OSI. A função principal desta camada é controlar o funcionamento da malha de comunicação. Ela é responsável pelo estabelecimento e pela manutenção do fluxo de pacotes através da rede. Devido a uma falta de consenso no âmbito da ISO, foram adotados dois tipos de serviços oferecidos por esta camada, quais sejam: circuitos virtuais e datagramas. Como não há necessidade de uma ligação física direta entre os usuários, uma tarefa importante desta camada é o roteamento de pacotes. Outras questões relativas ao tráfego são o tratamento de congestionamentos, o controle da vazão e a contabilidade dos serviços. Congestionamentos ocorrem quando um nó da rede é forçado a trabalhar no seu limite de capacidade, causando um engarrafamento das informações que por ela trafegam. O controle de vazão refere-se ao controle do desequilíbrio entre as velocidades de transmissão de um nó e de 19

recepção de outro. A contabilidade deve-se à necessidade de contagem dos pacotes enviados pelos diversos usuários, de maneira a produzir informações que permitam a cobrança dos serviços prestados. Em suma: a camada de rede é responsável pelo controle das operações de comunicação da rede, incluindo o empacotamento de mensagens, o roteamento dos pacotes e o controle de fluxo. O RM-OSI tem como quarta camada a de transporte. Esta camada inicia uma mudança de ênfase da ISO. Inicialmente, preocupada com as funções de hardware e software necessárias para garantir a transmissão adequada de pacotes, quadros e bits, a partir desta camada a ISO passa a dirigir sua atenção às funções mais voltadas à garantia de uma transferência de dados entre os usuários finais, sem que estes tenham que preocupar com detalhes de operação da rede em níveis mais baixos de abstração. A função básica desta camada consiste da eventual necessidade de divisão das mensagens oriundas da camada superior em pacotes e do envio destes à rede, garantindo sua transmissão adequada e sua chegada correta ao destinatário desejado. Nesta camada a ISO define apenas serviços orientados a conexão. Portanto, ela pode ser bastante simples se a camada de rede oferecer um serviço de circuitos virtuais, mas será bastante mais complexa se o serviço oferecido pela camada de rede for do tipo datagrama. Pode-se usar qualquer dos três tipos de correspondências entre as conexões de sessão e de transporte. Entretanto, tudo deve ser feito de modo que as camadas superiores fiquem isoladas das trocas inevitáveis na tecnologia de hardware subjacente. A camada de transporte é a primeira realmente fim-a-fim, isto é, as camadas inferiores têm, digamos, consciência de que suas unidades de dados passam por vários nós intermediários antes de chegarem ao seu destino, já, do ponto de vista das camadas superiores, as mensagens vão diretamente da fonte ao destino, sem a intervenção de elementos intermediários. Sintetizando: a camada de transporte controla a transferência de dados do sistema-fonte ao sitema-destino, através do estabelecimento, da manutenção e da liberação de conexões bidirecionais entre um par de entidades de sessão, para aliviar as entidades das camadas superiores das tarefas de transporte de dados entre elas. A camada seguinte é a de sessão. Nesta camada há uma maior preocupação com as aplicações propriamente ditas, do que com a mera comunicação. Esta camada administra a sessão, unindo e desunindo duas entidades para relacionamento. Para isto, verifica a autenticidade das entidades que desejam se comunicar. Desta forma, serve de interface entre os usuários e a rede. Controla a troca de dados, delimita e sincroniza as operações sobre estes dados e decide sobre o tipo de comunicação a ser usado ( simplex, semi-duplex ou duplex). Em síntese: a camada de sessão organiza e sincroniza o diálogo e gerencia a troca de dados entre duas entidades da camada de apresentação. A próxima camada na hierarquia do RM-OSI é a de apresentação. Esta camada ocupa-se da interpretação da sintaxe dos dados, transformando-os e formatando-os sempre que necessário, para resolver problemas de diferenças entre as sintaxes dos diversos sistemas interligados. Nesta camada são usadas técnicas de compressão de dados, para reduzir o número de bits transmitidos, e de criptografia, para aumentar a privacidade na transferência destes. Resumindo: a camada de apresentação visa gerenciar as estruturas de dados abstratas (como letras, números e símbolos) convertendo-as da representação interna ao computador, para aquela na rede e vice-versa. A camada de aplicação é a mais alta do RM-OSI. Todas as outras camadas existem para dar suporte a esta. Seus serviços são diretamente observados pelos usuários. As entidades desta camada são   janelas que viabilizam a representação de cada usuário final, dentro da rede. As funções desta camada são totalmente dependentes das aplicações. Alguns exemplos clássicos de aplicações são: - terminais virtuais: fazem a conversão de informações transferidas entre dois terminais de modo a possibilitar sua legibilidade em ambos, independentemente de suas diferenças físicas; - transferência de arquivos: faz a conversão do conteúdo dos arquivos transferidos entre dois equipamentos de forma a compatibilizar as diferenças de representação existentes, oferecendo mecanismos básicos para a manipulação de arquivos a distância, como abertura, edição e fechamento de arquivos; 20

-

tratamento de mensagens: permite a preparação, disseminação (remessa e recebimento) e o armazenamento de mensagens ou arquivos, entre os usuários, incluindo-se neste serviço o correio eletrônico o teletexto e o facsímile; e Submissão remota de tarefas: serviço que permite submeter tarefas a máquinas diferentes, utilizando sistemas operacionais distintos, compartilhando recursos e capacidade de processamento, de forma transparente.

O modelo TCP/IP do DoD Em dezembro de 1969 o departamento de defesa (DoD –  Department of Defense) norte americano colocou em operação a rede Arpanet. Inicialmente consistia de quatro nós, chamados de IMPs ( Inerface Message Processors). Originalmente, os IMPs eram minicomputadores Honeywell DDP516, com memória de 12k palavras de 16 bits. Atualmente, constitui-se de muitos milhares de IMPs espalhados pelo mundo todo. Como a Arpanet é anterior ao RM-OSI, ela não segue esta padronização. Contudo, ela possui protocolos que correspondem, aproximadamente, às camadas do modelo da ISO. Com o passar do tempo e com sua internacionalização, a Arpanet deu origem à Internet e os protocolos de comunicação nela utilizados passaram a ser conhecidos pela sigla TCP/IP, correspondente às duas camadas principais do conjunto de protocolos da Arpanet, como será visto em seguida. O nível físico não é definido pela Arpanet pois baseia-se em ligações telefônicas alugadas. Vista em âmbito da Internet, sua camada física corresponde àquelas das redes locais a ela conectadas. O protocolo mais baixo, IMP-IMP, é uma mistura aproximada das camadas dois e três do RM-ISO, incluindo um mecanismo de roteamento bastante elaborado e um mecanismo para a verificação da recepção pelo IMP-destino, de cada pacote enviado pelo IMP-fonte, o que não existe no ambiente da ISO. A camada de rede é chamada, na Arpanet, de IP (  Internet Protocol), sendo um serviço do tipo datagrama, projetado para ligar um vasto número de redes locais e de longa distância. À camada de transporte corresponde o TCP ( Transmission Control Protocol), que embora eqüivalha, genericamente, ao modelo da ISO, pouco tem em comum com aquele, no que se refere a formatos e demais detalhes. As camadas de sessão e apresentação inexistem na Arpanet, já que sua falta nunca foi sentida nestes mais de 30 anos de funcionamento. Também, não existe uma camada de aplicação propriamente dita, como entendida no âmbito do RM-OSI. O que existe é uma série de serviços oferecidos por meio de vários protocolos bem estabelecidos, tais como: o ftp ( file transfer   protocol) para transferência de arquivos; o SMTP (simple mail transfer protocol) para correio eletrônico; e o telnet (terminal emulation) para terminais virtuais. A Figura 3 mostra a arquitetura básica do modelo TCP/IP do DoD, em comparação com a RM-OSI/ISO.

21

aplicação

ftp

smtp

telnet

outras

apresentação sessão transporte

TCP

rede

IP IMP

enlace física

IEEE 802

RM-OSI da ISO

X.25

outros

TCP/IP do DoD

Figura 3 – Comparação entre o RM-OSI da ISO e o TCP/IP do DoD

22

Aspectos de projeto De modo geral, um sistema distribuído pode ser visto como um conjunto de processos que são executados de forma concorrente, cada um dos quais acessando um sub-conjunto de recursos do sistema por meio de um mecanismo que envolve a troca de mensagens através de uma malha de comunicação a qual, nem sempre, é confiável. A partir desta observação pode se identificar uma série de aspectos, que devem ser considerados durante o projeto desses sistemas. O primeiro desses aspectos conduz à questão da identificação e da modelagem dos objetos no sistema. Tais objetos são os processos, os arquivos, a memória, os dispositivos periféricos, a rede de comunicação e os processadores. A cada objeto do sistema é associado um conjunto de operações que possibilita o acesso a ele. Para organizar tal acesso, a cada objeto é associado um processo, o qual fica de responsável pela manipulação do objeto. Esses processos que gerenciam o acesso aos objetos são as únicas entidades visíveis no sistema digital e são chamados de servidores dos respectivos objetos. Eles são ditos as únicas entidades visíveis, porque quando um processo qualquer precisa que algum serviço seja executado por outro objeto do sistema ele o solicita enviando uma mensagem de requisição ao servidor do objeto. Este recebe as requisições vindas de diversos processos do sistema, organiza-as e aciona o objeto adequado no seu devido tempo. Após a execução da tarefa solicitada, ele retorna a resposta ao processo que originou o pedido. Portanto, em um sistema digital há servidores de processos, servidores de arquivos, servidores de rede, servidores de impressão, e assim por diante. Por outro lado, os objetos podem residir nas mais diferentes partes do sistema e a referência a cada um deles deve ser inequivocamente distinguida. A identificação dos objetos pode ser feita por nome, que é mais fácil e intuitiva, por endereços, que é mais precisa e contém informação estrutural embutida, e por serviços, que é pouco usada. Como foi mencionado anteriormente, do ponto de vista dos elementos do sistema, a rede de comunicação pode ser abstraída por suas portas. Estas portas correspondem a endereços lógicos de objetos no sistema. Num nível de abstração mais elevado estes endereços lógicos são associados a nomes de objetos do sistema distribuído. Ao contrário, num nível de abstração mais baixo os endereços lógicos correspondem a endereços físicos dos objetos. Os processos responsáveis pelo mapeamento de nomes em endereços lógicos do sistema, e vice-versa, são chamados de servidores de nomes e os processos responsáveis pelo mapeamento entre endereços lógicos e físicos são ditos servidores de rede. Outro aspecto relevante do projeto de um sistema distribuído diz respeito ao modo como é feita a coordenação da interação entre os objetos do sistema e como eles fazem para se comunicar. Para que eles possam interagir é necessário sincronizar o funcionamento dos objetos, caso contrário corre-se o risco de solicitar algo a determinado objeto quando ele não é mais capaz de executar a tarefa solicitada ou de receber informações de objetos quando esta não faz mais sentido para o objeto que a solicitou. O grande problema para a sincronização em sistemas digitais é a ausência de conhecimento sobre o estado global do sistema. Portanto, é necessário impor certos requisitos para a sincronização, tais como: - Barreira de Sincronização: um conjunto de processos deve atingir um ponto de sincronização comum antes de continuar. - Condição de coordenação: um processo deve esperar por uma condição que outro processo iterativo ajustará assincronamente, para manter uma ordem de execução. - Exclusão Mútua: processos concorrentes devem ter acesso exclusivo a certos recursos compartilhados do sistema. Além disto, o uso de mecanismos para prevenir e evitar bloqueios fatais, isto é, a ocorrência de uma espera circular de processos por objeto do sistema, mesmo atendendo aos requisitos de sincronização mencionados, pode não ser praticável. Às vezes, até mesmo a detecção e a recuperação do sistema após a ocorrência de um bloqueio fatal não é trivial, devido à falta de 23

conhecimento sobre o estado global do sistema. Uma forma de contornar este problema é utilizando protocolos de entendimento. Tais protocolos usam algoritmos com troca de mensagens contendo informações sobre estados locais do sistema, para atingir um consenso sobre seu estado global. A comunicação no sistema, que em seu nível mais baixo se dá através de mecanismo de troca de mensagens, em seus níveis mais altos, para atingir a transparência desejada no sistema distribuído, lança mão de modelos como cliente/servidor e chamadas a procedimentos remotos. Estes modelos usam o conceito de solicitação de serviços a servidores do sistema em moldes semelhantes aos usados para fazer chamadas a rotinas, das quais recebem os resultados. Resolvida a questão do compartilhamento, ou da multiplicação de cópias dos objetos, vem à baila a questão de como gerenciá-los de forma controlada e como distribuir a carga de processamento pelo sistema. Como a memória está distribuída pelos processadores do sistema, deseja-se manter a transparência apresentando-a ao usuário como uma grande memória compartilhada sem sobrecarregar o sistema de comunicação de forma significativa. Além disto, os protocolos de comunicação utilizados devem manter a consistência dos dados compartilhados e a coerência dos dados para os quais existam várias cópias no sistema, sempre considerando que há uma migração dos processos pelo sistema. Por sua vez, a carga de processamento pode ser distribuída estática ou dinamicamente. No primeiro caso, a distribuição estática, é dita escalonamento e visa minimizar o tempo necessário para completar as tarefas de um conjunto de processos. O principal problema a ser equacionado é uma forma de reduzir este tempo, mantendo o mínimo de sobrecarga no sistema de comunicação. No segundo caso, a distribuição dinâmica, é dita compartilhamento de carga e visa maximizar o uso de um conjunto de processadores. O principal desafio aqui é ajustar os mecanismos que implementam a estratégia considerada mais adequada para efetuar a migração dos processos. Finalmente, há o aspecto de como proteger os objetos do sistema e garantir sua segurança. A segurança de um sistema distribuído está sempre ameaçada, porque o sistema é aberto, isto é, permite o acesso de vários usuários, em certos casos e com certas restrições, mesmo usuários não cadastrados podem acessar o sistema. Alguns usuários podem violar intencionalmente a segurança do sistema, causando problemas para os demais. Todavia, há também os problemas causados aos sistema pelo seu mau uso, não intencional, provocando falhas no sistema. O problema da ocorrência de falhas no sistema pode ser amenizado pelo emprego de objetos redundantes no sistema. Já a questão da segurança passa pela autenticação e autorização dos usuários do sistema. A autenticação requer a identificação correta de clientes, serviços e mensagens que circulam no sistema. A autorização tem a ver com o controle de acesso à rede física, controle este que é dificultado por lidar com computadores heterogêneos, sujeitos administrações de características diversas e, por conseguinte, usando modelos de segurança diferentes.

24

Modelo Cliente/Servidor O modelo cliente/servidor é um paradigma de programação, que representa as interações entre processos e estruturas do sistema. Este modelo pode ser usado para melhorar a estrutura do sistema operacional, movendo-se a maior quantidade possível de funções para níveis mais altos do sistema, reduzindo o tamanho do núcleo. Processos do sistema são chamados de clientes, quando usam serviços, e de servidores, quando fornecem serviços. Para pedir um serviço um processo cliente solicita-o a um processo servidor o qual, após realizar o serviço solicitado, envia a resposta ao cliente. Desta forma, o núcleo precisa apenas gerenciar a comunicação entre clientes e servidores. Como tudo o que o usuário percebe é a comunicação lógica entre cliente e servidor, este modelo facilita a implementação do conceito de transparência. O modelo cliente/servidor não é baseado no estabelecimento de uma conexão. Portanto, pode ser implementado sobre um canal de comunicação real por passagem de mensagens. Tudo que deve ser implementado no núcleo são as primitivas de comunicação send  e receive, não importando se o serviço de transporte utilizado é ou não orientado a conexão, ou se a mensagem é transferida para um servidor na mesma máquina ou em uma máquina remota. A simplicidade deste modelo é tal que, para maior eficiência, pode ser implementado sobre um protocolo que corresponde apenas às três camadas inferiores do modelo OSI. Tamanha é a simplicidade do modelo que a própria resposta do servidor é usada como confirmação do recebimento da solicitação. As primitivas de comunicação podem ser classificadas segundo três critérios, quais sejam: o estado em que ficam os processos durante a transmissão de uma mensagem, a forma como as mensagens são disponibilizadas, e a confiabilidade do mecanismo de troca de mensagens. De acordo com o primeiro critério as primitivas podem ser classificadas em bloqueadoras, quando o processo fica paralisado durante a transmissão da mensagem, e não bloqueadoras, quando o processo fornece uma cópia da mensagem ao sistema de comunicação, por ocasião da solicitação do serviço, e segue seu processamento, enquanto o sistema de comunicação se encarrega de tratar da solicitação. De acordo com o segundo critério, as primitivas podem ou não se utilizarem de buffers para comunicação. Caso não utilizem buffers, cada processo deve informar ao processo remoto, através da primitiva receive, um endereço onde este processo deve armazenar a mensagem enviada. O problema é que se a mensagem chega antes do processo que deve recebê-la emitir o comando receive, a mensagem que está chegando pode ser perdida. Para reduzir o risco de perda de mensagens por não saber onde armazená-las, pode-se criar um buffer durante o estabelecimento da comunicação. Este buffer é usualmente conhecido como caixa postal. Desta forma, o processo que enviar uma mensagem sabe, de ante mão, onde armazená-la e o processo receptor também sabe onde deve procurá-las. Ainda há o risco de perda de mensagens se um processo tentar acessar uma caixa postal cheia, mas este risco é muito reduzido se o tamanho da caixa postal for definido adequadamente. O terceiro critério diz respeito à confiabilidade das primitivas. Em princípio, devido à simplicidade do modelo cliente/servidor, admite-se que ele não é confiável, pois usa-se a resposta como confirmação do recebimento da solicitação, ficando o servidor, sem qualquer confirmação do recebimento da resposta. Esta confiabilidade pode ser aumentada se ao receber a resposta e repassá-la ao cliente, o núcleo do cliente confirmar seu recebimento ao núcleo do servidor. Todavia, ainda há margem para dúvidas, pois ao utilizar a resposta como confirmação do recebimento da solicitação, o sistema abre a possibilidade do cliente receber uma mensagem do 25

servidor que não é a resposta à sua solicitação e interpretá-la como se fosse. Para melhorar mais ainda a confiabilidade do sistema, reduzindo o risco de falsas interpretações, pode-se determinar que os recebimentos tanto da solicitação de serviço quanto da resposta do servidor sejam explicitamente reconhecidos pelos núcleos correspondentes.

26

Serviços de Tempo Em qualquer sistema é necessário que se possa determinar quando um evento ocorre e quanto tempo ele demora, para que se possa definir que evento ocorre primeiro. É importante saber a ordem de ocorrência dos eventos em um sistema, por exemplo, para saber qual evento requisitou acesso a algum recurso do sistema primeiro. Imagine-se que dois processos compartilham uma variável; um deles deseja ler o valor da variável e outro deseja atualizar este valor. Imagine-se que a variável represente a existência ou não de incêndio nos tanques de um posto de combustíveis. Imagine-se que um processo A leia esta variável periodicamente e, caso haja algum problema, tome as providências cabíveis para combater o incêndio. Imagine-se que um processo B escreva nesta variável sempre que detectar perigo de incêndio no compartimento dos tanques. Imagine-se que o processo B tenha detectado um problema instantes antes do processo A tentar ler a variável, mas que por algum defeito no serviço de tempo do computador seja concedido acesso à variável ao processo A antes do B. Então o processo A verificará que não há problemas com o tanques e aguardará a próxima ocasião para ler a variável. Logo em seguida o processo B atualizará a variável, indicando a existência de fogo no compartimento dos tanques, mas nenhuma ação de combate será tomada enquanto o processo A não voltar a ler a variável, o que poderá ocorrer tarde demais. Certamente, este exemplo é um pouco exagerado, pois há outras formas mais seguras de se noticiar um evento de alto risco como o mencionado. Todavia, ele ilustra a necessidade de se garantir o correto ordenamento dos eventos em um sistema de computação. Entretanto, isto pode ser uma tarefa difícil, sobretudo em sistemas distribuídos, nos quais as informações relevantes estão espalhadas pelas máquinas. Os processos em um sistema distribuído tomam decisões baseados exclusivamente em informações disponíveis na máquina em que estão sendo executadas. Cada máquina tem o seu relógio, não existindo uma fonte comum de tempo ou qualquer outra fonte precisa, que forneça um tempo global confiável. Por mais precisos que sejam os relógios internos de cada máquina e ainda que se conseguisse sincronizá-los no início do funcionamento do sistema, há uma discrepância entre os períodos de oscilação dos seus cristais a qual, com o transcorrer do tempo, aumenta sistematicamente a diferença entre os tempos instantâneos reais das diversas máquinas. Suponham-se dois processos sendo executados em máquinas diferentes, que devem cooperar entre si. Suponha-se que as informações a serem trocadas dependam do exato ordenamento, por exemplo, suponha-se que os processos estejam cooperando para montar uma ponte, utilizando estruturas pré-fabricadas. Um processo A é responsável por colocar parte do leito da ponte sobre um pilar, a ser colocado por outro processo B. Suponha-se que o pilar seja colocado pelo processo B após 5 intervalos de tempo e que o processo A seja programado para colocar o leito sobre este pilar no final do sexto intervalo de tempo. Se cada processo seguir o seu relógio interno, os quais foram calibrados com um relógio mestre no início do funcionamento, e que devido às diferenças nos respectivos cristais funcionam segundo o diagrama abaixo, a ponte vai cair, com certeza. Notese, no diagrama, que o instante previsto para a instalação do pilar corresponde ao quinto período do tempo real e o instante previsto para a instalação do leito da ponte corresponde ao sexto período do tempo real. Todavia, o instante real da instalação do leito corresponde ao sexto período da linha correspondente ao relógio do processo A e o instante real da instalação do pilar corresponde ao quinto período da linha correspondente ao relógio do processo B. Os dois instantes reais de instalação estão atrasados em relação ao tempo real e, pior que isto, o momento real da instalação do pilar ocorre depois da instalação do leito, o que significa que provavelmente o pilar será colocado sobre o leito da ponte e não por baixo deste, como previsto. Tempo real Relógio de A Relógio de B

1 1

2

3 2

1

4

5 4

3 2

3

6 5

7

8 6

4

9

10 8

7 5

6

27

Portanto, embora os relógios físicos permitam uma boa aproximação do tempo real tanto para medir instantes quanto intervalos de tempo eles devem concordar com determinada marcação de tempo a qual não pode diferir do relógio real mais do que um determinado valor. Usam-se então temporizadores compostos por um cristal, um contador e um registrador de retenção. A cada oscilação o cristal decrementa o contador em uma unidade. Ao atingir o valor zero, o contador provoca uma interrupção de tempo e o valor armazenado no registrador de retenção é recarregado no contador. Este processo pode ser repetido indefinidamente. O sinal de interrupção pode ser transmitido para o restante do sistema e, periodicamente, re-sincronizar os relógios. Desta forma, poder-se-ia “concordar” com uma marcação de tempo, ainda que ela não fosse necessariamente correspondente ao tempo real. Este relógio lógico poderia, ao menos, preservar o ordenamento dos eventos. Contudo, este relógio lógico sofre de outro problema que é o atraso de comunicação. Para sincronizar os relógios das máquinas do sistema uma delas, eleita como servidora de tempo, deve transmitir periodicamente uma informação de sincronismo às demais. Não obstante, ela está a distâncias diferentes das outras máquinas e o volume de comunicação de cada uma também é distinto. Conseqüentemente, o atraso para que a informação com o sincronismo de tempo chegue a cada uma das máquinas é diferente o que provoca a incerteza nas marcações individuais de tempo. Desta forma, não adianta utilizar um relógio atômico (de Césio 133) como marcador de tempo físico, pois sua precisão seria perdida na disseminação da informação pelo sistema. Para contornar este problema Leslie Lamport sugeriu um esquema que possibilita a ordenação total de eventos no sistema, baseado em uma relação de acontecimento/anterioridade. Sejam três processos P1, P2 e P3 cujos tempos seguem conforme tabela abaixo. Suponha que no instante 6, P1 envia uma mensagem A para P2. Suponha que no instante 24, P2 envie uma mensagem B para P3. As mensagens levam a informação do instante de tempo em que foram emitidas. O tempo de trânsito dependerá do relógio considerado. No caso das mensagens A e B, levando em conta o relógio da máquina receptora das mensagens, tem-se tempos de trânsito iguais a 10 e 16 unidades de tempo, respectivamente. No instante 60 o processo P3 envia uma mensagem C para o processo P2. Contudo, esta mensagem será recebida pelo processo P2 quando seu relógio marcar 56 unidades de tempo. Isto corresponde a um tempo de trânsito negativo. Como se sabe não ser possível tratar tempos negativos, o receptor (no caso P2) incrementa seu relógio. Como P2 sabe que a mensagem C partiu de P3 no instante 60 e que ela levou algum tempo para transitar entre os dois processos, P2 atribui a seu relógio o valor 61, correspondendo ao instante de saída da mensagem mais um intervalo de comunicação mínimo de uma unidade de tempo. A partir daí ele volta a incrementar seu relógio com seu período normal. Situação semelhante ocorre se no instante 69, P2 enviar uma mensagem D para P1, o qual a receberá no instante 54 e atualizará seu relógio para 70. Para evitar ambigüidades, caso um processo receba mais de uma mensagem no mesmo instante, ele associará ao tempo de cada mensagem o número do processo que a originou. Desta forma, se duas mensagens forem recebidas pelo processo P3 no instante de tempo 50, uma originária de P1 e outra de P2, elas receberão o identificador de tempo 50.1 e 50.2, respectivamente.

28

P1

P2

P3

0 6 12 18 24 30 36 42 48 54 60

0 8 16 24 32 40 48 56 64 72 80

0 10 20 30 40 50 60 70 80 90 100

A

D

B C

P1

P2

P3

0 6 12 18 24 30 36 42 48 70 76

0 8 16 24 32 40 48 61 69 77 85

0 10 20 30 40 50 60 70 80 90 100

A

D

B C

29

Ramificações no Fluxo de Controle de Processos Processos são as entidades mais fundamentais num sistema operacional. Processos concorrentes são programas em execução assíncrona que interagem, cada qual com seu próprio espaço de endereços. Para estender a concorrência internamente aos processos, muitos sistemas operacionais modernos possibilitam ramificações no fluxo de controle dos processos, as quais são mais conhecidas pelo termo inglês threads. Threads são processos leves que compartilham um espaço de endereços lógicos. Threads podem ser implementados tanto no espaço de usuário quanto no núcleo do sistema. A implementação de threads no espaço de usuários é flexível, pois suas incorporações e posterior atualização em sistemas existentes é relativamente simples, mas para se obter eficiência deve-se implementá-los no núcleo. O mecanismo de threads funciona como descrito a seguir. Ao ser executado, um programa pode gerar ramificações no seu fluxo de controle. Essas ramificações têm seus estados locais individuais, mas permanecem associados ao processo que as gerou. Cada um desses sub-processos, os threads, possui um bloco de controle (TCB – Thread Control Block ) semelhante ao que existe para processos. Todavia, como os threads são processos leves, associados aos processos geradores, seus TCBs incluem informações locais reduzidas, tais como o contador de programa, apontadores de pilha e o conjunto de registradores. Como só possuem informações locais, armazenadas em certos registradores da UCP, compartilhando o restante das informações de contexto com o processo gerador, a mudança de contexto de threads é mais rápida que a do processo principal. Exemplificando o que foi mencionado no parágrafo anterior, o bloco de controle de processos deve conter informações completas sobre o processo. Tais informações podem ser divididas em dois grupos: o das informações compartilhadas com os threads e o das informações específicas sobre o processo. Por compartilharem seu espaço de endereçamento com os threads, os processos funcionam como se fossem máquinas virtuais onde rodam os threads. Desta forma, os blocos de controle destas ramificações armazenam apenas as informações individualizadas, que não podem ser compartilhadas com o processo que as gerou. Então as informações mencionadas poderiam ser divididas como sugerido na tabela a seguir. Todas a informações específicas contidas no TCB são equivalentes às contidas no PCB. Uma diferença marcante entre processos e suas ramificações é que, enquanto os processos oscilam entre três estados, os threads incluem um quarto estado, o concluído. Portanto, os threads podem assumir qualquer um dos seguintes estados: - pronto: em condições de ser executado, aguardando o momento adequado para usar o processador; - ativo: em execução no processador; - bloqueado: sem condições de ser executado, esperando que outra ramificação libere algum recurso que necessita, para poder voltar ao estado de pronto para execução; e - concluído: terminou o processamento e aguarda a leitura de resultados obtidos pelo processo gerador. Quando o objetivo é a agilidade no processamento, os threads devem ser implementados no espaço de usuários. Desta forma, ao contrário dos processos, que têm seu funcionamento controlado diretamente pelo sistema operacional, o funcionamento dos threads é supervisionado pelo procedimento de tempo de execução, que serve como interface entre a “máquina virtual” (processos) onde rodam os threads e o sistema operacional.

30

Nos Processos (PCB) Informações Compartilhadas Espaço de Endereçamento Variáveis Globais Arquivos Abertos Temporizadores Sinais Semáforos Contabilização de Recursos

Informações Específicas Processos Filhos Contador de Programa Pilha Conjunto de Registradores Estado

Nas Ramificações (TCB) Informações Compartilhadas Informações Específicas Processos Filhos Contador de Programa Utiliza informações do processo que a gerou Pilha Conjunto de Registradores Estado Como, ao serem implementados no espaço de usuários o sistema operacional não “enxerga” os threads, cada processo tem que multiplexar entre seus threads a quota de tempo que recebe do processador. O escalonamento dos threads e as respectivas chamadas ao sistema são tratados pelo procedimento de tempo de execução. Como a chamada efetiva ao sistema operacional provocaria uma interrupção na execução do processo gerador e, conseqüentemente, de todos os demais threads, quando uma ramificação faz uma chamada ao sistema operacional sua requisição é colocada em uma fila FIFO e o thread  é bloqueado, ativando-se outra ramificação que esteja pronta para execução. Quando se esgotarem os threads executáveis ou a quota de tempo ao sistema operacional na ordem em que foram feitas e o processo é então bloqueado. Deste modo, as ramificações não podem usufruir do sistema de interrupções do sistema operacional e, por conseguinte, não são preemptivas. Isto significa que uma vez ativadas elas só param de executar se solicitarem algum recurso do sistema, se a quota de tempo do processo se esgotar ou se terminarem sua execução. Por outro lado, se a questão é eficiência os threads podem ser implementados diretamente no núcleo de modo que possam ser “vistos” pelo sistema operacional. Neste caso, eles passam a ser tratados como processos, possibilitando maior flexibilidade no bloqueio dos threads e mais eficiência em seu escalonamento. Como as ramificações passam a poder fazer chamadas diretas ao sistema elas também passam a ser sensíveis ao sistema de interrupções passando, desta feita, a ser preemptíveis, sem que para tanto seja necessário interromper a execução do processo que as gerou, isto é, o sistema operacional pode interromper um thread  mantendo o processo que o gerou e suas outras ramificações em execução. Então os threads competem em pé de igualdade com os processos por ciclos do processador; em contrapartida, o sistema perde em portabilidade, as mudanças de contexto dos threads passam a ter a mesma complexidade das dos processos e a concorrência fica reduzida a dois níveis, quais sejam, processos e threads. Uma aplicação deste mecanismo é na implementação de processos servidores que prestam serviços semelhantes a vários processos clientes. Se o servidor for único e deve esperar por uma operação ou por um resultado, as requisições feitas a ele devem ser bloqueadas, prejudicando o processamento de alguns clientes. Se forem usadas várias cópias do servidor, para minimizar o problema de bloqueio de requisições, tais cópias utilizam espaços de endereçamento diferentes, criando problemas de consistência. O emprego de ramificações na estrutura de controle do servidor permite o agrupamento dos threads num mesmo espaço de endereçamento admitindo o acesso

31

concorrente de vários clientes e um único servidor. Destacam-se dois tipos de ramificações, como descritas nos próximos dois parágrafos. O primeiro exemplo envolve ramificações estáticas idênticas, como nos servidores de terminais. Quando um cliente (usuário) chama um servidor, este cria uma ramificação que passa a atender aquele usuário enquanto ele estiver conectado. Como as ramificações são locais ao servidor elas não necessitam chamar o núcleo do sistema operacional para sincronizá-las. Como elas compartilham o mesmo espaço de endereçamento, quando precisam acessar algum recurso compartilhado a exclusividade do acesso é feita via monitores ou semáforos. Como a ramificação fica no sistema enquanto o usuário estiver conectado, ela ocupa espaço de memória ainda que o usuário não requisite nenhuma operação do servidor. O segundo exemplo envolve ramificações dinâmicas, como nos servidores de arquivos. Nestes servidores têm-se várias operações como leitura ou escrita. Têm-se ainda um processo principal que serve como coordenador das atividades. Para cada operação solicitada ele cria uma ramificação, que lhe devolve o controle logo após sua criação. Se, por exemplo, forem solicitadas diversas operações de leitura, serão geradas várias ramificações do processo responsável pela operação de leitura. Estas ramificações serão executadas independentemente umas das outras e se extinguirão tão logo terminem o serviço para o qual foram criadas. Desta forma, a utilização do espaço de memória pode ser otimizada, reaproveitando-se a porção de memória liberada pelos threads, para uso por novos threads, permitido a redução no custo do sistema. Resumindo, podem se destacar quatro aspectos no projeto de mecanismos de ramificações do fluxo de controle de processos, são eles: a gerência, o uso de regiões críticas, o emprego de variáveis globais e a localização da implementação. Sob o ponto de vista gerencial as ramificações podem ser criadas estática ou dinamicamente. No caso estático são criadas em tempo de compilação, com pilha de tamanho fixo, o que torna esta abordagem simples, mas inflexível. No caso dinâmico são criadas e destruídas conforme a necessidade, aumentando a flexibilidade, mas complicando consideravelmente o gerenciamento. Sob o ponto de vista posicional o mecanismo pode ser implementado na área de usuário o ambiente de execução guarda valores dos registradores em tabletes, quando deve mudar de contexto, sem chamar o sistema operacional. O principal problema é que como chamadas ao sistema não são repassadas de imediato, para não bloquearem o processo, os threads não são preemptíveis. Quando implementados no núcleo do sistema operacional os problemas se revertem, pois aumenta a autonomia dos threads na mesma proporção em que se reduz sua agilidade.

32

Mecanismos de Linguagem para Sincronização Uma linguagem de programação concorrente permite especificar atividades concorrentes, tais quais o modo como os processos são sincronizados e como eles se comunicam. As linguagens concorrentes são obtidas, via de regra, pelo acréscimo de certas construções próprias para sincronização e a comunicação de processos concorrentes a linguagens seqüenciais. Os principais construtores das linguagens são: - Estrutura do programa: especifica a composição do programa (procedimentos, comandos, variáveis...); - Estrutura de dados: representam objetos no programa; - Estrutura de controle: regulam o fluxo de execução do programa (if-then-else, while-do, ...); - Chamadas a procedimentos e ao sistema: ativam rotinas especiais ou serviços do sistema; - Estruturas de entrada e saída: recebem dados de entrada e produzem resultados da execução dos programas; e - Estruturas de atribuição: afetam o estado da execução do programa. A tabela a seguir apresenta os principais métodos para a sincronização de processos e as características das linguagens que os implementam. Como pode ser visto, estes métodos se dividem em dois grupos: um que emprega variáveis compartilhadas; e outro que utiliza o mecanismo de passagem de mensagens. O primeiro grupo é mais indicado para processos que compartilham um espaço de endereçamento, enquanto, o segundo é mais usado em processos concorrentes entre os quais o acoplamento é mais fraco. TIPOS

MÉTODOS DE SINCRONIZAÇÃO Semáforo Sincronização Monitor por Variáveis Região Crítica Condicional Compartilhadas Serializador Expressão de Caminho Sincronização Processamento Seqüencial Concorrente por Passagem Chamada a Procedimentos Remotos de Mensagens  Rendezvous

CARACTERÍSTICAS DAS LINGUAGENS Chamadas ao Sistema e Variáveis Abstração de Tipos de Dados Estruturas de Controle Tipos de Dados e Estruturas de Controle Tipos de Dados e Estruturas de Programa Estruturas de Entrada e Saída Chamadas a Procedimentos Chamadas a Procedimentos e Comunicação

Os métodos de sincronização do primeiro grupo foram vistos durante o estudo de sistemas centralizados. Portanto, a seguir são apenas apresentados algoritmos para a implementação distribuída do problema dos leitores e escritores. SEMÁFORO var mutex, db: semáforo; rc: inteiro

“processo leitor” P(mutex); rc:=rc+1; if rc=1 then P(db);  V(mutex);





P(mutex); rc:=rc-1; if rc=0 then V(db);  V(mutex); 33

“processo escritor” P(db);

 V(db);

MONITOR rw: monitor var rc: inteiro; busy: booleano; toread, towrite: condição; procedure startread begin if busy then toread.wait; rc:= rc+1; toread.signal; end procedure endread begin rc:= rc-1; if rc=0 then towrite.signal; end procedure startwrite begin if busy or rc≠0 then towrite.wait; busy:=true; end procedure endwrite begin busy:=false; toread.sinal or towrite.signal; end begin rc:=0; busy:=false; end

“processos leitores” rw.startread

rw.endread

“processos escritores” rw.starwrite

rw.endwrite

REGIÃO CRÍTICA CONDICIONAL var db: compartilhada; rc: inteiro

34

“processo leitor” region db begin rc:=rc+1 end;



region db begin rc:=rc-1 end;

“processo escritor” region db when rc=0 begin

end;

SERIALIZADOR rw; serializador var readq, writeq: filas; rcrowd, wcrowd: grupos;

“processo leitor” begin enqueue (readq) until empty (wcrowd);  joincrowd (rcrowd) then begin end; end

“processo escritor” begin enqueue (writeq) until (empty (wcrowd) and empty (rcrowd));  joincrowd (wcrowd) then begin end; end

EXPRESSÃO DE CAMINHO path1 ([read], write) end

A primeira linguagem a considerar mecanismo de sincronização foi a CSP, que usa o conceito de encontrar comandos de entrada de um processo ao de saída de outro. Um processo transmissor P envia um comando de saída explicitando que deseja transmitir uma expressão, por exemplo exp, ao processo Q, isto é, Q!exp. O processo receptor Q envia um comando de entrada informando que está apto a receber uma variável, por exemplo var, do processo P, isto é, P?var. No encontro destes comandos, a expressão exp é atribuída remotamente à variável var. Como leitores e escritores não têm a menor idéia que o outro existe, é necessário um processo servidor que receba solicitações de leitura e escrita e providencie leituras concorrentes e escritas exclusivas. O problema principal com este método é que se necessita saber os nomes dos possíveis leitores e escritores de antemão, de forma a encapsular a leitura e a escrita no servidor. Estendendo a idéia de comandos de entrada e saída síncronos de CSP para o de chamada de procedimentos, resultam os métodos de chamada a procedimentos remotos (RPC) e de rendezvous. O método RPC baseia-se na comunicação entre clientes e servidores, em parte, já visto anteriormente, e que será visto com mais detalhes oportunamente. O rendezvous é semelhante ao RPC, mas os procedimentos são ativos e devem expressar sua disposição para receber chamadas. Uma implementação do problema dos leitores e escritores com o método rendezvous é apresentada a seguir.

35

 RENDEZVOUS

task rw is entry startread; entry endread; entry startwrite; entry endwrite; end; task body is rc: integer:=0; busy: boolean:=false; begin loop select when busy=false → accept startread do rc:=rc+1 end; or → accept endread do rc:=rc-1 end; or when rc=0 and busy=false → accept startwrite do busy:=true end; or → accept endwrite do busy:=false end; end loop end;

Exemplos da implementação de alguns métodos de sincronização nas linguagens O CCAM, LINDA, ORCA e JAVA foram compilados pelos alunos da disciplina projeto de sistemas operacionais no primeiro semestre letivo de 2001 e seus trabalhos podem ser consultados no seguinte URL: http://www.dcce.ibilce.unesp.br./~norian/cursos/pso/trabalho1.html

Comunicação entre Processos Durante o seu funcionamento muitos processos precisam trocar informações com outros processos. Para que esta comunicação ocorra, os sistemas distribuídos necessitam de um mecanismo específico. Como já visto, pode–se considerar um sistema distribuído estratificado em diversos níveis de abstração. Nos níveis inferiores temos conexões para o transporte de mensagens, as quais são devidamente empacotadas e roteadas através da malha de comunicação. Estes níveis menos abstratos do sistema, que incluem o sistema operacional e a rede física, devem permanecer transparentes aos processos. Para tanto, no nível da camada de transporte do sistema é oferecido um serviço de troca de mensagens, que se constitui o menor nível de comunicação entre processos de um sistema. Utilizando este mecanismo, no nível de apresentação é implementado um serviço de solicitação e resposta via chamada de rotinas, como se fossem pontos de interface de programas de amplificação para a camada de transporte. Tal mecanismo é conhecido por chamada a procedimentos remotos e serve como mecanismo de condução das transações da camada de aplicação. Traduzindo em outras palavras, transações são seqüências de solicitações e/ou respostas que requerem tratamento atômico da comunicação. Seqüências de solicitações ou respostas acionam chamadas a procedimentos remotos as quais passam as solicitações por meio de

36

mensagens aos processos comunicantes. Segue-se uma tabela que exemplifica a estratificação mencionada. Comunicação ⇑ ⇓

Aplicação Apresentação Transporte S.O. (rede) Enlace/Físico

Transações Chamadas a Procedimentos Remotos Passagem de Mensagem Conexões de Transporte Chaveamento de Pacotes

No texto a seguir serão vistos cada um dos níveis de abstração superiores, que possibilitam a comunicação entre processos. Inicialmente será analisado o mecanismo de passagem de mensagens, sobre o qual se baseiam os demais. Na seqüência serão apresentadas as chamadas a procedimentos remotos e, para concluir esta seção, será visto o mecanismo de transações. Passagem de Mensagens

Uma mensagem é um conjunto de objetos com estrutura semântica definida pelos processos cooperantes. Somente estes processos interpretam a informação contida no corpo da mensagem. O restante do sistema manipula somente o cabeçalho da mensagem, que contém as informações de controle. Um processo, para enviar uma mensagem, utiliza primitivas do serviço de transporte. O mais comum são as primitivas send(destino,msg) e receive(fonte,msg), onde msg significa mensagem. Os processos usam uma de quatro formas para trocar mensagens entre si, quais sejam: por nome, por ligação, utilizando caixas de correio e através de portas. Quando a troca de mensagens entre processos é feita por nome, ou seja, um processo identifica seu interlocutor pelo seu nome, se estabelece um caminho de comunicação bidirecional entre eles. Este caminho é lógico e o fato de existir não implica nenhuma conexão física. Portanto, só pode haver um caminho por vez entre dois processos. Para ser único, o nome do processo é formado pelo endereço da sua máquina hospedeira concatenado com a identificação do processo. Esta formulação do nome dos processos permite que um processo X que estiver sendo executado em uma máquina A, converse com outro processo X que estiver sendo executado em uma máquina B. Ela também permite que um processo Y sendo executado em uma máquina C, se comunique com qualquer dos processos X mencionados, sem confundi-los. Sendo a troca de mensagens feita por ligação, elimina-se a limitação de uma conexão por par de processos. Neste caso, podem ser estabelecidos vários caminhos de dados (circuitos virtuais) unidirecionais, os quais são gerenciados pelo núcleo do sistema operacional. Os caminhos podem ser estabelecidos, por exemplo, em tabelas de ligação armazenadas nas máquinas que participam do sistema. Sempre que há uma solicitação de um processo para se comunicar com outro estas tabelas são consultadas e a melhor rota é estabelecida para a comunicação. As mensagens contém o endereço da máquina destino e as tabelas contém informações sobre a(s) melhor(es) rotas para se atingir cada destino possível. Tais tabelas são atualizadas dinamicamente, para incluir informações sobre rotas e máquinas defeituosas, que não podem ser acessadas, e sobre máquinas e rotas que voltaram a fazer parte ativa do sistema. Além destas, outras informações sobre as condições de tráfego são atualizadas dinamicamente nestas tabelas. No caso de se estabelecerem caixas de correio para serem usadas na comunicação entre os processos a ligação se torna mais flexível, por assim dizer. As caixas de correio seguem o modelo dos produtores e consumidores, permitindo ligações multiponto e multicaminho. No caso de

37

ligações multiponto, se estabelece uma caixa de correio e informa-se aos processos comunicantes a localização desta caixa. Sempre que um processo desejar transferir uma mensagem ele a coloca na caixa de correio. Quando qualquer um dos demais processos estiver pronto para receber alguma mensagem ele examina a caixa de mensagens e verifica se alguma mensagem lhe foi enviada. Se houver mensagem o processo a lê, caso contrário ele continua seu processamento normal, o qual pode incluir uma fase para o processo aguardar a chegada de uma mensagem. Note-se que este mecanismo permite a divulgação de informações para todos os processos comunicantes, basta que o processo emissor deposite uma mensagem com um endereço genérico que permita sua leitura por todos os demais processos. As ligações multicaminho servem, por exemplo, para aumentar a capacidade de comunicação entre dois processos. Para que isto ocorra, basta que um dos processos comunicantes abra duas (ou mais) caixas de correio para determinada ligação. Enquanto nos dois casos anteriores a comunicação precisa ser feita diretamente entre os dois processos comunicantes, neste caso a comunicação pode ser indireta, devido à possibilidade de se estabelecer ligações multiponto por meio deste mecanismo. A troca de mensagens feita por intermédio de portas é, em verdade, um caso especial de caixa de correio, que utiliza uma abstração de uma fila FIFO, de tamanho finito, mantida pelo núcleo. Enquanto as caixas de correio utilizam elementos de armazenamento intermediário que podem ser acessados aleatoriamente, as portas fazem uso de filas cujo acesso deve ser feito ordenadamente. Os sistemas podem ou não utilizar elementos de armazenamento intermediário para as mensagens. Quando estes elementos de armazenamento intermediário são utilizados no sistema, é possível tratar mensagens com outras aguardando na fila e reduzir as diferenças de velocidade entre os processos comunicantes. Aqueles processos que utilizam tais elementos de armazenamento intermediário implementam o modelo dos produtores e consumidores, enquanto aqueles que não utilizam esses elementos implementam o modelo rendezvous. Além da questão da utilização de elementos para o armazenamento temporário de mensagens, o seu envio pode ser síncrono ou não. As considerações sobre a forma de comunicação feitas a seguir referem-se à Tabela I.

Tabela I – Formas de Comunicação entre Processos Processo 1 envia núcleo fonte

Processo 2 núcleo destino recebe

canal de comunicação

1



2



mensagem



3

8



7



reconhecimento



6

4 (pedido) ↓ (serviço) ← 5 (resposta) →

O primeiro caso consiste no envio assíncrono, sem bloqueio, ou seja, o processo é liberado tão logo a mensagem seja montada e repassada para o núcleo fonte. Neste caso o processo que origina a mensagem fica envolvido apenas nas etapas 1 e 8 da Tabela I. Os outros casos referem-se a formas de envio síncrono. Portanto, o segundo caso consiste no envio síncrono com bloqueio simples. Neste caso, o processo só é liberado após a mensagem ser transmitida ao canal de comunicação. Isto porque se houver algum problema com o envio da mensagem e o núcleo do sistema fonte perdê-la antes dela ser transmitida ao canal ela não poderá mais ser recuperada, pois o processo que solicitou o envio a deu por enviada. Então esta abordagem mantém o processo bloqueado até que o núcleo tenha sucesso na sua transmissão para o canal de comunicação. Desta forma o processo que origina a mensagem fica envolvido nas etapas 1, 2, 7 e 8 da Tabela I.

38

O terceiro caso consiste no envio síncrono com bloqueio, dito, confiável. Agora o processo só é liberado após a mensagem ser recebida pelo núcleo do processo destino. Isto garante que se houver algum problema com o canal de comunicação o processo transmissor poderá retransmitir a mensagem. Este procedimento aumenta a confiabilidade da comunicação, pois o canal de comunicação costuma ser um meio não confiável. Desta feita o processo que origina a mensagem fica envolvido nas etapas 1, 2, 3, 6, 7 e 8 da Tabela I. O quarto e último caso considerado é o de envio síncrono com bloqueio explícito, quando o processo transmissor fica bloqueado durante toda a transmissão, só sendo liberado após o recebimento da solicitação pelo processo destino e durante o período em que este estiver computado a resposta desejada. Portanto, o processo que origina a mensagem fica paralizado durante todas as etapas da comunicação, envolvendo as etapas 1, 2, 3, 4, 5, 6, 7 e 8 da Tabela I. Há outro mecanismo bloqueante que é o modelo de solicitação/resposta, no qual o processo que origina a mensagem fica bloqueado durante todo o processo de comunicação, inclusive pelo tempo necessário para o processo receptor da mensagem gerar a resposta solicitada, ou seja, durante o cômputo no servidor. Este mecanismo faz com que o processo que origina a mensagem fique paralizado, não somente durante as etapas 1 a 8, mas também durante o período de serviço, indicado na Tabela I. Até aqui foi visto o mecanismo básico de passagem de mensagens entre processos. Agora serão consideradas duas estruturas de comunicação, através das quais as mensagens podem ser transferidas de um processo para outro. Estas estruturas são interfaces para programas de aplicação conhecidas por  pipes e sockets. Elas servem para poupar usuário da preocupação com detalhes de implementação, tais como o tamanho da memória para armazenamento intermediário das mensagens, a capacidade do canal de comunicação e a sincronização de acessos. Para que este mascaramento do sistema possa ser feito define-se uma interface para utilização pelos programas aplicativos, ficando o gerenciamento da comunicação por conta do núcleo do sistema. Incialmente, tratar-se-á da materialização do modelo de produtores e consumidores em uma estrutura chamada de  pipe. Cria-se uma fila FIFO, de tamanho finito, mantida pelo núcleo. Esta fila é criada através de uma chamada ao sistema, que devolve dois descritores, um de leitura e outro de escrita na fila. O processo que deseja se comunicar utiliza o descritor de escrita para escrever suas mensagens no final da fila (entrada do  pipe). O processo com o qual ele deseja se comunicar utiliza o descritor de leitura para ler as mensagens depositadas no início da fila (saída do  pipe). Pipes existem somente enquanto os processos leitores e escritores estiverem ativos, pois, como só permitem comunicação unidirecional e exclusivamente entre os processos chamador e chamado não teria sentido manter filas FIFO ativas no sistema após o encerramento das atividades de qualquer dos processos comunicantes. É possível criar named-pipes, que são arquivos FIFO especiais associados a um nome e um caminho, através dos quais processos não relacionados podem utilizar  pipes. Os  pipes tratam os dados como seqüências de bits, sem interpretá-los. A interpretação do conteúdo semântico das mensagens fica por conta do processo que as recebe. Por questões de confiabilidade da transmissão, ou se escrevem todos os bytes de uma mensagem no  pipe ou nada se escreve. O uso de  pipes restringe-se a um só domínio dentro de um sistema de arquivos. Para viabilizar a comunicação entre domínios diversos, onde nem estruturas de dados nem arquivos podem ser compartilhados e nomeados de forma inequívoca, usam-se sockets. Eles são pontos finais de canais de comunicação, gerenciados pelo serviço de transporte. Através deles as operações de entrada e saída na rede são modeladas como entrada e saída em arquivos. O sistema cria um descritor, que é um ponto de comunicação lógico, local ao processo. Este descritor, que deve ser associado (pelo sistema) a um ponto de comunicação físico, é utilizado para enviar e receber dados através da camada de transporte do sistema. Isto é possível porque é feito um mapeamento entre o descritor lógico, na interface de aplicação, e o descritor físico, na interface de rede. Este mapeamento pode ou não ser feito através de uma conexão. Se for feito por meio de uma 39

conexão explícita, o processo que inicia a comunicação identifica o processo com quem deseja se comunicar e o sistema estabelece uma ligação entre seu ponto lógico de comunicação (local) e o ponto de comunicação físico do processo remoto. Esta associação permanece enquanto os processos estiverem trocando informações, não sendo mais necessário identificar os processos comunicantes durante esta transmissão. Um problema com este tipo de comunicação é a necessidade de um conjunto de protocolos para manter a segurança da comunicação. Tipicamente é usado um protocolo em duas camadas: uma camada que pretende garantir a privacidade dos dados transmitidos por meio da criptografia simétrica dos dados; e outra para garantir a integridade e a autenticidade dos dados, utilizando mecanismos de verificação de erros e promovendo em clientes e servidores uma criptografia assimétrica com uso de chave pública. Chamadas a Procedimentos Remotos

O mecanismo de chamadas a procedimentos remotos é uma abstração, no nível de linguagens, do mecanismo de comunicação por solicitação/resposta, baseado na passagem de mensagens. Sintaticamente, uma chamada a um procedimento remoto é idêntica a uma chamada a um procedimento local. Entretanto, a semântica é diferente, pois envolve atrasos e eventualmente algum tipo de tratamento de falhas. Chamadas a procedimentos remotos escondem dos clientes detalhes de chamadas ao sistema, de conversões de dados e de comunicação na rede. O cliente é aquele que inicia a chamada para execução de algum serviço em uma máquina remota. A solicitação é passada a um ordenador, na máquina do cliente, que agrega os parâmetros necessários à transmissão da mensagem. Após a devida parametrização a mensagem é passada ao núcleo, que se encarrega de sua transmissão utilizando o mecanismo de transporte da rede. A mensagem é enviada ao servidor, que é aquela máquina na qual o procedimento será executado. Ao receber uma mensagem o núcleo do servidor a repassa para o respectivo ordenador, o qual fará a devida extração dos parâmetros e encaminhará a solicitação ao servidor. Após processada, o servidor retornará uma resposta ao cliente, por um caminho simétrico, isto é, a resposta será repassada ao ordenador do servidor, que empacotará a mensagem agregando-lhe parâmetros. Esta mensagem empacotada será repassada ao núcleo do servidor, que se comunicará com o núcleo do cliente e efetivará o transporte da mensagem com a resposta pela rede. Ao recebê-la, o núcleo do cliente a repassará para o ordenador do cliente, que desempacotará a mensagem, finalmente entregando a resposta para quem a solicitou. Um exemplo de chamada a procedimento remoto é a chamada a uma rotina para a leitura de algum arquivo em uma máquina remota. O cliente solicita o serviço executando a atribuição arquivo = lê (da, pos, nbytes), em que da é um descritor do arquivo, pos é a posição do arquivo onde a leitura deve ser iniciada e nbytes é o número de bytes a serem lidos do arquivo. Ao iniciar o procedimento os dados são colocados na pilha do servidor, juntamente com as variáveis locais, para serem usadas durante a execução. Ao concluir a execução estes dados são eliminados da pilha. Para que os dados de controle, como os mencionados no exemplo do parágrafo anterior, possam ser utilizados pelo servidor, permitindo a execução correta do procedimento desejado, é necessário transferir estes parâmetros entre o cliente e o servidor. Esta passagem de parâmetros pode ser efetuada de diversas formas, como as quatro vistas a seguir. O cliente pode passar diretamente o(s) valor(es) do(s) parâmetro(s) a ser(em) utilizadas pelo servidor. Ao receber o(s) parâmetro(s) o servidor o(s) copia para uma (ou mais) variável local, conforme necessário. Modificações feitas pelo servidor nas variáveis locais não afetam o valor das variáveis do cliente. Desta forma, o servidor pode trabalhar livremente sem interferir no processo que o chamou. Todavia, muitas vezes o que se quer é exatamente a alteração controlada de algumas variáveis. Neste caso, há um duplo trabalho, pois o servidor deverá fazer as alterações necessárias

40

durante o processamento, enviar uma cópia dos valores obtidos para o cliente, que então deverá efetuar as modificações cabíveis nas cópias locais das variáveis. Uma alternativa para o problema levantado no parágrafo anterior seria o cliente enviar o nome da variável local, em vez do seu valor. Desta forma, o servidor poderia atuar diretamente sobre as cópias das variáveis existentes no cliente. Entretanto, em um sistema distribuído este acesso a variáveis em máquinas remotas não é um processo fácil, requerendo a avaliação de expressões sintáticas em tempo de execução. Portanto, este método não é muito utilizado na prática de sistemas distribuídos. Outra alternativa ao problema mencionado nos dois parágrafos anteriores seria a passagem de um ponteiro (endereço) para servir de referência à variável, ou às variáveis, que se deseja tratar. Neste caso o problema é que o espaço de endereçamento em máquinas diferentes é, também, diferente. Portanto, não tem sentido a passagem de parâmetros por referência em um sistema distribuído. A terceira alternativa, e última a ser apresentada neste texto, é uma mescla das chamadas por valor e por referência. Ao fazer uma chamada o cliente cria um vetor com os valores dos parâmetros que deseja transferir ao servidor e passa o endereço deste vetor, e seu tamanho, para o ordenador do cliente. Com estas informações, o ordenador copia os valores adequados em uma mensagem e a envia ao ordenador do servidor. Este, ao recebê-la, copia os valores para um vetor na memória do servidor e chama-o, passando-lhe um ponteiro para o vetor criado. Note-se que este ponteiro nada tem a ver com o utilizado pelo cliente. Durante o processamento no servidor, todas as modificações feitas refletem-se nos valores contidos no vetor presente em sua memória. Ao concluir o procedimento, com as informações no vetor eventualmente modificadas, o servidor aciona seu ordenador, devolvendo-lhe o ponteiro para o vetor utilizado. A mensagem segue então seu caminho de volta ao ordenador do cliente que, de posse do vetor modificado, atualiza o vetor na memória do cliente. Esta alternativa é razoavelmente eficiente para estruturas simples, como arranjos. Todavia, seu tratamento não é trivial para estruturas mais complexas, como árvores e grafos. Uma vez estabelecidos os mecanismos para a troca de mensagens, outro problema que surge é a localização do servidor. Para solicitar um serviço o cliente deve saber a quem faze-lo. Isto só pode ser feito se ele puder identificar cada servidor existente no sistema. Uma maneira de viabilizar esta espécie de consciência é gravar, em cada cliente, os números das máquinas e as versões de cada um dos programas servidores. Um problema com esta abordagem é que qualquer mudança deve ser comunicada a todos os clientes em potencial. Se um servidor for transportado de uma máquina A para outra máquina B, porque esta está temporariamente menos carregada do que aquela, esta mudança deve ser comunicada a todos os clientes. Se for feita uma cópia de um servidor em uma máquina diferente, para ampliar as possibilidades de serviço e melhorar a distribuição de carga do sistema, esta mudança também deve ser comunicada a todos os clientes. Em fim, qualquer modificação em qualquer dos servidores deve ser comunicada a todos os possíveis clientes. Este procedimento causará um grande congestionamento no sub-sistema de comunicação do sistema distribuído. Uma solução para este problema é apresentada no parágrafo a seguir. Cria-se um processo de ligação dinâmica, responsável pela coordenação dos serviços do sistema. Um servidor, ao iniciar suas atividades envia para o processo de ligação uma mensagem contendo seu nome, sua versão, um identificador e um manipulador (seu endereço ethernet ou IP, por exemplo). Este processo corresponde ao registro do serviço no processo de ligação. O servidor pode cancelar seu serviço enviando outra mensagem ao processo de ligação com seu nome, sua versão e seu identificador. Quando um cliente solicita um serviço, seu ordenador verifica se já está ligado ao servidor. Se não estiver, manda uma mensagem ao processo de ligação contendo o nome e a versão do servidor desejado. Se o serviço não estiver disponível, o processo de ligação retorna uma indicação de falha ao ordenador do cliente. Se houver um servidor adequado, o processo de ligação retorna o manipulador e o número de identificação do servidor. O ordenador do cliente então envia mensagens ao endereço contido no manipulador, contendo os parâmetros e a identificação do servidor. Esta identificação é necessária para que o núcleo do servidor possa 41

direcionar a solicitação para o servidor correto. As vantagens na utilização de processos de ligação incluem: a distribuição de carga; a tolerância a falhas; e a autenticação. Havendo vários servidores o processo de ligação pode auxiliar na distribuição da carga de trabalho respondendo aos clientes de acordo com algum critério de distribuição dos serviços entre os possíveis servidores. Periodicamente, ou quando houver suspeita de falha, o processo de ligação pode pedir confirmação do serviço para o servidor suspeito e, caso não obtenha esta confirmação em um prazo aceitável pode cassar o registro do servidor. O servidor pode determinar ao processo de ligação quais clientes admite servir, desta forma, ao receber uma solicitação de serviço o processo de ligação verifica se o requerente está na relação de possíveis clientes do servidor requisitado. Caso não esteja, em vez de enviar os dados do servidor, o processo de ligação envia ao pretenso cliente uma mensagem de falha no serviço. A principal desvantagem deste esquema é a sobrecarga de trabalho no processo de ligação, especialmente se o sistema apresentar muitos clientes que buscam serviços rápidos e muitas cópias dos servidores. Para reduzir, ou distribuir melhor a carga de trabalho no processo de ligação a solução natural seria criar cópias do processo de ligação. Entretanto, esta solução recai no problema de sobrecarga da rede de comunicação, devido à necessidade de manter todas as cópias coerentes. Como mencionado anteriormente, embora as chamadas a procedimentos remotos sejam semelhantes conceitual e sintaticamente, elas diferem semanticamente das chamadas locais devido a falhas e exceções na rede. As falhas são problemas causados por defeitos em clientes, servidores ou na rede de comunicação. Exceções são condições anormais geradas na execução de procedimentos dos servidores e ordenadores. Por exemplo, se um cliente não é capaz de localizar um servidor pode ser que o servidor esteja fora do ar, mas também pode ser que ele esteja tentando acessar uma versão desatualizada do servidor. A queda do servidor caracteriza uma falha do sistema, o qual pode ou não ser alertado do ocorrido, dependendo das características de projeto do servidor e da razão de sua queda. Quando o servidor avisa sobre a falha o núcleo do cliente pode tentar solicitar o serviço novamente até conseguir ao menos uma resposta de algum servidor, ou desistir assim que identifica o problema ao receber uma resposta atrasada ou mesmo nenhuma resposta. Quando o servidor não avisa nada o cliente pode tentar novamente até “cansar”, neste caso podendo receber de zero a um número indeterminado de respostas. A tentativa de acesso a uma versão desatualizada do servidor caracteriza uma exceção do sistema. Esta desatualização pode ser causada pela compilação de um cliente antes de uma atualização do ordenador de um servidor. Quando este cliente tentar utilizar o serviço deste servidor ele solicitará ao processo de ligação acesso à versão antiga do servidor. Entretanto, o processo de ligação já terá retirado de sua lista de serviços disponíveis, a pedido do servidor, sua versão antiga. Portanto, o processo de ligação retornará para o cliente uma mensagem indicando a inexistência do serviço solicitado. Pode ocorrer, também, do cliente solicitar um serviço e quebrar antes de receber a resposta. Neste caso, o servidor fica com sua memória ocupada, e eventualmente outros recursos bloqueados, por não poder enviar a resposta. Estes procedimentos são ditos órfãos, pois o servidor não tem como saber o que ocorreu com o cliente. Algumas soluções para este problema são: (a) Extermínio: Quando um cliente que falhou volta a funcionar ele explicitamente elimina todos seus órfãos. Isto requer que o cliente tenha conhecimento de suas atividades passadas e seja capaz de localizar os processos órfãos. Para tanto, as informações sobre cada chamada devem ser gravadas em disco. Além disto, se os órfãos também realizarem chamadas a procedimentos remotos criarão novos órfãos, cuja localização é muito difícil, quando não impossível. Há ainda o problema da rede ser seccionada, pela queda de um gateway, por exemplo, deixando alguns órfãos inacessíveis. (b) Reencarnação: As solicitações seguem com identificação do instante em que foram emitidas. Quando um cliente se recupera de uma falha ele faz uma transmissão geral, indicando o instante no qual ele re-iniciou suas atividades. Cada servidor verifica se possui algum processo de execução remota iniciado anteriormente àquele instante e, caso possua, tenta localizar seu proprietário. Se não conseguir localizá-lo o servidor

42

descarta o processo. Esta solução requer a inversão de papéis entre clientes e servidores, complicando um projeto que, de outra forma, seria simples. (c) Expiração: A cada operação remota solicitada é atribuído um período de tempo para sua execução. Se não for concluída neste período o cliente deve solicitar explicitamente sua prorrogação. Se um servidor encontrar processos de execução remota com período de execução expirado ele os elimina. Para que isto funcione, quando um cliente quebrar, ele deve esperar um certo tempo antes de retornar, para garantir a eliminação dos órfãos que, por ventura, tenha deixado no sistema. As chamadas a procedimentos remotos são a base de comunicação para aplicativos. Portanto, devem ser seguras, de forma a evitar a vulnerabilidade dos sistemas a ataques. Um dos aspectos que concorrem para a segurança dos sistemas que permitem a execução de procedimentos solicitados remotamente é a autenticação dos solicitantes, isto é, a capacidade do sistema de verificar a identidade dos processos clientes e servidores. Outro aspecto importante é questão da integridade, quando o sistema deve poder garantir que mensagens contendo solicitações e respostas sejam entregues inalteradas aos respectivos destinatários. Um terceiro aspecto a ser levado em consideração é a questão da confidencialidade das informações, neste caso o sistema deve garantir que mensagens de solicitação e de resposta não tenham seu conteúdo revelado a terceiros. Note-se que enquanto o segundo aspecto diz respeito à não modificação do conteúdo da mensagem, o terceiro corresponde à não visualização do conteúdo das informações. Finalmente, o quarto aspecto que deve ser levado em conta é a originalidade das mensagens, isto é, a garantia pelo sistema de que as mensagens não apareçam mais do que uma vez. O principal mecanismo para garantir os aspectos de segurança nos sistemas, mencionados acima, é a criptografia. Resumidamente, consiste na utilização de um conjunto de chaves públicas e privadas (secretas). As públicas são geradas a partir das secretas através de um algoritmo, de tal sorte que mesmo conhecendo o algoritmo a recuperação da informação seja computacionalmente intratável sem o conhecimento da chave secreta.

43

Transações É um conjunto de comunicações assíncronas tipo solicitação/resposta que satisfazem às propriedades de atomicidade, seriação e durabilidade. Estas propriedades visam a transparência de concorrência em sistemas distribuídos, isto é, permitem o compartilhamento de objetos sem interferência. Elas são descritas nos parágrafos seguintes. A atomicidade tem por objetivo garantir a consistência de objetos replicados. Para tanto, ou todas as operações de uma transação são executadas ou nenhuma delas o é, mesmo em presença de falhas. A transação deve parecer indivisível para o mundo externo. Através da propriedade de seriação busca-se manter a consistência dos resultados. A execução de transações entrelaçadas é equivalente à execução das transações em alguma ordem serial. Transações concorrentes não podem interferir entre si. Com a propriedade de isolamento deseja-se proporcionar a consistência de percepção do estado do sistema. Os resultados parciais de uma transação incompleta não são visíveis a outras, antes que os participantes da transação entrem em acordo a seu respeito. Por meio da propriedade de durabilidade procura-se complementar a idéia de consistência de percepção do estado do sistema. Neste caso, o sistema garante que os resultados de uma transação, para as quais já se tenha chegado a um acordo, se tornarão permanentes, mesmo que ocorra uma falha depois da concordância das partes. Uma vez que (e somente quando) as partes envolvidas numa transação entrem em acordo, as mudanças efetuadas passam a ser permanentes. Todas as propriedades dizem respeito à consistência, motivo pelo qual a segunda foi chamada de seriação. As duas últimas propriedades (isolamento e durabilidade) poderiam ser combinadas em uma só, dita permanência dos resultados. Neste caso, juntando-se as iniciais das propriedades: permanência, atomicidade e seriação, tem-se PAS. Para viabilizar a coordenação das atividades, o processador que iniciar uma transação é tomado como coordenador, os demais como participantes. Inicialmente, o coordenador informa a todos os processadores participantes sobre as solicitações. A transação se encerra quando todos os participantes concordarem com a decisão de efetivar os resultados obtidos ou de abortar a transação. Esta decisão é tomada em função da possibilidade ou não de satisfazer as propriedades PAS.

 Protocolo de aceitação em duas fases Este protocolo envolve um coordenador, aquele que inicia uma transação, e os demais processos, ditos participantes da transação. Na primeira fase o coordenador registra a sua concordância prévia com a transação e distribui a todos os participantes uma solicitação. Cada um dos participantes recebe a mensagem de solicitação. Se o participante estiver pronto para concordar com a transação ele registra sua concordância (em caráter precário) e envia ao coordenador uma mensagem indicando sua concordância. Caso contrário, ele envia uma mensagem indicando sua discordância e aborta o processamento interno. Durante esta fase o coordenador permanece coletando as respostas dos diversos participantes. Na segunda fase o coordenador computa a decisão final. Se houver unanimidade, isto é, todos os votos recebidos forem favoráveis à concordância com a transação, ele confirma sua concordância (até então prévia) e envia uma mensagem confirmando a transação. Caso não haja unanimidade, o coordenador envia uma mensagem informando a desistência da transação e aborta seu

44

processamento interno. Cada participante recebe a informação sobre a decisão final. Se houve acordo geral o participante muda o status de sua concordância, de precário para efetivo, e procede qualquer processamento eventualmente relacionado com esta transação, enviando a resposta ao coordenador. Se não houve acordo geral o participante aborta o processamento desta transação. Durante o restante desta fase, se necessário, o coordenador aguarda eventuais respostas dos participantes. Toda operação deve ser gravada em disco, em um arquivo log de atividades. Recuperação de falhas ocorre em três situações: - Quando as falhas ocorrem antes de um acordo prévio, o processo só necessita abortar a transação. - Quando as falhas ocorrem após um acordo definitivo, o coordenador reenvia a mensagem de confirmação do acordo aos participantes, os quais devem atualizar os resultados. - Quando as falhas ocorrem entre um acordo prévio e um definitivo o coordenador pode abortar ou tentar retransmitir a mensagem de confirmação do acordo. Esta retransmissão só pode se efetivar se os participantes forem capazes de identificar duplicatas. No caso do participante falhar deve contatar outro, para verificar o estado da transação.

Coordenação Distribuída Quando não há variável compartilhada ou controlador comum, como quando processos têm que trocar informações para chegar a uma conclusão sobre o sistema ou entrar em alguma acordo, processos têm que coordenar seu funcionamento. A exclusão mútua garante que processos concorrentes tenham acesso serial a recursos e dados. A exclusão mútua entre processos pode ser controlada ou pode se dar através de disputas. Quando é controlada, uma permissão de acesso única circula entre possíveis usuários e a circulação é regulada, podendo o detentor da permissão acessar o recurso desejado. Quando se dá por meio de disputa, cada processo compete livremente e de maneira eqüânime pelo direito de acesso ao recurso do sistema. Esta competição é baseada num critério de resolução, por exemplo, antigüidade do pedido prioridade. Por disputa

(a) MUTEX com prioridade temporal: - Cada processo que quer acessar um recurso envia um pedido com o tempo lógico (tempo de Lamport) em que o fez. - Cada processo, ao receber um pedido proceda da seguinte forma: • se não fez pedido → envia um pur (pedido de uso de recurso); • se fez pedido, e o seu é mais recente que o recebido → envia um pur; • se estiver usando recurso → adia o envio do pur; • se fez um pedido, e o seu é mais antigo que o recebido → adia envio do pur. - Quem receber pur de todos estará liberado para acessar o recurso. - Após usar o recurso o processo envia uma nlr (notificação de liberação de recurso) para todos e um pur para os demais na fila. - Ao receber uma nlr cada processo: • tira o processo que liberou o recurso da fila de requisições. • se for o próximo da fila e tiver todos os pur acessa o recurso.

45

(b) Votação: - Encarando um pur como um voto, ao receber um pedido o processo vota somente naquele candidato. - Para evitar bloqueios (por exemplo, cada candidato com 01 voto) se um processo receber um pedido de um candidato com tempo inferior àquele no qual ele já votou pode tentar mudar seu voto. Neste caso, envia uma solicitação de desistência ao candidato em que votou. - Se o candidato não estiver usando o recurso, devolve-lhe uma confirmação de desistência, caso contrário, usa o recurso e posteriormente envia uma nlr. - Ao receber um uma confirmação de desistência o processo pode confirmar a mudança de voto, desmarcando o processo que enviou a confirmação de desistência e enviando seu voto para o processo mais atrativo. - Ao receber uma nlr, o processo remove aquele que enviou a mensagem, da fila de pendências e vota no próximo da fila, isto é, o mais antigo. - Um processo usa o recurso quando tiver a maioria dos votos. Controlada

A exclusão mútua controlada evita a sobrecarga de mensagens das abordagens por disputa. Usando uma mensagem de sinalização para controlar o acesso ao recurso faz com que a circulação da mensagem seja garantida pela organização lógica dos processos. As estruturas topológicas possíveis são em anel, em árvore e por irradiação, como discutidas a seguir. A estrutura em anel configura os processos segundo um anel ordenado logicamente. A posse de uma mensagem de controle dá o direito, a determinado processo, de usar o recurso desejado. Esta organização é simples e não é sujeita a bloqueios e é justa, pois, a menos que algum processo esteja usando o recurso, a mensagem de controle fica circulando na rede para quem desejar usar. Um problema é que este esquema gera tráfego na rede mesmo que ninguém queira usar recurso algum. Outro problema é uma possível demora para o recebimento da mensagem de controle, uma vez que o anel pode ser grande e todos os processos podem querer usar o recurso. Para compensar isto, a mensagem de controle pode levar informações de status, por exemplo, nível de prioridade definido pelo usuário ou uma marca de tempo. A estrutura em árvore configura os processos como uma árvore dinâmica lógica. Cada processo mantém uma fila FIFO e um ponteiro para seu antecessor na árvore. A mensagem de controle fica com o processo na raiz da árvore. O processo que quer a mensagem de controle a solicita ao processo imediatamente anterior. Quando um processo recebe uma solicitação ele a coloca em sua FIFO e, se não possuir a mensagem de controle, a solicita para seu antecessor. Se ele possuir a mensagem de controle e a estiver usando aguarda, se não envia a mensagem de controle ao processo que a solicitou e corrige seu ponteiro para apontar para quem ele enviou a mensagem de controle. Se um processo possuir a mensagem de controle e não a estiver usando e sua fila não estiver vazia, ele envia a mensagem de controle para a primeira entrada na fila. A abordagem por irradiação utiliza um vetor, T, de marcas, indexadas pelo número do processo, as quais representam o número de vezes que o processo usou o recurso. Utiliza também uma fila, Q, de pedidos, que é uma fila FIFO com os pedidos feitos. Cada processo mantém um número de seqüência local, com o número de vezes que solicitou o recurso, este número segue com todas as solicitações. Cada processo mantém um vetor de seqüência, S, que contém o número de seqüência mais alto de cada processo, que tenha chegado ao seu conhecimento. Além disto, cada processo mantém uma variável com o número de seqüência da sua última solicitação. O algoritmo de funcionamento da exclusão mútua controlada por irradiação é o seguinte: 1- processo i requer uso de recurso irradiando uma solicitação (i, seq) 2- processo j recebe mensagem e atualiza a i-ésima posição de seu vetor S j[i]=max (S j[i], seq) e coloca o número de i no fim da fila Q.

46

3- Se j possui a marca e não está usando, ele remove o processo no início da fila (digamos k) e lhe envia a marca, desde que S j[k] = T j[k] + 1. 4- O processo k então utiliza o recurso ao receber a marca. 5- Após usar o recurso, k atualiza T fazendo T k[k] = Sk[k] e compara seus vetores T e S, acrescentando a Q todo o processo r para o qual S k[r]=Tk[r]=1, r≠k, que não esteja em Q. 6- Se Q não estiver vazia, volta ao passo 3. 7- Se Q estiver vazia, aguarda nova solicitação. Exemplo (emprestado do livro do Chow & Jonson [1]): Considerem-se quatro processos cuja situação inicial corresponde a todos os vetores S=[15,20,10,8], estando a marca de posse do processo P1, valendo T=[15,20,10,8] e Q=[ ∅]. Considerem-se a seguinte seqüência de eventos a partir desta situação inicial. Uma ilustração deste exemplo pode ser vista no esquema incluído logo após esta listagem da seqüência de eventos. 1. P2, P3 e P4 solicitam a marca, emitindo mensagens do tipo (pid, seq) em que pid representa o identificador do processo que solicitou a marca e seq o número de seqüência da solicitação. Neste caso, os números de identificação dos processos serão 1 para o processo P1, 2 para P2, 3 para P3 e 4 para P4. Então as solicitações irradiadas, de acordo com estas informações e com a situação inicial, serão: (2,21), (3,11) e (4,9) para as solicitações oriundas de P2, P3 e P4, respectivamente. 2. Suponhamos que a solicitação de P2 se atrase e que P3 tenha chegado instantes antes de P4. Todos atualizam seus vetores S nas posições correspondentes aos processadores P3 e P4, isto é, S j[3] e S j[4]. P1 coloca os pid, na ordem em que foram recebidos, na fila Q. 3. P1 remove o pid=3 da fila, compara S1[3]=11 com T1[3]=10+1 e envia a marca (T e Q) para P3. 4. P3 usa o recurso. 5. P3 atualiza o vetor T, fazendo T3[3]= S3[3]=11. Neste instante todos recebem a solicitação de P2 e atualizam S 1[2]. 6. P3 compara T e S e atualiza Q, incluindo pid=2 no fim da fila. 7. P3 remove pid=4 da fila, compara S3[4]=9 com T3[4]=8+1 e envia a marca (T e Q) para P4. 8. P4 usa o recurso. 9. P4 atualiza o vetor T, fazendo T 4[4]= S4[4]=9. 10. P4 compara T e S e constata que Q está atualizado (não há novas solicitações para serem incluídas na fila). 11. P4 remove pid=2 da fila, compara S 4[2]=21 com T4[2]=20+1 e envia a marca (T e Q) para P2. 12. P2 usa o recurso. 13. P2 atualiza o vetor T, fazendo T 2[2]= S2[2]=21. 14. P2 compara T e S e constata que Q está atualizado. 15. Como Q está vazia, P2 retém a marca até a chegada de nova solicitação. 1

2

3

4

1

2

3

4

↓ início da fila

P1

15

21

11

9

15

20

10

8

3

4

P2

15

21

11

9

15

21

11

9

P3

15

21

11

9

15

20

11

8

4

2

P4

15

21

11

9

15

20

11

9

2

Vetor de Seqüência (S)

Vetor de Marcas(T)

Fila (Q) de Solicitações

47

Algoritmos de Eleição Num sistema distribuído é necessário um processo capaz de coordenar diversas atividades, tais como, o acesso a certos recursos para garantir a exclusividade de uso, mecanismos para a detecção de impasses e a sincronização de processos. Todavia, este processo coordenador costuma ser um ponto fraco do sistema, pois, sua falha pode impedir o seu funcionamento correto. A solução vem a ser o desenvolvimento de algoritmos que possibilitem a eleição de um novo coordenador, tão logo a falha do atual for detectada. Os mecanismos usados para a eleição podem ser divididos segundo o tipo de topologia do sistema em que atuam. Pode-se ter algoritmos adequados a topologias fixas, sejam elas arranjadas física ou logicamente, como as topologias em anel, em estrela ou em malha completamente conectada. Também pode-se identificar algoritmos adequados para topologias variáveis, como aqueles utilizados em árvores dinâmicas. Como exemplo de algoritmo para topologias fixas apresenta-se, a seguir, o algoritmo da força bruta [8]. Cada processo tem sua identidade como sendo sua prioridade. O processo de maior prioridade é o coordenador. Quando um processo nota a falta do coordenador, ele inicia a eleição do novo coordenador. A eleição procede de acordo com a seguinte lógica: 1- O processo que notou a falta do coordenador envia uma mensagem de eleição para todos os processos com identidade maior que o seu. Esta mensagem tem por finalidade identificar se algum dos processos de maior prioridade está ativo. 2- Se nenhum responder, o processo que iniciou a eleição se considera vencedor e se torna o novo coordenador. Neste ponto ele avisa os processos de menor prioridade sobre sua nova situação. Como é possível que vários processos tentem se eleger o novo coordenador, antes deste status se definir, o processo deve aguardar o recebimento do reconhecimento de todos os processos de menor prioridade. 3- Se algum processo de maior prioridade responder, ele assume o controle da eleição e o que a iniciou aguarda. A seguir apresenta-se um exemplo de um algoritmo utilizado para topologias de estrutura variável. Este algoritmo vê o sistema como uma árvore cuja organização pode ser modificada. Uma árvore é um grafo acíclico, com N nós interligados por N-1 canais de comunicação de forma que o custo de comunicação entre cada par de nós é o mínimo possível. O nó coordenador é a raiz da árvore. Para eleger um coordenador um ou mais processos irradiam para todos os nós da árvore uma mensagem de “campanha para líder” (CPL), incluindo o instante em que a mensagem foi gerada. Quando a mensagem é recebida por um nó-folha ele responde ao seu nó-pai com um reconhecimento do CPL (um voto para determinado CPL). Uma vez que tenham votado os nós não aceitam outras mensagens CPL e aguardam o anúncio de novo coordenador. Como podem haver várias mensagens CPL, oriundas de processos diversos, transitando, caso um nó receba mais de uma, ele vota naquele mais antigo (de menor tempo). Caso haja empate, o de maior pid, é o escolhido. Quando todos os nós-filhos tiverem votado, o nó-pai envia seu voto para o nó-avô, e assim por diante. Este algoritmo elege como novo coordenador aquele que iniciou o processo de eleição primeiro (com identificação de tempo menor), pois, ele será o único nó que receberá os votos de todos os seus filhos.

48

Acordo Distribuído Este problema é talvez o fundamental em sistemas distribuídos. Consiste em determinar uma forma de fazer com que um conjunto de processadores cheguem a consenso sobre determinada informação. Formalmente, tem-se um conjunto de M processadores P = {p 1, p2, ..., pM}, um subconjunto F dos quais não estão funcionando adequadamente F = {f 1, f 2, ..., f N}. Cada processador p i armazena um conjunto de valores V, baseado nos quais é calculado um valor de acordo A i. Para cada par de processadores pi e p j, funcionando normalmente, Ai=A j é o valor de consenso. O objetivo é obter um algoritmo distribuído capaz de resolver este problema. Para testar estes algoritmos, utilizam-se adversários. O adversário é um deamon que tem muito controle sobre o ambiente no qual o protocolo que estiver sendo testado é executado. O adversário tenta criar as piores situações para a execução do protocolo em teste, por exemplo, destruindo ou modificando mensagens ou alterando o protocolo de alguns dos processadores.

Falhas Bizantinas Um protocolo básico para resolver o problema de consenso é conhecido como seu funcionamento pode ser explicado pela seguinte estória:

acordo bizantino

e

Certa vez um sultão turco liderou uma invasão à cidade de Bizâncio. O imperador de Bizâncio reuniu seus exércitos para enfrentar os invasores. O comando de cada exército foi confiado a um general. Os exércitos bizantinos marcharam ao encontro dos turcos e tinham força suficiente para resistir a eles se agissem coordenadamente, isto é, ou todos atacassem ou todos recuassem. Após alguns dias de marcha, os exércitos bizantinos acamparam perto dos turcos. Os generais bizantinos pretendiam atacar ao amanhecer, mas cada um tinha a sua própria opinião sobre o poderio do exército turco. Conseqüentemente, também tinha sua preferência sobre quando atacar e quando se retirar. Portanto, eles deveriam tomar uma decisão de consenso para definir se e quando atacar. Então eles mandaram mensageiros para os acampamentos ao longo da noite para tentar definir esta decisão de consenso. Contudo, este esquema tinha dois problemas: Por um lado os mensageiros podiam ser simplesmente assassinados e a mensagem nunca chegar. Por outro lado, os generais bizantinos eram facilmente corrompíveis e o sultão poderia pagá-los para enviarem mensagens falsas a seus parceiros para tentar evitar que eles chegassem a um acordo coerente. Sabendo destes problemas, os generais concordaram em analisar todas as mensagens recebidas e tomar como decisão de consenso a opinião da maioria. Caso houvesse empate, a decisão seria a de defender seus exércitos, não atacando. Para organizar a comunicação é estabelecida uma ordem para que cada general distribua sua opinião aos demais generais. Para que este consenso seja possível, é necessário que o número total de generais seja no mínimo igual ao triplo de generais corrompidos mais um. Embora seja possível chegar em um acordo, o número de mensagens necessário é grande, pois cada general além de enviar sua opinião aos demais deve transmitir as informações que recebeu dos demais a todos os outros. Imagine que se tem M generais, N dos quais foram corrompidos. Cada general tem sua opinião O i e recebe dos demais as respectivas opiniões O j, j ≠ i. Cada general forma um conjunto de decisões da seguinte maneira: D i = {di1, di2, ..., diM} dii = Oi dij = O j se os valores recebidos do j-ésimo general forem majoritariamente O j, caso contrário fazem dij = recuar.

49

Finalmente, a decisão com maior número de votos em D i é tomada. Caso haja um empate a decisão é não atacar. Por exemplo, supondo quatro generais, três dos quais estão sob o comando geral do outro. Supondo que um dos generais foi corrompido. Neste caso é possível chegar a um consenso porque há generais suficientes. Inicialmente o general no comando manda sua opinião aos demais. Em seguida cada um dos três generais comandados envia aos outros dois a mensagem que recebeu do comandante. Por fim, cada um dos generais determina a decisão de consenso, baseado nas informações recebidas. Esta situação está esquematizada na figura abaixo. Como pode ser visto, neste caso, cada general recebe três comandos para atacar e um para recuar. Então, mesmo com um general desonesto tentando ludibriar os outros, através da retransmissão de mensagem com as ordens alteradas, é possível chegar a um consenso sobre o valor correto.

A C A A G1

A A G2

A

G3

A

desonesto

R

G2

A A

A

A G1

R ?

G3 A G3

R

R

G1

G2

Para tornar o problema mais geral e dar-lhe um aspecto computacional, tornar-se-ão os generais por processadores e se usará um parâmetro t para representar o número máximo de traidores aceitável. Desta forma, o protocolo dos generais bizantinos parametrizado a “t” traidores, GB(t) chegará a um consenso correto se M > 3t e N ≤ t. Isto significa que para poder admitir até N = t processadores falhos e ainda assim chegar a conclusões corretas, o sistema necessitará de pelo menos M = 3t+1 processadores. Este algoritmo tem um custo computacional exponencial em “t”, ou seja, o número de mensagens no sistema é O(M t). É possível fazer algumas restrições ao problema, para reduzir o número de mensagens em trânsito, reduzindo seu custo computacional. De qualquer forma, processadores que não funcionam corretamente não costumam atuar com tanta eficiência para obstruir o bom funcionamento do sistema. Portanto, de um ponto de vista de engenharia, não compensa tentar tornar o sistema imune a falhas bizantinas. Geralmente vale mais a pena empregar os recursos necessários para tanto na melhoria dos componentes do sistema e no desenvolvimento de técnicas de engenharia de programas aplicadas a sistemas distribuídos. Ainda assim, o estudo do protocolo dos generais bizantinos é interessante, do ponto de vista computacional, devido ao grande volume de trabalho desenvolvido para torná-lo eficiente. Estes trabalhos geraram soluções computacionais importantes.

50

 Impossibilidade de Consenso O modelo de falhas bizantinas subentende consenso em um ambiente bastante adverso. Mesmo assim, apesar do esforço computacional exigido, foi mostrado que é possível aos processadores entrarem em acordo a cerca de informações existentes no sistema. Entretanto, ao analisar as falhas bizantinas supunha-se um sistema síncrono, no qual todos os processadores que estivessem em condições de funcionamento transmitiriam suas opiniões dentro de um período de tempo pré-estabelecido. Ao tratar sistemas distribuídos assíncronos não se tem esta garantia de resposta em tempo pré-definido. De fato não há algoritmo capaz de garantir que todos os processadores em funcionamento cheguem a um consenso em tempo finito. Diante desta incapacidade, pode-se demonstrar que é impossível para um sistema distribuído assíncrono chegar a um consenso, mesmo na ausência de falhas bizantinas. Fischer, Lynch e Patterson [9] demonstraram formalmente esta impossibilidade. Esta demonstração consiste em provar que há casos nos quais o protocolo fica impedido de chegar a um consenso. O problema com esta demonstração é abrir espaço para se argumentar que sendo impossível chegar a um consenso os sistemas distribuídos são inúteis. Não obstante, para contradizer estes resultados teóricos os sistemas distribuídos assíncronos têm sido desenvolvidos e amplamente utilizados. Então a teoria estaria errada? Não, em verdade o que ocorre é que sistemas reais não garantem 100% de funcionamento funcionamento correto. Ao produzir um sistema admite-se o uso de protocolos parcialmente corretos, desde que sejam suficientemente robustos e falhem apenas em algumas situações raras. Quando isto ocorrer lança-se mão de intervenção humana para identificar o problema e resolvê-lo. Como às vezes as situações supostamente raras não são tão raras como se espera, uma alternativa é dotar o protocolo de um certo grau de indeterminismo de tal sorte que ao ser executado com os mesmos dados, ele não recaia sempre no mesmo ponto de falha. Embora ele possa vir a falhar por razões menos nobres, as falhas passam a ser menos freqüentes e não sistemáticas. Uma terceira alternativa é admitir uma consistência parcial, isto é, em vez de exigir que todos os processadores entrem em acordo sobre determinada informação, exige-se apenas daqueles processadores que “desejem” cooperar. Desta forma, somente as “opiniões” de processadores que estiverem respondendo suficientemente rápido serão consideradas.

Consenso Distribuído Aleatório A facilidade de entrar em acordo é inversamente proporcional aos obstáculos enfrentados pelos algoritmos. A dificuldade de se chegar a um consenso motivou várias pesquisas recentes para o desenvolvimento de protocolos que incorporam não determinismo na busca pelo consenso. Estes protocolos, ao invés de manterem processadores aguardando indefinidamente pela resposta de seus pares, param após um certo número de passos. O mais básico destes protocolos usa o conceito de memória compartilhada. Tem-se M processadores, N dos quais pode falhar. Tem-se um vetor V com a preferência de cada processador. Se em algum instante v i for igual para todos os processadores, tem-se um consenso. Se não houver um consenso, o i-ésimo processador “tira cara ou coroa” e escolhe seu novo valor de preferência. Eventualmente todos vão concordar com o mesmo valor. Infelizmente, há alguns problemas com este protocolo. O primeiro problema é que ele admite que o sistema é síncrono, pois requer que os processadores do sistema aguardem a sua vez para testar V e decidir sobre o consenso. Uma alternativa para tornar o algoritmo assíncrono seria associar a v i o

51

número de ciclos que o processador p i já executou. Quando todos os processadores estiverem no mesmo ciclo e preferirem a mesma coisa, o consenso foi encontrado. O segundo problema é que processadores podem falhar e, conseqüentemente, não atualizarão seu número de ciclos, inviabilizando o acordo. Uma alternativa para isto é se concentrar nos processadores mais rápidos. Permitindo-se que apenas os processadores mais rápidos e aqueles com um ciclo a menos que os mais rápidos discordem e obrigando os mais lentos a concordarem com os mais rápidos é possível contornar o problema com os processadores que vierem a falhar durante a execução do algoritmo.

52

Escalonamento de Processos Para que um processo possa ser executado em um sistema qualquer, deve-se determinar quando ele poderá usar a CPU e quais recursos estarão disponíveis. Se o sistema for distribuído, além disso, deve-se definir em qual processador ele deverá ser executado. O objetivo do escalonamento é resolver estas questões melhorando, na medida do possível, as métricas de desempenho global do sistema, tais como tempo de execução dos processos e a taxa de uso dos processadores. Contudo, estas melhorias não devem comprometer muito os aspectos de transparência de desempenho e de posição. Os sistemas tradicionais foram projetados para máquinas cujas arquiteturas tinham pouca, ou nenhuma, capacidade de reconfiguração. O uso de diversas máquinas e de malhas de comunicação configuráveis traz outras questões à baila. Por exemplo, passa a haver a necessidade de se considerarem atrasos de comunicação, a capacidade do meio de comunicação para a migração de processos e a sincronização de processos.

 Modelos As estratégias de escalonamento seguem modelos que podem ser classificados segundo o grau de interação entre os processos no sistema, os quais podem necessitar de troca significativa de informações durante sua execução ou podem rodar de forma essencialmente desconexa. Portanto, tem-se dois modelos básicos, quais sejam: de processos interativos e de processos disjuntos. Os modelos de processos interativos podem ser ainda subdivididos em modelos de precedência e de comunicação. Os modelos interativos de precedência têm por objetivo principal minimizar o tempo total para a execução das tarefas, incluindo-se o tempo necessário ao cômputo dos procedimentos internos aos diversos processos cooperantes e o tempo devido à comunicação entre eles. Por outro lado, os modelos interativos de comunicação têm como meta otimizar os custos devido à comunicação e aos cômputos. Enquanto nos modelos de precedência os processos concorrentes são gerados por construtores de linguagens e por via de conseqüência resultam em processos cujo interação se dá na forma síncrona, nos modelos de comunicação os processos que coexistem se comunicam de forma assíncrona. Já no modelo de processos disjuntos, a interação se dá de forma implícita e os processos podem ser executados de forma independente. No caso deste modelo, o objetivo é maximizar o uso dos processadores, minimizando o tempo de resposta (tempo de serviço + tempo na fila de espera) dos processos. Para atingir seus objetivos, os modelos de escalonamento podem incluir estratégias migratórias ou não. Por migração, neste escopo, refere-se à possibilidade de um processo mudar de máquina durante sua execução. A migração de processos busca reduzir o tempo gasto pelos processos na fila de espera por processamento. O custo mais importante para se conseguir isto é uma sobrecarga no sistema de comunicação. A migração facilita o compartilhamento de carga entre os processadores. Distribuir mais eqüitativamente a carga, respeitando as oscilações na capacidade de processamento do sistema, pode melhorar seu desempenho. Todavia, se os processos não forem disjuntos sua migração pode resultar na colocação de processos fortemente conexos, isto é, com grande necessidade de comunicação, em processadores diferentes, aumentando os requisitos do subsistema de comunicação e, conseqüentemente, comprometendo o ganho obtido com o compartilhamento da carga de processamento. Resumindo o que foi visto até agora, os modelos de escalonamento têm por objetivo maximizar a utilização dos processadores do sistema para minimizar o tempo médio e a taxa de resposta dos

53

processos a ele submetido. O grau de utilização do processador é medido através do número de ciclos da UCP gastos efetivamente com serviços para o usuário. O tempo médio de resposta, uma das variáveis a serem minimizadas, corresponde ao quociente entre a soma dos tempos de processamento mais os tempos de espera de todos os processos e o número total de processos considerados. O outro parâmetro a ser minimizado á a taxa de resposta, a qual corresponde à relação entre o tempo efetivamente gasto por um processo para ser executado em determinada máquina e o tempo gasto pelo mesmo processo para ser executado sozinho na mesma máquina. De modo geral, exceto para casos muito simples, o escalonamento para otimizar o tempo total necessário para completar a execução de um conjunto de processos iterativos (dito makespan) é NP-Completo. Além de ter solução muito difícil, a utilização de algoritmos determinísticos requer o conhecimento prévio das necessidades de todos os processos do sistema, tais como seus requisitos de comunicação, os arquivos que cada um precisará e quando cada arquivo será usado, a carga de processamento de cada processo e suas necessidades de utilização de outros recursos. Não sendo possível um conhecimento exato destas necessidades, no mínimo devem estar disponíveis aproximações estatísticas muito precisas. Quando isto não é possível, porque os requisitos dos processos podem mudar radicalmente de uma hora para outra, é necessário usar métodos heurísticos para proceder o escalonamento. Também, a busca da solução ótima pode ser muito cara devido à complexidade do algoritmo necessário, à quantidade de informações que devem estar disponíveis e ao rigor requerido do processamento. O bom senso ainda diz que, se um algoritmo relativamente simples puder fornecer resultados suficientemente próximos dos obtidos com algoritmos mais complexos é recomendável a utilização do mais simples. Como os bons algoritmos heurísticos para escalonamento distribuído conseguem fazer um balanceamento da carga de processamento de forma aceitável, bem como sobrepor comunicação e computação escondendo ao máximo os detalhes de comunicação e atingindo a maior concorrência possível, usualmente os algoritmos utilizados para o escalonamento de processos são heurísticos, distribuídos e sub-ótimos. Pode-se dividir os algoritmos de escalonamento em dois grandes grupos: os de distribuição estática e os de distribuição dinâmica. Os estáticos tratam do problema de compartilhamento de carga, isto é, procuram distribuir eqüitativamente a carga de processamento antes mesmo do início da execução dos processos. Os dinâmicos tratam do problema de balanceamento de carga, ou seja, procuram ajustar as oscilações na carga de processamento durante a execução dos processos, para que todos os processadores tenham suas potencialidades exploradas ao máximo possível.

 Escalonamento Estático Para fazer a partilha da carga de processamento os algoritmos de escalonamento estáticos procedem o mapeamento de processos em processadores antes da execução dos processos. Por princípio estes algoritmos consideram os processos não preemptíveis, ou seja, uma vez iniciado o processo só deixa o processador após concluída sua execução. Para obter um bom resultado estes algoritmos devem dispor de dados sobre o comportamento dos processos, tais como: tempo estimado de execução, relações de precedência e padrões de comunicação.

54

As principais desvantagens são que as decisões sobre o escalonamento são centralizadas e imutáveis. Alguns algoritmos de escalonamento estático levam em consideração as relações de precedência entre os processos e outros seus padrões de comunicação, como visto a seguir.

 Modelo de Precedência Neste modelo são usados dois grafos com informações sobre os processos e os processadores. O grafo de informações sobre os processadores indica o custo da comunicação entre eles, considerando nulo o custo de comunicação entre processos alocados a um mesmo processador. Um exemplo com três processadores pode ser visto no grafo da figura a seguir, em que os nós representam os processadores do sistema e os números associados às arestas representam o custo de comunicação entre os processadores.

0

2

P1

P3

1

0

1 P2

0 O grafo de informação sobre os processos representa, em seus nós, os processos e os respectivos tempos estimados para sua execução. As arestas deste grafo são dirigidas, representando as precedências entre os processos. Isto significa que se um processo A estiver na origem de uma aresta e um processo B em seu destino, o processo B só poderá ter sua execução iniciada após receber os resultados do processo A, pois aquele depende destes. Além desta relação de precedência, às arestas são associados números indicativos da quantidade de mensagens, com resultados, a serem transferidos de um processo a outro. Um exemplo de grafo de precedência com 7 processos é apresentado na próxima figura. Pode-se ver, por exemplo, que o processo G depende, para sua execução, de resultados oriundos dos processos D, E e F. Também pode-se ver que o processo E leva seis unidades de tempo para ser executado e que o início de sua execução depende da recepção de quatro mensagens vindas do processo A e uma outra do processo C. A/6

B/5

4

C/4

3

1

1

3 D/6

E/6

1

3 F/4

2 1 G/4 55

Como primeira aproximação pode-se usar a estratégia de escalonamento por lista, isto é, tomando por base o grafo de precedências e sem considerar o custo de comunicação, procura-se alocar os processos de modo que nenhum processador fique ocioso se houver alguma tarefa que ele possa processar. Para o caso do grafo de precedência da figura anterior o escalonamento por lista resulta na seguinte distribuição:

P1

A/6

P2

B/5

P3

D/6 F/4 2

C/4

E/6

G/4 7 4

Note-se que neste caso o makespan é de 16 unidades de tempo, sendo o caminho crítico ADG ou AEG. Todavia, para que se tenha um escalonamento realista é necessário levar em consideração os tempos de comunicação entre os processos. Neste caso pode-se usar o escalonamento por lista estendido, mostrado na figura a seguir, e que resulta num makespan de 28 unidades de tempo. P1 A/6 P2 B/5 P3 C/4

2 2

D/6

10

F/4

G/4

17

10

E/6

8

Nota-se da figura acima que o makespan aumentou muito devido ao tempo de comunicação entre processos. Principalmente pelo fato do processo E ter sido alocado a um processador (P3) diferente daquele ao qual foram alocados os processos A e G (P1). Pior que isto é o fato do custo de comunicação entre P1 e P3 ser o mais elevado do sistema. Quando não for possível, como neste caso, alocar de forma eficiente todos os processos em caminhos críticos a um mesmo processador deve-se, ao menos, tentar alocá-los a processadores com menor custo de comunicação. Procurando otimizar este aspecto utiliza-se o algoritmo ETF (  Earliest Task First ) que computa o tempo de comunicação durante o escalonamento, alocando inicialmente a tarefa escalonável primeiro. A figura abaixo ilustra o resultado deste algoritmo para o exemplo utilizado até aqui. P1

A/6

P2

B/5

P3

C/4

E/6 2 4

D/6 F/4

6 1 G/4 6

Neste caso verifica-se um makespan de 18 unidades de tempo, bem próximo do ideal. Note-se que, inicialmente, somente A, B e C podem ser executados, sendo cada um alocado a um processador. Ao concluir a execução de B e C libera-se F para execução. Alocando F em P2, correspondendo à execução de F o mais breve possível, forçar-se-á a alocação de D e E a P1 e P3, respectivamente, ou vice-versa. Entretanto, isto levaria a situação semelhante ao resultado do escalonamento por lista estendida. A melhor solução é atrasar o início da execução de F em uma unidade de tempo, o suficiente para alocá-lo ao processador P3. Neste meio tempo conclui-se o processamento de A, liberando D e E para execução. Se D for alocado a P1 e a E a P2, suas execuções iniciarão nos instantes correspondentes a 8 e 10 unidades de tempo, respectivamente, devido aos atrasos relativos à comunicação dos resultados entre P1 e P2. Por outro lado, se a alocação for invertida, isto é, D em P2 e E em P1, o processamento de E pode iniciar imediatamente após a conclusão de A (no

56

instante 6) e o de D pode iniciar uma unidade de tempo depois. Nota-se assim que a solução apresentada é a melhor possível para o problema em questão.

 Modelo de Comunicação Freqüentemente, processos são independentes e não necessitam obedecer relações de precedência, precisam apenas trocar informações, isto é, se comunicar. Neste caso o algoritmo pode levar em conta a minimização de uma função de custo cujos parâmetros são o custo de execução de um processo em um dado processador, representado por e j(pi), e o custo de comunicação entre dois processadores, representado por ch,k (ph, pk), considerando-se nulo o custo de comunicação entre dois processos que estiverem sendo executados no mesmo processador, ou seja, ci,i (pi, pi) = 0. A função a ser minimizada é: Custo (G,P) =



e j(pi) +

 j ∈ V(G)

∑ ch,k (ph, pk),

onde G(V,A) é um grafo em que

h,k ∈ A(G)

os vértices (V) representam os processadores do sistema e às arestas (A) estão associados pesos referentes ao custo de comunicação entre os processos em seus extremos. Um exemplo é apresentado na tabela e grafo a seguir:

Processo

Custo no Processador

1 2 3 4 5 6

A

B

5 2 4 6 5

10 ∝

4 3 2 4



Custo de Computação 1

6

12 4

2

8 12 3

6

3 11

4 5 5

57

Custo de Comunicação Neste exemplo tem-se dois processadores A e B e seis processos. O custo de computação de cada processo em cada processador é mostrado na tabela acima, em unidades de tempo genéricas. Há de se notar que o processo 2 tem um tempo de execução infinito no processador B. Situação análoga ocorre com o processo 6 no processador A. Isto significa que o processo 2 não pode ser executado no processador B, assim como o processo 6 não pode ser executado no processador A. Estas situações podem ocorrer, por exemplo, devido à falta de recursos nos processadores para executar determinados processos. Para casos com o deste exemplo, onde se tem dois processadores, há uma solução obtenível em tempo polinomial. Aumentando-se o grafo de comunicação com os nós referentes aos processadores e incluindo arestas com os custos respectivos, obtém-se um grafo de trabalho. Neste grafo, uma aresta entre um processador e um processo tem o peso correspondente à execução do processo no outro processador. Por exemplo, o processo 4 leva 3 unidades de tempo para ser executado no processador B. Então à aresta entre o nó correspondente ao processo 4 e o nó correspondente ao processador A é associado o peso 3. Situação simétrica ocorre com a aresta entre o nó correspondente ao processo 4 e o nó correspondente ao processador B, ou seja, a ela é associado o peso 6; que é o tempo necessário para executar o processo 4 no processador A. Esta inversão se faz necessária, pois, uma vez obtido este grafo de trabalho o objetivo é achar um corte no grafo, que minimize a função de custo. O grafo de trabalho para o exemplo aqui apresentado,  juntamente com o corte de custo mínimo, é mostrado na figura abaixo.

10

1

6 2



12 4

6

4 A

4 3 2 3



2

8 12

5 B

4 6

3

5 4

11 5

Custo de Corte = 38

5

Observe-se que o custo de corte é obtido somando-se os pesos das arestas interceptadas pela linha de corte e, neste caso, corresponde a 38 unidades de tempo. Este tempo é obtido alocando-se o processo 6 no processador B e os demais em A. Ressalva-se que conforme os números de processos e de processadores crescem, o problema torna-se NP-Completo e sua solução computacional torna-se inviável.

 Escalonamento Dinâmico

58

O principal problema do escalonamento estático é a necessidade do conhecimento das informações sobre os processos antes do procedimento ser aplicado.

O escalonamento dinâmico ataca os problemas de balanceamento e de compartilhamento de carga durante a execução dos processos, considerando que eles podem migrar de uma máquina a outra. A questão do compartilhamento de carga busca reduzir a ociosidade dos processadores atribuindo processos àqueles com filas de espera menores. A questão do balanceamento de carga tenta reduzir o tempo de resposta e atingir um certo grau de justiça no sistema através da equalização do tamanho das filas de espera. A idéia chave para o funcionamento do escalonamento dinâmico é a política utilizada para a migração dos processos. A transferência de processos entre máquinas do sistema pode ser baseada em informações locais, caso em que os algoritmos costumam ser simples porém muito distantes da otimalidade, ou baseada em informações globais, quando a qualidade da alocação costuma ser melhor mas o custo computacional cresce significativamente. Uma política de transferência baseada em informações locais pode calcular periodicamente a carga da máquina em que o processo for criado. Quando esta carga estiver abaixo de um limiar pré-estabelecido, o processo é executado na máquina em que foi criado, caso contrário ele deverá encontrar outra máquina para rodar. Uma política de transferência baseada em informações globais deve coletar informações sobre a carga nas outras máquinas do sistema e, em função destas informações, decidir em qual máquina o novo processo deve ser executado. O procedimento de migração pode ser iniciado pelo transmissor ou pelo receptor. No primeiro caso o processo que se considera sobrecarregado busca outro que esteja mais livre. No segundo caso o processador que estiver ocioso anuncia sua disponibilidade e aguarda que outro processador lhe envie serviço. O procedimento de migração sofre do problema de estabilidade, que consiste no fato do tempo necessário aos algoritmos para atualizarem suas informações sobre os processadores não ser nulo. Dessa forma é possível que processos fiquem pulando entre dois processadores devido ao assincronismo nestas atualizações. Quando a carga de processamento é relativamente baixa, a transferência iniciada pelo transmissor produz resultados melhores. Ao iniciar um processo, nas transferências iniciadas pelo transmissor, incrementa-se o tamanho da sua fila de processos. Se ele estiver abaixo de um limiar pré-fixado coloca-se o processo na fila de espera para execução do processador transmissor. Caso o tamanho da fila seja maior que o limiar estabelecido, o transmissor testa possíveis receptores. Se encontrar algum processador cuja fila esteja menor que a sua, ele transfere o processo para o receptor, caso contrário ele mantém o processo na sua própria fila.

59

O maior problema, neste caso, é que quando a carga sobe muito todos os processadores se considerarão sobrecarregados e procurarão receptores para seus processos, o que sobrecarregará o sistema de comunicação, prejudicando o funcionamento do sistema. Em contraposição, pode-se deixar a tarefa de busca para os possíveis receptores. Neste caso, sempre que a carga em um processador cair abaixo do limiar ele busca a carga em outros processadores. Para que este esquema funcione os processos devem ser preemptíveis, pois serão inicializados, primeiramente, nos processos em que forem criados. Estes algoritmos são mais estáveis quando a carga de processamento é alta, mas deve-se tomar cuidado para transferir processos somente quando os benefícios obtidos com a migração forem maiores que o custo com sua transferência. Além disso, os algoritmos de transferência iniciados pelo receptor podem gerar muita sobrecarga no sub-sistema de comunicação quando a carga de processamento no sistema distribuído for muito baixa. Todos os tipos de distribuição de carga discutidos necessitam ativar a execução de processos remotamente. Isto pode ser feito segundo o modelo cliente/servidor. Toda a comunicação é feita indiretamente através de um elo lógico, fazendo os limites físicos entre os processos local e remoto transparentes. Dependendo de como as mensagens de solicitação são interpretadas, podem-se Ter três cenários de aplicações importantes, quais sejam:

(1) serviço remoto: a mensagem é interpretada como a solicitação de um serviço conhecido em um local remoto, como em uma chamada a procedimentos remotos; (2) execução remota: a passagem contém um programa para ser executado em local remoto. Mantém a visão da máquina que solicitou a execução, usando a máquina remota só para aliviar a carga computacional. Não é preemptível; (3) migração de processos: a mensagem representa um processo sendo transferido a um local remoto, para continuar sua execução. É um mecanismo preemptivo.

 Algoritmos Genéticos

O funcionamento de um algoritmo genético começa com uma população inicial cujos indivíduos evoluem através das gerações, tentando imitar a natureza biológica. A população inicial é gerada aleatoriamente e a habilidade de seus indivíduos se reproduzirem e propagarem suas características pelas várias gerações depende do ajuste de cada um deles. No caso de algoritmos para o escalonamento de processos o juste de um indivíduo é definido como a diferença entre o makespan do indivíduo com o maior makespan da população e o makespan do indivíduo considerado. Ressalte-se que o melhor indivíduo, dito o melhor adaptado, é aquele com o menor makespan e, consequentemente, com o maior ajuste. A transformação de gerações é obtida por meio de alguns operadores genéticos, tais como seleção, cruzamento e mutação, aplicados aos indivíduos de cada população. O funcionamento destes operadores é visto a seguir. O operador de seleção torna possível ao algoritmo tomar decisões, de certa forma tendenciosas, para favorecer os bons indivíduos na mudança de gerações. Para que isto ocorra são feitas réplicas dos indivíduos melhor adaptados e os menos adaptados são eliminados. Conseqüentemente, após a seleção a população tende a ser dominada pelos indivíduos melhor adaptados. Iniciando com uma população P1, esta transformação ocorre pela geração iterativa de uma nova população P2, com o mesmo tamanho de P1. Entende-se por tamanho de uma população o número total de seus indivíduos. Esta transformação se dá da seguinte forma: Inicialmente, o melhor indivíduo de P1 é selecionado e uma réplica deste indivíduo é colocada em P2 (o indivíduo é mantido em P1). Então 60

são feitas iterações e, em cada uma delas, seleciona-se aleatoriamente um indivíduo de P1. A probabilidade de escolha é proporcional ao grau de ajuste do indivíduo ao problema. É feita uma réplica do indivíduo escolhido e, a exemplo do caso inicial, o indivíduo é mantido em P1 enquanto sua réplica é inserida em P2. O processo iterativo é repetido até que P2 atinja o tamanho de P1. Observe-se que por este processo de seleção cada indivíduo pode ser escolhido mais de uma vez ou mesmo nenhuma vez. Desta forma, os indivíduos não selecionados (os quais deverão ser com grande probabilidade os menos adaptados) são eliminados nas novas gerações. Os algoritmos genéticos baseiam-se no princípio que o cruzamento de dois indivíduos pode gerar rebentos com características melhores que ambos os pais. O operador de cruzamento parte de dois indivíduos de uma população e gera dois novos indivíduos trocando parte das características genéticas dos pais. Supondo que se tem dois pais, p 1 e p2, cujas características genéticas são dadas pelos binômios (p1a, p1b) e (p2a, p2b), respectivamente. Supondo que cada parte dos genes de um dos pais tenha o mesmo número de genes da parte correspondente do outro, isto é, |p 1a | = |p2a | e |p1b | = |p2b |. então o cruzamento se dá trocando as partes correspondentes dos pais, para gerar os filhos f 1 (p1a, p2b) e f 2 (p2a, p1b). Deste modo, cada rebento mantém algumas características dos pais. O problema é que se uma das partes tiver genes correspondentes iguais em ambos os pais, as características correspondentes àquele gene não serão alteradas pelo processo de cruzamento. Por exemplo, imagine-se as características genéticas dos pais p 1 e p2 representadas por (a1, a2, a3) e (b1, b2, b3), respectivamente. Imagine-se a 1≠b1, a2≠b2 e a3=b3. Então qualquer ponto que se use para cruzamento não produzirá mudança no efeito do gene na terceira posição da seqüência. No pior caso, se os binômios para cruzamento forem ([a 1,a2],[a3]) e ([b1,b2],[b3]), ao efetuar o cruzamento obtém-se os filhos f 1 = (a1,a2,b3) e f 2 = (b1,b2,a3). Entretanto, f 1 = p1 e f 2 = p2, pois a3 = b3. A forma de se introduzir diversidade nos indivíduos da população e evitar a situação recém descrita é através do operador de mutação. Este operador produz uma pequena alteração em alguma característica genética de alguns indivíduos. Tanto o gene a ser alterado quanto o indivíduo são escolhidos aleatoriamente, com baixa taxa de probabilidade. Retomando o exemplo citado no parágrafo anterior, o operador de mutação seria aplicado a f 1 e a f 2, para escolher algum de seus genes ao acaso. Eventualmente, a terceira posição de f 1 poderia ser escolhida, por exemplo. Neste caso, b3 seria modificado, ficando diferente de a 3. Portanto, f 1 passaria a ser diferente de f 2 e, mais importante do que isto, os rebentos produzidos pelo cruzamento de f 1 e f 2 seriam diferentes dos pais, podendo resultar em possíveis melhoramentos genéticos. A estrutura de um algoritmo genético é um laço composto por uma seleção seguida por uma seqüência de cruzamentos e outra seqüência de mutações. Após a divisão aleatória da população em pares de indivíduos, os elementos destes pares são cruzados. Após o cruzamento, cada indivíduo da nova população é (ou não) modificado por um processo de mutação com baixa probabilidade. Esta probabilidade é determinada no início do processo e mantém-se constante durante toda a execução do algoritmo. A condição para o término do algoritmo pode ser o estabelecimento de um número máximo de iterações, a definição de um tempo máximo de execução, a verificação da estabilidade dos resultados, etc. Um exemplo de aplicação de um algoritmo genético puro aplicado ao escalonamento de tarefas em um sistema multiprocessador é descrito por Hou et al. [10] e algumas melhorias feitas sobre este algoritmo são discutidas por Corrêa et al. [11].

Compartilhamento de Memórias Distribuídas Há três paradigmas importantes para a troca de informações entre processos em sistemas distribuídos, quais sejam, a passagem de mensagens, a chamada a procedimentos remotos e o compartilhamento de memória.

61

A comunicação via passagem de mensagens é feita através da interpretação de dados transferidos no sub-sistema de comunicação. A chamada a procedimentos remotos é uma comunicação em um nível mais alto de abstração que possibilita a troca de informações através da passagem de parâmetros entre processos utilizando o mecanismo de passagem de mensagens. O compartilhamento de memória possibilita a comunicação direta entre os processos em sistemas fortemente acoplados, ou seja, viabiliza a troca de informações entre processos através de uma memória de uso comum ligada ao sub-sistema de comunicação. O problema da utilização direta do mecanismo de passagem de mensagens é a falta de transparência. Por outro lado, embora a chamada a procedimentos remotos possibilite a comunicação de dados com transparência, sua eficiência é limitada pelo mecanismo utilizado. Como não é possível acessar diretamente o espaço de endereçamento de máquinas remotas, o compartilhamento real de informações é difícil, em particular quando se trata de estruturas de dados grandes e complexas, abarrotadas de apontadores. Por fim, o compartilhamento de memória, que poderia resolver os problemas de acesso transparente à informações reais é restrito, em sua forma original, a sistemas fortemente acoplados. O objetivo primordial do compartilhamento de memórias distribuídas é viabilizar o compartilhamento direto de informações entre processos comunicantes por meio da simulação de um espaço de endereçamento lógico compartilhado, sobre um conjunto de memórias locais distribuídas fisicamente. Este conjunto de memórias chamar-se-á Memória Compartilhada Distribuída (MCD). Para sua implementação utiliza-se o mecanismo de passagem de mensagens, o qual fica transparente por ser encapsulado em uma camada com programas para o gerenciamento e o mapeamento entre a MCD e o sistema de passagem de mensagens. Dependendo da localização dos dados, a forma de acesso a eles deve ser diferente, isto é, em um sistema distribuído fracamente acoplado, onde tenha sido implementado um sistema para compartilhamento de memória, os dados podem ser armazenados localmente, na memória do processador onde o processo estiver sendo executado, ou remotamente, na memória de algum outro processador. A forma para acessar os dados em um e no outro caso é diferente. Em sistemas multiprocessadores de grande porte convém manter a parte do espaço de endereçamento mais freqüentemente acessada por um conjunto de processos mais próxima do(s) processador(es) em que estiverem sendo executados, deixando o restante do espaço de endereçamento mais afastado. Os multiprocessadores com arquiteturas que permitem este tipo de hierarquia de acessos à memória em vários níveis são ditos sistemas com forma irregular de acesso à memória ou, do inglês, sistemas NUMA ( Non-Uniform Memory Access) [12]. Como este tópico, vários outros aspectos do projeto e da implementação de MCDs são muito semelhantes aos que devem ser considerados para sistemas de memória cache de multiprocessadores (MCM). Por exemplo, a informação tratada em cada acesso à memória pode consistir em uma palavra, um bloco de palavras, uma página ou um segmento. Esta dimensão corresponde à granularidade do procedimento de acesso. A escolha da granularidade é um item importante a ser considerado em ambos os casos. Outro aspecto importante em sistemas NUMA é a possibilidade de mover páginas de memória compartilhada entre os módulos de memória do sistema, ou copiar alguma página muito concorrida para diversos módulos em processadores diferentes. Esta multiplicação de páginas em sistemas distribuídos requer a manutenção do coerência no sistema de memória. O controle de consistência (ou da coerência de memória) é um aspecto muito estudado em MCDs e muito importante no projeto de MCDs.

62

Devido a esta analogia entre MCMs e MCDs e tendo em vista a quantidade de estudos já realizados para MCMs, que podem ser aplicados a MCDs, a seguir serão analisados alguns aspectos desses sistemas, quando aplicados em arquiteturas NUMA.  Arquiteturas NUMA

Para iniciar, a configuração genérica de uma arquitetura NUMA é apresentada na Fig. 1,  juntamente com suas versões para implementação de MCMs e MCDs. Na fig. 1-a vê-se que uma máquina NUMA genérica é composta por um conjunto de processadores, cada um dos quais com um módulo de memória local. Além deste conjunto de pares (processador, memória) há uma malha de comunicação interligando todos os conjuntos e, na interface entre cada par processador-memória e a malha de comunicação há um controlador cujo objetivo é manter a coerência do sub-sistema de memória. Nas Fig. 1-b e 1-c tem-se, respectivamente, as implementações de MCMs e MCDs, como mencionado anteriormente e descrito nos dois parágrafos seguintes. Na fig. 1-b tem-se o diagrama do que seria uma implementação, sobre uma máquina NUMA genérica, de um sistema MCM. Neste caso, os módulos de memória local são “caches” utilizadas principalmente para reduzir o tempo médio de espera nos acessos à memória e minimizar o tráfego de acessos à memória global. A memória global pode ser uma grande memória RAM compartilhada por todos os processadores, mas também pode ser um conjunto de módulos de memória distribuídos pelo sistema. O barramento, por sua vez, pode ser um barramento comum, mas isto tende a gerar grande disputa pelo seu uso. Alternativamente, é possível utilizar vários barramentos, ou uma rede de conexão. Isto é recomendável, principalmente se o sistema for composto por muitos processadores e tiver vários módulos de memória compondo a memória global. Na fig. 1-c tem-se o diagrama do que seria a implementação, sobre uma máquina NUMA genérica, de um sistema MCD. Neste caso, não há uma memória global física. Os módulos de memória locais são utilizados para formar um espaço de endereçamento global, correspondendo a uma grande memória virtual. Este espaço de endereçamento é compartilhado por todos os processadores do sistema. Os processadores acessam cada porção não local deste espaço de endereçamento através de uma rede de conexão, geralmente formada como uma LAN ( local area network ) ou WAN (wide area network ).

Processador (P1)

Módulo de memória MM1

P2

MM2

...

Pn

CCM2

MMn CCMn

Controlador de coerência de memória

Malha de comunicação Fig. 1-a: Diagrama em blocos de uma máquina NUMA Genérica

63

Memória Global Barramento Comum CCM

CCM M

P1

M

P2

...

CCM

1

M

P3

2

Controladores

CCM M

Pn

3

n processadores

caches locais

Fig. 1-b: Implementação de uma MCM Memória Rede de conexão CCM P1

CCM M 1

P2

...

CCM M 2

P3

M

Controladores de coerência

CCM Pn

3

M n

processadores

Memórias locais

Fig. 1-c: Implementação de uma MCD Os atrasos para acesso aos dados nas memórias variam de acordo com a organização hierárquica da memória e com as políticas de atualização de dados. Só para constar, pode-se citar o exemplo da máquina NUMA de Stanford, DASH[4], que consiste em 16 nós de processamento, cada um dos quais com quatro processadores. Cada processador possui uma memória cache de 64 Kbytes no primeiro nível, as quais formam um segundo nível com 256 K bytes. A rede de conexão é uma malha bidimensional roteada como rosca sem fim. A coerência na memória é mantida por um protocolo que se reporta a uma estrutura especial de diretório para invalidar cópias dos dados após a escrita em uma delas. O acesso a dados se dá em quatro estágios possíveis, quais sejam: local, remoto em dois passos, remoto em três passos ou ausente. Nos três primeiros casos o dado é encontrado na cache e no quarto é necessário um acesso à memória global. As latências típicas são, respectivamente, de 1, 30, 100 e 135 ciclos de relógio do processador. O principal objetivo das MCDs é alcançar transparência do sistema e o das MCMs e melhorar o acesso à memória. Para atingir seu objetivo as MCMs exploram as localidades temporal (freqüência de referência) e espacial (proximidade das instruções) de programas aplicativos. Apesar dito, aplicações distribuídas precisam compartilhar informações e conforme aumenta o número de processadores o barramento se torna o gargalo do sistema. A solução de hardware para este problema é o aumento da banda de passagem da memória utilizando-se vários barramentos ou uma rede de ligações 64

escalonável. A solução por software é fazer várias cópias da mesma informação em módulos de memórias locais, para evitar a necessidade de acessos globais. Em MCDs implementadas sobre redes fracamente acopladas a abordagem por software parece ser a única viável. As MCDs conseguem prover a transparência na comunicação de dados usando o mecanismo de passagem de mensagens. Com isto os usuários não precisam se preocupar com o movimento e eventuais conversões de dados, com o tratamento de parâmetros necessários à troca das mensagens ou com a manipulação dos detalhes dos protocolos de comunicação. Outras vantagens das MCDs são que o tráfego entre o processador e o sistema de memória fica menos sensível ao tamanho da rede de comunicação; que a compreensão do sistema e sua implementação se tornam relativamente fáceis devido à grande quantidade de coisas feitas para uniprocessadores; e que o sistema pode ser facilmente escalado, uma vez que para aumentar o espaço de endereçamento basta aumentar a quantidade de módulos de memória.

 Alocação, Migração e Cópia de dados O objetivo de se alocar, migrar ou copiar dados no sistema de memória é minimizar o tempo médio de acesso aos dados. Para isto, deve-se colocar a maior quantidade possível de dados na memória local em MCMs ou em MCDs. Quanto maior a quantidade de dados disponíveis nas memórias locais, maior será a taxa de sucesso nas buscas (TSB) locais e, consequentemente, menor será a necessidade de buscas em módulos remotos. Portanto, quanto maior a memória local, melhor a TSB. Além do tamanho da memória local há outros dois “tamanhos” que influem na TSB, são eles: o da unidade básica para o compartilhamento de dados; e o da unidade buscada quando há um insucesso na busca local. Embora possam ser diferentes (palavras, linhas, páginas,...), o uso de blocos iguais em ambos os casos facilita tanto a implementação do sistema quanto a discussão a seguir. O tamanho do bloco influencia significativa e dicotomicamente a TSB. Quanto maior for o bloco, maior será a cobertura na localidade das referências e, portanto, maior será a TSB. Por outro lado, blocos muito grandes trazem consigo muita informação irrelevante para o processamento atual, podendo remover informações úteis já presentes na memória local, para lhe abrir espaço, reduzindo assim a TSB. Além disto, o sistema de controle de coerência necessita que as referências a blocos sejam armazenadas em uma estrutura de diretórios (ou uma tabela) para a localização e eventual gerenciamento das cópias. Se forem utilizados blocos grandes haverá menos blocos no sistema e o espaço de memória necessário para armazenar esta estrutura será menor, isto significa menos sobrecarga no sub-sistema de memória. Entretanto, blocos grandes gastam mais recursos para serem transferidos, representando maior sobrecarga para o sub-sistema de comunicação. Todavia, por considerações diametralmente opostas a estas, o uso de blocos pequenos geram pouca sobrecarga no sub-sistema de comunicação e muita sobrecarga no sub-sistema de memória. Quando ocorre um insucesso na busca local os dados desejados devem ser acessados remotamente, isto pode ser feito por migração ou cópia do bloco correspondente. A migração corresponde à eliminação do bloco de um módulo de memória seguido da sua implementação no módulo requisitante. A cópia consiste na criação de réplicas do bloco em vários módulos de memória. Quando se utiliza o processo de migração o problema de coerência se restringe à coerência entre a única cópia do bloco no sistema e seu original na memória principal. A migração pode se dar por iniciativa do processador que detém a cópia do bloco, o qual ao detectar que determinado bloco não lhe tem mais serventia no momento ele o oferece a outro processador na expectativa de que o outro 65

use o bloco em futuro próximo. Este esquema não é muito usado por requerer uma carga de gerenciamento e uma sobrecarga no sub-sistema de comunicação, muitas vezes desnecessárias. O mais usado é a migração sob demanda, na qual o processador que detém a cópia a mantém até que ela seja solicitada por outro processador ou eliminada para dar lugar a outro bloco. O problema com o esquema de migração é o efeito pingue-pongue, que ocorre quando um processador A solicita um bloco que está armazenado em outro processador B. O bloco é transferido de B para A, mas em seguida o processador, que cedeu o bloco, necessita dele novamente, requisitando a transferência do bloco de A para B. Logo após esta Segunda transferência o processador A requer novamente o bloco. Desta forma há uma seqüência de transferências do mesmo bloco entre os dois processadores, provocando uma sobrecarga no subsistema de comunicação. Este problema pode ser agravado quando ocorre um falso compartilhamento, isto é, quando o processador A necessita de uma informação no início do bloco e o processador B necessita de outra informação no final do bloco. Isto ocorre principalmente com blocos muito grandes e poderia ser evitado se o tamanho do bloco fosse reduzido. Para evitar o problema do efeito pingue-pongue pode-se fazer diversas cópias de certos blocos compartilhados, melhorando a concorrência no sistema, além de reduzir a sobrecarga do subsistema de comunicação. Ressalte-se que além da sobrecarga no subsistema de comunicação, pode haver um problema de latência na transferência dos blocos, isto é, um processador pode ser obrigado a esperar que o outro termine de usar o bloco, caso o tempo entre dois acessos consecutivos por um mesmo processador seja menos que o tempo necessário para a transferência e o uso do bloco pelo outro processador. O principal inconveniente do uso de réplicas é o custo computacional acrescido pelo sistema de controle de coerência de dados.

 Modelos de Consistência de Memória Nas MCDs o compartilhamento das informações se dá na própria memória em que os processadores são também armazenados. Isto significa que se esta memória não estiver totalmente consistente, não só os resultados dos processos, mas suas próprias integridades, podem ser comprometidas. Por exemplo, se alguma variável utilizada para a sincronização dos processos for afetada, os processos podem não ser sincronizados adequadamente e podem, até mesmo, falhar. Os termos consistência e coerência são, geralmente, utilizados como sinônimos. Todavia, Chow e Johnson [1] sugerem uma distinção entre eles. Coerência seria utilizado com um sentido mais restrito, enquanto consistência teria uma conotação mais relaxada no que se refere ao ordenamento temporal de eventos. Desta forma, a definição destes termos com base nas operações de escrita e leitura seriam: (a) um sistema é coerente quando uma operação de leitura sempre retorna o valor dado pela ultima escrita na mesma variável compartilhada; e (b) um sistema é consistente se todas as cópias de uma variável compartilhada contiverem a mesma informação, quando todas as operações de escrita forem completadas em algum instante no tempo. A seguir apresentam-se alguns modelos de consistência ordenados segundo sua restritividade. Elas estão agrupadas em duas categorias: os de acesso geral e os de acesso sincronizado. Os da primeira categoria podem não ser tão restritivos se a exigência de ordenamento das operações de escrita for relaxado, com relação aos processadores ou às posições de memória. Os da segunda limitam a coerência apenas a acessos de sincronização. Um diagrama em blocos com esta classificação é apresentado na Figura 2.

66

Consistência Atômica: Este modelo requer que a MCD se comporte como a memória em um sistema centralizado sem cópias de dados. Todos os processadores devem executar os eventos na mesma ordem, os quais devem parecer executados em seqüência (atomicamente). É o modelo mais restritivo e geralmente só é usado como base para a avaliação de outros modelos de consistência. Consistência Atômica Relaxamento de Ordem de Tempo-Real Consistência Seqüencial Relaxamento Relativo ao Processador Consistência Fraca

Consistência Causal Relaxamento Relativo ao Processador

Consistência de Liberação

Consistência de Processador Relaxamento Relativo a Posição

Consistência de Entrada

Memória Lenta

Nenhum Suporte de Coerência no Sistema

Fig. 2 - Uma Taxonomia de Modelos de Consistência Consistência Seqüencial: Como operações de acesso podem sofrer atrasos diferentes e, portanto, ser observadas em ordem diversa por processadores diferentes, não se exige o ordenamento real dos eventos, mas apenas que o resultado das operações seja observado pelos processos na ordem e que forem solicitados. P1

W(x)1

P2

W(y)2

P3

R(y)2

R(x)0

R(x)1

Consistência Causal: Somente escritas com relação de causa precisam ser observadas na mesma ordem por todos os processadores. P1

W(x)1

P2

R(x)1

P3

R(x)1

W(x)3 W(x)2 R(x)3

R(x)2

67

P4

R(x)1

R(x)2

R(x)3

Consistência de Processador: Apenas escritas solicitadas pelo mesmo processador precisam ser executados e observados na ordem em que foram emitidos. P1 P2

W(x)1 R(x)1

W(x)2

P3

R(x)1

R(x)2

P4

R(x)2

R(x)1

W(x)1 e W(x)2 estão relacionados causalmente porque R(x)1 e seus efeitos burlam a regra da causalidade, pois são observados em ordens diferentes por P3 e P4.

Consistência de Memória Lenta: Apenas escritas à mesma posição na memória solicitadas pelo mesmo processador precisam ter sua ordem respeitada. Seus efeitos são visíveis localmente, imediatamente, e demoram para ser propagados (daí o nome) pelo sistema. P1 P2

W(x)1 W(y)2

W(x)3 R(y)2

R(x)1

R(x)3

Consistência Fraca: Somente acessos a variáveis de sincronização precisam seguir um ordenamento seqüencial. Nenhum acesso a variáveis de sincronização é solicitado enquanto todas as operações de leitura e escrita pendentes não estiverem atendidas. Nenhuma solicitação de leitura ou escrita é emitida enquanto os acessos a variáveis de sincronização pendente, não forem resolvidos. Consistência de Liberação: A sincronização pode ser divida em “obtém variável” e “libera variável”. Admite-se que uma operação para obter variável bloqueia acessos até que a operação seja completada. Quando a operação de liberação, todas as operações anteriores devem ter sido executadas antes da liberação da variável. Pode-se melhorar a concorrência se a operação para obtenção do semáforo não atrasar (bloquear) acessos anteriores e se a operação para liberação não retardar acessos futuros. Consistência de Entrada: Este modelo bloqueia objetos em vez de seções críticas para a exclusão mútua na sincronização. É chamado de consistência de entrada porque a variável compartilhada é consistente na entrada da região crítica.

Sistemas de Memória Cache em Multiprocessadores Apesar de serem conceitualmente semelhantes MCMs e MCDs têm alguma diferenças de implementação. Enquanto as MCMs pressupõem uma memória global compartilhada por um conjunto de módulos de memória cache através de um barramento comum, as MCDs consistem de um conjunto de módulos de memória que formam um espaço de endereçamento global virtual compartilhado via uma rede de comunicação fracamente acoplada. Contudo, as MCMs são estudas há muito tempo e suas semelhanças com MCDs faz com que a análise do problema de coerência dos dados nessas memórias seja bastante oportuno neste texto.

68

 Estruturas de Memórias Cache As memórias cache são buffers de alta velocidade cujo objetivo primordial é minimizar a diferença de velocidade entre a UCP e o sistema de memória. Seu funcionamento tem por base o princípio da localidade, que pode ser enunciado da seguinte forma: a informação a ser utilizada no futuro próximo é provavelmente aquela que está sendo processada atualmente (localidade no tempo) e que está adjacente àquela em uso corrente (localidade no espaço). De acordo com este princípio, a UCP necessita que apenas uma porção do espaço de endereçamento de um processo esteja ao seu alcance imediato em dado intervalo de tempo. Como a memória principal tem um tempo de acesso muito alto em relação ao funcionamento da UCP,   justifica-se transferir a parte relevante das informações sobre o processo em execução, para uma memória que consiga fornece-las à velocidade de processamento da UCP. No caso de um sistema multiprocessador, há várias UCPs e cada uma delas terá seu módulo de memória cache. Neste caso, cada UCP poderá ter sua cópia de determinada porção do espaço de endereçamento. Para manter a consistência várias cópias e evitar que algum processador utilize informações desatualizadas, deve-se adotar uma estrutura que permita o gerenciamento destas informações dispersas. Uma estrutura possível envolve o estabelecimento de uma cópia principal do bloco de informação. Esta cópia deve permanecer na memória principal ou em algum módulo de memória que possa ser acessado por qualquer processador do sistema. Ao bloco de informação deve se associar um conjunto com tantos bits quantos forem os processadores do sistema, chamado de diretório, mais alguns bits para armazenar informações de estado. As demais cópias, armazenadas nas memórias cache dos diversos processadores, necessitam apenas de alguns bits de estado. Cada um dos bits do diretório da cópia principal, quando no valor lógico 1, indica que o processador correspondente possui cópia do bloco associado ao diretório. Além do diretório, pelo menos dois bits de estados são necessários: um para indicar se o bloco é compartilhado ou não e outro para indicar se a informação contida no bloco é válida ou não. O primeiro bit é chamado de bit de exclusividade (E) e o segundo de bit de validade (V). Admitindose que toda a alteração de informação seja transcrita imediatamente para a memória principal, a cópia principal do bloco não necessita do bit de validade, pois seu conteúdo estará sempre atualizado. Por outro lado, cada uma das demais cópias de informação precisarão de um bit de validade o qual, ao ser colocado no nível lógico zero, indica que a informação contida naquele bloco está desatualizada. Quando a UCP correspondente tentar acessar alguma informação no bloco marcado como inválido haverá uma falha de acesso à cache e a informação correta será buscada na memória principal. Já, o bit de exclusividade é usado em todas as cópias do bloco, inclusive na principal. Quando o valor lógico deste bit for um, ele indicará que o processador pode fazer operações de escrita no bloco. Quando seu valor lógico for zero, ele indicará que o bloco é compartilhado e, portanto, apenas operações de leitura são permitidas. Cada um dos bits do diretório representa um processador do sistema. Quando o i-ésimo bit do diretório tiver valor lógico um, significará que o processador correspondente possui uma cópia do bloco. Quando o valor lógico do i-ésimo bit do diretório for zero, indicará que o respectivo processador não possui cópia do bloco em questão.

69

 Protocolos de Coerência Há duas classes de protocolos para estabelecer a coerência de dados em sistemas de memória de multiprocessadores, quais sejam, por hardware e por software.

cache

As abordagens por software geralmente se baseiam em soluções estáticas adotadas em tempo de compilação, quando acessos compartilhados são automaticamente convertidos em primitivas de coerência e sincronização, aliadas a alguns procedimentos de programação e a providências na implementação do sistema operacional, que alcançam compartilhamento e coerência por meio de mecanismos de gerenciamento de memória virtual. Estas abordagens são mais baratas que as de hardware, as quais usam técnicas tradicionalmente usadas na manipulação de memórias cache estendidas para arquiteturas escaláveis, e alguns autores a reputam como mais facilmente escalonável com o número de processadores da máquina. Todavia, são menos eficientes, pois, por não poderem prever o comportamento exato dos programas durante sua execução elas são obrigadas a usar soluções conservativas. Por exemplo, uma solução consiste em evitar a existência de cópias inconsistentes de dados nos módulos de memória cache. Isto é conseguido se a possibilidade de copiar certos dados à cache for limitada aos períodos em que tais dados não forem compartilhados. Para que isto seja possível o compilador deve ser capaz de identificar, pela análise do código do programa, quando cada variável será compartilhada e quando é seguro permitir que se façam cópias dessas variáveis na cache. A partir desta análise o compilador pode então marcar as variáveis como possíveis ou não de cópia em cada intervalo, providenciando para que, no final destes intervalos, a memória principal esteja consistente com os dados nas caches e invalidando as cópias. Um bom compilador poderá fazer esta análise, mas como ele faria a marcação das variáveis e a invalidação das cópias? Uma possível solução para este problema é a divisão do programa em unidades computacionais, com características próprias de acesso à variáveis. Por exemplo, os tipos de acesso podem ser separados em: (1) (2) (3) (4)

apenas leitura por diversos processos; apenas leitura por vários processos e leitura/escrita por um único processo; leitura/escrita por um único processo; e leitura/escrita por diversos processos.

Considerando-se que os processos em questão estejam em processadores diferentes. No primeiro caso, não já problema e a informação pode ser copiada em todos os módulos de memória desejados. No segundo caso, somente o processo de leitura/escrita pode copiar a variável e deve-se usar uma política de atualização write-through para manter os dados consistentes. No terceiro caso, o dado pode ser copiado para a cache e, como o processo é o único a acessá-lo, pode ser atualizado com uma política copy-back . No quarto e último caso, não é permitido copiar a informação na cache. Estas unidades computacionais devem ser identificadas pelo programador, para facilitar o trabalho do compilador. Isto pode ser feito utilizando-se construções tipo fork/join, laços for paralelos, etc. As abordagens por hardware são mais populares e mais freqüentemente utilizadas em sistemas multicomputadores comerciais. Isto deve-se a algumas vantagens, tais como: a possibilidade do reconhecimento dinâmico das inconsistências em tempo de execução; a possibilidade de melhor desempenho devido à geração de sobrecarga somente quando se dá o compartilhamento da informação; e por liberarem tanto o programador quanto o compilador desta responsabilidade.

70

Os protocolos desta classe podem ser subdivididos em dois grupos, quais sejam, os de diretório e os espiões. Os protocolos de diretório mantêm nos módulos de cache as cópias e alguma informação de estado e as informações globais do estado do sistema são mantidas por um controlador central em algum tipo de diretório. Os protocolos espiões mantêm a coerência de forma totalmente distribuída. Para isto, cada controlador de cache tem informação sobre o estado local dos dados e tem a responsabilidade de divulgar a todo o sistema qualquer alteração ocorrida em dados compartilhados. Todos os controladores ficam espionando o barramento do sistema e quando detectam informação sobre alteração em variável compartilhada eles são capazes de corrigir suas cópias conforme as necessidades. As duas principais políticas para manutenção da coerência são write-update e write-invalidate. Naquela, todas as cópias são atualizadas, sempre que um processo escrever em uma delas. Nesta, todas as cópias são marcadas como inválidas. A atualização da memória principal pode ser, como anteriormente, write-through ou copy-back .

 Algoritmos de Gerenciamento de MCDs Há quatro algoritmos de gerenciamento para MCDs principais, identificados de acordo com o tratamento que dispensam aos blocos de dados, por ocasião de solicitações de operações de escrita e leitura. As possibilidades são de (não) copiar ou (não) migrar o bloco. Ao migrar o bloco, o sistema procura explorar a localidade das referências do processo. Ao copiar, o objetivo é facilitar a concorrência. Em qualquer caso, as operações de leitura e escrita não devem ser capazes de observar que os acessos não são feitos à mesma cópia dos dados. O primeiro dos quarto algoritmos é chamado de algoritmo do servidor central, pois mantém uma máquina responsável por responder a todos os pedidos de acesso a variáveis compartilhadas. Todos os acessos, tanto de leitura quanto de escrita são feitos remotamente. Em ambos os casos envia-se uma solicitação de leitura/escrita do cliente ao servidor; o servidor recebe o pedido, executa a operação solicitada e envia a resposta ao cliente; no caso de operação de leitura o cliente recebe como resposta o dado solicitado, no caso de operação de escrita o cliente recebe de resposta um sinal de reconhecimento do sucesso da operação. Esta é a estratégia mais simples, mas sofre de um problema grave, qual seja, o servidor passa a ser um ponto fraco do sistema, um possível “gargalo” que limita o número de máquinas no sistema ou faz o desempenho cair. O segundo algoritmo é denominado algoritmo de migração, pois, sempre que um bloco é acessado ele é transferido para a máquina que o solicitou. Neste caso, todos os acessos de leitura/escrita são feitos localmente. A vantagem deste algoritmo, além de não ter um servidor único para servir de gargalo do sistema, é que o custo de comunicação é tão menor quanto maior for o número de acessos ao conteúdo de um bloco, entre uma transferência e outra deste bloco. A deficiência deste algoritmo é o efeito pingue-pongue, caso o número de acessos entre transferências seja pequeno. Problema este que é agravado se houver falso compartilhamento. O terceiro algoritmo é dito cópia na leitura, pois quando é solicitada uma operação de leitura copia o bloco para o processo que solicitou. No caso de uma operação de escrita a página migra para o processo solicitante. Desta forma obtém-se boa concorrência na leitura e garante-se a coerência dos dados na escrita. Sempre que for necessário escrever em um bloco que não está armazenado localmente, o processo o localiza e envia uma solicitação ao hospedeiro remoto. Recebendo o pedido ele envia o bloco solicitado. Quando o cliente recebe o bloco, ele transmite uma mensagem para invalidar todas as outras cópias daquele bloco, após o que ele atualiza a informação desejada. O algoritmo de cópia na leitura, juntamente com o protocolo write-invalidate, é uma opção popular para muitas implementações de sistemas MCDs. O quarto e último algoritmo a ser tratado aqui é o algoritmo de cópia total, pois ele copia o bloco solicitado tanto em operações de leitura quanto de escrita. Neste caso há um grande problema de 71

consistência nos dados, pois o algoritmo permite vários escritores simultâneos. Uma possibilidade de manter a consistência dos dados nas várias cópias é fazer o seqüenciamento global das operações de escrita, mantendo o seqüenciamento apenas local das operações de leitura em relação às escritas locais. Devido a este problema, o algoritmo de cópia na leitura é mais utilizado que o de cópia total para sistemas MCD. Uma discussão destes algoritmos, incluindo a análise de seu desempenho, é apresentada por Stumm e Zhou [13]. Outra fonte de informação sobre estes algoritmos é o artigo de Nitzberg e Lo [14].

 Localização de Dados Sistemas MCDs não usam servidores centralizados, para evitar a limitação do paralelismo devido à conseqüente serialização no tratamento das solicitações e para evitar o gargalo resultante da redução na velocidade geral do sistema devido à sobrecarga nos servidores. Uma alternativa seria a irradiação das solicitações de dados, para atingir diversos servidores distribuídos. Contudo, neste caso todos os nós de processamento devem processar o pedido irradiado. Desta forma, a latência de rede pode resultar em uma longa espera até que o acesso ao dado desejado seja feito. Todavia, sistemas para gerenciamento de MCDs precisam localizar as informações. Para que isto possa ser feito, com uma melhor distribuição de carga pelo sistema e evitando a necessidade de irradiação das solicitações de acesso, muitos sistemas MCD usam um esquema distribuído baseado na propriedade de estruturas de dados para executar duas tarefas essenciais: (1) localizar o proprietário atual de um bloco, após algumas migrações; e (2) identificar todas as cópias de um bloco para invalidação ou atualização. Diversos esquemas são utilizados para localizar os blocos no sistema. Um deles mantém uma estrutura de diretório distribuída onde cada entrada do diretório representa um bloco naquele nó. Caso o bloco seja compartilhado o diretório também mantém informação sobre a localização das cópias do bloco. Somente o nó proprietário do bloco tem informação sobre ele. Acessos para leitura a blocos remotos devem ser solicitados ao seu proprietário.

Exemplo de write-invalidate : Para dado com artilhado remoto

(Conceito de diretório – DASH

Pedido de escrita Envia dados e contador inválido 2) Controlador do diretório (CD) envia dados e invalida contador de blocos para o solicitante. CD envia pedido de invalidação da cópia no cluster B. 2c) Nova entrada do bloco no diretório. dirty remote | cópia em C

Cluster A: home cluster 

3) Cópia invalidada Reconhece Invalidação da cópia

Pedido para invalidar cópia cluster  B

1) UCP solicita escrita para home cluster  4) Completa escrita

cluster  C:

solicitante

72

Outro esquema utiliza uma tabela, na qual cada entrada contém um indicador da posição provável do bloco. O bloco é localizado seguindo estes ponteiros. Um esquema semelhante pode ser implementado usando-se uma lista ligada, chamada de lista de cópias, ou uma árvore spanning, chamada de conjunto de cópias. Nestes casos cada processador mantém um conjunto de apontadores para cada bloco compartilhado que ele conhece. Um endereço aponta para o processador do qual o bloco foi copiado (suposto dono do bloco) e os demais endereços apontam para os processadores para os quais foi enviada uma cópia do bloco. Por ocasião de uma operação de escrita uma mensagem é repassada pela lista/conjunto para a invalidação de todas as cópias.

Exemplo de write-invalidate : Para dado remoto sujo Retransmissão do pedido de escrita Solicitação de escrita 2) CD retransmite pedido para proprietário 4) Nova entrada do bloco no diretório “dirty remote” | cópia em B

5) CD envia reconhecimento para novo proprietário

1) UCP solicita escrita para “home cluster” dados

Atualização de propriedade reconheci6) Completa Escrita mento

Cluster A: home cluster 

Cluster B:

solicitante

3) CD envia dados para solicitante e atualização de propriedade para “home cluster”

Cluster C: proprietário

Sistemas de Arquivos Distribuídos Alguns dados em sistemas de computação precisam ser armazenados por longos períodos. A posterior recuperação destes dados requer que a eles sejam associados localizadores. Tais parâmetros de localização, em um contexto mais abstrato, são associados com nomes. Os objetos de dados aos quais foram associados nomes são chamados de arquivos do sistema de computação. Um sistema de arquivos é a estrutura organizacional dos arquivos no sistema de computação. O sistema de arquivos é responsável por atribuir nomes, criar, remover, modificar e proteger todos os arquivos do sistema. Um sistema de arquivos distribuído é um sistema de arquivos que possibilita a dispersão geográfica de módulos da sua estrutura. Apesar desta dispersão a visão passada aos usuários é a de um sistema centralizado. O compartilhamento dos arquivos do sistema por vários usuários para facilitar o trabalho cooperativo, a reprodução de diversas cópia de alguns arquivos para facilitar o acesso a seus dados, e a migração de arquivos pelos módulos de memória do sistema são aspectos que devem passar desapercebidos para os usuários. As principais características de um sistema de arquivos distribuídos são: (1) Dispersão de clientes, o que corresponde ao fato de que sistemas distribuídos são compostos de várias máquinas, heterogêneas, as quais devem apresentar aos seus usuários um procedimento 73

de ingresso no sistema uniforme, e devem possibilitar que o usuário enxergue o sistema de arquivos da mesma forma, independentemente da máquina na qual ele entrou no sistema. Além disto, o mecanismo de acesso aos arquivos deve ser único, independente do fato do arquivo estar na máquina em que o usuário está trabalhando ou em alguma outra máquina do sistema. (transparência de acesso). (2) Dispersão de arquivos, que diz respeito à localização dos arquivos. Por um lado, os nomes atribuídos aos arquivos não devem conter informação sobre sua localização (transparência de localização/posição), para que os acessos a arquivos em diferentes partes do sistema possam ser homogêneos. Por outro lado, ao se mover um arquivo de uma posição geográfica do sistema de computação para outra, sem alterar sua posição na estrutura organizacional do sistema de arquivos seu nome não deve ser alterado (independência de posição). (3) Diversidade de usuários, corresponde ao problema de manter a coerência dos dados e a correta execução dos processos. Como o sistema admite vários usuários, os quais podem acessar, compartilhadamente, um mesmo arquivo do sistema, quando um desses usuários atualiza algum dado do arquivo isto não deve afetar negativamente a execução de outros processos do sistema (transparência de concorrência). Nos casos em que o acesso ao arquivo é entrelaçado os usuários devem achar que o arquivo está sendo acessado isoladamente. (4) Diversidade de arquivos, refere-se ao fato de existirem réplicas de arquivos no sistema, para facilitar a disponibilidade de alguns deles através da redundância, bem como para permitir acessos concorrentes de modo a aumentar a eficiência do sistema (transparência de réplicas). O que se deseja, neste caso, é possibilitar aos clientes fazerem atualizações nas réplicas sem que os demais percebam a existência de várias cópias dos arquivos.

 Implementação de Sistemas de Arquivos Distribuídos A principal questão a ser considerada na implementação de sistemas de arquivos distribuídos é a manutenção da transparência tanto durante o compartilhamento dos arquivos por processos do sistema quanto por ocasião da proliferação de cópias de arquivos em diversas estações do sistema. Inicialmente, serão vistos alguns conceitos básicos sobre a implementação de arquivos e sistemas de arquivos em geral. Posteriormente, este estudo será direcionado para a análise dos protocolos utilizados com o fim de garantir a transparência mencionada no parágrafo anterior. Arquivos consistem em três componentes: nome, atributos e dados. O nome é um nome simbólico atribuído inicialmente ao arquivo, o qual é mapeado em uma identificação única pelo serviço de diretório. As informações típicas armazenadas como atributos são a identificação do proprietário do arquivo, seus tipos e tamanhos, os instantes de criação e da última atualização e as autorizações de acesso ao arquivo. Os dados podem ser seqüências de bytes, de blocos ou de registros indexados e o tipo de acesso depende da estrutura utilizada. De modo geral os acessos podem ser: seqüencial, usando apontadores para o próximo byte; direto, no qual os pedidos de leitura e escrita tragam o endereço do bloco; e indexado seqüencial, que se utiliza uma chave índice. Os componentes principais em um sistema de arquivos são os serviços de diretório, de autorização, de arquivos e de sistema. Os três primeiros são responsáveis pela interface do usuário com o sistema de arquivos e o último é transparente para o usuário. A função do serviço de diretório é o mapeamento de nomes simbólicos dos arquivos do sistema em seus endereços exclusivos, bem como a adição e remoção de arquivos do sistema. A função do serviço de autorização é controlar a lista de permissões de arquivos. O sistema de arquivos pode ser dividido em um conjunto de serviços básicos e outro de serviços para o gerenciamento de transações. A função do serviço de arquivos básico é controlar a leitura e a escrita nos arquivos do sistema, bem como ajustar e recuperar os atributos dos arquivos. Já, a função do serviço de transação em arquivos é gerenciar as réplicas e a concorrência nos arquivos. A função do serviço

74

do sistema é gerenciar dispositivos, cache e blocos, como por exemplo, interfacear os acionadores de dispositivos para alocar espaço e realizar operações reais de leitura e escrita. Sendo os diretórios estruturados hierarquicamente, a operação de anexação de um diretório remoto ao sistema de arquivos de um cliente a partir de uma determinada posição é dita montagem de arquivos ou diretórios. A montagem é útil na construção de um sistema de arquivos grande a partir de diversos dispositivos de armazenamento localizados em vários servidores de arquivos. Uma vez montados, os arquivos anexados podem ser acessados como se fizessem parte do sistema de arquivos do cliente. Há três formas de montagem: explícita, na inicialização do sistema e automática. A montagem explícita é flexível, mas de difícil gerenciamento; ela não é transparente porque requer que o usuário monte explicitamente todo o sistema de arquivos. A montagem na inicialização do sistema é uniforme e estática, porque é feita pelo sistema durante o processo de inicialização e não é modificada posteriormente, mas gasta espaço do sistema montando coisas desnecessárias. A montagem automática é feita durante o funcionamento do sistema, sob demanda; ela é dinâmica e transparente, montando os arquivos somente quando eles forem acessados. Para acessar um arquivo é necessário estabelecer uma conexão entre um cliente e um servidor de modo a possibilitar o fluxo de pedidos e respostas. Esta conexão foi chamada de sessão. Há certas informações necessárias para o bom andamento de uma sessão, quais sejam: -

indicação dos arquivos abertos e por quem; os descritores e manipuladores de arquivos; ponteiro para a posição de arquivos seqüenciais; informações sobre montagem; informações de estado; e chaves de bloqueio de variáveis e de segurança.

As informações sobre os arquivos que estão abertos e quem abriu cada arquivo podem ser armazenadas tanto no servidor quanto no cliente, bem como a chave de segurança utilizada para transferências durante a sessão. Por outro lado, os descritores e os manipuladores dos arquivos, os ponteiros para a posição atual de acesso a arquivos seqüenciais e as informações sobre a montagem, isto é, as ligações pertinentes, devem ser armazenadas no cliente. Finalmente, as chaves para a proteção das variáveis compartilhadas devem ser armazenadas no servidor. O servidor pode manter as informações de estado tornando o processamento mais rápido e o tráfego leve, uma vez que estas informações estão prontamente disponíveis. Embora o controle seja mais flexível o retorno de falhas é mais complexo e lento, uma vez que o servidor precisa recuperar as informações de estado anteriores à falha. Ao contrário, é possível não manter qualquer informação de estado no servidor. Neste caso o servidor é mais simples, mais confiável e mais tolerante a falhas, mas as mensagens são maiores e o seu processamento mais lento, uma vez que elas devem contem as informações de estado.

Compartilhamento de Arquivos Isto significa que o(s) arquivo(s) pode(m) ser acessado(s) por vários usuários ao mesmo tempo. Para isto ser viável o arquivo e as operações sobre ele devem ser multiplexadas no espaço ou no tempo. No primeiro caso há várias cópias do arquivo e portanto os acessos a ele se sobrepõem. Há duas vantagens em manter cópias de um arquivo em pontos distintos do sistema, são elas: aumenta a disponibilidade dos dados e melhora o desempenho do sistema, pois o tempo para atender uma requisição será menor quando for usada um cópia mais próxima do cliente; e às vezes é interessante manter um cópia de trabalho do arquivo para armazenar alterações temporárias, até que as 75

alterações definitivas sejam definidas e possam ser efetuadas no arquivo. Todavia, nestes casos ocorre o problema de controle de coerência dos dados, isto é, deve-se decidir se a cópia na memória local é coerente com a cópia original, ou não. No segundo caso os acessos ao arquivo são entrelaçados, ou seja, permitem que várias seqüências de operações oriundas de diversos clientes sejam executadas como em um sistema de compartilhamento do tempo. O problema neste caso, é evitar que as seqüências de operações interfiram umas com as outras evitando inconsistências e a geração de resultados errados, problema este conhecido por controle de concorrência. O acesso a arquivos compartilhados pode ser remoto, via cache ou por carga e descarga. Em cada caso pode ser feito para simples leitura ou escrita, para transações ou para sessões. Cada leitura/escrita, para transações ou para sessões. Cada leitura/escrita é uma solicitação/resposta ao/do servidor de arquivos. Transações são seqüências de operações de leitura/escrita, tratadas atenciosamente, ao mesmo arquivo. Geralmente, estas seqüências são delimitadas por construtores tipo begin/end. Sessões são seqüências de transações e operações de leitura e escrita. O acesso remoto não mantém arquivos no cliente, todo o pedido de acesso é enviado ao servidor remoto via rede. Deste modo, só há uma cópia do arquivo e os acessos a ela são serializados pelo servidor. O problema é que esta serialização impede a concorrência e provoca atrasos. No caso de solicitações simples de leitura/escrita não há compartilhamento real e no caso de transações é necessário um controle de concorrência. No acesso via cache parte do arquivo é mantido localmente para leitura e operações de escrita, assim como falhas no acesso à cache local, resultam em acesso remoto e a correspondente atualização da cache. Este procedimento reduz os atrasos e melhora a concorrência no sistema. O controle de concorrência é usado tanto para operações simples de leitura e escrita quanto para transações, mas nestas também é controlada a coerência dos dados. No acesso para carga ou descarga todo o arquivo é carregado para acesso local. Quando atualizado, o arquivo e carregado remotamente. Nos casos de leitura/escrita simples e de transações, os controles são os mesmos que os utilizados no acesso via cache. No caso de sessões, o compartilhamento é ignorado, por se tratar de composições dos casos anteriores. Como foi visto, o compartilhamento de arquivos cria problemas de coerência e de consistência dos dados, cuja solução depende do modelo semântico utilizado no procedimento de atualização dos dados nos arquivos compartilhados. Três modelos populares são: (1) Modelo semântico do UNIX: o resultado de uma operação de escrita é propagado para o arquivo principal e suas cópias, imediatamente. O problema é que neste modelo supõe-se que não há atrasos além dos de propagação na rede de comunicação, que os arquivos compartilhados não são copiados e que todas as operações de leitura e escrita são dirigidas e podem ser serializadas pelo servidor de arquivos. Como em um sistema distribuído isto não é possível, deve-se utilizar uma política de controle write-through associada a um protocolo write-invalidate ou write-update para garantir a coerência das cópias obtendo, assim, um modelo funcionalmente equivalente à semântica do UNIX. (2) Semântica de transações: Os resultados de operações de escrita são armazenados temporariamente em uma memória de trabalho e só são efetivados no final da transação, quando determinadas condições de consistência forem satisfeitas. Neste caso, as cópias dos dados não são necessariamente sempre coerentes, mas garante-se a consistência dos resultados segundo alguma ordem de execução das transações. Caso algo de errado durante a execução de determinada transação os resultados parciais são descartados e o sistema é mantido no estado em que se encontrava antes do início da transação. (3) O modelo semântico de sessões: o cliente utiliza uma cópia do arquivo na qual todas as alterações são feitas temporariamente. O resultado só é escrito permanentemente no final da 76

sessão, isto é, as atualizações permanentes são atrasadas mais do que no caso do modelo de transações. Neste caso, essencialmente não há compartilhamento de arquivos abertos simultaneamente por clientes diferentes, uma vez que as atualizações só são efetivadas quando do encerramento das sessões. O problema do modelo de sessões é como fazer com que as cópias de arquivos escritos em seqüência sejam coerentes. Uma possibilidade é exigir que a toda operação de escrita corresponda um “fechar arquivo”, forçando sua atualização. Isto equivaleria à semântica do UNIX com política write-through. Outra possibilidade é criar cópias apenas para leitura. Quando se desejar escrever algo no arquivo, deve-se criar uma nova versão do mesmo, para atualizá-lo. O controle destas versões pode ser feito por uma função do serviço de diretório, o qual está em um nível hierárquico superior ao do serviço de arquivos. As atualizações seriam mantidas em um local temporário até a finalização da sessão. Quando alguém quiser escrever algo no arquivo é criada um nova versão, que se torna a versão corrente. Quando alguém quiser abrir um arquivo, sempre lhe é dada a versão mais recente. Há vários esquemas para a resolução de conflitos pelo controle de versão, quando da escrita de um arquivo. Estes esquemas devem ser utilizados quando um processos tenta atualizar um arquivo, baseado em uma versão desatualizada, devido à atualização da versão corrente por outro processo. Três possíveis esquemas são: (a) ignora conflito: Cria-se uma nova versão independentemente do que possa ter ocorrido no sistema (b) resolve conflito de versão: Se o conjunto de dados atualizado no arquivo temporário e aquele gravado na versão corrente forem disjuntos é possível combinar os dois arquivos, mantendo todas as atualizações feitas. (c) Resolve conflito de seriação: Se houver interseção entre o conjunto de dados no arquivo temporário e os já atualizados na versão corrente, as atualizações podem ser descartadas e o processo pode ser recomeçado com a cópia corrente do arquivo. Isto força uma ordenação serial e aleatória das atualizações.

 Réplicas de Arquivos e Dados O objetivo do uso de réplicas é a melhoria do desempenho e o aumento da disponibilidade dos objetos copiados no sistema. O melhor desempenho se dá porque a manutenção de réplicas possibilita o acesso concorrente de vários processos ao mesmo conjunto de informações. Analogamente, a existência de várias cópias de objetos do sistema, produz uma redundância que os torna disponíveis a diversos processos. Todavia, esta redundância deve ser implementada de tal forma que os usuários não percebam a existência de várias cópias. Além disto, as transações sobre os objetos devem ser operadas de tal modo que ao final ou ela se completa integralmente ou o estado resultante é idêntico ao estado anterior ao início da transação, isto é, as atualizações nas cópias devem ser atômicas. Além disto, as atualizações são propagadas para todas as cópias do objeto e são serializadas. Genericamente uma arquitetura para o gerenciamento de cópias deve utilizar um agente servidor de arquivos (ASA) que serve de  front end para os gerenciadores de cópias (GC). O ASA pode acessar qualquer GC, provendo, com isto, a transparência das cópias para os clientes. Há várias formas de se fazer operações de leitura e escrita, utilizando-se diversos algoritmos. A seguir serão vistas algumas destas formas e, para concluir, serão apresentados dois algoritmos para implementar estes mecanismos. Os algoritmos são de votação por quorum e de propagação de atualizações por gossip.

77

Operações de Leitura • • •

A cópia primária: ASA lê apenas do gerenciador primário, para forçar a consistência. A alguma cópia: ASA lê de qualquer GC, para propiciar concorrência. A um quorum: ASA lê de um grupo de GCs, para decidir sobre a concorrência, mantendo a consistência.

Operações de Escrita • • • • •

A cópia primária: tudo é escrito em uma cópia primária e seu GC propaga o resultado para os demais. A todos os GCs: todas as atualizações a todos os GCs são atômicas e as atualizações subseqüentes devem aguardar. A todos disponíveis: atualiza todos os CGs que estiverem operacionais. Quando um GC se recuperar de uma falha, ele deve se atualizar antes de tornar seus dados disponíveis. A um quorum: atualização atômica a um grupo de GCs. Por gossip: as atualizações são dirigidas a qualquer GC, o qual as propagará para os demais, na hora que lhe aprouver. Para a descrição dos algoritmos ver páginas 222 a 226 do livro-texto[1].

78

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF