You are on page 1of 33

UNIVERSIDADE REGIONAL INTEGRADA DO ALTO URUGUAI E DAS MISSÕES CAMPUS DE ERECHIM DEPARTAMENTO DE ENGENHARIAS E CIÊNCIA DA COMPUTAÇÃO CURSO DE CIÊNCIA

DA COMPUTAÇÃO

ELVIS ROBERTO FABIANE

TOLERÂNCIA A FALHAS

ERECHIM - RS 2012

PREFÁCIO

O aumento do uso dos computadores em quase todos os aspectos da vida moderna levou a necessidade de ter sistemas de computadores altamente confiáveis. A tolerância a falhas é um segmento em que a confiabilidade dos sistemas computacionais pode ser aumentada além do que pode ser atingido por métodos tradicionais. Sistemas de tolerância a falhas utilizam redundância para mascarar os vários tipos de defeitos. Tolerância a falhas não é uma área nova. Alguns conceitos de tolerância a falhas foram propostos pelo próprio Von Neumann. A primeira conferência na área – IEEE’s International Conference on Fault Tolerant Computing Systems (FTCS) – iniciou a mais de duas décadas atrás. Entretanto, o trabalho inicial em tolerância a falhas foi focado quase exclusivamente em tolerância falhas suportada em hardware. A área de aplicativos tolerante a falhas é relativamente nova. Tolerância a falhas é geralmente considerada como consistindo de duas subáreas: tolerância a falhas em hardware e tolerância a falhas em software. Essa divisão não é precisamente definida, de modo geral, a tolerância a falhas em software abrange aqueles métodos em que a tolerância a falhas contra diferentes tipos de defeito é amplamente suportada em software, enquanto a tolerância a falhas em hardware é onde a tolerância a falhas é suportada amplamente em hardware. Considerando que a atividade de tolerância a falhas em hardware geralmente vem sob o alcance da comunidade de engenharia elétrica, os tópicos sobre tolerância a falhas em software são, em grande parte, de interesse para a comunidade de ciência da computação. Este livro é uma tentativa de organizar o acervo de conhecimentos na área de tolerância a falhas em software. Como a maioria das técnicas propostas nesta área usam sistemas distribuídos como plataforma básica, o livro foca na tolerância a falhas em sistemas distribuídos. No entanto, sempre que necessário (por exemplo, falhas de projeto de software), o caso será discutido como um caso especial de sistemas distribuídos. O livro trata um sistema distribuído tolerante a falhas como consistindo de níveis de abstração. Os diferentes níveis fornecem diferentes serviços tolerantes a falhas. Os níveis mais baixos são as abstrações que são tão frequentemente necessárias por técnicas de tolerância a falhas do que os níveis mais altos que nós os consideramos como “blocos de construção básicos”. Há as abstrações de processos fail-stop (implementação que requer protocolos de acordo Bizantino), armazenamento estável, comunicação confiável, clock sincronizados, e detecção de defeitos. Para suportar serviços tolerantes a falhas, um sistema distribuído é geralmente suposto a fornecer essas abstrações. O nível seguinte é a abstração de difusão atômica e confiável, que também é um alicerce para muitas técnicas de tolerância a falhas, mas exige a entrega confiável de mensagens ponto-aponto e abstrações de processos fail-stop para implementação. Abstrações de difusão atômica e segura são úteis para suportar os serviços tolerantes a falhas em que a comunicação um para muitos é necessária. Estes dois níveis mais baixos fornecem "blocos de construção", que por si só são de uso limitado, mas são utilizados para apoiar os serviços tolerantes a falhas. Os próximos cinco níveis lidam com os serviços tolerantes a falhas. Talvez o mais simples serviço de usuário tolerante a falhas está se recuperando de um sistema distribuído para um estado consistente, se ocorrer algum erro. O próximo nível de atendimento ao usuário tolerante a falhas é garantir a atomicidade de ações. Enquanto a recuperação do estado consistente simplesmente garante que um estado consistente do sistema é atingido, com ações atômicas há ações definidas pelo usuário ou operações que precisam ser executadas atomicamente, mesmo se ocorrerem falhas durante a execução da ação. O foco dos serviços destes dois níveis é a coerência cm as falhas. Com ações atômicas, uma ação geralmente é anulada se ocorrer uma falha enquanto a ação está em execução. Evidentemente, o serviço tolerante a falhas próxima desejável tem que assegurar que as ações sejam sempre completas, mesmo se ocorrerem falhas. Para isso, os dados em que as ações ou processos operam deve ser acessível mesmo se ocorrerem falhas. Estes formam os próximos dois níveis de serviços tolerantes a falhas. Métodos e protocolos in níveis acima discutidos visam fazer algum serviço de usuário tolerante a falhas contra nós e falhas de comunicação. Mesmo com estes, um sistema pode falhar

devido à presença de falhas de design de software em aplicativo de usuário. Assim, ao mais alto nível nós consideramos falhas de design de software. Neste nível, o objetivo é fornecer uma abstração em que o software mascare suas próprias falhas. Os vários níveis são mostrados na figura P.1.

Figura P.1. Níveis de um sistema distribuído tolerante a falhas

O livro é organizado da seguinte forma. Cada nível de abstração é discutido em um capítulo separado. Para cada abstração, é sua precisa definição e motivação é dada, junto com um levantamento dos importantes métodos de apoio a abstração. O capítulo 2 descreve o sistema distribuído geral e suas propriedades. O capítulo 3 contém os “blocos de construção básicos”, Para cada uma das abstrações discutidas nesse capítulo, um ou dois métodos importantes para apoio a abstração são descritos. Capítulo 4 discute a abstração de transmissão segura e suas variações. Protocolos para apoiar os diferentes tipos de transmissão são descritos. Capítulo 6 ao 9 discute os serviços de tolerância a falhas. O capítulo 5 descreve as duas abordagens para recuperação de um estado consistente e alguns métodos para fazer isso. Capítulo 6 discute como ações podem ser feitas atômicas em um sistema distribuído. Protocolos de recuperação necessários para isso também são discutidos. Data Resiliency é discutido no capítulo 7. Várias abordagens para o gerenciamento de replicação de dados para fornecer uma “visão de uma cópia” são descritos. Capítulo 8 discute resilient processes. Métodos para apoiar a resiliência para diferentes métodos de comunicação são descritos. Finalmente, o capítulo 9 discute os métodos para fazer um software tolerante a falhas contra seus próprios defeitos de projeto. O caso uniprocess é discutido como um caso especial de sistemas distribuídos. Embora os sistemas de tolerância a falhas comercial e experimental construídos não terem sido discutidos separadamente, muitas das técnicas usadas por esses sistemas tem sido tratadas nos diversos capítulos. Este livro pode ser usado com um manual para uma graduação/curso de alto nível sobre tolerância a falhas em um departamento de Ciência da Computação, ou por um curso profissional em tolerância a falhas. Também pode ser usado como uma referencia por pesquisadores/ profissionais que trabalham na área. Pode também ser usado como uma referência ou um co-texto para um curso sobre tolerância a falhas em um departamento de Engenharia Elétrica, e pode também ser usado como uma referência ou um co-texto em um curso sobre sistemas distribuídos.

A área de tolerância a falhas é muito abrangente. Assim, embora a maioria dos temas importantes foram abordados no livro, alguns temas foram deixados de fora. Além disso, todos os temas não foram discutidos em grandes detalhes, como alguns tópicos abrangidos no livro tem um grande volume de literatura e um tratamento compreensivo desses tópicos faria o livro volumoso. Ao longo do livro, a ênfase foi colocada sobre as técnicas e algoritmos ao invés de formalismo, pois o foco do livro é avaliar as importantes técnicas para várias abstrações. Há muitas pessoas que me ajudaram a escrever este livro, eu gostaria de expressar minha gratidão a todos eles. Alunos em meu curso de tolerância a falhas. Tanto no Indian Institute of Technology Kanpur quanto na University of Maryland College Park, merecem um agradecimento especial, como a interação com eles influenciou este livro. Eu sou grato a todas as pessoas que responderam minhas perguntas nos e-mails e que facilmente copiavam e enviavam artigos que não eram acessíveis para mim. Também sou grato aos estudantes e outros que ajudaram na leitura crítica de vários trechos do livro. Comentários ou sugestões sobre o livro serão bem-vindas e podem ser enviadas a mim pelo e-mail jalote@iitk.ernet.in. Pankaj Jalote

CAPÍTULO 1
INTRODUÇÃO
O aumento do uso de computadores e o aumento da dependência sobre eles levaram a necessidade de sistemas de computadores altamente confiáveis. Há muitas áreas onde computadores realizam tarefas de vida crítica. Alguns exemplos disso são sistemas de controle de aviões, sistemas de monitoramento de pacientes, orientação de mísseis e sistemas de controle, e sistemas de controle de tráfego aéreo. Nestes sistemas, defeitos nos computadores podem levar a catástrofes, talvez com perda de vida humana. Há outras áreas de aplicação com dependência crítica dos computadores, onde defeitos podem causar grandes perdas financeiras ou perda de oportunidade. Os principais exemplos disso são bancos e mercado de ações. Está claro que nessas aplicações, sistemas altamente tolerantes a falhas são necessários. Está claro também que a necessidade por estes sistemas continuarão aumentando. Confiabilidade é definida como a credibilidade de um sistema computacional tal que a dependência do serviço que este proporciona pode ser justificada [Lap85, Lap95]. O serviço prestado por um sistema é o seu comportamento percebido por seus usuários, onde um usuário é outro sistema (humano ou físico) que interage com o sistema computacional [Lap85, Lap95]. Confiabilidade é um conceito geral, e dependendo da aplicação, diferentes atributos podem ser enfatizados. Os atributos mais significativos da dependabilidade são confiabilidade, disponibilidade e segurança. Confiabilidade lida com continuidade de serviço, disponibilidade com facilidade para uso, segurança com prevenção de consequências catastróficas para o ambiente, e segurança com prevenção de acesso não autorizado e/ou manuseio de informações. O atributo de maior importância para a tolerância a falhas é a confiabilidade (e em certa medida a disponibilidade). Um defeito no sistema ocorre quando o seu comportamento não é consistente com suas especificações. Defeitos são causados por falhas em componentes do sistema. Quanto maior o número de componentes, maiores as chances de existir um componente defeituoso. A pura complexidade e o número de componentes que estão presentes em um moderno sistema computacional atual aumentam a probabilidade de um dos componentes serem defeituosos. Uma vez que defeitos são causados por falhas, uma abordagem direta para melhorar a confiabilidade de um sistema é tentar evitar a ocorrência de falhas ou que elas sejam introduzidas no sistema. Esta abordagem é chamada de prevenção de falhas. A segunda abordagem para

aumentar a confiabilidade é a tolerância a falhas. O objetivo dessa abordagem é proporcionar o serviço, apesar da presença de falhas no sistema. Supõe-se que as técnicas de prevenção de falhas nunca serão capazes de eliminar todas as possíveis falhas, e qualquer sistema real possa ter ou desenvolver falhas. Para aumentar a confiabilidade de um sistema além do que pode ser conseguido através de técnicas de prevenção de falhas, os sistemas devem ser concebidos de modo a que possam prestar o serviço, apesar de falhas. Na abordagem tradicional de prevenção a falhas, a confiabilidade elevada é conseguida através da eliminação de falhas possíveis antes que o sistema seja colocado em uso regular. Um sistema empregando apenas prevenção a falhas não tem redundância, e todos os componentes devem funcionar corretamente, sem falhar, o tempo todo para que o sistema funcione corretamente. Uma vez que todas as possíveis falhas não podem ser antecipadas e eliminadas antes da implantação do sistema, para evitar falhas assume-se que falhas no sistema irão ocasionalmente ocorrer. Para isso, métodos de manutenção manual são concebidas para reparar o sistema quando a falha ocorre. Daí as características fundamentais de um sistema que depende desta abordagem são: (1) ausência de redundância no sistema de falhas de máscara (2), os sistemas falham ocasionalmente, quando qualquer um dos componentes falhar, (3) procedimentos de manutenção manual são usados para reparar o sistema quando ocorrem falhas. Para tais sistemas, falha ocasional e reparação manual são tidas como necessárias. A principal consequência dessas características é que esse sistema está inacessível quando ele está sendo reparado após falhas e nenhuma operação pode ser realizada durante este tempo. Em outras palavras, os trabalhos que estão sendo executadas pelo computador irão, periodicamente, serem realizados para o intervalo de reparação. Para aplicações onde isso é aceitável, sistemas de computador que empregam a prevenção falha só será suficiente. No entanto, existem aplicações onde esta importante consequência do uso apenas da prevenção de falhas não é aceitável. As principais razões por que isso não pode ser aceitável são: (1) inaceitabilidade dos atrasos em tempo real causado pelo manual de manutenção (2), a inacessibilidade de sistemas de reparo manual, e (3) os excessivos custos elevados de perda de tempo e manutenção. A primeira razão existirá para qualquer sistema com restrições de tempo-real. Exemplos destes são aplicações de controle de processos, sistemas de orientação, controle de tráfego aéreo e fly-by-wire. A segunda razão é predominante para os sistemas que têm de ser nãotripulados (ou ocupado por pessoas que não estão qualificados para a reparação de sistemas de computador) de forma contínua. Um bom exemplo disso é a exploração espacial não tripulada. A terceira razão predomina em aplicações como o cozimento, os sistemas de suporte à vida crítico, e sistemas de defesa. Para aliviar muitos dos problemas da abordagem tradicional da prevenção de falhas, a tolerância a falhas pode ser usado. A tolerância a falhas utiliza redundância de proteção a falhas de máscara. Ou seja, o sistema contém componentes que não são necessários, se nenhuma tolerância a falhas é suportada. Estes componentes redundantes são usados para evitar defeitos no sistema no caso de alguns componentes falharem. Essencialmente, a redundância é usada para substituir o manual de manutenção de "reparação e reconfiguração" automatizada, que, por sua vez, aumenta a confiabilidade e disponibilidade do sistema. As duas abordagens - a prevenção de falhas e tolerância a falhas - são de natureza complementar. Empregando a tolerância a falhas sem usar as técnicas de prevenção de falhas para construir os componentes não faz muito sentido. Para a tolerância a falhas ser bem sucedida, é desejável que os componentes do sistema sejam individualmente confiáveis. Ou seja, técnicas de prevenção de falhas devem ser utilizados para construir os componentes que serão utilizados para a construção do sistema tolerante a falhas. Além de usar os métodos de prevenção de falhas para fazer componentes de confiança, os métodos de prevenção também são necessários para verificar e validar o sistema tolerante a falhas. Mesmo com alta confiabilidade não se pode usar os componentes de uma maneira imprópria. Assim, os métodos devem ser utilizados para validar que o sistema tolerante a falhas é realmente tolerante, capaz de mascarar os vários tipos de falhas.

Os métodos de prevenção de falhas focam em metodologias de concepção, teste e validação; considerando que os métodos tolerantes a falhas nos concentram em como usar os componentes (construída usando métodos para evitar falhas) de tal forma que as falhas podem ser mascaradas. A teoria e as técnicas para a construção de sistemas distribuídos tolerantes a falhas é o tema deste livro.
1.

CONCEITOS BÁSICOS E DEFINIÇÕES

Antes de discutirmos qualquer aspecto de sistemas tolerantes a falhas, temos de definir o que entendemos por um sistema, erro, defeito, falha, e tolerância a falhas. Estes termos têm sido usados de diversas formas em diferentes contextos, e os termos de falha, defeitos e erros têm sido frequentemente usados como sinônimos. Agora algum consenso sobre as definições desses termos e conceitos que eles representam. Nesta seção, damos definições gerais a estes termos a seguir os conceitos apresentados em [AL81, Rob82, Lap92]. 1.1.1 Modelo de Sistema Primeiro temos que perguntar o que é um sistema, a fim de discutir os sistemas tolerantes a falhas. O conceito de sistema é bastante geral e existe em outras disciplinas também. Mesmo em computação, o conceito é bastante geral e representa várias coisas em contextos diferentes. Assim, uma definição bastante ampla e geral de um sistema é necessário. Nós definimos um sistema como um mecanismo de identificação que mantém um padrão de comportamento em uma interface entre o sistema e o seu ambiente. Esta definição implica que o sistema deve interagir com seu meio ambiente, e que um ambiente do sistema é necessário para a definição de um sistema. Essencialmente, a especificação do ambiente define os limites do sistema de interesse. O “padrão de comportamento” na interface então, é o comportamento externo do sistema, o que é observado pelo ambiente. O ambiente pode ser dividido em “sub-ambientes” com cada partição de ter seu próprio padrão de comportamento para com o sistema. Esta partição é normalmente feito para gerenciar a complexidade de definir o comportamento de um sistema, e chamar a atenção para os sub-interfaces que são de interesse primário. Ao fazer isso, por exemplo, podemos ignorar, se desejar, a interface de um sistema de computador com o ar em torno dela e seu comportamento na geração de calor que a interface (embora isso possa ser a sub-interface de interesse para o designer de dissipadores de calor para as placas e chips). Um usuário do sistema pode ser considerado um “sub-ambiente”, ou a parte ou o meio ambiente, cujo principal objetivo é usar o serviço prestado pelo sistema. Para isso, ativamente interage com o sistema, fornece insumos para ele, e recebe as saídas do sistema. O usuário pode ser um humano ou outro sistema. Note que a interface do prazo, na definição do sistema, é um conceito e não uma entidade. Nos sistemas de computador, muitas vezes, a interface de hardware representa identificáveis ou entidades físicas. Tais entidades, com esta definição do sistema, podem se considerar como um sistema com suas interfaces conceituais. Na definição do sistema, a interface é usado apenas para identificar uma fronteira entre o sistema e o seu ambiente. Esta definição do sistema é a partir do ponto de vista do seu comportamento externo. Embora seja necessária para identificar o que um sistema seja, ele não oferece nenhum ajuda para especificar a estrutura interna de um sistema. É preciso fazer mais para isso. A maioria das disciplinas de engenharia utilizam um sistema / subsistema hierarquia para definir uma estrutura do sistema. Nós seguimos a definição em [AL81, Rob82]. Um sistema é considerado como sendo composto por um número de componentes ou subsistemas, que interagem sob o controle de um projeto. Cada subsistema é um sistema de direito próprio, com o seu próprio comportamento externo e sua própria estrutura interna. Se um sistema em estudo é um dos sistemas a nível de N, em seguida, cada sistema a nível N é um subsistema de nível (N+1). Cada sistema no nível N é composto de uma série de subsistemas nível (N-1), e cada

sistema a nível regional (N-1) é composto por uma série de subsistemas nível (N-2), e assim por adiante. Este sistema / subsistema hierarquia continua até um nível acima, o que não é possível ou não desejável para especificar os detalhes do sistema. Subsistemas neste último nível são chamados de “componentes do sistema” ou “componentes atômicos”. O nível no qual os componentes são considerados atômicos é aplicação típica ou problemas de dependência. Por exemplo, se estamos interessados no comportamento de cada porta em um sistema de computador (talvez a considerar as falhas ao nível da porta), então essa hierarquia continuará, até chegarem aos portões. Por outro lado, se estamos interessados apenas no comportamento dos principais componentes, como memória, CPU, armazenamento secundário, e uma rede de interligação (como é o caso típico em sistemas distribuídos), então nossa hierarquia não precisa olhar para a estrutura detalhada composta de placas, chips, ou portões. O comportamento externo (ou o estado externo) de um sistema é uma abstração de seu estado interno. O estado interno do sistema compreende os estados externos dos seus componentes. Durante a execução, o estado interno do sistema passa por uma sequência de mudanças, determinadas pela interação entre seus componentes. Algumas das mudanças no estado interno são refletidas como mudanças no estado externo do sistema. Para decidir se o comportamento de um sistema é correto ou não, temos que ter alguma base de comparação, que narra o comportamento correto do sistema. Tradicionalmente, o comportamento esperado ou correto de um sistema é dado por suas especificações. As especificações incluem o serviço esperado, o que inclui as saídas, bem como as interações do sistema e as condições sob as quais o serviço deve ser prestado. Idealmente, queremos que as especificações sejam completas, consistentes e corretas. Integralidade implica que o comportamento total do sistema é especificado em todas as situações possíveis. Consistência implica que as especificações não se contradizem tal que é impossível para um sistema de implementá-las .Finalmente, a correção implica que as especificações especificam o que era "realmente intencionado". Se as especificações são suspeitos de terem falhas, por si só, então não podemos dizer se o comportamento de um sistema é "incorreto" ou se as especificações estão com defeito. Em tais situações, geralmente alguma autoridade externa é empregada para se pronunciar. De modo geral, com o processo de arbitragem, podemos supor que as especificações são autoritários. Este conceito de especificações autoritárias também pode ser formalizado através do conceito de Sistema de Referência Autorizadas [Rob82], no qual, o autoritário Sistema de Referência (ASR) para um sistema simboliza o processo de autoridade que determina se ou não uma interpretação proposta de uma especificação ou a especificação em si, está correto. Nós vamos usar as especificações termo para se referir às especificações autoritárias, ou um ASR. 1.1.2 Defeito, Erro, e Falha Para o modelo de sistema acima definido, tendo em conta as especificações do sistema, a falha de um sistema pode ser definida. Uma falha do sistema ocorre quando desvia primeiro o comportamento do sistema a partir do que é exigido por suas especificações [AL81]. Ou seja, um sistema falha quando não consegue prestar o serviço desejado. Um erro é a parte do estado do sistema que é susceptível de conduzir a [Lap85, Lap92] falha subsequente. Se houver um erro no estado do sistema, então existe uma sequência de ações que podem ser executadas pelo sistema e que levará a uma falha no sistema, salvo algumas medidas corretivas que são empregadas. A causa de um erro é uma falha. Como o erro é uma propriedade do estado do sistema, ele pode ser observado e avaliado. Falhas, em contrapartida, não são uma propriedade do estado do sistema, e não podem ser facilmente observadas (a menos que os mecanismos especiais são utilizados para registrar a ocorrência de alguns tipos de eventos). Normalmente, a ocorrência de uma falha é deduzida através

da detecção monitorada, e se as formas Estado acompanham a parte do comportamento esperado do sistema, então se for detectado um erro implica que ocorreu uma falha. Desde defeitos são essencialmente observados para detectar o erro na saída, eles serão detectados se o erro for realmente observado. Por exemplo, se o estado não está sendo monitorado continuamente, mas é avaliado em intervalos fixos, então a falha não pode ser observada. Da mesma forma, uma falha também pode passar despercebida se o estado de saída completa não está sendo monitorado e ocorrer um erro em uma parte que não está sendo monitorado. Uma vez que geralmente não é possível avaliar o estado inteiro para determinar um erro, é importante que o estado a ser avaliado seja escolhido com cuidado, se queremos “pegar” a maioria das falhas. Em geral, sempre que algo der errado nós atribuímos isso a alguma falha Dizemos que um sistema não está funcionando corretamente devido à presença de uma falha. Falha está associada a uma noção de defeito. Um sistema defeituoso é o único com defeitos. Nós definimos a falhas como os defeitos que têm o potencial de gerar erros. Apesar de uma falha ter potencial para gerar erro, ela não pode gerar qualquer erro durante o período de observação. Em outras palavras, a presença de culpa não garante que irá ocorrer um erro. O inverso, porém, é verdade. Um erro no estado do sistema implica a presença de falhas no sistema. Por exemplo, se uma célula de memória é de tal forma que ele sempre retorna o valor 0 independentemente do que esteja armazenada nele, então ele contém um erro. No entanto, esta falha pode não se manifestar até que a célula de memória com defeito é utilizado e um valor de 1 é armazenada nele, antes da recuperação. Enquanto a célula de memória com defeito não for usado, ou um valor de 0 é armazenado no mesmo, o fato de que a memória contém um erro não vai se manifestar. Falhas podem ser caracterizadas como transitórias ou permanentes. Falhas transitórias são falhas de duração limitada, causada pelo mau funcionamento temporário do sistema ou devido a alguma interferência externa. Falhas transitórias podem causar uma falha ou um erro, apenas no período em que elas existem. O erro causado por falhas transientes também podem existir apenas por um curto período. Isso faz com que a detecção de falhas fique muito difícil. Se uma falha transiente raramente ocorre e os danos causados podem ser corrigidos, em seguida, a sua detecção não é necessária. No entanto, se a falha é intermitente e transitória ocorrendo repetidamente (mas sempre por um curto período), então a sua detecção é desejável. Detecção de falhas é bastante difícil e caro. Falhas permanentes são aquelas em que uma vez que o componente falha, ele nunca (ou por um longo período de tempo) funciona corretamente novamente. Muitas técnicas de tolerância a falhas assumem que os componentes falham permanentemente. Em sistemas distribuídos, também, nós somos amplamente interessados em falhas permanentes causadas por defeitos permanentes. Falhas também podem ser caracterizadas pela fase em que são introduzidas [Lap92]. Falhas de projeto são aquelas que surgem durante o projeto do sistema ou durante a modificação do sistema. Falhas operacionais são aquelas que aparecem durante a vida útil do sistema e são causadas devido a razões físicas. Geralmente, falhas de projeto são muito mais difíceis de tolerar que as falhas operacionais. Exceto para o último capítulo do livro, que trata de falhas de software de design, o resto do livro trata de falhas operacionais. 1.1.3 Tolerância a Falhas Por fim, definimos o que se entende por tolerância a falhas, o tema deste livro. Um sistema é tolerante a falhas se pode mascarar a presença de falhas no sistema usando a redundância. O objetivo de tolerância a falhas é evitar o erro do sistema, mesmo se há presença de falhas. O sistema, como um todo, não pode ser tolerante a falhas contra seus próprios fracassos. Isto é, uma vez que ocorreu falha no sistema nada pode ser feito, pois a falha já ocorreu. No entanto, um sistema pode ser feito de tolerância a falhas contra o fracasso de seus componentes. E esse é o objetivo de tolerância a falhas: para evitar o fracasso do sistema global, quando alguns de seus subsistemas falharem. Em outras palavras, ela mascara a falta de um subsistema de nível superior.

Um sistema é considerado tolerante a falhas se o comportamento do sistema, apesar do fracasso de alguns dos seus componentes, é coerente com as suas especificações. Assim, se algum componente de um sistema é defeituoso, então o sistema é tolerante a falhas se a falha do componente (e a presença de falhas no componente) é mascarado, ou seja, não se reflete no comportamento externo do sistema. Muitas vezes não é necessário que o comportamento do sistema inteiro seja mantido como está (na verdade, pode ser impossível, uma vez que o mascaramento da falha do componente pelo uso de redundância terá algum efeito sobre o comportamento externo, pelo menos no desempenho ), mesmo que alguns dos componentes falharem. Existem alguns serviços ou imóveis de interesse particular (para o pedido de que o sistema tolerante a falhas está sendo projetado), e são essas propriedades que devem ser preservados, apesar da falha de alguns componentes definidos. Na verdade, como veremos no decorrer do livro, a maioria dos problemas para os quais foram propostas soluções pode ser caracterizada pela propriedade ou funcionalidade do sistema a ser preservado, e os tipos de falhas a serem tratadas. O objetivo da maioria das técnicas específicas propostas é o de preservar alguma propriedade desejada sob a face de um conjunto de falhas. Redundância é a chave para apoiar a tolerância a falhas, pois não pode haver tolerância a falhas sem redundância. Redundância é definida como as partes do sistema que não são necessárias para o correto funcionamento do sistema, se nenhuma tolerância a falhas deve ser apoiada. Ou seja, o sistema funciona corretamente sem redundância, se não houver falhas. Redundância em um sistema pode ser de hardware, software, ou tempo. A redundância de hardware inclui componentes de hardware que são adicionados a sistema de apoio à tolerância a falhas. Redundância de software inclui todos os programas e instruções que são empregados para apoiar a tolerância a falhas. Uma técnica comum para tolerância a falhas é executar alguma instrução (ou sequência de instruções) muitas vezes. Esta técnica requer redundância de tempo, isto é, o tempo extra para executar tarefas para tolerância a falhas. Em sistemas distribuídos, com frequência, todas as três formas de redundância são utilizadas. A redundância de hardware é empregada na forma de processadores extras, a memória, ou links de comunicação. Redundância de software é empregada para a gestão destes componentes de hardware extra e usá-los corretamente para a prestação de serviço continuado, no caso de alguns componentes falharem. O tempo extra também é geralmente exigido pelos métodos de tolerância a falhas em sistemas distribuídos. 1.2 FASES NA TOLERÂNCIA A FALHAS Conforme definido anteriormente, um sistema tolerante a falhas tenta impedir o fracasso do sistema, apesar do fracasso de alguns dos seus componentes. Por natureza, a implementação de tolerância a falhas em qualquer sistema particular estará intimamente ligada com o sistema e sua arquitetura e design. Assim como projetar um sistema depende das propriedades / requisitos do sistema, projetando um sistema tolerante a falhas é também uma função das necessidades e da funcionalidade do sistema. Claramente, nenhuma técnica geral, pode ser proposta para “acrescentar” a tolerância a falhas em um sistema. No entanto, alguns princípios gerais que são úteis na concepção de sistemas tolerantes a falhas podem ser identificados. Aqui vamos especificar algumas atividades que em geral a maioria dos sistemas que empregam tolerância a falhas tem a desempenhar. Ao fornecer tolerância a falhas, quatro fases podem ser identificadas: a detecção de erros, confinamento de danos, recuperação de erros, e tratamento de falhas e sistema de serviço de forma contínua [AL81]. A detecção de erros é a fase em que a presença de uma falha é deduzida através da detecção de um erro no estado de alguns subsistemas. Uma vez que um erro foi detectado, isso implica que a falha do componente ocorreu. Qualquer dano causado devido à falha deve ser identificado e delimitado na segunda fase do confinamento. Muitas vezes, o projeto do sistema tem de incorporar mecanismos para ajudar a limitar a propagação de erros no sistema, assim, limitando os danos aos limites pré-determinados. Estas duas fases são realmente as fases de detecção, que são

necessários para iniciar as atividades para tolerar a falha. Depois destes, o erro no estado tem que ser corrigido. Isso é feito na fase de recuperação de erro. Desde que haja um erro no estado do sistema, é necessário remover o erro de tal forma que ele não faça com que se propague para ações futuras. Com a recuperação de erro, o sistema vai atingir um estado livre de erros. Até agora, as atividades estão centradas em torno de erro no estado do sistema. Na fase final de tratamento de falhas e sistema de serviço continuado, a culpa ou o componente defeituoso tem de ser identificado, e o sistema tolerante a falhas tem a função tal que os componentes defeituosos não são utilizados ou utilizados de uma forma diferente ou de configuração, que a falha não volte a causar defeitos. Estas são as quatro atividades gerais que são geralmente executados em qualquer sistema de apoio a tolerância a falhas. Em algumas situações, algumas dessas fases podem ser feitas implicitamente ou podem ser simples, mas a sequência geral das atividades é a especificada por estas fases. No restante desta seção, vamos discutir estas etapas em detalhes. 1.2.1 Detecção de erros O ponto de partida de qualquer atividade de tolerância a falhas é a detecção de erro. Conforme discutido no exemplo acima, falhas e erros não podem ser diretamente observados, mas têm de ser deduzidos a partir da presença de erros. Como o erro é definido pelo estado de um sistema (ou subsistema), os checks podem ser realizados para ver se há um erro ou não. A partir da presença de erros, defeitos e as falhas podem ser deduzidas. Assim, os mecanismos de detecção de erros são muitas vezes referidos como " detecção de defeitos / falhas", e na presença de um erro na saída de um componente é declarado como falha do componente. A eficácia de um regime de tolerância a falhas dependerá claramente da eficácia do mecanismo de detecção de erros utilizada. No entanto, tais mecanismos exaustivos para detecção de erros muitas vezes não são praticáveis. Devido à importância da detecção de erros, vamos primeiro determinar o que é uma seleção ideal para a detecção de erros. Há algumas propriedades importantes que uma verificação de detecção de erro deve satisfazer [AL81]. Primeiro, uma seleção ideal deve ser determinada unicamente a partir das especificações do sistema e não deve ser influenciada pelo design interno do sistema. Qualquer influência do sistema sobre a seleção pode causar o mesmo erro na detecção de erros, o sistema deve ser tratado como uma "caixa preta". Em segundo lugar, uma seleção ideal deve ser completa e correta. Isto implica que deve ser capaz de detectar todos os possíveis erros no comportamento do sistema que podem ocorrer a partir da presença dessas falhas que o sistema tolerante a falhas visa segurar, e que nunca declara um erro quando não há erros presentes . Com uma verificação completa e correta, pode-se afirmar que, se nenhum erro foi detectado, então não há falhas (de interesse) no sistema, e se for detectado um erro, um erro (e, portanto, uma falha) está presente no sistema. Claramente, se a checagem não está completa, alguns erros podem passar despercebidos, o que mais tarde causa um defeito no sistema. Em terceiro lugar, a verificação deve ser independente do sistema com relação à suscetibilidade de falhas. Se tivermos um cheque que também falha quando o sistema falhar, então o cheque é de nenhum valor prático. Gostaríamos de uma seleção para que um modo de falha independente, isto é, ele falha independentemente do sistema. Se isso for satisfeito, a probabilidade de que a seleção vai falhar, ao mesmo tempo que o sistema seja minimizado. Em sistemas reais, estes critérios raramente são totalmente satisfeitos. Apenas aproximações são possíveis. Normalmente, não é possível executar uma verificação completa sobre o estado de saída, uma vez que essa verificação pode ser muito complexa e, portanto, mais chances de estar com defeito. Além disso, uma verificação completa pode impor restrições financeiras e de desempenho que são impraticáveis. Da mesma forma, verificações práticas não são susceptíveis de serem executadas em todas as saídas possíveis em todos os momentos possíveis, para colocar essa verificação, as informações sobre a estrutura do sistema é frequentemente utilizado. Essas informações são normalmente utilizados para garantir uma maior "cobertura". Finalmente, mesmo a independência do cheque a partir do sistema não pode ser plenamente obtido, uma vez que, finalmente, a verificação e o sistema devem compartilhar algum ambiente (alimentação, mesma

caixa, mesma sala, etc.) e nada de errado com o meio ambiente, então, causar tanto a falhar (por exemplo, se há radiação que faz com que todo o hardware para executar de forma incorreta, uma seleção baseada em hardware também falhará junto com o sistema de hardware). Tendo em vista as limitações práticas, os controles para a aceitação são frequentemente utilizados para detecção de erros, em vez de cheques ideais. Uma checagem aceita não é uma checagem ideal, mas uma aproximação. O objetivo dos controles de aceitação é manter o custo do controle de detecção de erro baixa, e ao mesmo tempo maximizar os erros que forem detectados. Estes controles não garantem que não há erros detectados, mas tentar capturar a maioria dos erros de interesse, especialmente os que são mais prováveis de ocorrer. Os controles de detecção de erros que são empregados em sistemas de computador podem ser de diferentes tipos, dependendo do sistema e as falhas de seu interesse. No entanto, existem alguns tipos gerais de cheques que são mais frequentemente empregados. Vamos agora discutir brevemente estes [AL81]. Replicação de Checks. Replicação de Checks são um dos controles mais comum e poderosos. Replicação de checks podem ser bastante completo e pode ser implementado sem o conhecimento da estrutura interna do sistema a ser replicados. Como o nome sugere, essa verificação de replicação envolve alguns componentes do sistema. Os resultados dos componentes diferentes são comparados, ou votados, para detectar erros. Devido a isto, é também um dos mais caros métodos de detecção de erros. O tipo e a quantidade de replicação depende da aplicação. Pode-se supor que o projeto do sistema está correto e as falhas ocorrem devido a causas físicas, e as falhas dos componentes são independentes, um componente pode ser replicados muitas vezes. Esta forma de replicação é utilizada frequentemente em hardware. Redundância Modular Tripla (TMR), que é discutido mais adiante neste capítulo, usa esse método. Replicação usando cópias idênticas de um componente funciona se o projeto do componente está correto. Tais replicações de verificação claramente não funcionam se o projeto do sistema pode estar com defeito. Para a manipulação de design de falhas, a replicação pode ser usada, mas os componentes de replicação devem ser diferente no design também. Ou seja, todos os componentes replicados implementam as especificações do componente, mas de forma independente. Se os defeitos causados por erro no projeto são independentes, tolerância a falhas contra defeitos de projeto podem ser alcançados. Vamos discutir isso mais tarde, no contexto de falhas de software. A replicação também é usada em sistemas para outros fins que não a detecção de erros. Por exemplo, em sistemas distribuídos, a replicação é amplamente utilizada, embora não com a finalidade de detecção de erro. Normalmente, os dados ou processos são replicados em um sistema distribuído em diferentes processadores de tal forma que o fracasso de alguns componentes do sistema distribuído pode ser tratado. A detecção de falhas é normalmente feita por cheques de tempo (Discutido a seguir). A replicação é utilizada em grande parte de serviço continuado e isolamento de falhas. Verificação de tempo. Se as especificações de um componente incluem restrições de tempo, os cheques de tempo podem ser usados para verificar se essas restrições são respeitadas ou não. Verificação de tempo normalmente definem um temporizador com um valor determinado a partir das especificações do componente. Se o timer "expira", isso significa que a restrição de tempo do componente é violada. Uma violação de programação, muitas vezes implica que o componente se comporta de forma incorreta e suas outras saídas também podem ser um erro. Assim, indiretamente, um "erro de timing" também significa um erro no estado do sistema. Verificações de tempo são frequentemente utilizadas tanto em sistemas de hardware e software para detector “situações problema”. Os conjuntos de temporizadores para detectar problemas de sincronismo são por vezes também chamado de “watchodog timers”. A maioria dos hardwares do sistema utilizam controles de tempo para detectar problemas no acesso à memória ou

acesso na bus. Em software, sistemas operacionais, por exemplo, os temporizadores são utilizados extensivamente para a detecção de situações que podem levar a mais problemas. Em sistemas distribuídos, erro de timing desempenha um papel central. Uma das falhas dos componentes mais comuns que as técnicas de tolerância a falhas tenta mascarar é a "falhas de nós". Um nó é um componente em um sistema distribuído. O sistema distribuído frequentemente especifica uma restrição de tempo que um nó de trabalho deve responder dentro de algum tempo definido. Esse tempo é geralmente calculado com base nos atrasos envolvidos na rede de comunicação. Se um nó não responder dentro do tempo limite, seu comportamento é declarado errado e o nó é assumido como tendo falhado. Esta é a forma mais comum de verificação para detectar a falha de nós. Verificações Estruturais e de Codificação. Em qualquer dado, dois tipos gerais de controle são possíveis: verificação semântica e verificação estrutural. Verificação Semântica tenta assegurar que o valor é consistente com o resto do sistema. Verificações estruturais consideram apenas os dados e garantir que, internamente, a estrutura dos dados é como deveria ser. Se a redundância é construída para a representação dos dados em si, então a verificação estrutural pode ser utilizada para identificar dados errados. A forma mais comum de verificação estrutural, amplamente utilizado em hardware, é a codificação. Na codificação, os bits extras são adicionados aos bits de dados, de tal forma que o valor desses bits extra é sempre relacionado com o valor dos bits de dados. Os mecanismos de controle são baseados na verificação de codificação dessa relação. Se os bits de codificação ou os bits de dados estão corrompidos, essa relação é violada, e o erro é detectado. Vamos continuar a discutir a codificação mais adiante neste capítulo. Verificações estruturais, embora mais amplamente utilizadas em hardware, podem também ser empregadas em sistemas de software. Em sistemas de software, verificação estrutural pode ser concebida para estruturas de dados, especialmente se houver alguma redundância nas estruturas de dados. As estruturas de dados que usam a redundância, a fim de facilitar os controles estruturais são chamadas de estruturas de dados robusta [TMB80a, TMB80b]. Nas estruturas de dados robusta, se as estruturas de dados corrompida, em seguida, usando a redundância e verificações estruturais, é possível detectar e até mesmo corrigir a parte danificada das estruturas de dados. No entanto, desde o caso de corrupção na estrutura de dados é muito frequente, em comparação com a corrupção de dados em níveis mais baixos no hardware, estruturas robustas de dados têm um uso limitado, mas muito mais se o hardware subjacente for muito confiável. Verificação de Razoabilidade. Verificação de Razoabilidade determina se o estado de algum objeto no sistema é "razoável". Um exemplo comum é a verificação da razoabilidade gama, onde é determinado que um determinado valor está dentro de um intervalo especificado. Estes testes de razoabilidade não garantem que o valor está correto, só que o valor está dentro de uma faixa (que inclui o valor correto também). Dado que os cheques não podem ser criados para a correção do valor, são utilizados intervalos. O intervalo de valores aceitáveis é determinado a partir da aplicação. Outra variação deste é acompanhar a taxa de mudança de algum valor, a taxa de mudança deve ser dentro de alguns limites. Este formulário pode ser de uso particular em sistemas de controle, onde a mudança de valores tem de ser contínua e, portanto, a taxa de variação de parâmetros torna-se limitada. Uma das formas mais comuns de verificação de intervalo que é frequentemente utilizado é o tempo de execução de verificação de escala realizada pelo sistema. Muitas linguagens, como Pascal, usam as declarações de estruturas de dados (e os limites de suas dimensões) dado pelo usuário para gerar automaticamente a verificação de intervalo de tempo de execução. Se o valor está fora do alcance, ou se a verificação de intervalo falha durante a execução, a execução do programa é abortado e sinais necessários são gerados. Estas verificações são gerados automaticamente a partir da redundância que está incorporada nos programas Pascal na forma de dados explícitos e declarações do tipo.

Outra verificação de razoabilidade é possível sobre as afirmações sobre o estado do sistema. Uma asserção é uma expressão lógica sobre o valor das diferentes variáveis, do sistema, que irá avaliar para true se o estado do sistema é consistente, caso contrário ela irá avaliar para false. Afirmações são por vezes utilizados na detecção de erro no software. Diagnóstico de cheques. Em diagnósticos de cheques, um sistema emprega algumas verificações sobre o seu componente para ver se o componente está funcionando corretamente. Diferentemente de outras formas de controle, onde a seleção faz parte do sistema para detectar um erro em seu estado, aqui a verificação é feita pelo sistema no seu componente. Diagnósticos de cheques são geralmente os valores de entrada especiais para os quais são conhecidos os valores corretos para o sistema de uso anterior. Para cada um dos valores de entrada a saída é comparada com o valor correto armazenado para determinar se existe um erro. Verificações de diagnósticos são frequentemente usadas em sistemas no momento em que ele esta sendo “ligado”. Neste contexto, são normalmente chamadas de "auto-verificações”. Verificação de diagnósticos geralmente exige que o sistema pare de executar as operações do usuário. Essa restrição faz com que seja seu uso seja limitado em muitos ambientes, no entanto, como mencionado acima, é usado com frequência para a verificação inicial do sistema quando ele está sendo ligado. Neste momento, já que não há usuários no sistema, uma verificação de diagnóstico é viável.

1.2.2 Confinamento e Avaliação de Dados Ao detectar um estado de erro no sistema, sabemos que em algum lugar do sistema existem falhas. No entanto pode haver um atraso de tempo entre o erro e o evento de detecção. Esse atraso pode ocorrer porque o sistema não está a ser continuamente monitorados para os erros. Devido à interação entre os componentes durante esse atraso, um erro pode se propagar para outras partes do sistema. Assim, no momento em que for detectado um erro na saída de um componente, o erro pode ter se espalhado para outras partes do sistema. Portanto, após a detecção de um erro, antes que o estado incorreto do sistema pode ser corrigido, é preciso determinar exatamente os limites da corrupção, ou partes que estão corrompidas. Este é o objetivo desta fase. Os erros do sistema se propagam através da comunicação entre os componentes do sistema. Assim, para avaliar a quantidade de danos no sistema, após ter sido detectado o erro, o fluxo de informações entre os diferentes componentes do sistema tem que ser examinado. Algum pressuposto tem de ser feitos sobre a origem do erro, ou quando o erro foi originado. Então, todo o fluxo de informações após o erro pode poderia propagá-lo para outras partes. O objetivo é identificar os lugares por onde essas informações passaram. O dano é, então, limitado a esses lugares. O limite pode ser identificado dinamicamente pelo registo e análise do fluxo de informações. No entanto, este método poderá ser complexo. A melhor maneira é criar um sistema onde "fire walls" sejam incorporados estaticamente ao sistema. Estes fire walls garantem que o fluxo de informações fique somente dentro desses fire walls. Se for detectado um erro dentro desta área definida estaticamente, então há uma grande probabilidade de que o erro não se espalhou para além dos fire walls (a menos que a falha tinha ocorrido antes da computação ter entrada na área dos firewalls). Na maioria dos sistemas, a estrutura estática é usada para assumir a propagação dos danos. Muitas vezes a atividade de avaliação de danos não é realizada de forma explícita, e a estrutura do sistema é usada na fase posterior de recuperação de erro para decidir a quantidade de recuperação, indiretamente fazendo suposições sobre o confinamento dos danos.

1.2.3 Recuperação de Erros Uma vez que o erro foi detectado e identificado, é hora de remover o erro. A menos que o erro seja removido, o estado errado pode causar uma falha no futuro. Nesta fase de recuperação, o sistema esta livre de erros. Esta é uma das mais importantes atividades e tem sido enfatizado em muitos trabalhos anteriores sobre tolerância a falhas. De fato, em alguns sistemas, a recuperação apropriada de erros é uma meta aceitável, isto é, todos esses sistemas querem que, em caso de ocorrerem falhas, o sistema deve ser restaurado para um estado consistente. Existem duas técnicas para recuperação de erros em geral: “backward recovery” e “forward recovery”. Na “backward recovery” o estado do sistema é restaurado para um estado anterior, na esperança de que o estado anterior seja isento de erros. Este método requer que o estado do sistema seja verificado e salvo (checkpointed) periodicamente em algum armazenamento estável que não seja afetado pela falha. Quando algum erro ou falha é detectado, o sistema é revertido para o último ponto de verificação (checkpointed). Se a falha ocorreu após o checkpoint ser estabelecido, o ponto de verificação será livre de erros e depois desta reversão, o sistema também será livre de erros. “Backward recovery” é uma das formas mais usadas de recuperação de erros. É bastante geral, e não depende muito da natureza da falha ou erro. Ele pode se recuperar de falhas arbitrárias, seja transitória ou permanente. Na verdade, é muito apropriado em caso de falha transitória, porque depois da recuperação nada mais precisa ser feito. O problema causador da falha já terá acontecido, e reiniciar o sistema a partir de um ponto salvo não irá produzir erro novamente. A principal desvantagem deste tipo de recuperação é a sobrecarga necessária. Primeiro, o ponto de verificação tem de ser feito com frequência no armazenamento estável. Isso afeta a execução normal do sistema, mesmo que não falha. Depois, há um processo de reversão envolvidos em caso de falha, o que faz com que haja algum desperdício de computação no sistema. Apesar da sobrecarga, devido ao seu caráter geral e da simplicidade, ela é usada com frequência. Na “forward recovery”, nenhum estado anterior está disponível, e o sistema não volta para um estado anterior. Em vez disso, a tentativa é a de "ir para a frente"e tentar tornar o sistema livre de erros, tomando as devidas ações corretivas. Na teoria, o conceito é interessante, pois é provável que seja eficiente em termos de sobrecarga. No entanto, pela própria natureza de recuperação para a frente, será necessário fazer uma avaliação do dano causado ao sistema, e sobre a natureza do dano, ou erro. Só se a natureza do erro for conhecida, é que o erro poderá ser removido por ações corretivas. Assim, um bom diagnóstico da razão e dos danos ao sistema deverá ser feito. Isso faz com que a “forward recovery” de um sistema seja uma abordagem dependente do aplicativo. Devido a isso, não é usado como tão comumente quanto a “backward recovery”. Em sistemas distribuídos, por exemplo, a “backwars recovery” é empregada na maioria dos casos. 1.2.4 Tratamento de Falhas e de serviço Continuado Nas três primeiras fases, o foco está no erro. Primeiro, o erro é detectado, o prejuízos são avaliados e só depois removidos. Depois disso, temos o sistema em um estado livre de erros. Isso pode ser suficiente se o erro foi causado por alguma falha transitória. Após a recuperação de erro, o sistema pode ser reiniciado (a partir de um estado livre de erros), e nenhum erro ocorrerá, pois a falha não existe mais. No entanto, se as falhas são permanentes, o que causou a falha e o erro ainda permanece no sistema, mesmo após a recuperação de erros. Se reiniciar o sistema após a recuperação de erros, então o mesmo problema fará com que as mesmas falhas e erros ocorram novamente. Para evitar isso, é essencial que os componentes defeituosos sejam identificados e não mais utilizados na computação após a recuperação. Isto é, de alguma forma o componente defeituoso tem de ser "ignorado", sem comprometer a computação. Este é o objetivo desta fase. Esta fase tem duas sub-fases importantes: localização do defeito e reparo do sistema. Na localização do defeito, o componente que contém a falha tem de ser identificado. A menos que o

componente defeituoso já seja conhecido e nenhum mecanismo pode ser empregado para reparar a falha ou assegurar-se que o componente defeituoso não causará uma falha novamente. Normalmente, após a detecção de erros e avaliação, o componente defeituoso é identificado como o componente que contém a fonte do erro. Na reparação do sistema, o sistema é "consertado" de tal forma que o componente defeituoso não seja mais utilizado. Um ponto importante a ser observado é que esse reparo é on-line e é feito sem intervenção manual para que seja considerado um sistema tolerante a falhas. Se o reparo manual é utilizado, então não é um sistema tolerante a falhas. Este reparo é feito por um sistema de reconfiguração dinâmico, de tal forma que a redundância, que está presente no sistema é usado para executar a tarefa do componente falho. Uma das mais simples estratégias para a reparação do sistema é a estratégia de reposição em espera. Neste, há um componente redundante de espera no sistema. Se o componente principal falhar, o componente que esta em modo de espera é utilizado e o componente defeituoso é ignorado. Uma vez que o sistema fora reparado, o serviço normal pode continuar, como se nada tivesse acontecido. Devido a isso, em geral, o efeito de tolerância a falhas é, no máximo, um pouco de descontinuidade no serviço ou alguma degradação de desempenho. Mas o sistema não ficara indisponível para os serviços do usuário. Em sistemas distribuídos, por falhas de nodos de processamento, a detecção de um nodo defeituoso pode ser feito com base na auto detecção de falhas. Como os nodos são tratados como uma entidade completa e, normalmente, um nó defeituoso não pode fazer outro nodo se comportar incorretamente (ou seja, a possibilidade de um erro ser propagado é remoto), e a detecção de falhas identifica o nó com defeito. O reparo do sistema é feito usando outros nós do sistema para realizar a tarefa do nó que falhou. 1.3 PANORAMA DA TOLERÂNCIA DE FALHA DE HARDWARE A tolerância a falhas pode ser, e é aplicada a níveis diferentes em um sistema de computador. Podemos tratar um sistema de computador como um sistema de camadas, com o mais alto nível de aplicações e portas (ou até mesmo em uma unidade menor) até no nível mais baixo. As camadas mais baixas deste modelo serão consideradas como o "hardware", enquanto as camadas superiores serão consideradas como "software do sistema (software)". É difícil definir o que constitui a tolerância a falhas de hardware e o que constitui tolerância a falhas de software. Intuitivamente, nós vamos considerar todas as técnicas que visam proporcionar tolerância a falhas de hardware, que visam mascarar componentes falhos, como os métodos de tolerância a falhas de hardware. O objetivo deste livro é de se concentrar nos princípios e técnicas de tolerância a falhas nos níveis de sistemas de software, onde a tolerância a falhas é suportado em grande parte do software. Por uma questão de completude, apresentamos uma breve descrição das técnicas de tolerância a falhas de hardware nesta seção. Esta é uma área vasta e livros completos podem ser escritos em alguns dos temas sobre a tolerância a falhas de hardware. Nosso objetivo aqui é apenas familiarizar o leitor com o que é a tolerância a falhas de hardware e quais as técnicas básicas. Para mais detalhes, o leitor poderá consultar [Lal85, Joh89]. 1.3.1 Processo de Desenvolvimento de Hardware O hardware moderno passa por diversas fases durante o seu desenvolvimento. Primeiro, vamos dar uma visão geral de um processo típico de desenvolvimento de hardware [AA86]. Inicialmente, muitos circuitos integrados (CIs) são fabricados em uma única pastilha de silício. Esses CIs são chamados matrizes nesta fase. Os testes são realizados nesta fase para detectar matrizes defeituosas, que são então marcadas. A pastilha de silício é então cortada em matrizes individuais, e as marcadas são descartadas. O percentual de pastilhas que não estão com defeito é chamado de rendimento de produção. O percentual de matrizes que não estão com defeito é

chamado o rendimento do processo de fabricação. Para a integração em larga escala (LSI) e a integração em escala muito grande (VLSI), o rendimento é frequentemente muito baixo (50% ou menos). Isto se deve à complexidade destes circuitos e do grande número de portas que estão presentes em um CI. A precisão exigida é enorme, mesmo uma partícula de poeira em uma matriz causará uma falha no teste. As matrizes que passam o teste são encapsuladas para o IC, as trilas entre os componentes são feitas e os pinos são fixados sobre as matrizes. Isto é novamente uma atividade de alta precisão. O chip encapsulado é então testado. Este é um teste importante, que exige muitas vezes complicados sistemas de alta velocidade. O equipamento de teste normalmente é carregado com uma sequência de entradas e as saídas corretas que são esperadas do CI para as entradas. O equipamento de teste processa estes casos de teste em altíssima velocidade. Para aplicações de alta confiabilidade, os chips podem ser "queimados” em altas temperaturas para acelerar as falhas e, em seguida testados novamente. Os chips que falham no teste são descartados. Os chips são os montados em placas de circuito impresso (PCB), e então essas placas são testadas. Aqui também tipicamente são feitos testes de entrada e saída, onde um equipamento especializado é empregado. Sondas são muitas vezes colocados em vários pontos do circuito para obter as saídas intermediarias. Os PCBs são colocados juntos para formar um sistema. O sistema é testado antes de ser enviado. Esta montagem do PCB em um sistema pode ser feito em fases, a primeira formação do subsistema e, em seguida, formando o sistema completo. Os testes aqui podem exigir a execução do sistema de uma maneira semelhante ao seu uso esperado. Quando o sistema passa no teste, ele será enviado para o uso dos clientes. 1.3.2 Falhas e Modelos de Erros Ao contrário do software, que não tem propriedades físicas e, portanto, nenhuma causa física de falha, falhas de hardware têm sua origem em causas físicas. Falhas de hardware podem ser de fabricação ou que ocorrem com o passar do tempo. Os dois não são independentes, e problemas na produção podem ter efeito sobre as falhas dependentes do tempo. As falhas físicas que ocorrem durante a fabricação incluem dispositivos com defeito, quebra de ligações, curtos entre as linhas, as impurezas das embalagens que afetam o funcionamento do chip. As falhas que ocorrem com o passar do tempo incluem quebras, curtos, e mudança na tensão de alimentação. A maioria das falhas físicas são curtos e aberturas de linhas. Apesar de que as falhas físicas são as causas básicas das falhas de um chip, na maioria dos casos, não nos interessa a falha física em nível micro que faz com que alguma peça de hardware fique com mau funcionamento. Só estamos interessados em saber se a falha está presente ou não, ou seja, estamos interessados nas consequências de uma falha física. Um método de fazer isso é descrever o efeito das falhas físicas em algum nível superior. Este maior nível pode ser no nível de portas e circuitos lógicos, em nível de bloco funcional, no nível do chip, etc. Um modelo abstrato é denominado modelo de falhas. Um modelo de falha especifica o efeito das falhas físicas. A esperança é que algumas falhas no modelo de falha poderão descrever com precisão a maioria das falhas físicas. Se isso puder ser feito, para apoiar a tolerância a falhas, apenas falhas do modelo necessitarão ser consideradas. Normalmente, muitas falhas a nível físico podem ser modeladas pela mesma falha em um nível mais alto. O modelo de falhas pode ser definido em um nível muito alto (digamos, um nível de placa ou subsistema) ou um nível baixo (nível de transistor). Se formos para um nível muito baixo, então o modelo de falha irá conter as falhas físicas em si, e se formos a um nível muito alto o modelo de falhas pode não ser capaz de cobrir todas as falhas dos níveis inferiores. Aqui iremos descrever alguns dos mais comuns modelos de falhas. Modelos de Falha em nível de portas logicas. Modelos de falha em nível de portas logicas são muito frequentes na tolerância de falhas de hardware. O modelo clássico deste nível é uma porta logica travada em 1 ou 0. Com isso, este modelo capta a falhas e quebras de ligação do circuito, que são as falhas mais comuns em CIs pequenos. Outro modelo de falhas, que é um subconjunto

travamento das portas, é o modelo de falhas por pinos. Neste, apenas os pinos dos chips são considerados presos, em 0 ou 1. A generalização desses modelos é o de múltiplas falhas , onde várias linhas estão presos em algum valor. Um modelo relacionado, embora é frequentemente caracterizado como um modelo de erro, é o modelo de erro unidirecional. Nesse sentido, em um conjunto de linhas em que todos os 0s tornam-se 1s, ou em todos os 1s tornam-se 0s. Esses casos geralmente ocorrem quando algumas linhas entram em curto com outras. Apesar de o modelo-preso refletir com precisão as deficiências físicas em CIs de pequeno porte, em CIs grande, feitos a partir da tecnologia MOS, algumas das falhas físicas não podem ser modeladas. Uma quebra em uma linha de um transistor CMOS muitas vezes faz com que o CI se comporte como se tivesse memória, e a falha só poderá ser detectada se a saída da porta CMOS é inicializada corretamente. Isso faz com que até mesmo um circuito combinacional se comporte como um circuito sequencial. O modelo que capta esta propriedade de memória em caso de cortes é chamado o modelo de falhas aberto. Por outro lado, os defeitos causados por um transistor que conduz permanentemente são modelados pelo modelo-preso. Com a densidade crescente de circuitos integrados, curtos entre linhas adjacentes se tornaram bastante comuns. Tais defeitos são modelados pelo modelo de falhas de ligação. Outro modelo de falhas que é considerado importante é o modelo de falhas por atraso. Este, por sua vez, modela a degradação por atraso em uma porta ou em um caminho no circuito. Tal degradação pode causar um valor lógico incorreto no travamento da saída. Modelos de falha em nível de funções. Esses modelos refletem as deficiências físicas com bastante precisão. No entanto, eles são muito detalhados pelo fato de se ter que considerar a estrutura do nível de portas de um circuito. Para fornecer tolerância a falhas, já que é mais provável que um chip com defeito será descartado, muitas vezes é mais importante saber simplesmente se um chip está ou não funcionando. O modelo de falhas no nível funcional tenta modelar falhas do nível de módulos funcionais. É claramente desejável que o modelo de falha inclua os efeitos da maior parte das falhas físicas ou em nível de portas lógicas. Um modelo de falhas muito geral para blocos funcionais é assumir que uma função N entradas, em falhas, pode ser transformada em outra função, com N entradas. Tal modelo de falhas não caracteriza o total comportamento, e é, portanto, de uso limitado para tolerância a falhas. Não parece haver nenhum modelo de falha geral no nível de bloco funcional. No entanto, os modelos de específicos, módulos funcionais têm sido propostos. Por exemplo, em um decodificador de N entradas e 2N saídas, normalmente linha é ativada correspondendo ao endereço de entrada. Tem sido demonstrado que todas as falhas nos níveis mais baixos de um decodificador podem ser descritas por um conjunto de três falhas [AF86]. Estas falhas são (1) uma linha incorreta é ativada, (2) mais de uma linha é ativada, e (3) nenhuma linha é ativada. Da mesma forma, os modelos têm sido propostos para multiplexador [AF86], e outros pequenos módulos funcionais. Modelos de falhas funcionais também estão usados, extensivamente para testes de redes lógicas, em que a célula defeituosa altera a sua função de forma arbitrária. Modelos de falha de memória. A memória principal (Random Access Memory, memória RAM) no sistema de um computador é, conceitualmente, um conjunto de células de memória. Para ter acesso, a memória principal também deve conter um decodificador de endereços, leitura / escrita, lógica e registradores de dados para transferência de dados. Um conjunto amplamente usado de modelo de falhas funcionais para a memória contém as seguintes falhas: (1) uma ou mais células são presos em 0 / 1, (2) algumas células são acoplados e quando uma célula altera o seu valor, outras células acopladas também mudam os seus valores, e (3) estado de uma alteração de memória celular como resultado de algum estado das células de memória. Agora vamos discutir algumas das técnicas mais comuns que estão sendo usadas, para suporte a tolerância a falhas no hardware. Esta é também uma área muito vasta em que há progresso constante. Nosso objetivo não é dar uma imagem completa ou um tutorial de todas as técnicas, mas apresentar alguns das mais conhecidas. Para mais detalhes, o leitor pode consultar [La185, SW82]. 1.3.3 Redundância Modular Tripla (TMR). - Fim Ricardo

A mais conhecida técnica de tolerância a falhas para hardware é a redundância modular tripla (TMR), que tem sido utilizado em sistemas tolerantes a falhas. O conceito foi originalmente sugerido por Von Neumann. Na TMR, a unidade de hardware (representado por M na fig. 1.1) é triplicada, e todas as três unidades trabalham em paralelo. As saídas destas três unidades são dadas ao “eleitor” (representada por V na fig. 1.1). O “eleitor” admite como correta a saída fonte mais votada.

Figura 1.1: redundância modular tripla Claramente, a organização TMR pode completamente mascarar o fracasso de uma unidade de hardware. Um dos recursos mais interessantes do TMR é que nenhuma ação explícita precisa ser realizada para detecção de erros, recuperação, etc TMR é particularmente adequado para falhas transitórias, uma vez que em um TMR básico, o “eleitor” não remove o componente com defeito depois que um erro ocorre. Também é evidente que este regime não pode lidar com o fracasso de duas unidades. De fato, uma vez que uma unidade falhar é essencial que ambas as unidades devem continuar a funcionar corretamente (para que o eleitor possa obter uma maioria). Devido a isso, a confiabilidade do sistema TMR torna-se menor do que um sistema simples (ou seja, um sem redundância) quando ocorre uma falha (veremos esse ponto mais adiante neste capítulo). No entanto, do ponto de vista prático, dupla falha também pode ser tratada com a TMR em muitos casos. Por exemplo, se o”eleitor” votar bit a bit e as duas unidades apresentarem respostas erradas em bits de diferentes posições, a falha dupla pode ser mascarada. Do mesmo modo, se duas unidades estão com defeito, de modo que uma linha de saída de uma unidade fica presa em ‘0’, e na mesma saída da outra unidade, ela fica presa em ‘1’, então a falha dupla também pode ser manipulada. Em outras palavras, há situações em que os erros vão "compensar" ao natural, ou em separado. Nessas situações, a falta de unidades também pode ser tratada por TMR. O esquema TMR depende criticamente o elemento de voto. No entanto, o elemento de voto é normalmente um circuito simples e o circuito mais confiável dessa complexidade que pode ser construído. Outro aspecto na implementação da TMR é que ele requer sincronização perfeita entre as diferentes unidades. Isto tem sido frequentemente feito usando um único clock. Isso requer que o relógio seja muito confiável. Uma generalização da abordagem TMR é a abordagem de RMN, no qual a unidade é replicada N vezes. 1.3.4 Redundância Dinâmica. Um sistema com redundância dinâmica consiste em diversas unidades, mas com apenas uma operando por vez [Lal85]. As outras unidades são essencialmente “reservas.” Se uma falha é detectada na unidade em operação, então ela é “desligada” e uma unidade de reposição é “ligada” por um circuito de comutação. Um dos principais problemas desta abordagem é como detectar se uma unidade falhou. As abordagens comuns para a detecção de falha em uma unidade são:
1.

Testes periódicos

2. 3.

Circuitos de alto-verificação Watchdog timers

Esquemas de redundância dinâmica podem ser classificados como sistema cold-standby ou sistema hot-standby, dependendo da forma como suas unidades reservas são mantidas. Em um sistema cold-standby, uma unidade está ligada e operacional, enquanto as unidades reservas não permanecem ligadas (ou seja, elas estão “frias”). Uma unidade defeituosa é trocada desligando-a e ligando uma unidade reserva. Em um sistema hot-standby, todas as unidades operam simultaneamente e seus resultados são comparados. Se os resultados são os mesmos, um é escolhido arbitrariamente. Caso contrário, a unidade defeituosa é detectada e o sistema é reconfigurado, de modo que o resultado do sistema saia de uma das unidades que estão operando normalmente. (Uma diferença fundamental desta abordagem da TMR é a maneira pela qual a unidade defeituosa é detectada e removida.). A forma mais comum para um sistema hot-standby é operar duas unidades em paralelo. Isso é chamado de um sistema duplex [Lal85]. Um circuito comparador compara continuamente os resultados dos dois. Se uma incompatibilidade ocorre, programas de diagnóstico são executados para localizar a falha. Na localização da falha, a reconfiguração é realizada através de circuitos de comutação. 1.3.5 Codificação Codificação é uma das técnicas mais importantes para a tolerância a falhas em hardware. Ela também é utilizada extensivamente para melhorar a confiabilidade da comunicação. Codificação tem sido usada em muitos sistemas. A ideia básica por trás da codificação é a de adicionar bits de verificação nos bits de informação, tais que os erros em alguns bits possam ser detectados e, se possível, corrigidos. O processo de adição de bits de verificação aos bits de informação é chamado de codificação. Ou seja, a informação é codificada por adição de bits de verificação. O processo inverso de extrair informações a partir dos dados codificados é chamado de decodificação. Por isso, a codificação essencialmente fornece verificações estruturais, em que o erro é detectado, detectando inconsistência na integridade estrutural dos dados. Codificação é também uma área onde uma grande quantidade de trabalho tem sido feito [RF89]. Aqui nós damos uma breve introdução a codificação e alguns códigos comuns de usuário em tolerância a falhas de hardware. Detectabilidade/Correctabilidade de um Código Um código define um conjunto de palavras que são possíveis para aquele código. Ou seja, para um esquema de codificação, existe um conjunto válido de palavras que satisfazem aquele esquema. A distância Hamming de um código é o número mínimo de posições de bits em que quaisquer duas palavras do código diferem. Se d é a distância Hamming, D é o número de erros de bits que o código pode detectar, e C é o número de erros de bits que ele pode corrigir, a seguinte relação é sempre verdadeira. d = C + D + 1, com D > C. No código binário regular, a distância Hamming é 1 (o código de dois números consecutivos geralmente difere em um bit), e assim a sua detectabilidade e correctabilidade são ambos 0. O método comum de adicionar um bit de paridade para uma palavra aumenta a distância Hamming para 2 (se a palavra binária difere de uma posição, então o bit de paridade também será diferente, criando uma distância de 2). Com d=2, podemos apenas detector erros de um único bit (ou seja, D=1). Valores maiores de D ou C não satisfazem a relação acima. Esta relação especifica a limitação fundamental de um determinado código.

Códigos de Hamming A adição de um bit de paridade, apesar de ser um método comum, só pode detector erros em 1 bit. Um método mais geral é usado códigos de Hamming em que vários bits de paridade são adicionados de modo que cada bit de paridade é a paridade de um subconjunto de bits de informação. O código pode também detectar e corrigir erros. É amplamente utilizado em memórias de semicondutores. Nos códigos de Hamming, os bits de paridade ocupam as posições de bits 1, 2, 4, ... (potência de 2) na codificação. As restantes são as posições dos dados. Se nos referirmos ao número de bits de paridade por k, e o número de bits de dados por m, para m=4 e k=3, o comprimento da palavra codificada é de 4+3=7 bits. Destes 7 bits, os bits em posições 1, 2 e 4 são os bits de paridade e bits em posições 3, 5, 6, 7 são os bits de dados, como mostrado abaixo. Os bits de paridade são rotulados c1, c2 e c3, e os bits de dados são marcados como d1, d2, d3 e d4.

O valor dos bits de paridade é definido pelas seguintes relações:

Um bit de paridade (ou um bit de verificação) é o OU-Exclusivo de um subconjunto de bits de dados. É interessante notar como essas relações são obtidas. Para c1, que é o bit de paridade na posição 1, todos os bits de dados, cuja posição na palavra codificada, quando representada em binário, tem o bit menos significativo (LSB) como 1, estão incluídos no c1. Da mesma forma, todos os bits de dados cuja representação binária contém um 1 na segunda posição será incluída no c2. Assim, c1 inclui bits de dados 1, 3 e 4, já que elas ocorrem em posições 3, 5 e 7 na palavra codificada, e a codificação binária desses números contém um 1 no LSB (estes são os números ímpares). O bit de dados d3 não está incluído, uma vez que ocorre na posição 6, cujo binário de codificação tem um 0 no LSB. Da mesma forma, para c2, bit de dados d1, d3 e d4 são tomados, uma vez que eles ocorrem em posições 3, 6 e 7, e a codificação binária desses tem um 1 no próximo LSB. Esse código de Hamming é capaz de detector e corrigir erros em apenas um bit. A distância de Hamming do código é de 3. Ao adicionar mais um bit de paridade, que contém a paridade da palavra do código de Hamming, pode-se obter um código com uma distância de Hamming de 4. Esses códigos podem detectar erros duplos e corrigir erros únicos. Muitos chips comerciais de memórias semicondutoras empregam esses códigos. A detecção de bits errados pode ser feita da seguinte forma. A partir da palavra de código recebida, obter o valor de bits de seleção, usando a relação dada acima. Se os bits de verificação obtidos coincidem com os bits de verificação que existem na palavra codificada, não há erro. Caso contrário, existe um erro na palavra codificada; ou os bits de dados ou os bits de verificação estão corrompidos. Para corrigir um erro de um único bit, é necessário determinar a localização do bit errado. Isso é feito da seguinte forma. Os bits de seleção obtidos a partir da relação acima são realizadas operações de OU-Exclusivo com os bits de seleção obtidos a partir do código. A partir daí, temos a localização dos bits errados. Para o exemplo acima, teremos três bits: e1, e2 e e3. Se não houver nenhum erro, então os bits serão 0. Se houver um erro, então os bits de localização de erro irão localizar o bit no erro. A correção é feita simplesmente complementando o bit. No exemplo acima, se o bit 3 está errado (ou seja, os dados bit d1), então teremos e1=1, e2=1 e e3=0. A partir daí, nós obtemos o endereço do bit incorreto como 011 (ou seja, o terceiro bit). Este sistema de correção de erros funciona, desde que há um único erro, e pode até mesmo corrigir erros em bits de seleção. O uso de código de Hamming se torna mais eficiente, em termos de número de bits necessários em relação ao número de bits de dados, à medida que aumenta o tamanho da palavra. Por exemplo, se o comprimento da palavra de dados é de 8 bits, o número de bits de seleção será de

4, e assim a sobrecarga será de 50%. Por outro lado, se o comprimento da palavra é de 84 bits, o número de bits de verificação será de 7, dando uma sobrecarga de 9%. Redundância Cíclica de Códigos (CRC) Estes códigos são aplicados a um bloco de dados, ao invés de palavras independentes. Os CRCs são comumente usados na detecção de erros na comunicação de dados. Eles também são chamados de códigos polinomiais. Nesse código, uma sequencia de bits é representado como um polinômio; Se o kº bit é 1, então o polinômio contém xk. Por exemplo, o polinômio para a sequencia de bits 1100101101 é x9 + x8 + x5 + x3 + x2 + 1. Há um gerador de polinômio G(x) de grau k. Por exemplo o polinômio gerador poderia ser x4 + x3 + 1, correspondente à sequencia de bits 11001. A codificação é feita da seguinte forma. Para a sequencia de bits de dados, adicione (k+1) bits no final. Essa sequencia de dados estendida é dividida (modulo 2) pelo polinômio gerador. Seja qual for o resto é adicionado ao final da sequencia de dados estendida para formar os dados codificados (esta adição realmente substitui os bits (k+1) adicionados aos bits de dados). A decodificação é fácil se não há erros; Os (k+1) bits extras são apenas descartados para obter os bits de dados originais. Para verificar se há um erro, os bits de dados são novamente divididos pelo polinômio gerador, e o restante final é verificado com os últimos (k+1) bits de dados obtidos. Se houver diferença, um erro ocorreu. CRCs podem detectar todos os erros em um único bit, mas não podem corrigir erros. Eles também podem detectar todos os erros de estouro de comprimento inferior a k. Um erro de estouro é onde bits consecutivos são corrompidos. Muitos outros erros também tem uma elevada probabilidade de serem detectados. Apenas os erros que são divisíveis por G(x) serão detectados. Códigos Berger Nesse código, o número de 0s são contados na palavra de dados, e a contagem é anexada como bits de verificação para formar o código. Se o tamanho da palavra é k bits, este esquema de codificação exige log2(k) bits extras. Códigos de Berger podem detectar todos os erros unidirecionais, incluindo aqueles que corrompem bits de verificação. Em um erro unidirecional, todos os bits no erro mudam de 1 para 0, ou de 0 para 1. Se o erro é da forma 1 para 0, então o número de 0s nos bits de dados vai aumentar, mas a contagem (cujos bits também podem mudar de 1 para 0) irá diminuir. Assim, uma discrepância ocorrerá. Da mesma forma, se os bits mudarem de 0 para 1, o número de 0s irá diminuir, mas a contagem vai aumentar. Códigos Berger são conhecidos por serem ótimos para a detecção de erros unidirecionais entre todos os códigos em que os bits de informação e de verificação podem ser separados. Um código de Berger alternativo pode ser obtido pela contagem de número de 1s na palavra de dados anexando o seu bit como complemento de bits de verificação. 1.3.6 Circuitos auto verificadores Nós discutimos uma variedade de técnicas de codificação acima. Todas as técnicas de codificação trabalham através da produção de um código, digamos, de comprimento n, tal que as palavras válidas (que são consistentes com o método de codificação) são um subconjunto das possíveis palavras de comprimento n. Ou seja, das 2 possíveis palavras, apenas um subconjunto delas são consideradas válidas. A redefinição representa a situação em que algum erro tenha ocorrido. O erro é detectado verificando se a palavra é válida ou não. Esta “redundância” é essencial para que um sistema de codificação funcione. Se todas as palavras possíveis fossem válidas, então um erro poderia converter uma palavra válida em outra palavra válida e, portanto, passar despercebida.

A detecção de erros com um esquema de codificação exige que um circuito verificador seja empregado para detectar se uma palavra é válida ou não. Se um circuito funcional produz uma saída codificada y, que será verificadora para a presença de um erro pelo verificador, então, a situação pode ser representada como mostrado na figura 1.2 [Toh86]. A verificadora da saída verifica se a saída do circuito funcional é válida ou não, conforme o esquema de codificação utilizado. Se y não é uma palavra codificada válida, então a saída do verificador de z será 1, indicando um erro.

Figura 1.2: Verificador de saída O esquema acima para a codificação pressupõe que o verificador de erros está funcionando corretamente, de tal forma que mesmo que o circuito funcional não funcione corretamente, o verificador irá detectar o erro. Se houver uma falha no circuito verificador, então o esquema de codificação pode deixar de fornecer a capacidade de detecção do erro. É possível que o verificador falho que se não tiver erro na entrada do verificador, ele não declare um erro, mas se tiver um erro, ele não o captura. Ou seja, alguns erros podem passar despercebidos, devido à presença de falhas no circuito verificador. É claramente desejável ter a capacidade de verificar a ocorrência de uma falha no verificador, para além da capacidade de verificar o erro na entrada para o verificador. Este é o objetivo dos circuitos de auto verificação. Nós daremos algumas definições para especificar formalmente o que se entende por circuitos de auto verificação [Cs68, Am73, Lal85]. Seja F o conjunto de falhas para que o circuito deve ser de auto verificação. Um circuito é seguro a falhas no que diz respeito a um conjunto de falhas F, se por qualquer falha no F, o circuito nunca produz uma palavra de código incorreto na saída para qualquer código de palavra na entrada. Um circuito é auto examinador a respeito de um conjunto de F falhas, que por qualquer falha em F, o circuito produz ao menos uma palavra-código a partir de uma palavra de código de entrada. Um circuito é totalmente auto verificador no que diz respeito a um conjunto de F falhas se e somente se ele é seguro a falhas e auto examinador a respeito a F. Um circuito é dito ser possível ser separado se para cada palavra-código de entrada (nãopalavra código) produz uma palavra-código de saída (não-palavra código). Um circuito é totalmente auto verificador se e somente se ele for auto examinador, seguro a falhas e for possível ser separado. Com um verificador auto verificado, é possível detector uma falha no circuito funcional ou no verificador. No entanto, em geral, não é possível dizer qual é defeituoso. Como exemplo, considere um verificador auto verificado de paridade [Toh86]. Suponha que (x8, …, x0) representa uma palavra código para o esquema de paridade ímpar. Divida o conjunto de variáveis em dois grupos, por exemplo, os bits de número par e os bits de número ímpar e conecte-os à árvore de portas XOR, conforme mostrado na figura 1.3 [Toh86]. Na operação normal, o número de 1s em um grupo é ímpar, e o outro grupo é par. Portanto, a saída Z = (z2, z1) terá (0,1) ou (1,0), mas nunca (0,0) ou (1,1). Pode ser visto que, se alguma porta está defeituosa, então uma das duas saídas será diferente, resultando em Z sendo (0,0) ou (1,1).

Não existem técnicas gerais para a concepção de verificadores auto verificados. Métodos específicos foram propostos para os diferentes tipos de códigos. No entanto, existem algumas técnicas para a síntese geral de auto verificação dos circuitos combinacionais e sequenciais [JW91]. 1.3.7 Tolerância a falhas em Multiprocessadores Um sistema multiprocessado é aquele que consiste de múltiplos processadores conectados por alguma rede de interconexão. Os processadores normalmente trabalham em conjunto para resolver um problema simples, e geralmente são fortemente acoplados, em contraste com sistemas distribuídos onde os diferentes processadores são amplamente independentes. Os processadores se comunicam entre si através de mensagens que passam através da rede de interconexão. A motivação básica para multiprocessadores é a necessidade de computação de alto desempenho. Recentes avanços na tecnologia VLSI permitem a construção de sistemas multiprocessados mais baratos. Uma vez que existem mais componentes em um sistema multiprocessado que pode falhar, há um grande interesse na área de tolerância a falhas de hardware para sistemas multiprocessados. Um sistema multiprocessado é considerado tolerante a falhas se, apesar da falha de um ou mais processadores, um número de processadores ativos permanecem conectados de acordo com a topologia original para que foram concebidos. Existem várias abordagens para apoiar a tolerância a falhas em multiprocessadores. Uma delas é a abordagem TMR, que temos discutido acima. Aqui vamos discutir brevemente algumas dessas abordagens. Uma nova abordagem para a tolerância a falhas em sistemas multiprocessados é a tolerância a falhas baseada em algoritmo, em que a tolerância a falhas é conseguida mediante a adaptação do esquema de tolerância a falhas para o algoritmo que será realizado no multiprocessador [HA84. B+90]. Nesta abordagem, os dados são codificados em um alto nível (ao contrário de byte/nível da palavra, como se faz com frequência). O algoritmo a ser realizado em seguida, é redesenhado para trabalhar com dados codificados para produzir uma saída codificada. Finalmente, as etapas de cálculo deste algoritmo modificado são distribuídas entre os processadores no multiprocessador de tal forma que a falha de um módulo afeta apenas uma parte dos dados, que podem ser recriados pela redundância presente nos dados codificados. Como exemplo dessa abordagem, considere operações de matriz [HA84]. Para uma matriz A, a coluna de soma de verificação da matriz Ac é aquela que é obtida a partir da matriz original, acrescentando uma linha de soma de verificação. Na linha de verificação, cada elemento é a soma dos elementos dessa coluna em particular. Da mesma forma, uma linha da soma da matriz Ar pode ser definida. A matriz da soma total Cf é aquela que tem uma linha extra e uma coluna extra, que são a linha e a coluna da soma de verificação. Com essa codificação, se a operação de matriz C = A * B deve ser realizada, então pode ser facilmente visto que Cf = Ac * Br. Ou seja, o algoritmo de multiplicação de matrizes tem de ser redesenhado para trabalhar em Ac e Br. Da mesma forma, temos Cf = Af + Bf. Relações semelhantes podem ser definidas para as operações de transposição e outras também. Agora, a matriz de dados foi codificada e o algoritmo de matriz foi redesenhado. A detecção de erros é feita de uma forma simples. Calcule a soma de verificação para os elementos de informação na matriz final, e depois compará-lo com a linha de soma de verificação e coluna produzida pelo algoritmo redesenhado. A interseção de linhas inconsistentes e elementos da coluna irão localizar o erro. O erro pode ser corrigido.

Figura 1.3: Verificação automática de paridade Outra abordagem para tolerância a falhas em multiprocessadores é empregar redundância dinamicamente [Agr88]. Um processador é assumido para produzir o resultado de cálculo e uma assinatura. A assinatura é um bom representante de resultado do processador e pode ser obtido através de técnicas de compressão de dados. A abordagem é a seguinte. Suponha que os processadores são numerados. Inicialmente, uma tarefa de entrada está prevista para um par de processadores, por exemplo P1 e P2. Estes produzem resultados R1 e R2 e assinaturas S1 e S2. As duas assinaturas são comparadas e, se corresponderem, então um dos resultados é retornado. Se as assinaturas não coincidirem, então o trabalho é agendado em outro processador, digamos P3. A assinatura S3 com as assinaturas anteriores S1 e S2. Se um par de assinaturas correspondentes for encontrado, então o resultado R3 é retornado. Caso contrário, o trabalho é agendado em outro processador, até que um par correspondente de assinaturas é encontrado. Estas são as abordagens para tolerância a falhas projetadas especificamente para algumas arquiteturas comuns. Aqui, discutiremos brevemente os métodos para arquiteturas de árvore e hipercubo. Um multiprocessador hipercubo consiste de 2n processadores que estão conectados por links diretos de acordo com o n-cubo binário de interconexão padrão [Pra86]. Assim, cada processador está diretamente ligado a n outros processadores, e a distância máxima entre os nodos (em termos de número de saltos o ligações entre si) é apenas log(n). Em uma técnica, dois processadores de reposição são utilizados para todos os oito processadores. Cada nodo é assumido que possui n + 2 portas. Ao adicionar estes processadores extras, a dimensão do hipercubo é aumentada em um. Quando a falha de um processador normal é detectada, ele é substituído por um processador de reposição (por exemplo S), e as ligações entre o processador de reposição para o processador com defeito e o link diagonalmente a ele, são desativados. S envia seu endereço e o endereço do processador falhado para todos os processadores de reposição conectados a ele. Esta informação é então usada para redefinir as conectividades para que a estrutura de hipercubo seja mantida. Para multiprocessadores organizados hierarquicamente, as redes de árvore oferecem uma rede de interconexão natural. Mas, uma desvantagem inerente dessas redes é que uma só falha pode desligar a rede. Diferentes abordagens têm sido propostas para fazer tal rede tolerante a falhas [Pra86]. Uma abordagem é aumentar a árvore com nós e links extras. O objetivo é preservar a estrutura da árvore original, apesar das falhas, reconfigurando as árvores usando nós e links extras. 1.4 Confiabilidade e Disponibilidade

O objetivo básico de tolerância a falhas é aumentar a confiabilidade de um dado sistema. Ao empregar a tolerância a falhas, muitas falhas potenciais são evitadas, aumentando a confiabilidade. Outro objetivo de tolerância a falhas é aumentar a disponibilidade do sistema, ou seja, aumentar o tempo para que o sistema esteja disponível para o utilizador de serviços (em oposição à execução de tarefas de contabilidade interna). Ao menos que uma estratégia de tolerância a falhas pode aumentar a confiabilidade ou a disponibilidade de um sistema, ela não é de interesse. Uma quantidade considerável de trabalho tem sido feita na avaliação de desempenho de sistemas tolerantes a falhas, ou seja, avaliar a confiabilidade, disponibilidade, ou alguma outra medida de desempenho de sistemas que empregam tolerância a falhas. Está além do escopo deste livro fazer uma análise do desempenho dos diversos esquemas de tolerância a falhas. No entanto, é importante entender os dois conceitos-chave – confiabilidade e disponibilidade – que são as forças motrizes por trás de tolerância a falhas. Nesta seção, daremos uma breve descrição destes conceitos com base em [Tri82]. Para mais informações o leitor pode se referir a [Tri82]. Durante o curso do livro, sempre que possível, vamos discutir isso para alguns dos esquemas propostos. 1.4.1 Preliminares A maioria dos trabalhos realizados na avaliação de desempenho de sistemas de computador tem fundamentos na teoria da probabilidade. A teoria das probabilidades é útil em situações onde o resultado de uma experiência não é certa. Tal experiência é chamada de um experimento aleatório. Um exemplo clássico de aleatoriedade é o lance de uma moeda. Um experimento aleatório pode ser “atirar a moeda n vezes”. A totalidade de todos os possíveis resultados de um experimento aleatório é chamada de espaço amostral do experimento. Por exemplo, para o experimento de “jogar a moeda duas vezes”, há quatro resultados possíveis: HH, HT, TH, TT (onde T representa a ocorrência de “coroa” e H representa a ocorrência de “cara”). Um evento é um subconjunto do espaço amostral. Um evento é mais frequentemente especificado pelas condições que definem o subconjunto para este evento. Como exemplo, considere o evento “pelo menos uma coroa ocorre.” Três dos quarto pontos no espaço amostral serão incluídos neste evento. A probabilidade de um evento representa o “risco relativo” que a realização da experiência vai resultar na ocorrência do evento. A probabilidade de um evento e é referido como P(e). Vamos agora dar algumas definições com base em [Tri82]. Definição. Uma variável aleatória X, em um espaço amostral S, é uma função que atribui um número real X(s) para cada ponto amostral s ϵ S. Estamos mais interessados em variáveis aleatórias contínuas, em que cada número real x, o conjunto { x | X(s) ≤ x} é um evento. Variáveis aleatórias contínuas são o ponto de partida na maioria dos modelos de confiabilidade. Por exemplo, na modelagem de confiabilidade, o “tempo de vida de um sistema” ou o “tempo de reparação de um sistema” são frequentemente modelados como variáveis aleatórias. Variáveis aleatórias podem ser discretas também. No entanto, na modelagem de confiabilidade, na maior parte, variáveis aleatórias contínuas são empregadas. Definição. A função de distribuição FX de uma variável aleatória X é definida como sendo a função: FX(x) = P(X ≤ x), -∞ < x < ∞. A função de distribuição é muitas vezes chamada de função de distribuição cumulativa, ou CDF. O subscrito X de F mostra que esta é a função de distribuição da variável aleatória X. Quando fora do contexto, o índice será omitido. Ao contrário de uma função de distribuição de uma variável aleatória discreta, a função de distribuição de uma variável aleatória continua (que é o que estamos tratando) é uma função contínua para todos os valores de x. Há algumas coisas a serem observadas sobre a função de distribuição: (1) Como F(x) é uma probabilidade, seu valor está entre 0 e 1, (2) F(x) é uma função monotonicamente não-decrescente; ou seja, se x1 ≤ x2, então F(x1) ≤ F(x2), e (3) F(x) tende para 0 quando x → ∞.

Definição. Para uma variável aleatória contínua X, f(x) = d(F(x))/dx é chamada de função de densidade de probabilidade (probability denity function) (pdf) de X. A seguir a definição para um pdf: O CDF pode ser obtido a partir do pdf, integrando-a:

A função de distribuição, ou a função de densidade, caracteriza o comportamento de uma variável aleatória, e as probabilidades de vários eventos podem ser determinados a partir deste. Frequentemente, nós não precisamos de caracterização detalhada e que são interessadas apenas na média, ou a expectativa de uma variável aleatória X, que é denotada por E[X]. Isso reflete o valor esperado da variável aleatória X. Definição. A expectativa, E[X], de uma variável aleatória X é definido por:

Desde que a integral seja absolutamente convergente, ou seja: As expectativas também são comumente usados em evolução de confiabilidade. Um método para caracterizar a confiabilidade de um sistema é para especificar o tempo médio de falha, que é a expectativa da vida útil do sistema (uma variável aleatória). 1.4.2. A distribuição exponencial Agora estamos prontos para discutir uma distribuição específica, chamada de distribuição exponencial, que é muito frequente na avaliação da confiabilidade de sistemas de computador. Na avaliação de sistemas de computador, muitas vezes, coisas como o tempo de chegada entre dois processos sucessivos em um sistema de computador, o tempo gasto em um processador ou dispositivo de E/S, tempo até a falha de um componente ou sistema, o tempo necessário para reparar uma falha de componente ou sistema etc, são modelados como variáveis aleatórias em distribuição exponencial. A função de distribuição exponencial é dada por:

A função de distribuição é mostrada na fig. 1.4 [tri82]. Se uma variável aleatória X é distribuída exponencialmente, em seguida, a pdf é dada por:

Figura 1.4: O CDF de uma variável aleatória exponencialmente distribuída com parâmetro λ = 1.

Figura 1.5: O pdf de uma variável aleatória exponencialmente distribuída.

Enquanto especificando o pdf normalmente apenas a parte diferente de zero é indicado. O pdf de uma variável aleatória exponencialmente distribuída é mostrado é figura 1.5 [tri82]. A distribuição exponencial é muito popular na análise analítica porque ela possui a propriedade memoryless ou Markov. O que significa a propriedade memoryless é que se determinou a distribuição de uma variável exponencialmente distribuídos depois de algum tempo t decorrido, a distribuição é mais exponencial. Por exemplo, suponha que X representa a vida de um sistema, que é exponencialmente distribuído. Suponha que o sistema ainda está vivo depois de um tempo t, então o tempo de vida residual do sistema também terá a função de distribuição mesmo. Seja Y = X - t representa o tempo de vida residual. Deixe a probabilidade condicional de Y <= y, dado que X> t, ser indicado por Gt (y). Gt (y) é uma probabilidade condicional:

Esta expressão, finalmente, torna-se [Tri82]:

Assim, Gt (y) é idêntica à distribuição original dos X, e é independente de t. Em outras palavras, se X representa o tempo de vida de um sistema, então a distribuição do tempo de vida restante do sistema a qualquer tempo t tem a mesma distribuição que o tempo de vida original. Ou seja, o sistema "esquece" o tempo que ele foi ativo, ou não mantém a história. Esta é a propriedade memoryless da distribuição exponencial. É frequentemente simplifica a análise analítica.

1.4.3 Confiabilidade Deixa a variável aleatória x representa a vida de um sistema. Ou seja, x representa o tempo até a falha do sistema. O tempo até a falha é modelado como uma variável aleatória, pois não pode ser previsto com certeza. Suponha que a variável x tem distribuição F. a confiabilidade do sistema é uma função R (t), que representa a probabilidade de que o sistema sobrevive até o tempo t (ie, ele não falhou até t).

R(t) = probabilidade de que o sistema esteja funcionando no tempo t = P(X > t) = 1 – F(t)
Como podemos ver, a definição implica que R (t = 0) = 1, significando que o sistema está trabalhando inicialmente. Tem também R (t = oo) = 0, implicando que nenhum componente tem uma vida útil infinita. Se a vida útil de um sistema é exponencialmente distribuída, então a confiabilidade desse sistema é: O parâmetro é chamado de taxa de falha do sistema. Essa noção de confiabilidade representa a confiabilidade como uma função do tempo. A partir disso podemos calcular o tempo médio para falha (MTTF), ou expectativa de vida, do sistema. MTTF é uma medida comumente utilizada para indicar a confiabilidade de um sistema, e é dada por:

A partir da expressão temos:

Se a vida útil do sistema é exponencialmente distribuído com parâmetro λ, então R(t) = e-λt, e temos a expectativa de vida, ou MTTF, do sistema como:

Em análise mais confiável, a vida de um sistema, ou componente, é assumido como sendo exponencialmente distribuído, e ao longo deste livro, salvo indicação em contrário, vamos usar essa suposição. A expressão acima dá o MTTF de um sistema único, trabalhando de forma isolada. Muitas vezes, um sistema pode ser considerado como uma combinação de muitos componentes independentes, cada uma tendo um tem uma vida distribuída exponencialmente. Vamos considerar um sistema em série, onde diferentes componentes são ligados em série para formar o sistema completo. Suponha que a vida útil do ith componente na série é exponencialmente distribuído com parâmetro λi. desde um sistema em série falhará se qualquer um dos componentes da série falhar, a confiabilidade do sistema global é dada por [Tri82]

Quando a vida de cada componente é exponencialmente distribuída, então esta é avaliada como um sistema com uma vida exponencialmente distribuídos com parâmetro λ definido da forma:

Portanto, o MTTF do sistema em série é:

Este diz que o MTTF de um sistema em série é menor do que MTTF de qualquer dos seus componentes. Isso é de se esperar, uma vez que a falha de qualquer componente causa a falha do sistema em série, e, portanto, o sistema tem uma série de confiabilidade mais baixos do que seus componentes individuais. Um sistema pode ser um sistema paralelo em que os componentes do sistema são ligados em paralelo. Nesta organização, o sistema falha quando todos os componentes falharem. Se X é a vida do sistema e Xi representa a vida de um componente i, temos:

Xmax = {X1, X2 ...., Xn}
Isto implica que a confiabilidade de um sistema paralelo é maior que a confiabilidade de seus componentes. Com o pressuposto exponencial, o MTTF do sistema passa a ser [Tri82]

Outra organização de um sistema, que é frequentemente usado em tolerância a falhas, é a redundância de espera. Nesse sentido, um componente (muitas vezes chamado de primário) opera, e outros agem como espera. Quando o primário falhar, o componente de espera é ligado e começa a operação. Nesta configuração, se X é o tempo de vida do sistema, e Xi é a vida do componente:

Com distribuição exponencial temos o MTTF do sistema como:

Daí o ganho de confiabilidade depende do número de componentes. Com 2 componentes com vidas exponencialmente distribuídos, a vida do sistema primário de espera é o dobro de um componente individual. Vimos que em hardware, TMR é um método amplamente utilizado para apoiar a tolerância a falhas. Em TMR, três sistemas estão conectados em paralelo, e suas saídas são escolhidas. O Sistema TMR pode mascarar a falha de um componente. Se R (t) é a confiabilidade do componente triplamente replicados, então, considerando as diferentes combinações de falhas em que um sistema TMR continua a funcionar, temos [Tri82]:

RTMR(t) = 3R2(t) – 2R3(t)
Com a distribuição exponencial RTMR (t) = 3e-2λt - 2e-3λt. A função RTMR nem sempre é maior do que a função R(t) = e-λt, que é sobre a confiabilidade do componente individual. Para valores menores de t, RTMR é maior do que R, e para valores maiores de t é o contrário. Podemos calcular t0, o "limite" de tempo para além do qual a confiabilidade da TMR é inferior ao de seus componentes, em t0 temos:

3e-2λt - 2e-3λt = e-λt0.

Resolvendo isso, temos t0 = ln2/λ = 0.7/λ. Portanto, a configuração TMR oferece maior confiabilidade somente se o "tempo de missão" do sistema, utilizando TMR é menor do que t0. Esse comportamento aparentemente anômalo ocorre porque, quando todos os três componentes estão funcionando, então TMR pode lidar com a falha de qualquer um dos componentes. No entanto, quando um componente falha, então TMR requer que os demais componentes devam funcionar corretamente para que o sistema funcione corretamente. O MTTF de um sistema TMR é: Este é reduzido para: Assim, o MTTF de um TMR é menor do que o MTTF de seus componentes. Isso mostra claramente que o trabalho com meios ou MTTF tem suas armadilhas. A função de confiabilidade para TMR dá uma imagem mais clara, ou seja, TMR é mais confiável por períodos curtos, mas é menos confiável se as durações da execução são grandes. 1.4.4 Disponibilidade Em um sistema real, se um componente falha, ele é reparado ou substituído por um novo componente. Quando este componente falha é substituído por outro, e assim por diante. O componente reparado é novo com a sua própria distribuição. Durante um longo período de tempo, um componente pode ser considerado como sendo de um dos dois estados: “Trabalhando” ou “em reparo”. O estado “trabalhando” significa que o componente atual é operacional, e o estado “em reparo” significa que ele falhou e ainda não foi substituído por um novo componente. Em caso de falha, o sistema vai de "trabalhando" para "em reparo", e quando a substituição for realizada, ele vai voltar para o estado de "trabalhando". Em tais situações, a disponibilidade é uma medida que é frequentemente usada para descrever o comportamento do sistema. Definição. A disponibilidade instantânea, A(t), de um componente é definida como a probabilidade de que o componente está funcionando corretamente no tempo t. Em disponibilidade, estamos interessados na probabilidade de uma instância de tempo determinado. Na falta de reparo ou substituição, a disponibilidade é simplesmente igual à confiabilidade. Ou seja, a disponibilidade no momento t é nada, mas R(t). No entanto, com a substituição, a vida de um componente pode ser visto como uma sequência de variáveis aleatórias independentes, cada um representando a vida do componente até a próxima falha (e reparação). Um componente pode ser representado por uma sequência de variáveis aleatórias: Tis e Dis. A variável aleatória Ti representa a duração do período de ith, funcionamento e Di representa o tempo de inatividade para a ith reparação ou substituição. Na análise de disponibilidade, que se interessaram na disponibilidade de estado estacionário, ou disponibilidade, após um período de tempo suficientemente longo. Para isso, limitando a disponibilidade de definir como o limite de A(t) como infinitas abordagens t. A disponibilidade de limitação é o que é comumente referido como a disponibilidade de um sistema. Note que, ao contrário de confiabilidade cujo valor limite quando t tende ao infinito é zero, limitando a disponibilidade é geralmente diferente de zero. Se o tempo médio até a falha de um componente é MTTR (ou seja, cada Ti tem MTTR como a média), então a disponibilidade, α, é dada por [Tri82]:

Esta é uma expressão geral para limitar a disponibilidade não depende da natureza de outras distribuições exponenciais. Muitas vezes, na análise de sistemas que empregam tolerância a falhas, disponibilidade é definida como a fração de tempo em que o sistema está disponível para o trabalho "útil". Se

partirmos do pressuposto que todo o tempo o sistema está operacional ele está disponível para trabalho útil, a expressão acima da disponibilidade segura. No entanto, muitos sistemas tolerantes a falhas requerem que o sistema executem várias atividades extras para suportar a tolerância a falhas. Estas atividades não precisam ser executadas se nenhuma tolerância a falhas foi implementada. Em tais situações, essas atividades extras são sobrecarga no apoio de tolerância a falhas, e não são consideradas como trabalho útil. Analise de disponibilidade se torna mais complicada em tais situações, uma vez que estas despesas usam alguma parte do tempo quando o sistema está operacional para realizar atividades que não são consideradas úteis. 1.5 RESUMO Este capítulo apresentou o tema da computação tolerante a falhas. A tolerância a falhas é útil em sistemas onde a alta confiabilidade é necessária. Como a insegurança é causada pela presença de falhas no sistema, o objetivo de tolerância a falhas é evitar a falha do sistema, apesar da presença de falhas. Isto é complementar a abordagem de prevenção de falhas, no qual o objetivo é minimizar a presença de falhas no sistema. Onde técnicas de prevenção à falhas não conseguem eliminar completamente as falhas no sistema, para aumentar a confiabilidade de um sistema, além do que pode ser conseguido através de técnicas de prevenção de falhas, tolerância a falhas é empregado. As técnicas de tolerância a falhas baseiam-se no uso da redundância para mascarar o efeito das falhas no sistema. Um sistema é definido como sendo composto de muitos componentes, cada componente a ser um sistema em seu próprio direito. No nível mais baixo são componentes atômicos ou sistema que não pode mais ser dividida, ou cuja estrutura interna não é de interesse. As especificações do sistema definem o comportamento correto do sistema. Uma falha de um sistema ocorre quando o sistema não pode fornecer o serviço desejado (especificado). Um erro é a parte do estado do sistema que pode levar à falha do sistema, e uma falha é a causa de um erro. Por definição, um sistema que não pode mascarar o seu próprio fracasso, se ocorrer uma falha em um sistema, o sistema falhou. O objetivo de um sistema tolerante a falhas é para mascarar o fracasso de alguns dos seus componentes nos níveis superiores. Ou seja, um sistema tolerante a falhas é aquele que evita a falha do sistema quando algum de seus componentes falhar. Há muitas fases de detecção de erros, confinamento de danos, recuperação de erros e tratamento de falhas e continuidade do serviço. Na primeira fase, a presença de uma falha é determinada pela detecção de um erro no status do sistema. A detecção de erros é o ponto de partida para apoiar a tolerância a falhas, uma estratégia de tolerância a falhas pode ser, no máximo, tão bom quanto os seus métodos de detecção de erros de replicação são cheques, cheques de cronometragem, verificações estruturais e de codificação, cheques, sensatez e verificações de diagnóstico. Como o erro pode ser detectado algum tempo após a falha ter ocorrido, o próximo passo no apoio à tolerância a falhas é determinar a extensão dos danos para o estado do sistema pela falha. Isso é feito na fase de confinamento de danos. Para avaliação dos danos, a interação entre diferentes componentes terão de ser examinadas ou estruturadas, pois é pela interação que os erros podem se propagar. O objetivo é identificar alguns limites dentro dos quais a propagação do erro é limitada. Esses limites podem ser dinamicamente determinados após o erro ter sido detectado através do exame das interações de componentes, ou a interação destes componentes pode ser restringida de tal modo que a propagação de erro é limitada a alguns limites predefinidos. O próximo passo é a recuperação de erros. Uma vez que a propagação de um erro tenha sido identificada, o erro tem de ser removido do sistema. Isto é feito através da recuperação de erros. As duas principais técnicas são recuperação de erros para trás e recuperação de erros para a frente. Em versões anteriores de recuperação de erros, durante o cálculo normal do estado do sistema é periodicamente verificado. Para a recuperação, o estado verificado do sistema é restaurado. Se a falha ocorreu depois da verificação, esta reversão irá remover o erro. Em recuperação para a frente, por outro lado, nenhum estado anterior do sistema está disponível. O objetivo é fazer com que o

estado do sistema esteja livre de erros, tomando algumas medidas corretivas. Enquanto para trás a recuperação é uma técnica geral, a recuperação para frente exige um bom diagnóstico sobre a natureza do erro. A última fase é o tratamento de falhas e continuidade do serviço. Nas fases anteriores, o foco era sobre a remoção de erro e erro. Mas a causa raiz de qualquer erro é a falha. Embora em alguns casos, particularmente com falhas transientes, apenas a recuperação de erro pode ser suficiente, em outros, após a recuperação de erro, devemos remover a falha que causou o erro, a fim de evitar falhas futuras. Isso é feito nesta fase. A primeira falha é localizada para identificar o componente defeituoso. Então, o sistema é "consertado", reconfigurando o sistema usando a redundância built-in de tal forma que tanto o componente que falhou não é utilizado ou é utilizado de uma maneira diferente. Uma vez que o livro trata de atividades de software de tolerância a falhas, uma breve descrição de tolerância a falhas em hardware também foi disponibilizada no capítulo. A visão geral descreve o processo de desenvolvimento de hardware, alguns modelos de falhas e algumas técnicas comuns para fornecer tolerância a falhas em hardware. Apesar de falhas de hardware serem frequentemente causadas por motivos físicos, como o doping inadequado, a migração de elétrons, as quebras de linha ou shorts, essas manifestações são muitas vezes semelhantes. Estes são chamados de modelos de falha. O modelo de falha mais comum é o stuck-at, em que alguma saída de uma porta é considerado preso em 0 ou 1. Modelos de falhas são o ponto de partida de apoio a tolerância a falhas no hardware. Para os modelos de falhas que devem ser tratadas, as técnicas são usadas para evitar a falha do sistema. Os métodos comuns para tolerância a falhas em hardware modular tripla redundância, codificação de redundância dinâmica, e circuitos de auto-verificação. Uma breve introdução sobre técnicas de tolerância a falhas em multiprocessadores também é dada. Como o objetivo de qualquer sistema tolerante a falhas é aumentar a confiabilidade e/ou disponibilidade de um sistema, nós também temos uma definição formal do que se entende por estes termos. Para variáveis aleatórias, que são frequentemente utilizadas em análises de confiabilidade e disponibilidade, é restringir a atenção a distribuição exponencial, que normalmente é assumida por qualquer análise. Distribuição exponencial tem a propriedade de "memoryless", em que depois de algum tempo a distribuição do "remanescente" é o mesmo que a distribuição original. Para definir a confiabilidade, a vida útil de um sistema é considerado como uma variável aleatória. A vida é o tempo até a falha. Confiabilidade de um sistema em um tempo é definido como a probabilidade de que o sistema esteja operacional em uma instância de tempo. A partir da função de confiabilidade, a expectativa de vida do sistema pode ser obtida tomando a expectativa. Isto também é chamado o tempo médio para falha (MTTF) do sistema e é uma medida comum de confiabilidade. Para a distribuição exponencial, também obtivemos expressões para a confiabilidade de um sistema em série na qual os componentes são ligados em série e o sistema falhará se qualquer um dos componentes falhar, e num sistema paralelo, no qual os componentes são ligados em paralelo, e o sistema falha somente quando todos falham. Quando um componente falha, ele geralmente é reparado ou substituído por um novo componente. Assim, durante um longo período de tempo, um sistema pode ser considerada em dois estados: "trabalhando" ou "em reparo". Disponibilidade instantânea de um sistema em um tempo é definida como a probabilidade de que o componente está funcionando corretamente na época. Disponibilidade é diferente de confiabilidade apenas por causa do reparo do sistema. Se não houve reparo, disponibilidade instantânea será a mesma confiabilidade do sistema. Disponibilidade de estado estacionário (frequentemente chamado de disponibilidade) é o limite da disponibilidade instantânea como o tempo tende ao infinito. Ela representa a fração de tempo em que o sistema está operacional. Se MTTF é o tempo médio até a falha do sistema, e MTTR é o tempo médio de reparo do sistema, então a disponibilidade do sistema é MTTF / (MTTF + MTTR), e é independente da distribuição do tempo de vida e tempos de reparação.