Processamento Confiável no Ambiente Operacional Seljuk-Amoeba Érica de Lima Gallindo Francisco Vilar Brasileiro [email protected] [email protected] Universidade Federal da Paraíba - UFPb/Campus II Centro de Ciências e Tecnologia - CCT Departamento de Sistemas e Computação - DSC Laboratório de Sistemas Distribuídos - LSD Av. Aprígio Veloso, 882 58109-970, Campina Grande, Paraíba http://www.dsc.ufpb.br/~lsd RESUMO O sistema operacional distribuído Amoeba não oferece nenhum mecanismo para tolerância a faltas de processamento. A funcionalidade do seu servidor de processamento se restringe a um balanceamento de carga dos processadores disponíveis, identificando aquele que está em melhor condição (e.g.: disponibilidade de memória, velocidade de CPU, etc) de realizar determinada tarefa. Neste artigo apresentamos um serviço de processamento confiável para o Amoeba, oferecendo variados graus de confiabilidade, que podem ser escolhidos pela aplicação em tempo de ativação. Este serviço é utilizado como base na implementação do ambiente operacional Seljuk-Amoeba. ABSTRACT Processing on the Amoeba distributed operating system is not fault-tolerant. The only concern of its processing service is to perform load balancing on the available processors, trying to find the processor that best suits a particular process in terms of memory availability and CPU speed. In this paper we introduce a fault-tolerant processing server for the Amoeba system. This server is used in the implementation of the Seljuk-Amoeba operating environment, which offers processing service with different failure semantics on a per-process basis. 1. INTRODUÇÃO Um sistema distribuído é composto por vários elementos computacionais, interligados por canais de comunicação, usualmente no âmbito de uma rede local. Em tais sistemas, a profusão de processadores possibilita a distribuição uniforme de tarefas entre os mesmos, com a finalidade de aumentar o desempenho global do sistema. Também torna-se possível recuperar as computações que estão executando em processadores que venham a falhar. Para se conseguir tolerância a faltas, introduz-se redundância, que é definida como aquela parte não necessária para o funcionamento correto do sistema, se nenhuma tolerância a faltas é suportada [Jalo94]. Quando a implementação dos mecanismos para tolerância a faltas fica a cargo da aplicação, a complexidade desta cresce. Em [Bras97] é proposta uma plataforma de desenvolvimento, denominada Seljuk, que facilita a implementação de aplicações distribuídas robustas. Uma das abordagens utilizadas pela plataforma é a disponibilização de serviços que permitem assumir que a aplicação irá executar sobre unidades de processamento, denominadas de nodos, que falham de forma controlada e previsível. Em tempo de ativação cada aplicação pode escolher a semântica de falha do serviço de processamento a ser usado. Quando os processadores sobre os quais o serviço é implementado, possuem uma semântica de falha menos restritiva do que aquela assumida pela aplicação, a plataforma implementa os protocolos necessários para garantir a semântica de falha requerida, liberando as aplicações desta tarefa. Além desses serviços, a plataforma também oferece outros serviços para tolerância a faltas, que podem ser usados pelos projetistas de aplicações distribuídas; detalhes sobre esses serviços são relatados em [VB97]. A semântica de falha de um processador depende, não somente das características do seu hardware propriamente, mas também, entre outros fatores, dos requisitos de confia nça no funcionamento 1 exigidos pela aplicação e da duração da sua execução (tempo de missão). Embora tenha-se conhecimento que processadores convencionais possam falhar de uma maneira arbitrária [HLD88], a probabilidade desse tipo de falha ocorrer é tão pequena, que é possível assumir uma semântica de falha controlada para esses processadores, e ainda assim, atender aos requisitos de confiabilidade de um grande número de aplicações. Entretanto, existe um número crescente de aplicações, cujos requisitos de confiança no funcionamento são suficientemente altos a ponto de, para essas aplicações, não ser possível assumir que processadores convencionais apresentem semântica de falha controlada. Para estas aplicações faz-se necessário a construção de nodos que garantam o comportamento assumido. Nodos com semântica de falha controlada podem ser construídos através do agrupamento de processadores convencionais redundantes, que falham de maneira independente, e de forma arbitrária (falhas Bizantinas). A computação é replicada e executada simultaneamente em cada um dos processadores formando o nodo. Os resultados da computação de cada um dos processadores são validados por um mecanismo apropriado (ex. comparação, votação majoritária, etc.), que garante a semântica de falha do nodo. [Bras95] apresenta protocolos que podem ser usados na implementação em software de um serviço de processamento com diferentes semânticas de falha controlada. Neste trabalho mostraremos a aplicação desse serviço no contexto do ambiente operacional Seljuk-Amoeba, que é uma implementação da arquitetura Seljuk utilizando o sistema operacional Amoeba [MRTRS90] como substrato. O Amoeba é um sistema operacional distribuído baseado na tecnologia de micro-núcleos. O princípio básico desta tecnologia é minimizar a parte do sistema operacional que executa em modo supervisor (o micro-núcleo propriamente dito), com o objetivo de aumentar a flexibilidade do sistema. Todo processador do Amoeba executa um pequeno pedaço de software chamado micronúcleo. Toda a funcionalidade do sistema operacional que não é provida pelo micronúcleo fica a cargo de processos servidores que executam em modo usuário. Um desses servidores é o Run Server, responsável pela alocação de processadores para a execução de tarefas. O Run Server não provê qualquer serviço para tolerância a faltas de processamento. Nossa proposta é estender a funcionalidade do Run Server, transformando-o em um servidor de processamento confiável, denominado FT Run Server. O FT Run Server 1 [LV91] propõe uma terminologia em Português que mapeia aquela sugerida por Laprie em seu conhecido artigo [Lapr89]. No artigo de Lemos e Veríssimo, a expressão confiança no funcionamento é usada para traduzir o termo dependability. baseia-se nos protocolos para construção de nodos replicados descritos em [Bras95]. As aplicações executadas pelo FT Run Server podem, em tempo de execução, informar a semântica de falha assumida para os nodos onde irão executar, bem como a semântica de falha real dos processadores. O FT Run Server deve então prover, de forma transparente, a semântica de falha requerida a partir da replicação do processamento em processadores disponíveis, caso os mesmos tenham uma semântica de falha menos restritiva. Além da introdução do FT Run Server, é necessário adaptar o serviço de comunicação do Amoeba, de forma que este implemente as necessidades de comunicação dos protocolos mencionados anteriormente. O restante deste artigo é estruturado como segue. A Seção 2 apresenta alguns conceitos básicos do Amoeba, que são de fundamental importância para o entendimento da nossa proposta. A Seção 3 descreve o comportamento do servidor de execução do Amoeba (Run Server). Na Seção 4 é explicado o funcionamento dos nodos com semântica de falha controlada propostos por [Bras95], enquanto que na Seção 5 será mostrado como estes nodos serão adaptados para provê o processamento confiável no ambiente operacional Seljuk-Amoeba. Finalmente, a Seção 6 apresenta as conclusões deste trabalho. 2. CARACTERÍSTICAS DO AMOEBA 2.1. OBJETOS E CAPABILITIES Todos os servidores do Amoeba funcionam com base no conceito de objeto, que é um tipo de dado sobre o qual são realizadas operações bem definidas. Os objetos, no Amoeba, são gerenciados por servidores específicos. As operações sobre os objetos são executadas de maneira síncrona, isto é, ao fazer uma chamada de procedimento remoto (Remote Procedure Call - RPC [BN84]) ao servidor que gerencia o objeto, o cliente fica bloqueado esperando por uma resposta. Quando um objeto é criado, o servidor que o gerencia devolve uma capability ao processo criador. Nas operações subsequentes, para utilizar o objeto, o cliente deverá apresentar essa capability ao servidor, a fim de identificar o objeto e comprovar a permissão de manipulá-lo. A capability é uma maneira uniforme de nomear e proteger todos os objetos do Amoeba. Uma capability consiste em uma estrutura de dados de 128 bits, como mostra a Figura 2.1. 48 Porta do Servidor 24 Objeto 8 Permissões 48 Verificação Figura 2.1 : Exemplo de uma capability Na capability, o campo Porta do Servidor é um endereço lógico onde se pode encontrar o servidor que gerencia o objeto. O segundo campo, Objeto, é usado pelo servidor para identificar o objeto em questão. Por exemplo, um servidor de arquivos gerencia diversos arquivos, com o campo Objeto o servidor poderá saber que arquivo em especial ele está manipulando. Já o campo Permissões informa as operações que o detentor da capability pode realizar sobre o objeto. Por último, existe o campo Verificação que serve para validar a capability. Como as capabilities são manipuladas diretamente por processos do usuário, se não existisse alguma forma de proteção estes poderiam falsificá-las. 2.2. P ROCESSOS E THREADS O conceito principal de qualquer sistema operacional é o processo - uma abstração de um programa em execução. Em muitos sistemas operacionais, como o UNIX, um processo consiste de um segmento de texto que contém as instruções de máquinas formando o código executável do programa, um segmento de dados contendo os dados globais inicializados, um segmento bss que contém os dados globais não inicializados e um segmento de pilha, todos no mesmo espaço de endereçamento. Além desses segmentos, outras informações sobre o processo (ex. contexto de execução, tempo de execução, localização dos segmentos na memória, etc.) são armazenadas em uma estrutura de dados do sistema operacional, normalmente chamada de tabela de processos. O processo, nestes sistemas operacionais, tem uma única linha de execução (thread). No caso do Amoeba um processo pode ter múltiplas threads, todas executando dentro do mesmo espaço de endereçamento. O elemento chave dos mecanismos de gerenciamento de processos no Amoeba é o descritor de processos. Este consiste de quatro partes, como ilustrado na Figura 2.2 abaixo. cpu 80386 mem 16 Mb virtual address length how segment 1 segment 2 source Segment Descriptor thread 1 program counter thread 2 stack pointer process capability handler capability system-call state Thread Descriptor Figura 2.2: Descritor de Processos A primeira parte do descritor de processos descreve as propriedades do processador no qual o processo deve executar. Um processo só pode executar em um processador que preencha os requisitos indicados no descritor deste processo. Na Figura 2.2, por exemplo, o processo deve ser executado em um processador da arquitetura 386 que tenha pelo menos 16M bytes de memória disponíveis. A próxima parte descreve o layout - mas não o conteúdo - do espaço de endereçamento virtual do processo. A memória virtual é segmentada e, para cada segmento tem-se um descritor de segmentos que contém os campos virtual address, length, how e source. O campo virtual address indica onde o segmento está mapeado dentro do espaço de endereçamento; o campo length indica o tamanho do segmento; o campo how descreve que tipo de acesso o processo tem ao segmento (e.g., leitura, execução), este campo indica também se o segmento pode crescer e em qual direção; o campo source contém uma capability para um objeto que provê o conteúdo do segmento de memória (e.g., um arquivo). A terceira parte do descritor de processos é o descritor da thread. No Amoeba, o micro-núcelo gerencia as threads de um processo. Para cada thread o descritor contém: program counter, stack pointer, o estado das system-calls, e registradores. As únicas sytem-calls cujos estados necessitam de descrição no descritor da thread são as bloqueantes. A última parte do descritor de processos contém duas capabilities necessárias para gerenciar o processo. A primeira é a capability para o próprio processo; um processo é visto como um objeto que é gerenciado pelo micro-núcleo do processador no qual ele executa. Pedidos de operações sobre este objeto (e.g. migrar, suspender, matar) são enviados à entidade que gerencia o objeto (o micro-núcleo). Assim, quando um processo é submetido a um processador, o micro-núcleo precisa conhecer a capability usada pelos pedidos de operações para endereçar o processo. A segunda, é uma capability para o manipulador de exceções de um processo. Usualmente, este é o processo pai, porém algumas vezes pode ser um debugger. Quando uma exceção ocorre, o micro-núcleo constrói um descritor de processos para o processo e envia o descritor que causou a exceção para o manipulador de exceções. 2.3. C OMUNICAÇÃO ENTRE PROCESSOS Sistemas operacionais distribuídos são estruturados, tipicamente, em torno do paradigma cliente-servidor. Neste modelo, um processo chamado de cliente, pede a outro processo, chamado de servidor, que faça um trabalho para ele. O cliente então fica bloqueado até que o servidor envie a este uma resposta. O mecanismo de comunicação usado para implementar o modelo cliente-servidor é o RPC. Embora o RPC seja uma boa abstração para o tipo de comunicação pedido/resposta, existe um grande número de aplicações que requer que um grupo de processos interaja de maneira fechada. Group Communication permite que uma mensagem seja enviada confiavelmente de 1 remetente para n receptores. Por exemplo, aplicações podem replicar dados a fim de obter tolerância a faltas. Tal aplicação necessitará de Group Communication para manter consistente os dados que estão replicados [BJ87]. No Amoeba encontramos tanto RPC quanto Group Communication. Para dar suporte ao RPC e Group Communication o Amoeba dispõe de um protocolo de rede chamado FLIP - Fast Local Internet Protocol [KRST93]. A Figura 2.3 abaixo mostra a organização da comunicação no micro-núcleo do Amoeba. O micro-núcleo contém duas camadas: uma camada superior que implementa RPC e Group Communication, e uma camada inferior que implementa o FLIP. Por exemplo, quando um cliente faz uma chamada trans (primitiva RPC que envia uma mensagem de um cliente a um servidor e espera por uma resposta), a camada RPC examina o cabeçalho e o buffer, constrói uma mensagem a partir destes campos, e passa esta mensagem para a camada de baixo (FLIP), que se encarrega de transmití-la para o destino. Cliente RPC modo usuário GC Camada FLIP núcleo Figura 2.3: Estrutura de comunicação no Amoeba 3. RUN SERVER Os processos servidores são componentes importantes na configuração do Amoeba. Entre os processos servidores do Amoeba está o servidor de execução (Run Server), que é o responsável pelo escalonamento de tarefas. Este não provê nenhum mecanismo para tolerar faltas de processamento; a sua funcionalidade se resume a um balanceamento de carga dos processadores. Para este balanceamento leva-se em consideração fatores como: memória disponível, velocidade do processador, arquitetura do hardware, fragmentação de memória, entre outros. O poder computacional do Amoeba está localizado em um ou mais pools de processadores. Um pool de processadores é formado por um conjunto de CPUs interligadas através de uma rede. Cada CPU tem sua própria memória local e executa o micro-núcleo do Amoeba. Antes de executar um processo, o Run Server tem que decidir em qual tipo de arquitetura ele deve executar e qual processador deve ser o escolhido. No Amoeba as tarefas são submetidas ao Run Server através de um interpretador de comandos (shell). Quando dá-se entrada a um comando pelo shell, este extrai a primeira palavra da linha de comando, assume que é o nome de um programa executável, procura por este programa no sistema de arquivos, e se encontrá-lo, faz com que este seja executado. Antes de solicitar a execução do programa, o shell localiza as arquiteturas para as quais este programa está disponível. Para localizar as arquiteturas disponíveis, o shell procura o comando no diretório /bin (diretório onde encontram-se os executáveis no amoeba). Se o programa estiver disponível para várias arquiteturas, este não será um arquivo, e sim um diretório contendo os executáveis para cada arquitetura disponível. Em seguida o shell faz uma RPC ao Run Server enviando a este os descritores de processos, para todas as arquiteturas disponíveis, do programa em questão, e solicitando ao Run Server a escolha de uma arquitetura e uma CPU específica para executar o programa. Para escolher a CPU, o Run Server verifica que arquiteturas dispõe para executar o programa. Isto é feito verificando o diretório onde localizam-se os vários pools de processadores, chamado de pooldir (Figura 3.1). Figura 3.1: Diretório de pools de processadores A seleção de qual processador usar, é feita da seguinte forma. Em primeiro lugar, faz-se a interseção dos descritores de processos enviados pelo shell e das arquiteturas disponíveis no pooldir. Por exemplo, se existem descritores de processos para as arquiteturas 386, SPARC e 680X0, e no pooldir encontram-se pools de processadores de arquiteturas 386, SPARC e VAX, somente as arquiteturas 386 e SPARC serão consideradas. Em seguida, o Run Server verifica quais das máquinas consideradas tem memória suficiente para executar o programa, eliminando aquelas que não satisfazem as exigências do processo. O Run Server mantém-se informado da memória e da CPU usada por cada um de seus processadores fazendo, regularmente, uma chamada getload a cada um, requisitando estes valores. Assim os números na tabela do Run Server estão continuamente atualizados. Por último, para cada uma das máquinas restantes, faz-se uma estimativa do poder computacional que está disponível para executar o novo processo. Cada CPU faz sua própria estimativa. Para esta estimativa leva-se em consideração o poder computacional da CPU e o número de threads ativas rodando nela. Por exemplo, se uma máquina de 20 MIPS tem quatro threads ativas no momento, a soma de mais uma thread significará que cada uma, incluindo a nova, terá 4 MIPS em média. Se outro processador tem 10 MIPS e uma thread ativa, nesta máquina o novo processo poderá ter até 5 MIPS. O Run Server escolhe então o processador que pode liberar a maior quantidade de MIPS e retorna para o shell a capability para este processador. O shell então, faz uma RPC ao servidor de processos do processador escolhido, enviando a capability fornecida pelo Run Server. Junto com a capability, o shell envia ao servidor de processos, o descritor de processo da arquitetura escolhida pelo Run Server, para que o processo possa ser criado. A Figura 3.2 mostra todos os passos envolvidos na criação de um processo no Amoeba. RPC Shell 1 2 4 Run Server capability para um processador RPC capability do processo pool de processadores 3 Servidor de Processo Figura 3.2: Fases da criação de um processo no Amoeba. Com base no funcionamento do Run Server então, apresentaremos um novo servidor de execução (tolerante a faltas de processamento) que, ao invés de disparar um pedido de execução de uma instância de uma determinada aplicação, possa solicitar a execução de várias instâncias desta, dependendo do nível de tolerância a faltas desejado. As várias cópias dos processos irão interagir, formando assim o nodo que terá a semântica de falha escolhida pela aplicação em tempo de execução. Antes de discutirmos as mudanças a serem introduzidas no Run Server, discutiremos na próxima seção a implementação de nodos replicados com semântica de falha controlada. 4. NODOS COM SEMÂNTICA DE FALHA CONTROLADA Nodos com semântica de falha controlada podem ser construídos agrupando-se processadores convencionais que falham de maneira independente. A computação é replicada e executada simultaneamente em cada um dos processadores formando um nodo. Os resultados da computação de cada um dos processadores são avaliados por um mecanismo apropriado que garante a semântica de falha do nodo, evitando que respostas incorretas, sejam repassadas para o nível da aplicação. Neste trabalho, usamos os protocolos de tolerância a faltas descritos em [Bras95]. Esses protocolos foram construídos baseados na arquitetura da família de nodos Voltan [SESTT92]. Essa família abrange duas classes de nodos com semântica de falha controlada, a saber: nodos com semântica de falha mascarada (fail-masking nodes) e nodos com semântica de falha silenciosa (fail-silent nodes). Os nodos com semântica de falha mascarada possuem uma semântica de falha equivalente à sua semântica operacional, isto é, o nodo ainda libera seu serviço mesmo na ocorrência de um número limitado de falhas de seus componentes, que são mascaradas; enquanto que nodos com semântica de falha silenciosa, possuem uma semântica de falha segura, isto é, depois de detectada uma falha, estes nodos não liberam nenhuma saída, eles simplesmente param. Nesse ambiente, assume-se que as aplicações distribuídas não-replicadas são compostas de um número de processos que não compartilham memória; a interação é feita somente por troca de mensagens. Considera-se ainda que os processos replicados comportam-se de maneira determinística [Bras97]; e que toda réplica correta de um processo recebe as mesmas mensagens, processando-as na mesma ordem. As mensagens enviadas pelos processos são assinadas, de forma que nenhum processador incorreto possa falsificar mensagens; os mecanismos de assinaturas digitais aqui supostos, estão descritos em [FB97]. Supõe-se também que o meio de comunicação, no qual as mensagens trafegam, é síncrono, isto é, existe um tempo máximo δ para o processamento e transmissão de mensagens trocadas entre quaisquer dois processos corretos. [CB97] mostra uma forma de implementar um canal de comunicação síncrono a partir de redes assíncronas. Considera-se que os nodos são compostos por N processadores, onde N=2π+1 no caso dos nodos com semântica de falha mascarada e N=π+1 no caso nodos com semântica de falha silenciosa; e que seus componentes podem sofrer no máximo π faltas (π>0). A restrição de N=2π+1 e N=π+1 é necessária apenas para que o esquema de validação funcione corretamente, já que assim, o processadores corretos podem obter uma maioria, no primeiro caso, e garante-se que pelo menos um processo está correto, no segundo caso. Na Figura 4.1 abaixo, é mostrada uma visão geral da arquitetura dos nodos Voltan. Além dos processos das aplicações (Servidor), cada processador correto de um nodo executa cinco processos, chamados de Receptor, Transmissor, Ordenador, Validador e Remetente. Servidor PMQ DMQ Links Ordenador Remetente Links Links RMQ Sim deve ser ordenada? Não Validador ECL ICL VMQ Links Receptor Mensagens originadas da rede Transmissor Mensagens destinadas à rede Figura 4.1: Visão de um processador correto executando processos em um nodo Voltan A função de cada processo é descrita a seguir. • Remetente: este processo recebe as mensagens produzidas pelo servidor daquele processador, assina-as e as envia para os outros processadores do nodo para que possam ser validadas. • Ordenador: executa um protocolo de ordenação em conjunto com os outros processadores do nodo. Sua função é construir filas de mensagens para serem tratadas pelos servidores de todos os processadores corretos do nodo; essas filas devem conter as mesmas mensagens e na mesma ordem. • Validator: a função deste processo depende do tipo do nodo. No caso de nodos de falha mascarada, o processo de validação é feito através de votação. Fazse uma comparação das mensagens assinadas e enviadas por outros processadores com as que foram geradas localmente. Se a comparação falha, a mensagem é descartada; caso contrário, a mensagem é contra-assinada. Se na mensagem existirem π+1 assinaturas, ela é considerada uma mensagem válida. Em seguida, esta é repassada ao processo Transmissor para entrega à aplicação. Se existirem menos que π+1 assinaturas na mensagem, ela é enviada para os outros processadores do nodo que ainda não a assinaram. Já no caso de nodos de falha silenciosa, o processo Validador comporta-se como um comparador. Este comparador é semelhante ao Validador, só com a diferença de que ao se detectar uma falha, ao invés de simplesmente descartar a mensagem recebida, este Validator pára por completo, como também o processo Remetente. • Transmissor: este é o processo responsável por enviar as mensagens com π+1 assinaturas para a aplicação destino. • Receptor: este processo autentica as mensagens recebidas de micro-núcleo outros processadores do nodo ou de outros nodos, descartando qualquer mensagem cuja autenticação falhe, ou que seja duplicada. Mensagens autenticada vindas originárias da rede de comunicação são enviadas para o processo Ordenador local; mensagens autênticas vindas de outro processador do nodo que tenham menos que π+1 assinaturas, são enviadas parao processo Validador local. A comunicação entre dois processos executando no mesmo processador é realizada através de listas e filas de mensagens. A diferença básica entre listas e filas é que na lista os processos podem retirar mensagens de qualquer posição, enquanto que na fila os processos só podem acessar mensagens que estejam no topo da mesma. Na Figura 4.1 são mostradas as seguintes filas e listas: • RMQ - Received Message Queue: contém mensagens válidas originadas da rede e autenticadas pelo processo Receptor, prontas para serem ordenadas. • DMQ - Delivered Message Queue: contém mensagens ordenadas prontas para serem processadas pelo processo Servidor. • PMQ - Processed Message Queue: contém mensagens ainda não assinadas produzidas pelo processo Servidor local. Estas têm que ser validadas pelo processo Validador, antes de sua transmissão ao seu destino. • ECL - External Candidate Message List: contém mensagens assinadas e autenticadas que tenham sido recebidas de outros processadores para validação. • ICL - Internal Candidate Message List: contém mensagens não assinadas, à espera de mensagens na lista ECL que coincidam com elas. • VMQ - Validate Message Queue: contém mensagens com π+1 assinaturas (válidas) prontas para serem transmitidas através da rede. 5. FT RUN SERVER O serviço de processamento do Amoeba, assume que o processo que está em execução, e o processador no qual ele executa, nunca irão falhar. A única preocupação do Run Server é fazer com que esse processo seja executado no processador que esteja com a menor carga de processamento no momento. Com o propósito de inserir mecanismos para tolerância a faltas no serviço de processamento do Amoeba, é necessário fazer com que um mesmo processo seja executado por mais de um processador ao mesmo tempo. Desta forma se um dos processadores falhar, outros ainda permanecerão executando réplicas do referido processo. No Amoeba pode-se ter processadores heterogêneos. Por este motivo, é possível obter-se tolerância a faltas a nível de projeto de hardware quando o mesmo processo é replicado em vários processadores de arquiteturas diferentes. Para permitir também tolerância a faltas de projeto de software, a organização dos binários no Amoeba foi adaptada de maneira mostrada na Figura 5.1. Em sua versão original, o Amoeba só tratava de executáveis para arquiteturas diferentes. Com as mudanças introduzidas, este agora manipula também várias versões de um mesmo programa para várias arquiteturas. A figura abaixo mostra um exemplo da organização atual do diretório onde se encontram os executáveis no Amoeba (/bin). Nesta figura, o /bin contém dois comandos: dir e sort. O comando dir está disponível apenas para a arquitetura VAX; o comando sort por sua vez, encontra-se disponível na arquitetura 80386 além de conter outras n implementações dele disponíveis para a mesma arquitetura. Esta diversidade de implementações do mesmo programa, permite que o projetista opte pela execução da aplicação em nodos replicados, de forma que cada réplica do nodo execute uma versão diferente do programa. Note que com a diversidade de arquiteturas de processadores e com a disponibilidade de n versões para cada uma destas arquiteturas, aumenta-se o grau de tolerância a faltas para esta aplicação. /bin pd.i80386 versão1 sort dir i80386 pd.VAX versão2 versão3 Figura 5.1: Sistema de arquivos do Seljuk-Amoeba Vejamos na Figura 5.2 como fica o cenário da criação de um processo, depois de introduzir-se no serviço de processamento as mudanças necessárias para que este utilize os mecanismos de nodos com semântica de falha controlada. RPC Shell FT Run Server modo usuário descritor de um nodo RPC descritor do nodo Servidor de Nodos SP processador A micro-núcleo Servidor de Nodos SP processador B nodo de falha controlada Figura 5.2: Serviço de processamento confiável no Amoeba Vamos supor que o shell é a aplicação que deseja utilizar o serviço de processamento confiável. Primeiro ele faz uma RPC com o FT Run Server, através de um de seus pedidos de execução de um processo (estes serviços são detalhados mais adiante). Além de uma indicação do código a ser executado e da semântica de falha dos processadores do sistema, alguns parâmetros adicionais são enviados nesta RPC. Um dos parâmetros contidos nesta chamada é a semântica de falha do nodo. Ao disparar a aplicação, pode-se escolher qual a semântica de falha assumida para o nodo onde ela executará; as semânticas de falha possíveis são a semântica de falha silenciosa e a semântica de falha mascarada. O fator de replicação também é passado pelo shell. Este fator é um valor numérico que informa ao FT Run Server quantos processadores independentes formarão o nodo. Um outro parâmetro fornecido pelo shell, é uma lista contendo quais os processadores que não se deseja utilizar na execução daquela aplicação (este parâmetro pode ser utilizado, por exemplo, se o projetista da aplicação achar que o processador de uma determinada arquitetura não é confiável, e não desejar submeter nenhuma tarefa a este; pode acontecer também, do shell disparar duas aplicações e necessitar que estas executem em conjuntos de processadores distintos, por exemplo, para implementar uma replicação de mais alto nível [VB97]). Finalmente, um último parâmetro a ser adicionado na RPC é uma variável que sinaliza se a aplicação será executada com diversidade de projeto ou não. Com isto, pode-se conseguir tolerância a faltas de projeto de software, já que cada réplica disparada pelo shell executará uma implementação diferente da mesma aplicação. Depois de fazer uma RPC com o FT Run Server, enviando todos os parâmetros necessários à execução da aplicação, o shell fica bloqueado esperando uma resposta. O FT Run Server então, retorna ao shell um descritor do nodo criado. Este descritor contém as seguintes informações: o identificador do nodo, uma listas dos processadores pertencentes a este nodo (o primeiro processador da lista é o coordenador do nodo), e uma capability para o nodo. Assim, de posse deste descritor de nodo, o shell faz uma RPC com o servidor de nodos do processador coordenador do nodo, solicitando a criação do processo (Figura 5.2). O servidor de nodos é um conceito não existente na versão original do Amoeba. Esta entidade foi introduzida para provê o serviço de criação das réplicas de um processo. O servidor de nodos do processador coordenador, além de solicitar ao servidor de processos local que crie o processo, envia o mesmo pedido aos outros servidores de nodos de todos os processadores pertencentes ao nodo. Os servidores de nodos dos outros processadores, por sua vez, solicitam aos seus respectivos servidores de processos a criação das réplicas. Tanto o servidor de nodos, quanto o servidor de processos, estão contidos no micro-núcleo que executa em toda máquina no Amoeba. No projeto do FT Run Server, para fazer o controle das réplicas, utilizamos os mecanismos para implementação de nodos com semântica de falha controlada apresentados na seção anterior. Na Figura 5.3 a seguir, são mostradas as threads que devem ser adicionadas ao micro-núcleo do Amoeba, bem como o fluxo de informação entre estas, para a implementação de um serviço de processamento confiável. Servidor PMQ modo usuário DMQ Links Ordenador Remetente RPCin / GCin RPCout / GCout Links Não o processo RMQ Sim deve ser ordenada? Não ECL Validador é replicado? micro-núcleo Sim Sim Não Links ICL o processo é replicado? VMQ Receptor Transmissor FLIP Mensagens originadas da rede Mensagens destinadas à rede Figura 5.3: Réplica implementando o serviço de processamento confiável Quando uma mensagem chega pelo adaptador de rede do processador, que faz parte de um nodo replicado, esta é enviada para a thread Receptor que faz a sua autenticação, descartando as mensagens que sejam duplicadas ou inválidas. Deve-se considerar o destino da mensagem para decidir se esta deve ser ordenada ou não. Se a mensagem foi enviada para um processo replicado então ela deve ser ordenada, de forma que todas as réplicas da aplicação (Servidor) recebam as mesmas mensagens e na mesma ordem. Caso contrário ela deve ser repassada para a parte do micro-núcleo do sistema operacional que implementa o recebimento de mensagens nos protocolos de comunicação de mais alto nível (representados na Figura 5.3 por RPCin/GCin). Outros dois tipos de mensagens podem ser recebidas pela thread Receptor. Mensagens enviadas pela threads Ordenador dos outros processadores que formam o nodo replicado, as quais devem ser repassadas para a thread Ordenador local; e mensagens enviadas pelas threads Validador dos outros processadores que são entregues à thread Validador para que sejam validadas. Uma vez que a mensagem tenha chegado à aplicação, esta é processada e depositada na PMQ. Em seguida, esta mensagem é entregue a thread Remetente. Se a mensagem é oriunda de um processo replicado, esta é depositada na ICL ficando à espera de uma mensagem na ECL que coincida com ela. Se a mensagem for proveniente dos links de comunicação com os outros processadores do nodo, esta é repassada para a parte do micro-núcleo que implementa a entrega das mensagens nos protocolos de comunicação de mais alto nível (no caso RPCout/GCout). Em seguida a mensagem é entregue a thread Transmissor para que este a envie para seu destino. 6. CONCLUSÃO O serviço de processamento confiável do ambiente operacional Seljuk-Amoeba é uma importante ferramenta para a construção de aplicações distribuídas robustas, na medida que ele provê uma redução na complexidade do desenvolvimento destas. O programador pode fazer suposições mais restritivas quanto à semântica de falha dos nodos e dessa forma ter a tarefa de implementação dos mecanismos para tolerância a faltas facilitada. A gerência dos processadores redundantes necessários para garantir a semântica assumida para o nodo fica a cargo do Seljuk-Amoeba. Este serviço também oferece a flexibilidade de permitir que a semântica de falha dos nodos nos quais a aplicação executa seja definida quando a aplicação é ativada. Dessa forma, se os requisitos de confiança no funcionamento da aplicação mudarem ao longo do tempo, não há necessidade nem mesmo de recompilação da aplicação, já que o serviço de processamento confiável está inserido no próprio sistema operacional. Uma outra vantagem do serviço é que o custo (em termos de perda de desempenho) do serviço recai apenas sobre aquelas aplicações com requisitos de confiança no funcionamento. Vale lembrar que quando a semântica de falha dos processadores disponíveis é pelo menos tão restritiva quanto a semântica de falha requerida pela aplicação, o FT Run Server comporta-se de forma semelhante ao Run Server, e não impõe nenhuma perda de desempenho para a aplicação. AGRADECIMENTOS Os autores agradecem o apoio financeiro do CNPq (processos 180.301/94-2 e 300.646/96-8). REFERÊNCIAS BIBLIOGRÁFICAS [AST92] A.S. Tanenbaum, Modern Operating Systems, Prentice-Hall, Amsterdam, 1992, ISBN 0-13-588187-0. [AST95] A.S. Tanenbaum, Distributed Operating Systems, Prentice Hall, Amsterdam, 1995, ISBN 0-13-219908-4. [BJ87] K.P. Birman e T.A. Joseph, “Reliable Communication in the Presence of Failures”, ACM Transactions on Computer Systems, Vol. 5, No. 1, pp. 4776, fevereiro 1987. [BN84] A.D. Birrell e B.J. Nelson, “Implementing Remote Procedure Calls”, ACM Transactions on Computer Systems, Vol 5, No. 1, pp. 39-59, fevereiro 1984. [Bras95] F.V.Brasileiro, “Constructing Fail-Controlled Nodes for Distributed Systems”, Tese de Doutorado, University of Newcastle upon Tyne, maio 1995. [Bras97] F.V. Brasileiro, “Seljuk: Um Ambiente para Suporte ao Desenvolvimento e à Execução de Aplicações Distribuídas Robustas”, submetido ao VII Simpósio de Computadores Tolerantes a Falhas, março de 1997. [CB97] V.S. Catão e F.V. Brasileiro, “Serviço de Comunicação Síncrona para Nodos Replicados”, submetido ao VII Simpósio de Computadores Tolerantes a Falhas, março de 1997. [HLD88] R.E. Harper, J.H. Lala, e J.J. Deyst, “Fault Tolerant Processor Architecture Overview”, Digest of Papers, FTCS-18, Tokyo, Japan, pp. 252-257, junho 1988. [KRST93] M.F. Kaashoek, R. Renesse, H. va Staveren, A.S. Tanembaum, “FLIP: An Internetwork Protocol for Supporting Distributed Systems”, ACM Transactions on Computer Systems, Vol. 11, No. 2, pp. 73-106, fevereiro 1993. [LV91] R. de Lemos e P. Verissímo, “Confiança no Funcionamento - Proposta para uma Terminologia em Português”, Comunicação Pessoal, dezembro de 1991. [MRTRS90] S. J. Mullender, G. van Rossum, A. S. Tanenbaum, R. van Renesse, e H. van Staveren, “Amoeba: A Distributed Operating System for the 1990’s”, IEEE Computer, Vol. 23, No. 5, pp. 44-53, maio 1990. [Mull95] S. J. Mullender, Distributed Systems, Addison-Wesley, ACM Press, 1993, ISBN 0-201-62427-3. [PJ94] Jalote, Pankaj, Fault Tolerance in Distributed Systems, Prentice Hall, New Jersey, 1994, ISBN 0-13-301367-7. [SESTT92] S.K. Shrivastava, P.D. Ezhilchelvan, N.A. Speirs, S. Tao, e A. Tully, “Principal Features of the VOLTAN Family of Reliable Node Architectures for Distributed Systems”, IEEE Transactions on Computers, Vol. 41, No. 5, pp. 452-549, maio 1992. [VB97] S.R. Vasconcelos e F.V. Brasileiro, “Serviços para Tolerância a Faltas no Ambiente Operacional Seljuk-Amoeba”, submetido ao VII Simpósio de Computadores Tolerantes a Falhas, março de 1997.