c++ - sobre - uma pessoa que tomou radiação pode passar essa radiação para outra pessoa




Compilando um aplicativo para uso em ambientes altamente radioativos (16)

Estamos compilando um aplicativo C / C ++ incorporado que é implantado em um dispositivo protegido em um ambiente bombardeado por radiação ionizante . Estamos usando o GCC e a compilação cruzada para o ARM. Quando implantado, nosso aplicativo gera alguns dados incorretos e trava com mais frequência do que gostaríamos. O hardware foi projetado para esse ambiente e nosso aplicativo é executado nesta plataforma há vários anos.

Existem alterações que podemos fazer em nosso código ou melhorias em tempo de compilação que podem ser feitas para identificar / corrigir erros de software e corrupção de memória causados ​​por distúrbios de um único evento ? Algum outro desenvolvedor teve sucesso em reduzir os efeitos nocivos de erros de software em um aplicativo de longa duração?


Alguém mencionou o uso de chips mais lentos para impedir que os íons lançassem bits tão facilmente. De maneira semelhante, talvez use um processador / ram especializado que realmente use vários bits para armazenar um único bit. Assim, fornecendo uma tolerância a falhas de hardware, porque seria muito improvável que todos os bits fossem invertidos. Então 1 = 1111, mas precisaria ser atingido 4 vezes para realmente virar. (4 pode ser um número ruim, pois se 2 bits forem invertidos, isso já é ambíguo). Portanto, se você optar por 8, obtém 8 vezes menos memória RAM e um pouco mais de tempo de acesso, mas uma representação de dados muito mais confiável. Provavelmente, você poderia fazer isso no nível do software com um compilador especializado (alocar x quantidade mais espaço para tudo) ou na implementação da linguagem (escrever wrappers para estruturas de dados que alocam as coisas dessa maneira).Ou hardware especializado que possui a mesma estrutura lógica, mas faz isso no firmware.


Aqui estão muitas respostas, mas tentarei resumir minhas idéias sobre isso.

Algo trava ou não funciona corretamente pode ser resultado de seus próprios erros - então deve ser facilmente corrigido quando você localizar o problema. Mas há também a possibilidade de falhas de hardware - e isso é difícil, se não impossível, de corrigir em geral.

Eu recomendaria primeiro tentar capturar a situação problemática registrando (pilha, registradores, chamadas de função) - registrando-as em algum lugar no arquivo ou transmitindo-as de alguma forma diretamente ("oh não - estou travando").

A recuperação de tal situação de erro é reinicializada (se o software ainda estiver ativo e funcionando) ou redefinida por hardware (por exemplo, hw watchdogs). Mais fácil de começar do primeiro.

Se o problema estiver relacionado ao hardware - o registro deve ajudá-lo a identificar em qual problema de chamada de função ocorre e que pode fornecer informações internas sobre o que não está funcionando e onde.

Além disso, se o código for relativamente complexo - faz sentido "dividi-lo e conquistá-lo" - o que significa que você remove / desativa algumas chamadas de função nas quais suspeita de problemas - normalmente desabilitando metade do código e habilitando outra metade - você pode obter "funciona" / tipo de decisão "não funciona", após o qual você pode se concentrar em outra metade do código. (Onde está o problema)

Se o problema ocorrer após algum tempo - pode haver suspeita de estouro de pilha - é melhor monitorar os registros de ponto de pilha - se eles crescerem constantemente.

E se você conseguir minimizar completamente o seu código até o tipo de aplicativo "olá mundo" - e ainda falhar aleatoriamente - são esperados problemas de hardware - e é preciso haver "atualização de hardware" - o que significa inventar tais cpu / ram / ... - combinação de hardware que toleraria melhor a radiação.

O mais importante é provavelmente como você recupera seus logs se a máquina totalmente parada / zerada / não funcionar - provavelmente a primeira coisa que o boottap deve fazer - é voltar para casa se houver uma situação problemática.

Se for possível no seu ambiente também transmitir um sinal e receber resposta - você pode tentar criar algum tipo de ambiente de depuração remota on-line, mas deve ter pelo menos a mídia de comunicação funcionando e algum processador / alguma memória RAM no estado de funcionamento. E por depuração remota, quero dizer o tipo de abordagem GDB / gdb stub ou a sua própria implementação do que você precisa recuperar do seu aplicativo (por exemplo, baixar arquivos de log, baixar pilha de chamadas, baixar ram, reiniciar)


Dados os comentários de supercat, as tendências dos compiladores modernos e outras coisas, eu ficaria tentado a voltar aos dias antigos e escrever todo o código em alocações de montagem e memória estática em todos os lugares. Para esse tipo de confiabilidade total, acho que a montagem não incorre mais em uma grande diferença percentual do custo.


Em primeiro lugar, projete seu aplicativo para evitar falhas . Certifique-se de que, como parte da operação de fluxo normal, ele espere redefinir (dependendo da sua aplicação e do tipo de falha, suave ou dura). É difícil ficar perfeito: operações críticas que exigem algum grau de transacionalidade podem precisar ser verificadas e ajustadas em um nível de montagem, para que uma interrupção em um ponto-chave não possa resultar em comandos externos inconsistentes. Falha rapidamente assim que qualquer corrupção irrecuperável da memória ou desvio de fluxo de controle é detectado. Falhas de log, se possível.

Em segundo lugar, sempre que possível, corrija a corrupção e continue . Isso significa soma de verificação e correção de tabelas constantes (e código de programa, se você puder) com frequência; talvez antes de cada operação principal ou em uma interrupção cronometrada e armazenando variáveis ​​em estruturas que se autocorrigam (novamente antes de cada operação principal ou em uma interrupção cronometrada, faça uma votação majoritária de 3 e corrija se houver um único desvio). Correções de log, se possível.

Terceiro, falha no teste . Configure um ambiente de teste repetitivo que inverta os bits na memória aleatoriamente. Isso permitirá que você replique situações de corrupção e ajude a projetar seu aplicativo em torno delas.


Isenção de responsabilidade: não sou profissional de radioatividade nem trabalhei para esse tipo de aplicação. Mas trabalhei em erros leves e redundância para arquivamento de longo prazo de dados críticos, que estão um pouco vinculados (mesmo problema, objetivos diferentes).

O principal problema com a radioatividade, na minha opinião, é que a radioatividade pode alternar bits, portanto a radioatividade pode / irá alterar qualquer memória digital . Esses erros são geralmente chamados de erros leves , podridão de bits etc.

A questão é: como calcular de forma confiável quando sua memória não é confiável?

Para reduzir significativamente a taxa de erros de software (às custas da sobrecarga computacional, pois serão principalmente soluções baseadas em software), você pode:

  • confie no bom e velho esquema de redundância e, mais especificamente, nos códigos de correção de erros mais eficientes (mesma finalidade, mas algoritmos mais inteligentes, para que você possa recuperar mais bits com menos redundância). Isso às vezes (incorretamente) também é chamado de soma de verificação. Com esse tipo de solução, você terá que armazenar o estado completo do seu programa a qualquer momento em uma variável / classe mestre (ou uma estrutura?), Calcular um ECC e verificar se o ECC está correto antes de fazer qualquer coisa, e se não, repare os campos. Essa solução, no entanto, não garante que seu software possa funcionar (simplesmente funcionará corretamente quando puder, ou parará de funcionar, caso contrário, porque o ECC pode informar se algo está errado) e, nesse caso, você pode interromper o software para que você não obtenha resultados falsos).

  • ou você pode usar estruturas de dados algorítmicas resilientes , que garante, até certo ponto, que seu programa ainda fornecerá resultados corretos, mesmo na presença de erros de software. Esses algoritmos podem ser vistos como uma mistura de estruturas algorítmicas comuns com esquemas de ECC nativamente misturados, mas isso é muito mais resiliente do que isso, porque o esquema de resiliência está fortemente ligado à estrutura, para que você não precise codificar procedimentos adicionais para verificar o ECC, e geralmente eles são muito mais rápidos. Essas estruturas fornecem uma maneira de garantir que seu programa funcione sob qualquer condição, até o limite teórico de erros leves. Você também pode combinar essas estruturas resilientes com o esquema de redundância / ECC para obter segurança adicional (ou codificar suas estruturas de dados mais importantes como resilientes e o restante, os dados dispensáveis ​​que você pode recompilar das principais estruturas de dados,estruturas de dados normais com um pouco de ECC ou uma verificação de paridade que é muito rápida de calcular).

Se você estiver interessado em estruturas de dados resilientes (que é um campo novo, mas emocionante, recente em engenharia de algoritmos e redundância), aconselho a ler os seguintes documentos:

  • Introdução de estruturas de dados de algoritmos resilientes por Giuseppe F.Italiano, Universidade de Roma "Tor Vergata"

  • Christiano, P., Demaine, ED; Kishore, S. (2011). Estruturas de dados tolerantes a falhas e sem perdas com sobrecarga aditiva. Em Algoritmos e Estruturas de Dados (pp. 243-254). Springer Berlin Heidelberg.

  • Ferraro-Petrillo, U., Grandoni, F., & Italiano, GF (2013). Estruturas de dados resilientes a falhas de memória: um estudo experimental de dicionários. Journal of Experimental Algorithmics (JEA), 18, 1-6.

  • Italiano, GF (2010). Algoritmos e estruturas de dados resilientes. Em Algoritmos e Complexidade (pp. 13-24). Springer Berlin Heidelberg.

Se você estiver interessado em saber mais sobre o campo de estruturas de dados resilientes, pode fazer o checkout dos trabalhos de Giuseppe F. Italiano (e seguir as referências) e o modelo de RAM defeituosa (introduzido em Finocchi et al. 2005; Finocchi e Italiano 2008).

/ EDIT: Ilustrei a prevenção / recuperação de erros de software principalmente para memória RAM e armazenamento de dados, mas não falei sobre erros de computação (CPU) . Outras respostas já apontaram para o uso de transações atômicas, como nos bancos de dados, por isso proponho outro esquema mais simples: redundância e voto majoritário .

A idéia é que você simplesmente faça x vezes a mesma computação para cada computação que precisa e armazene o resultado em x variáveis ​​diferentes (com x> = 3). Você pode comparar suas variáveis ​​x :

  • se todos concordarem, não haverá nenhum erro de computação.
  • se eles discordarem, você poderá usar uma votação majoritária para obter o valor correto e, como isso significa que o cálculo foi parcialmente corrompido, você também poderá acionar uma verificação de estado do sistema / programa para verificar se o restante está correto.
  • se a votação majoritária não puder determinar um vencedor (todos os valores de x são diferentes), é um sinal perfeito para você ativar o procedimento à prova de falhas (reinicializar, emitir um alerta ao usuário etc.).

Esse esquema de redundância é muito rápido comparado ao ECC (praticamente O (1)) e fornece um sinal claro quando você precisa de segurança . Também é garantido (quase) o voto da maioria para nunca produzir saída corrompida e também recuperar-se de pequenos erros de computação , porque a probabilidade de que x computações produzam a mesma saída é infinitesimal (porque há uma quantidade enorme de saídas possíveis, é quase impossível obter aleatoriamente 3 vezes o mesmo, ainda menos chances se x> 3).

Portanto, com o voto da maioria, você está protegido contra saída corrompida e, com redundância x == 3, pode recuperar 1 erro (com x == 4, serão 2 erros recuperáveis ​​etc.) - a equação exata é nb_error_recoverable == (x-2) onde x é o número de repetições de cálculo porque você precisa de pelo menos 2 cálculos concordantes para se recuperar usando o voto da maioria).

A desvantagem é que você precisa calcular x vezes em vez de uma vez, para ter um custo de computação adicional, mas a complexidade linear é tão assintoticamente que você não perde muito pelos benefícios que obtém. Uma maneira rápida de votar por maioria é calcular o modo em uma matriz, mas você também pode usar um filtro mediano.

Além disso, se você deseja ter certeza de que os cálculos são conduzidos corretamente, se você pode criar seu próprio hardware, pode construir seu dispositivo com x CPUs e conectar o sistema para que os cálculos sejam duplicados automaticamente nas x CPUs com uma votação majoritária. mecanicamente no final (usando portas AND / OR, por exemplo). Isso geralmente é implementado em aviões e dispositivos de missão crítica (consulte redundância modular tripla ). Dessa forma, você não teria nenhuma sobrecarga computacional (já que os cálculos adicionais serão feitos em paralelo) e você terá outra camada de proteção contra erros de software (uma vez que a duplicação do cálculo e a votação majoritária serão gerenciados diretamente pelo hardware e não pelo software - que pode ser corrompido mais facilmente, pois um programa é simplesmente bits armazenados na memória ...).


O que você pergunta é um tópico bastante complexo - não é fácil de responder. Outras respostas estão ok, mas cobriram apenas uma pequena parte de todas as coisas que você precisa fazer.

Como visto nos comentários , não é possível corrigir 100% dos problemas de hardware; no entanto, é possível com alta probabilidade reduzi-los ou capturá-los usando várias técnicas.

Se eu fosse você, criaria o software do mais alto nível de integridade de segurança (SIL-4). Obtenha o documento IEC 61513 (para a indústria nuclear) e siga-o.


Se o seu hardware falhar, você poderá usar o armazenamento mecânico para recuperá-lo. Se sua base de códigos for pequena e tiver algum espaço físico, você poderá usar um armazenamento de dados mecânico.

Haverá uma superfície de material que não será afetada pela radiação. Várias engrenagens estarão lá. Um leitor mecânico funcionará em todas as marchas e será flexível para subir e descer. Para baixo significa que é 0 e para cima significa que é 1. De 0 e 1, você pode gerar sua base de código.


Talvez ajude a saber se isso significa que o hardware seja "projetado para este ambiente". Como isso corrige e / ou indica a presença de erros do SEU?

Em um projeto relacionado à exploração espacial, tínhamos um MCU personalizado, que geraria uma exceção / interrupção nos erros do SEU, mas com algum atraso, ou seja, alguns ciclos podem passar / as instruções serão executadas após o insn que causou a exceção do SEU.

Particularmente vulnerável era o cache de dados; portanto, um manipulador invalidaria a linha de cache incorreta e reiniciaria o programa. Somente que, devido à natureza imprecisa da exceção, a sequência de insns encabeçada pela exceção que gera insn pode não ser reinicializável.

Identificamos as seqüências perigosas (não reinicializáveis) (como lw $3, 0x0($2) , seguidas por um insn, que modifica $2 e não depende de dados $3 ) e fiz modificações no GCC, para que essas sequências não ocorram (por exemplo, como último recurso, separando o dois insns por a nop ).

Apenas algo a considerar ...


Use um planejador cíclico . Isso permite adicionar tempos de manutenção regulares para verificar a correção dos dados críticos. O problema mais frequentemente encontrado é a corrupção da pilha. Se o seu software for cíclico, você poderá reinicializar a pilha entre os ciclos. Não reutilize as pilhas para chamadas de interrupção, configure uma pilha separada de cada chamada de interrupção importante.

Semelhante ao conceito Watchdog são os cronômetros de prazo. Inicie um temporizador de hardware antes de chamar uma função. Se a função não retornar antes que o cronômetro do prazo seja interrompido, recarregue a pilha e tente novamente. Se ainda assim falhar após 3/5 tentativas, você precisará recarregar a partir da ROM.

Divida seu software em partes e isole essas partes para usar áreas de memória e tempos de execução separados (especialmente em um ambiente de controle). Exemplo: aquisição de sinal, dados de posse, algoritmo principal e implementação / transmissão de resultados. Isso significa que uma falha em uma parte não causará falhas no restante do programa. Portanto, enquanto reparamos a aquisição do sinal, o restante das tarefas continua com dados obsoletos.

Tudo precisa de CRCs. Se você executar fora da RAM, mesmo o seu .text precisará de um CRC. Verifique os CRCs regularmente se estiver usando um planejador cíclico. Alguns compiladores (não o GCC) podem gerar CRCs para cada seção e alguns processadores têm hardware dedicado para fazer cálculos de CRC, mas acho que isso ficaria fora do escopo da sua pergunta. A verificação de CRCs também solicita que o controlador ECC na memória repare erros de bit único antes que se torne um problema.


Você deseja mais de 3 máquinas escravas com um mestre fora do ambiente de radiação. Todas as E / S passam pelo mestre que contém um mecanismo de votação e / ou nova tentativa. Os escravos devem ter um watchdog de hardware cada um e a chamada para batê-los deve ser cercada por CRCs ou similares para reduzir a probabilidade de choques involuntários. O bumping deve ser controlado pelo mestre, para que a conexão perdida com o mestre seja igual à reinicialização em alguns segundos.

Uma vantagem dessa solução é que você pode usar a mesma API para o mestre e para os escravos, para que a redundância se torne um recurso transparente.

Editar: A partir dos comentários, sinto a necessidade de esclarecer a "ideia da CRC". A possibilidade do escravo esbarrar em seu próprio cão de guarda é quase zero se você cercar o solavanco com CRC ou digerir verificações em dados aleatórios do mestre. Esses dados aleatórios são enviados apenas pelo mestre quando o escravo sob escrutínio está alinhado com os outros. Os dados aleatórios e CRC / resumo são limpos imediatamente após cada aumento. A frequência de resposta do mestre-escravo deve ser mais que o double do tempo limite do watchdog. Os dados enviados pelo mestre são gerados exclusivamente todas as vezes.


Aqui estão alguns pensamentos e idéias:

Use a ROM de forma mais criativa.

Armazene o que puder na ROM. Em vez de calcular coisas, armazene tabelas de consulta na ROM. (Verifique se o compilador está exibindo suas tabelas de consulta na seção somente leitura! Imprima os endereços de memória em tempo de execução para verificar!) Armazene sua tabela de vetor de interrupção na ROM. Obviamente, execute alguns testes para ver o quão confiável sua ROM é comparada à sua RAM.

Use sua melhor RAM para a pilha.

Os SEUs na pilha são provavelmente a fonte mais provável de falhas, porque é onde coisas como variáveis ​​de índice, variáveis ​​de status, endereços de retorno e ponteiros de vários tipos normalmente vivem.

Implemente rotinas de timer-tick e watchdog.

Você pode executar uma rotina de "verificação de integridade" a cada marca de timer, bem como uma rotina de vigilância para lidar com o bloqueio do sistema. Seu código principal também pode incrementar periodicamente um contador para indicar progresso, e a rotina de verificação de integridade pode garantir que isso ocorra.

Implemente error-correcting-codes no software.

Você pode adicionar redundância aos seus dados para poder detectar e / ou corrigir erros. Isso aumentará o tempo de processamento, potencialmente deixando o processador exposto à radiação por mais tempo, aumentando assim a chance de erros; portanto, você deve considerar o trade-off.

Lembre-se dos caches.

Verifique os tamanhos dos caches da sua CPU. Os dados que você acessou ou modificou recentemente provavelmente estarão em um cache. Eu acredito que você pode desativar pelo menos alguns dos caches (com um grande custo de desempenho); você deve tentar isso para ver como os caches são suscetíveis aos SEUs. Se os caches forem mais difíceis que a RAM, você poderá ler e reescrever regularmente dados críticos para garantir que eles permaneçam no cache e traga a RAM de volta à linha.

Use manipuladores de falhas de página de maneira inteligente.

Se você marcar uma página de memória como não presente, a CPU emitirá uma falha de página quando você tentar acessá-la. Você pode criar um manipulador de falhas de página que faça alguma verificação antes de atender à solicitação de leitura. (Os sistemas operacionais de PC usam isso para carregar de forma transparente as páginas que foram trocadas para o disco.)

Use a linguagem assembly para coisas críticas (que podem ser tudo).

Com a linguagem assembly, você sabe o que há nos registros e o que há na RAM; você sabe quais tabelas de RAM especiais a CPU está usando e pode projetar coisas de maneira indireta para manter seu risco baixo.

Use o objdump para realmente examinar a linguagem assembly gerada e descobrir quanto código cada uma de suas rotinas ocupa.

Se você estiver usando um grande sistema operacional como o Linux, estará pedindo problemas; há tanta complexidade e tantas coisas para dar errado.

Lembre-se de que é um jogo de probabilidades.

Um comentarista disse

Toda rotina que você escreve para detectar erros estará sujeita à falha da mesma causa.

Embora isso seja verdade, as chances de erros nos (digamos) 100 bytes de código e dados necessários para que uma rotina de verificação funcione corretamente são muito menores do que as chances de erros em outros lugares. Se sua ROM é bastante confiável e quase todo o código / dados está realmente na ROM, então suas chances são ainda melhores.

Use hardware redundante.

Use 2 ou mais configurações de hardware idênticas com código idêntico. Se os resultados diferirem, uma redefinição deve ser acionada. Com 3 ou mais dispositivos, você pode usar um sistema de "votação" para tentar identificar qual deles foi comprometido.


Escrever código para ambientes radioativos não é realmente diferente de escrever código para qualquer aplicativo de missão crítica.

Além do que já foi mencionado, aqui estão algumas dicas diversas:

  • Use as medidas diárias de segurança "pão e manteiga" que devem estar presentes em qualquer sistema embarcado semi-profissional: vigilância interna, detecção interna de baixa tensão, monitor interno de relógio. Essas coisas nem precisam ser mencionadas no ano de 2016 e são padrão em praticamente todos os microcontroladores modernos.
  • Se você tiver um MCU orientado para a segurança e / ou automotivo, ele terá certos recursos de watchdog, como uma janela de tempo específica, dentro da qual você precisará atualizar o watchdog. É preferível se você tiver um sistema em tempo real de missão crítica.
  • Em geral, use um MCU adequado para esse tipo de sistema, e não alguns fluff genéricos que você recebeu em um pacote de flocos de milho. Hoje em dia, quase todos os fabricantes de MCUs possuem MCUs especializados projetados para aplicações de segurança (TI, Freescale, Renesas, ST, Infineon etc.). Eles possuem muitos recursos de segurança internos, incluindo núcleos de etapa de bloqueio: o que significa que existem 2 núcleos de CPU executando o mesmo código e devem concordar entre si.
  • IMPORTANTE: Você deve garantir a integridade dos registros internos do MCU. Todos os registros de controle e status dos periféricos de hardware graváveis ​​podem estar localizados na memória RAM e, portanto, são vulneráveis.

    Para se proteger contra a corrupção de registros, escolha um microcontrolador com recursos incorporados de "gravação única" dos registros. Além disso, você precisa armazenar valores padrão de todos os registros de hardware no NVM e copiar esses valores para seus registros em intervalos regulares. Você pode garantir a integridade de variáveis ​​importantes da mesma maneira.

    Nota: sempre use programação defensiva. Isso significa que você precisa configurar todos os registros no MCU e não apenas os usados ​​pelo aplicativo. Você não quer que algum periférico aleatório de hardware acorde subitamente.

  • Existem todos os tipos de métodos para verificar erros na RAM ou NVM: somas de verificação, "padrões de caminhada", ECC de software, etc. A melhor solução hoje em dia é não usar nenhum deles, mas usar um MCU com ECC embutido e verificações semelhantes. Como fazer isso no software é complexo, a verificação de erros por si só pode, portanto, introduzir bugs e problemas inesperados.

  • Use redundância. Você pode armazenar memória volátil e não volátil em dois segmentos "espelhados" idênticos, que sempre devem ser equivalentes. Cada segmento pode ter uma soma de verificação CRC anexada.
  • Evite usar memórias externas fora do MCU.
  • Implemente uma rotina de serviço de interrupção padrão / manipulador de exceção padrão para todas as possíveis interrupções / exceções. Mesmo os que você não está usando. A rotina padrão não deve fazer nada, exceto desligar sua própria fonte de interrupção.
  • Compreender e adotar o conceito de programação defensiva. Isso significa que seu programa precisa lidar com todos os casos possíveis, mesmo aqueles que não podem ocorrer na teoria. Examples .

    O firmware de missão crítica de alta qualidade detecta o máximo de erros possível e os ignora de maneira segura.

  • Nunca escreva programas que dependam de comportamento mal especificado. É provável que esse comportamento possa mudar drasticamente com alterações inesperadas do hardware causadas por radiação ou EMI. A melhor maneira de garantir que seu programa esteja livre dessa porcaria é usar um padrão de codificação como o MISRA, junto com uma ferramenta de análise estática. Isso também ajudará na programação defensiva e na eliminação de erros (por que você não deseja detectar erros em qualquer tipo de aplicativo?).
  • IMPORTANTE: Não implemente confiança nos valores padrão das variáveis ​​de duração do armazenamento estático. Ou seja, não confie no conteúdo padrão dos .bss . Ou .bss . Pode haver uma quantidade de tempo entre o ponto de inicialização e o ponto em que a variável é realmente usada; pode haver bastante tempo para a RAM ser corrompida. Em vez disso, escreva o programa para que todas essas variáveis ​​sejam definidas no NVM em tempo de execução, imediatamente antes do momento em que essa variável for usada pela primeira vez.

    Na prática, isso significa que, se uma variável é declarada no escopo do arquivo ou como static , você nunca deve usar = para inicializá-la (ou poderia, mas é inútil, porque você não pode confiar no valor de qualquer maneira). Sempre defina-o em tempo de execução, imediatamente antes do uso. Se for possível atualizar repetidamente essas variáveis ​​do NVM, faça-o.

    Da mesma forma em C ++, não confie em construtores para variáveis ​​de duração de armazenamento estático. Peça ao construtor que chame uma rotina pública de "configuração", que você também pode chamar mais tarde em tempo de execução, diretamente do aplicativo de chamada.

    Se possível, remova o código de inicialização "copiar" que inicializa .data e .bss (e chama os construtores C ++) completamente, para que você obtenha erros de vinculador se escrever código contando com isso. Muitos compiladores têm a opção de pular isso, geralmente chamado de "inicialização mínima / rápida" ou similar.

    Isso significa que todas as bibliotecas externas precisam ser verificadas para que não contenham essa confiança.

  • Implemente e defina um estado seguro para o programa, para onde você reverterá em caso de erros críticos.

  • A implementação de um sistema de relatório de erros / log de erros é sempre útil.

O que poderia ajudá-lo é um watchdog . Os cães de guarda foram usados ​​extensivamente na computação industrial nos anos 80. As falhas de hardware eram muito mais comuns na época - outra resposta também se refere a esse período.

Um cão de guarda é um recurso combinado de hardware / software. O hardware é um contador simples que diminui de um número (digamos 1023) para zero. TTL ou outra lógica pode ser usada.

O software foi projetado para que uma rotina monitore a operação correta de todos os sistemas essenciais. Se essa rotina for concluída corretamente = encontrar o computador funcionando corretamente, o contador voltará para 1023.

O design geral é para que, em circunstâncias normais, o software impeça que o contador de hardware atinja zero. Caso o contador atinja zero, o hardware do contador executa sua tarefa única e redefine o sistema inteiro. De uma perspectiva de contador, zero é igual a 1024 e o contador continua em contagem regressiva novamente.

Esse watchdog garante que o computador conectado seja reiniciado em muitos, muitos casos de falha. Devo admitir que não estou familiarizado com hardware capaz de executar essa função nos computadores de hoje. As interfaces para o hardware externo agora são muito mais complexas do que costumavam ser.

Uma desvantagem inerente do watchdog é que o sistema não está disponível desde o momento em que falha até que o contador do watchdog atinja zero + tempo de reinicialização. Embora esse tempo seja geralmente muito menor do que qualquer intervenção externa ou humana, o equipamento suportado precisará poder continuar sem o controle do computador durante esse período.


Pode ser possível usar C para escrever programas que se comportam de maneira robusta nesses ambientes, mas apenas se a maioria das formas de otimização do compilador estiver desabilitada. Os compiladores de otimização são projetados para substituir muitos padrões de codificação aparentemente redundantes por "mais eficientes" e podem não ter idéia de que o motivo pelo qual o programador está testando x==42 quando o compilador sabe que não há como x possivelmente conter algo mais, porque o programador deseja impedir a execução de determinado código com x mantendo algum outro valor - mesmo nos casos em que a única maneira de manter esse valor seria se o sistema recebesse algum tipo de falha elétrica.

Declarar variáveis ​​como volatile geralmente é útil, mas pode não ser uma panacéia. De particular importância, observe que a codificação segura geralmente requer que operações perigosas tenham intertravamentos de hardware que exigem várias etapas para serem ativados e que o código seja escrito usando o padrão:

... code that checks system state
if (system_state_favors_activation)
{
  prepare_for_activation();
  ... code that checks system state again
  if (system_state_is_valid)
  {
    if (system_state_favors_activation)
      trigger_activation();
  }
  else
    perform_safety_shutdown_and_restart();
}
cancel_preparations();

Se um compilador converter o código de maneira relativamente literal e se todas as verificações do estado do sistema forem repetidas após a prepare_for_activation() , o sistema poderá ser robusto contra praticamente qualquer evento de falha única plausível, mesmo aqueles que corromperiam arbitrariamente o contador do programa e pilha. Se ocorrer uma falha logo após uma chamada para prepare_for_activation() , isso implicaria que a ativação seria apropriada (já que não há outro motivo para prepare_for_activation() ter sido chamado antes da falha). Se a falha fizer com que o código chegue a prepare_for_activation() inadequada, mas não houver eventos subsequentes de falha, não haveria maneira de o código alcançar subsequentemente trigger_activation() sem ter passado pela verificação de validação ou chamado cancel_preparations primeiro [se a pilha falha, a execução pode prosseguir para um ponto imediatamente antes do trigger_activation() após o retorno do contexto chamado prepare_for_activation() , mas a chamada para cancel_preparations() teria ocorrido entre as chamadas para prepare_for_activation() e trigger_activation() , tornando a última chamada inofensiva.

Esse código pode ser seguro no C tradicional, mas não nos compiladores C modernos. Esses compiladores podem ser muito perigosos nesse tipo de ambiente, porque eles se esforçam para incluir apenas códigos que serão relevantes em situações que poderiam ocorrer por meio de algum mecanismo bem definido e cujas conseqüências resultantes também seriam bem definidas. Código cujo objetivo seria detectar e limpar após falhas pode, em alguns casos, acabar piorando as coisas. Se o compilador determinar que a tentativa de recuperação, em alguns casos, invocará um comportamento indefinido, poderá inferir que as condições que exigiriam essa recuperação nesses casos não podem ocorrer, eliminando o código que seria verificado por eles.


Você também pode estar interessado na rica literatura sobre o assunto da tolerância a falhas algorítmicas. Isso inclui a atribuição antiga: escreva uma classificação que classifique corretamente sua entrada quando um número constante de comparações falhará (ou, a versão um pouco mais ruim, quando o número assintótico de comparações com falha for escalonado como log(n) para n comparações).

Um lugar para começar a ler é o artigo de Huang e Abraham, de 1984, " Tolerância a falhas baseada em algoritmos para operações matriciais ". A ideia deles é vagamente semelhante à computação criptografada homomórfica (mas não é a mesma coisa, pois eles estão tentando detectar / corrigir erros no nível da operação).

Um descendente mais recente desse artigo é Bosilca, Delmas, Dongarra e " tolerância a falhas baseada em algoritmo aplicada à computação de alto desempenho ".


Esta resposta pressupõe que você esteja preocupado em ter um sistema que funcione corretamente, além de ter um sistema de custo mínimo ou rápido; a maioria das pessoas brincando com coisas radioativas valoriza a correção / a segurança em detrimento da velocidade / custo

Várias pessoas sugeriram mudanças de hardware que você pode fazer (tudo bem - já há muitas coisas boas aqui nas respostas e não pretendo repetir tudo), e outras sugeriram redundância (ótima em princípio), mas acho que não alguém sugeriu como essa redundância pode funcionar na prática. Como você falha? Como você sabe quando algo "deu errado"? Muitas tecnologias funcionam com base em que tudo funcionará, e o fracasso é, portanto, algo complicado de lidar. No entanto, algumas tecnologias de computação distribuída projetadas para escala esperam falhas (afinal, com escala suficiente, a falha de um nó de muitos é inevitável com qualquer MTBF para um único nó); você pode aproveitar isso para o seu ambiente.

Aqui estão algumas idéias:

  • Verifique se todo o seu hardware é replicado n vezes (em que n é maior que 2 e, de preferência, ímpar) e se cada elemento de hardware pode se comunicar com outro elemento de hardware. A Ethernet é uma maneira óbvia de fazer isso, mas existem muitas outras rotas muito mais simples que dariam uma melhor proteção (por exemplo, CAN). Minimize os componentes comuns (até fontes de alimentação). Isso pode significar amostragem de entradas ADC em vários locais, por exemplo.

  • Verifique se o estado do seu aplicativo está em um único local, por exemplo, em uma máquina de estados finitos. Isso pode ser totalmente baseado em RAM, embora não exclua o armazenamento estável. Assim, ele será armazenado em vários locais.

  • Adote um protocolo de quorum para alterações de estado. Veja RAFT por exemplo. Enquanto você trabalha em C ++, existem bibliotecas conhecidas para isso. Mudanças no FSM somente seriam feitas quando a maioria dos nós concordasse. Use uma biblioteca boa conhecida para a pilha de protocolos e o quorum, em vez de rolar uma, ou todo o seu bom trabalho de redundância será desperdiçado quando o protocolo de quorum for interrompido.

  • Verifique se você soma o seu FSM (por exemplo, CRC / SHA) e armazena o CRC / SHA no próprio FSM (além de transmitir a mensagem e somar as próprias mensagens). Faça com que os nós verifiquem seu FSM regularmente com relação a essa soma de verificação, mensagens recebidas da soma de verificação e verifique se a soma de verificação corresponde à soma de verificação do quorum.

  • Crie o maior número possível de verificações internas em seu sistema, fazendo com que os nós que detectam sua própria falha sejam reinicializados (isso é melhor do que continuar trabalhando metade, desde que você tenha nós suficientes). Tente deixá-los remover-se do quorum de maneira limpa durante a reinicialização, caso não voltem a aparecer. Na reinicialização, faça com que eles somam a imagem do software (e qualquer outra coisa que eles carregam) e façam um teste de RAM completo antes de se reintroduzirem no quorum.

  • Use o hardware para apoiá-lo, mas faça-o com cuidado. Você pode obter RAM de ECC, por exemplo, e ler / gravar regularmente nela para corrigir erros de ECC (e entrar em pânico se o erro for incorreto). No entanto (a partir da memória), a RAM estática é muito mais tolerante à radiação ionizante do que a DRAM em primeiro lugar; portanto, pode ser melhor usar a DRAM estática. Veja o primeiro ponto em 'coisas que eu não faria' também.

Digamos que você tenha 1% de chance de falha de qualquer nó em um dia e vamos fingir que você pode tornar as falhas totalmente independentes. Com 5 nós, você precisará de três falhas no prazo de um dia, o que representa uma chance de 0,001%. Com mais, bem, você entendeu a idéia.

Coisas que eu não faria:

  • Subestime o valor de não ter o problema para começar. A menos que o peso seja uma preocupação, um grande bloco de metal em torno do seu dispositivo será uma solução muito mais barata e mais confiável do que uma equipe de programadores pode criar. O acoplamento óptico de entradas de EMI é um problema, etc. Seja como for, tente ao fornecer seus componentes para obter os que são classificados como melhores contra radiação ionizante.

  • Role seus próprios algoritmos . As pessoas já fizeram isso antes. Use o trabalho deles. Tolerância a falhas e algoritmos distribuídos são difíceis. Use o trabalho de outras pessoas sempre que possível.

  • Use configurações complicadas do compilador na esperança ingênua de detectar mais falhas. Se você tiver sorte, poderá detectar mais falhas. O mais provável é que você use um caminho de código no compilador que tenha sido menos testado, principalmente se você o tiver rolado.

  • Use técnicas que não foram testadas em seu ambiente. A maioria das pessoas que cria software de alta disponibilidade precisa simular modos de falha para verificar se o seu HA funciona corretamente e perder muitos modos de falha como resultado. Você está na posição 'feliz' de ter falhas frequentes sob demanda. Portanto, teste cada técnica e garanta que sua aplicação real melhore o MTBF em uma quantidade que exceda a complexidade para introduzi-lo (com a complexidade vem os bugs). Aplique isso especialmente aos meus algoritmos de quorum, etc.





fault-tolerance