Nous compilons une application C / C ++ embarquée qui est déployée dans un appareil blindé dans un environnement bombardé de rayonnements ionisants . Nous utilisons GCC et la compilation croisée pour ARM. Une fois déployée, notre application génère des données erronées et se bloque plus souvent que nous le souhaiterions. Le matériel est conçu pour cet environnement et notre application fonctionne sur cette plate-forme depuis plusieurs années.
Y a-t-il des changements que nous pouvons apporter à notre code, ou des améliorations au moment de la compilation qui peuvent être apportées pour identifier / corriger les erreurs logicielles et la corruption de mémoire causées par des perturbations d'un seul événement ? D'autres développeurs ont-ils réussi à réduire les effets néfastes des erreurs logicielles sur une application de longue durée?
Réponses:
Travaillant depuis environ 4-5 ans avec le développement de logiciels / micrologiciels et les tests d'environnement de satellites miniaturisés *, je voudrais partager mon expérience ici.
* ( les satellites miniaturisés sont beaucoup plus sujets aux perturbations d'un seul événement que les satellites plus gros en raison de ses tailles relativement petites et limitées pour ses composants électroniques )
Maintenant, cette situation est normalement gérée au niveau matériel et logiciel. Ici, comme vous le demandez, je partagerai ce que nous pouvons faire au niveau logiciel.
... des fins de récupération ... . Permet de mettre à jour / recompiler / reflasher votre logiciel / firmware dans un environnement réel. Il s'agit d'une fonctionnalité presque indispensable pour tout logiciel / micrologiciel dans un environnement hautement ionisé. Sans cela, vous pourriez avoir autant de logiciels / matériels redondants que vous le souhaitez, mais à un moment donné, ils vont tous exploser. Alors, préparez cette fonctionnalité!
... version de travail minimale ... Avoir réactif, plusieurs copies, la version minimale du logiciel / firmware dans votre code. C'est comme le mode sans échec sous Windows. Au lieu d'avoir une seule version entièrement fonctionnelle de votre logiciel, ayez plusieurs copies de la version minimale de votre logiciel / firmware. La copie minimale aura généralement une taille beaucoup moins importante que la copie complète et n'aura presque toujours que les deux ou trois fonctionnalités suivantes:
... copier ... quelque part ... Disposer d'un logiciel / firmware redondant quelque part.
Vous pouvez, avec ou sans matériel redondant, essayer d'avoir un logiciel / firmware redondant dans votre ARM uC. Cela se fait normalement en ayant deux ou plusieurs logiciels / micrologiciels identiques dans des adresses distinctes qui s'envoient des battements de cœur - mais un seul sera actif à la fois. Si un ou plusieurs logiciels / micrologiciels ne répondent pas, passez à l'autre logiciel / micrologiciel. L'avantage d'utiliser cette approche est que nous pouvons avoir un remplacement fonctionnel immédiatement après qu'une erreur se soit produite - sans aucun contact avec le système / la partie externe qui est responsable de détecter et de réparer l'erreur (dans le cas d'un satellite, il s'agit généralement du Centre de contrôle de mission ( MCC)).
À strictement parler, sans matériel redondant, l'inconvénient est que vous ne pouvez pas éliminer tous les points de défaillance. À tout le moins, vous aurez toujours un seul point de défaillance, qui est le commutateur lui-même (ou souvent le début du code). Néanmoins, pour un appareil limité par sa taille dans un environnement hautement ionisé (comme les satellites pico / femto), la réduction du point de défaillance unique à un point sans matériel supplémentaire vaudra la peine d'être envisagée. De plus, le morceau de code pour la commutation serait certainement beaucoup moins que le code pour l'ensemble du programme - réduisant considérablement le risque d'y inclure un événement unique.
Mais si vous ne le faites pas, vous devez avoir au moins une copie dans votre système externe qui peut entrer en contact avec l'appareil et mettre à jour le logiciel / firmware (dans le cas du satellite, c'est à nouveau le centre de contrôle de mission).
... situation erronée détectable .. L'erreur doit être détectable , généralement par le circuit de correction / détection d'erreurs matérielles ou par un petit code pour la correction / détection d'erreurs. Il est préférable de mettre ce code petit, multiple et indépendant du logiciel / firmware principal. Sa tâche principale est uniquement de vérifier / corriger. Si le circuit matériel / firmware est fiable(comme il est plus durci par les radiations que les autres - ou ayant plusieurs circuits / logiques), alors vous pourriez envisager de corriger les erreurs avec. Mais si ce n'est pas le cas, il vaut mieux en faire une détection d'erreur. La correction peut être effectuée par un système / appareil externe. Pour la correction d'erreurs, vous pourriez envisager d'utiliser un algorithme de correction d'erreurs de base comme Hamming / Golay23, car ils peuvent être mis en œuvre plus facilement à la fois dans le circuit / logiciel. Mais cela dépend en fin de compte des capacités de votre équipe. Pour la détection d'erreurs, normalement CRC est utilisé.
... matériel prenant en charge la récupération Maintenant, vient à l'aspect le plus difficile sur cette question. En fin de compte, la récupération nécessite que le matériel qui est responsable de la récupération soit au moins fonctionnel. Si le matériel est définitivement cassé (se produit normalement après que sa dose totale d'ionisation atteigne un certain niveau), il n'y a (malheureusement) aucun moyen pour le logiciel d'aider à la récupération. Ainsi, le matériel est à juste titre la préoccupation la plus importante pour un appareil exposé à un niveau de rayonnement élevé (tel qu'un satellite).
En plus de la suggestion ci-dessus d'anticiper l'erreur du firmware due à un événement unique, je voudrais également vous suggérer d'avoir:
Algorithme de détection et / ou de correction d'erreur dans le protocole de communication inter-sous-système. C'est un autre presque indispensable pour éviter les signaux incomplets / incorrects reçus d'un autre système
Filtrez votre lecture ADC. N'utilisez pas la lecture ADC directement. Filtrez-le par filtre médian, filtre moyen ou tout autre filtre - ne faites jamais confiance à une seule valeur de lecture. Échantillon plus, pas moins - raisonnablement.
la source
La NASA a un document sur les logiciels durcis aux radiations . Il décrit trois tâches principales:
Notez que le taux de balayage de la mémoire doit être suffisamment fréquent pour que des erreurs multi-bits se produisent rarement, car la plupart de la mémoire ECC peut récupérer des erreurs mono-bit, et non des erreurs multi-bits.
Une récupération d'erreur robuste comprend le transfert du flux de contrôle (généralement le redémarrage d'un processus à un point avant l'erreur), la libération des ressources et la restauration des données.
Leur principale recommandation pour la restauration des données est d'éviter d'en avoir besoin, en traitant les données intermédiaires comme temporaires, de sorte que le redémarrage avant l'erreur ramène également les données à un état fiable. Cela ressemble au concept de "transactions" dans les bases de données.
Ils discutent de techniques particulièrement adaptées aux langages orientés objet tels que C ++. Par exemple
Et, il se trouve que la NASA a utilisé C ++ pour des projets majeurs tels que Mars Rover .
Ils ont évité certaines fonctionnalités C ++ qui pouvaient créer des problèmes:
new
etdelete
)new
pour éviter la possibilité de corruption du tas du système).la source
Voici quelques réflexions et idées:
Utilisez la ROM de manière plus créative.
Stockez tout ce que vous pouvez dans la ROM. Au lieu de calculer des choses, stockez les tables de recherche dans la ROM. (Assurez-vous que votre compilateur génère vos tables de recherche dans la section en lecture seule! Imprimez les adresses mémoire au moment de l'exécution pour vérifier!) Stockez votre table de vecteur d'interruption dans la ROM. Bien sûr, exécutez quelques tests pour voir la fiabilité de votre ROM par rapport à votre RAM.
Utilisez votre meilleure RAM pour la pile.
Les SEU de la pile sont probablement la source la plus probable de plantages, car c'est là que vivent généralement des éléments comme les variables d'index, les variables d'état, les adresses de retour et les pointeurs de diverses sortes.
Implémentez des routines d'horloge de minuterie et de surveillance.
Vous pouvez exécuter une routine de "vérification de l'état d'esprit" à chaque tic du minuteur, ainsi qu'une routine de surveillance pour gérer le verrouillage du système. Votre code principal peut également incrémenter périodiquement un compteur pour indiquer la progression, et la routine de vérification de l'intégrité peut garantir que cela s'est produit.
Implémentez des codes de correction d'erreur dans le logiciel.
Vous pouvez ajouter une redondance à vos données pour pouvoir détecter et / ou corriger les erreurs. Cela augmentera le temps de traitement, laissant potentiellement le processeur exposé aux rayonnements pendant plus longtemps, augmentant ainsi le risque d'erreurs, vous devez donc envisager le compromis.
N'oubliez pas les caches.
Vérifiez la taille de vos caches CPU. Les données auxquelles vous avez accédé ou modifié récemment seront probablement dans un cache. Je pense que vous pouvez désactiver au moins certains des caches (à un coût élevé de performance); vous devriez essayer ceci pour voir dans quelle mesure les caches sont sensibles aux SEU. Si les caches sont plus robustes que la RAM, vous pouvez régulièrement lire et réécrire les données critiques pour vous assurer qu'elles restent dans le cache et ramener la RAM en ligne.
Utilisez intelligemment les gestionnaires de défauts de page.
Si vous marquez une page mémoire comme non présente, le CPU émettra une erreur de page lorsque vous essayez d'y accéder. Vous pouvez créer un gestionnaire de défauts de page qui effectue une vérification avant de répondre à la demande de lecture. (Les systèmes d'exploitation PC l'utilisent pour charger de manière transparente les pages qui ont été échangées sur le disque.)
Utilisez le langage d'assemblage pour les choses critiques (qui pourraient être tout).
Avec le langage d'assemblage, vous savez ce qui est dans les registres et ce qui est dans la RAM; tu sais quelles tables RAM spéciales le processeur utilise, et vous pouvez concevoir les choses de manière détournée pour limiter vos risques.
Utilisation
objdump
pour regarder réellement le langage d'assemblage généré et déterminer la quantité de code que chacune de vos routines prend.Si vous utilisez un gros système d'exploitation comme Linux, vous demandez des ennuis; il y a tellement de complexité et tant de choses qui tournent mal.
N'oubliez pas que c'est un jeu de probabilités.
Un commentateur a dit
Bien que cela soit vrai, les chances d'erreurs dans les (disons) 100 octets de code et de données nécessaires pour qu'un programme de vérification fonctionne correctement sont beaucoup plus faibles que les chances d'erreurs ailleurs. Si votre ROM est assez fiable et que presque tout le code / les données sont réellement dans la ROM, alors vos chances sont encore meilleures.
Utilisez du matériel redondant.
Utilisez 2 configurations matérielles identiques ou plus avec un code identique. Si les résultats diffèrent, une réinitialisation doit être déclenchée. Avec 3 appareils ou plus, vous pouvez utiliser un système de "vote" pour essayer d'identifier celui qui a été compromis.
la source
Vous pouvez également être intéressé par la riche littérature sur le sujet de la tolérance algorithmique aux pannes. Cela inclut l'ancienne affectation: Écrivez un tri qui trie correctement son entrée lorsqu'un nombre constant de comparaisons échoue (ou, la version légèrement plus mauvaise, lorsque le nombre asymptotique de comparaisons échouées évolue comme
log(n)
pour lesn
comparaisons).Un article pour commencer à lire est l'article de Huang et Abraham de 1984 " Algorithm-Based Fault Tolerance for Matrix Operations ". Leur idée est vaguement similaire au calcul crypté homomorphe (mais ce n'est pas vraiment la même chose, car ils tentent de détecter / corriger les erreurs au niveau de l'opération).
Un descendant plus récent de cet article est Bosilca, Delmas, Dongarra, et Langou " La tolérance aux pannes basée sur l'algorithme appliquée au calcul haute performance ".
la source
L'écriture de code pour les environnements radioactifs n'est pas vraiment différente de l'écriture de code pour toute application critique.
En plus de ce qui a déjà été mentionné, voici quelques conseils divers:
IMPORTANT: vous devez garantir l'intégrité des registres MCU internes. Tous les registres de contrôle et d'état des périphériques matériels accessibles en écriture peuvent se trouver dans la mémoire RAM et sont donc vulnérables.
Pour vous protéger contre les corruptions de registre, choisissez de préférence un microcontrôleur avec des fonctionnalités intégrées de "réécriture unique" des registres. De plus, vous devez stocker les valeurs par défaut de tous les registres matériels dans NVM et copier ces valeurs dans vos registres à intervalles réguliers. Vous pouvez garantir l'intégrité des variables importantes de la même manière.
Remarque: utilisez toujours une programmation défensive. Cela signifie que vous devez configurer tous les registres dans le MCU et pas seulement ceux utilisés par l'application. Vous ne voulez pas qu'un périphérique matériel aléatoire se réveille soudainement.
Il existe toutes sortes de méthodes pour vérifier les erreurs dans la RAM ou la NVM: sommes de contrôle, "schémas de marche", logiciel ECC, etc. contrôles similaires. Parce que faire cela dans un logiciel est complexe, et la vérification des erreurs en elle-même pourrait donc introduire des bogues et des problèmes inattendus.
Comprendre et adopter le concept de programmation défensive. Cela signifie que votre programme doit gérer tous les cas possibles, même ceux qui ne peuvent pas se produire en théorie. Exemples .
Un micrologiciel critique de haute qualité détecte autant d'erreurs que possible, puis les ignore de manière sûre.
IMPORTANT: n'implémentez aucune dépendance des valeurs par défaut des variables de durée de stockage statique. Autrement dit, ne faites pas confiance au contenu par défaut du
.data
ou.bss
. Il pourrait y avoir un certain temps entre le point d'initialisation et le moment où la variable est réellement utilisée, il aurait pu y avoir beaucoup de temps pour que la RAM soit corrompue. Au lieu de cela, écrivez le programme de sorte que toutes ces variables soient définies à partir de NVM au moment de l'exécution, juste avant le moment où une telle variable est utilisée pour la première fois.En pratique, cela signifie que si une variable est déclarée à la portée du fichier ou en tant que
static
, vous ne devez jamais utiliser=
pour l'initialiser (ou vous pourriez, mais c'est inutile, car vous ne pouvez pas compter sur la valeur de toute façon). Réglez-le toujours au moment de l'exécution, juste avant utilisation. S'il est possible de mettre à jour ces variables à plusieurs reprises à partir de NVM, faites-le.De même en C ++, ne vous fiez pas aux constructeurs pour les variables de durée de stockage statique. Demandez au (x) constructeur (s) d'appeler une routine de "configuration" publique, que vous pouvez également appeler plus tard au moment de l'exécution, directement à partir de l'application appelante.
Si possible, supprimez entièrement le code de démarrage "copie vers le bas" qui initialise
.data
et.bss
(et appelle les constructeurs C ++), afin que vous obteniez des erreurs de l'éditeur de liens si vous écrivez du code en vous appuyant sur de tels codes. De nombreux compilateurs ont la possibilité de sauter ceci, généralement appelé "démarrage minimal / rapide" ou similaire.Cela signifie que toutes les bibliothèques externes doivent être vérifiées afin qu'elles ne contiennent pas une telle dépendance.
Implémentez et définissez un état sûr pour le programme, où vous reviendrez en cas d'erreurs critiques.
la source
TRUE
égal à0xffffffff
utiliser ensuitePOPCNT
avec un seuil.%01010101010101010101010101010101
, XOR puis POPCNT?.text
section, en changeant un code op ou similaire.Il peut être possible d'utiliser C pour écrire des programmes qui se comportent de manière robuste dans de tels environnements, mais uniquement si la plupart des formes d'optimisation du compilateur sont désactivées. Les compilateurs d'optimisation sont conçus pour remplacer de nombreux modèles de codage apparemment redondants par des modèles "plus efficaces", et peuvent ne pas avoir d'indication que la raison pour laquelle le programmeur teste
x==42
lorsque le compilateur sait qu'il n'y a aucun moyen dex
contenir autre chose est parce que le programmeur veut empêcher l'exécution de certains codes avec lax
détention d'une autre valeur - même dans les cas où la seule façon de conserver cette valeur serait que le système reçoive une sorte de problème électrique.Déclarer des variables
volatile
est souvent utile, mais peut ne pas être une panacée. Il est particulièrement important de noter que le codage sûr nécessite souvent que les opérations dangereuses aient des verrouillages matériels qui nécessitent plusieurs étapes pour s'activer et que le code soit écrit à l'aide du modèle:Si un compilateur traduit le code de façon relativement littérale, et si toutes les vérifications de l'état du système sont répétées après le
prepare_for_activation()
, le système peut être robuste contre presque tous les événements de glitch simple plausibles, même ceux qui corrompraient arbitrairement le compteur et la pile du programme. Si un pépin se produit juste après un appel àprepare_for_activation()
, cela impliquerait que l'activation aurait été appropriée (car il n'y a aucune autre raisonprepare_for_activation()
aurait été appelée avant le pépin). Si le problème fait que le code atteint deprepare_for_activation()
façon inappropriée, mais qu'il n'y a aucun événement de problème subséquent, il n'y aurait aucun moyen pour le code d'atteindre ultérieurementtrigger_activation()
sans avoir passé la vérification de validation ou appelé d'abord cancel_preparations [si la pile est défectueuse, l'exécution pourrait passer à un endroit juste avanttrigger_activation()
après le contexte qui a appeléprepare_for_activation()
renvoie, mais l'appel àcancel_preparations()
aurait eu lieu entre les appels à , rendant ainsi ce dernier appel inoffensif.prepare_for_activation()
ettrigger_activation()
Un tel code peut être sûr en C traditionnel, mais pas avec les compilateurs C modernes. De tels compilateurs peuvent être très dangereux dans ce type d'environnement car agressifs, ils s'efforcent d'inclure uniquement du code qui sera pertinent dans des situations qui pourraient survenir via un mécanisme bien défini et dont les conséquences résultantes seraient également bien définies. Un code dont le but serait de détecter et de nettoyer les pannes peut, dans certains cas, finir par aggraver les choses. Si le compilateur détermine que la tentative de récupération invoquerait dans certains cas un comportement indéfini, il peut en déduire que les conditions qui nécessiteraient une telle récupération dans de tels cas ne peuvent pas se produire, éliminant ainsi le code qui les aurait vérifiées.
la source
-O0
ou un commutateur équivalent? GCC fera beaucoup de choses étranges si vous lui donnez la permission , mais si vous lui demandez de ne pas les faire, il est généralement aussi assez littéral.-O2
.-O0
une mauvaise idée est qu'elle émet des instructions beaucoup plus inutiles. Exemple: un appel non en ligne contient des instructions pour sauvegarder les registres, passer l'appel, restaurer les registres. Tout cela peut échouer. Une instruction qui n'est pas là ne peut pas échouer.-O0
c'est une mauvaise idée: elle a tendance à stocker des variables en mémoire plutôt que dans un registre. Maintenant, il n'est pas certain que la mémoire soit plus sensible aux SEU, mais les données en vol sont plus sensibles que les données au repos. Les mouvements de données inutiles doivent être évités et-O2
y contribuent.v1=v2+0xCAFEBABE
et toutes les mises à jour des deux variables sont effectuées ...C'est un sujet extrêmement large. Fondamentalement, vous ne pouvez pas vraiment récupérer d'une corruption de mémoire, mais vous pouvez au moins essayer d' échouer rapidement . Voici quelques techniques que vous pourriez utiliser:
données constantes de somme de contrôle . Si vous avez des données de configuration qui restent constantes pendant longtemps (y compris les registres matériels que vous avez configurés), calculez sa somme de contrôle à l'initialisation et vérifiez-la périodiquement. Lorsque vous voyez un décalage, il est temps de réinitialiser ou de réinitialiser.
stocker les variables avec redondance . Si vous avez une variable importante
x
, écrire sa valeurx1
,x2
etx3
et que vous lisez(x1 == x2) ? x2 : x3
.mettre en œuvre la surveillance du déroulement du programme . XOR un indicateur global avec une valeur unique dans les fonctions / branches importantes appelées depuis la boucle principale. L'exécution du programme dans un environnement sans rayonnement avec une couverture de test proche de 100% devrait vous donner la liste des valeurs acceptables du drapeau à la fin du cycle. Réinitialisez si vous voyez des écarts.
surveiller le pointeur de pile . Au début de la boucle principale, comparez le pointeur de pile avec sa valeur attendue. Remise à zéro en cas d'écart.
la source
Ce qui pourrait vous aider, c'est un chien de garde . Les chiens de garde ont été largement utilisés en informatique industrielle dans les années 1980. Les pannes matérielles étaient alors beaucoup plus courantes - une autre réponse se réfère également à cette période.
Un chien de garde est une fonction combinée matériel / logiciel. Le matériel est un simple compteur qui décompte d'un nombre (disons 1023) à zéro. TTL ou une autre logique pourrait être utilisée.
Le logiciel a été conçu de telle sorte qu'une routine surveille le bon fonctionnement de tous les systèmes essentiels. Si cette routine se termine correctement = trouve que l'ordinateur fonctionne correctement, il remet le compteur à 1023.
La conception globale est telle que, dans des circonstances normales, le logiciel empêche que le compteur matériel atteigne zéro. Si le compteur atteint zéro, le matériel du compteur effectue sa tâche unique et réinitialise l'ensemble du système. Du point de vue du compteur, zéro est égal à 1024 et le compteur continue à nouveau de décompter.
Ce chien de garde garantit que l'ordinateur connecté est redémarré dans de très nombreux cas d'échec. Je dois admettre que je ne connais pas le matériel capable d'exécuter une telle fonction sur les ordinateurs d'aujourd'hui. Les interfaces avec le matériel externe sont désormais beaucoup plus complexes qu'auparavant.
Un inconvénient inhérent du chien de garde est que le système n'est pas disponible à partir du moment où il échoue jusqu'à ce que le compteur du chien de garde atteigne zéro + heure de redémarrage. Bien que ce délai soit généralement beaucoup plus court que toute intervention externe ou humaine, l'équipement pris en charge devra pouvoir fonctionner sans contrôle informatique pendant cette période.
la source
Cette réponse suppose que vous êtes soucieux d'avoir un système qui fonctionne correctement, en plus d'avoir un système à coût minimum ou rapide; la plupart des gens qui jouent avec des objets radioactifs apprécient l'exactitude / la sécurité par rapport à la vitesse / au coût
Plusieurs personnes ont suggéré des modifications matérielles que vous pouvez apporter (très bien - il y a déjà beaucoup de bonnes choses dans les réponses et je n'ai pas l'intention de les répéter toutes), et d'autres ont suggéré la redondance (excellente en principe), mais je ne pense pas quelqu'un a suggéré comment cette redondance pourrait fonctionner dans la pratique. Comment échouez-vous? Comment savoir quand quelque chose a «mal tourné»? De nombreuses technologies fonctionnent sur la base que tout fonctionnera, et l'échec est donc une chose délicate à gérer. Cependant, certaines technologies informatiques distribuées conçues pour l'évolutivité s'attendent à une défaillance (après tout, avec une ampleur suffisante, la défaillance d'un nœud parmi plusieurs est inévitable avec n'importe quel MTBF pour un seul nœud); vous pouvez exploiter cela pour votre environnement.
Voici quelques idées:
Assurez-vous que votre matériel entier est répliqué
n
fois (oùn
est supérieur à 2, et de préférence impair), et que chaque élément matériel peut communiquer avec l'autre élément matériel. Ethernet est un moyen évident de le faire, mais il existe de nombreuses autres voies beaucoup plus simples qui offriraient une meilleure protection (par exemple CAN). Minimisez les composants communs (même les blocs d'alimentation). Cela peut signifier par exemple l'échantillonnage des entrées ADC à plusieurs endroits.Assurez-vous que l'état de votre application est en un seul endroit, par exemple dans une machine à états finis. Cela peut être entièrement basé sur la RAM, mais n'empêche pas un stockage stable. Il sera ainsi stocké en plusieurs endroits.
Adoptez un protocole de quorum pour les changements d'état. Voir RAFT par exemple. Comme vous travaillez en C ++, il existe des bibliothèques bien connues pour cela. Les modifications apportées au FSM ne seraient apportées que si la majorité des nœuds sont d'accord. Utilisez une bonne bibliothèque connue pour la pile de protocoles et le protocole de quorum plutôt que d'en lancer une vous-même, sinon tout votre bon travail sur la redondance sera gaspillé lorsque le protocole de quorum raccroche.
Assurez-vous que la somme de contrôle (par exemple CRC / SHA) de votre FSM et stockez le CRC / SHA dans le FSM lui-même (ainsi que la transmission dans le message et la somme de contrôle des messages eux-mêmes). Demandez aux nœuds de vérifier régulièrement leur FSM par rapport à ces sommes de contrôle, les messages entrants de somme de contrôle et vérifiez que leur somme de contrôle correspond à la somme de contrôle du quorum.
Créez autant de vérifications internes que possible dans votre système, ce qui permet aux nœuds qui détectent leur propre échec de redémarrer (c'est mieux que de continuer à travailler à moitié si vous avez suffisamment de nœuds). Essayez de les laisser se retirer proprement du quorum lors du redémarrage au cas où ils ne reviendraient pas. Au redémarrage, demandez-leur de contrôler l'image du logiciel (et tout ce qu'ils chargent) et de faire un test de RAM complet avant de se réintroduire dans le quorum.
Utilisez du matériel pour vous soutenir, mais faites-le avec soin. Vous pouvez obtenir la RAM ECC, par exemple, et la lire / écrire régulièrement pour corriger les erreurs ECC (et paniquer si l'erreur n'est pas corrigible). Cependant (à partir de la mémoire), la RAM statique est beaucoup plus tolérante aux rayonnements ionisants que la DRAM en premier lieu, il peut donc être préférable d'utiliser une DRAM statique à la place. Voir également le premier point sous «Choses que je ne ferais pas».
Supposons que vous ayez 1% de chances de défaillance d'un nœud donné en une journée, et supposons que vous pouvez rendre les défaillances entièrement indépendantes. Avec 5 nœuds, vous aurez besoin de trois pour échouer en une journée, ce qui représente une chance de 0,00001%. Avec plus, eh bien, vous avez l'idée.
Choses que je ne ferais pas :
Sous-estimer la valeur de ne pas avoir le problème pour commencer. À moins que le poids ne soit un problème, un gros bloc de métal autour de votre appareil sera une solution beaucoup moins chère et plus fiable qu'une équipe de programmeurs. Il en va de même pour le couplage optique des entrées EMI, etc.
Lancez vos propres algorithmes . Les gens ont déjà fait ça. Utilisez leur travail. La tolérance aux pannes et les algorithmes distribués sont difficiles. Utilisez le travail des autres lorsque cela est possible.
Utilisez des paramètres de compilation compliqués dans l'espoir naïf de détecter plus d'échecs. Si vous êtes chanceux, vous pouvez détecter plus d'échecs. Plus probablement, vous utiliserez un chemin de code dans le compilateur qui a été moins testé, en particulier si vous l'avez roulé vous-même.
Utilisez des techniques non testées dans votre environnement. La plupart des personnes qui écrivent des logiciels à haute disponibilité doivent simuler des modes de défaillance pour vérifier leur fonctionnement HA correctement, et manquer de nombreux modes de défaillance en conséquence. Vous êtes dans la position «chanceuse» d'avoir des pannes fréquentes sur demande. Testez donc chaque technique et assurez-vous que son application réelle améliore le MTBF d'une quantité qui dépasse la complexité pour l'introduire (avec la complexité vient les bogues). Appliquez particulièrement ceci à mes conseils concernant les algorithmes de quorum, etc.
la source
Puisque vous demandez spécifiquement des solutions logicielles et que vous utilisez C ++, pourquoi ne pas utiliser la surcharge d'opérateur pour créer vos propres types de données sûrs? Par exemple:
Au lieu d'utiliser
uint32_t
(etdouble
,int64_t
etc.), créez le vôtreSAFE_uint32_t
qui contient un multiple (minimum de 3) de uint32_t. Surchargez toutes les opérations que vous souhaitez (* + - / << >> = ==! = Etc) et effectuez les opérations surchargées indépendamment sur chaque valeur interne, c'est-à-dire ne le faites pas une seule fois et copiez le résultat. Avant et après, vérifiez que toutes les valeurs internes correspondent. Si les valeurs ne correspondent pas, vous pouvez mettre à jour la mauvaise avec la valeur la plus courante. S'il n'y a pas de valeur la plus courante, vous pouvez notifier en toute sécurité qu'il y a une erreur.De cette façon, peu importe si une corruption se produit dans l'ALU, les registres, la RAM ou sur un bus, vous aurez toujours plusieurs tentatives et une très bonne chance de détecter des erreurs. Notez cependant que cela ne fonctionne que pour les variables que vous pouvez remplacer - votre pointeur de pile par exemple sera toujours sensible.
Une histoire parallèle: j'ai rencontré un problème similaire, également sur une ancienne puce ARM. Il s'est avéré être une chaîne d'outils qui utilisait une ancienne version de GCC qui, avec la puce spécifique que nous avons utilisée, a déclenché un bogue dans certains cas extrêmes qui corromprait (parfois) les valeurs transmises aux fonctions. Assurez-vous que votre appareil n'a aucun problème avant de le blâmer sur la radio-activité, et oui, parfois c'est un bug du compilateur =)
la source
Avertissement: je ne suis pas un professionnel de la radioactivité et je n'ai pas travaillé pour ce type d'application. Mais j'ai travaillé sur les erreurs logicielles et la redondance pour l'archivage à long terme des données critiques, qui est quelque peu lié (même problème, objectifs différents).
À mon avis, le principal problème avec la radioactivité est que la radioactivité peut changer de bit, donc la radioactivité peut / va altérer toute mémoire numérique . Ces erreurs sont généralement appelées erreurs logicielles, pourriture de bits, etc.
La question est alors: comment calculer de manière fiable lorsque votre mémoire n'est pas fiable?
Pour réduire considérablement le taux d'erreurs logicielles (au détriment des frais de calcul car il s'agira principalement de solutions logicielles), vous pouvez soit:
s'appuyer sur le bon vieux schéma de redondance , et plus spécifiquement sur les codes de correction d'erreurs plus efficaces (même objectif, mais des algorithmes plus intelligents pour que vous puissiez récupérer plus de bits avec moins de redondance). Ceci est parfois (à tort) également appelé somme de contrôle. Avec ce type de solution, vous devrez à tout moment stocker l'état complet de votre programme dans une variable / classe principale (ou une structure?), Calculer un ECC et vérifier que l'ECC est correct avant de faire quoi que ce soit, et si non, réparez les champs. Cette solution ne garantit cependant pas que votre logiciel peut fonctionner (simplement qu'il fonctionnera correctement quand il le pourra, ou cessera de fonctionner sinon, car ECC peut vous dire si quelque chose ne va pas, et dans ce cas, vous pouvez arrêter votre logiciel pour que vous n'obtiennent pas de faux résultats).
ou vous pouvez utiliser des structures de données algorithmiques résilientes, qui garantissent, jusqu'à une certaine limite, que votre programme donnera toujours des résultats corrects même en présence d'erreurs logicielles. Ces algorithmes peuvent être vus comme un mélange de structures algorithmiques communes avec des schémas ECC mélangés nativement, mais cela est beaucoup plus résilient que cela, car le schéma de résilience est étroitement lié à la structure, de sorte que vous n'avez pas besoin de coder des procédures supplémentaires pour vérifier l'ECC, et ils sont généralement beaucoup plus rapides. Ces structures permettent de garantir que votre programme fonctionnera dans toutes les conditions, jusqu'à la limite théorique des erreurs logicielles. Vous pouvez également mélanger ces structures résilientes avec le schéma de redondance / ECC pour une sécurité supplémentaire (ou coder vos structures de données les plus importantes comme résilientes, et le reste, les données consommables que vous pouvez recalculer à partir des structures de données principales,
Si vous êtes intéressé par les structures de données résilientes (qui est un nouveau domaine récent mais passionnant en algorithmique et en ingénierie de redondance), je vous conseille de lire les documents suivants:
Introduction aux structures de données des algorithmes résilients par Giuseppe F.Italiano, Universita di Roma "Tor Vergata"
Christiano, P., Demaine, ED et Kishore, S. (2011). Structures de données tolérantes aux pannes sans perte avec surcharge supplémentaire. Dans Algorithms and Data Structures (pp. 243-254). Springer Berlin Heidelberg.
Ferraro-Petrillo, U., Grandoni, F., et Italiano, GF (2013). Structures de données résilientes aux défauts de mémoire: une étude expérimentale des dictionnaires. Journal of Experimental Algorithmics (JEA), 18, 1-6.
Italiano, GF (2010). Algorithmes résilients et structures de données. Dans Algorithms and Complexity (pp. 13-24). Springer Berlin Heidelberg.
Si vous souhaitez en savoir plus sur le domaine des structures de données résilientes, vous pouvez consulter les travaux de Giuseppe F. Italiano (et parcourir les références) et le modèle Faulty-RAM (présenté dans Finocchi et al.2005; Finocchi et Italiano 2008).
/ EDIT: J'ai illustré la prévention / récupération des erreurs logicielles principalement pour la mémoire RAM et le stockage de données, mais je n'ai pas parlé des erreurs de calcul (CPU) . D'autres réponses ont déjà indiqué l'utilisation de transactions atomiques comme dans les bases de données, je proposerai donc un autre schéma plus simple: redondance et vote majoritaire .
L'idée est que vous effectuez simplement x fois le même calcul pour chaque calcul que vous devez faire, et stockez le résultat dans x variables différentes (avec x> = 3). Vous pouvez ensuite comparer vos variables x :
Ce schéma de redondance est très rapide par rapport à l'ECC (pratiquement O (1)) et il vous fournit un signal clair lorsque vous avez besoin d'une sécurité intégrée . Le vote majoritaire est également (presque) garanti de ne jamais produire de sortie corrompue et également de récupérer des erreurs de calcul mineures , car la probabilité que x calculs donnent la même sortie est infinitésimale (car il y a une énorme quantité de sorties possibles, il est presque impossible de obtenir au hasard 3 fois la même chose, encore moins de chances si x> 3).
Donc, avec un vote majoritaire, vous êtes à l'abri de la sortie corrompue, et avec la redondance x == 3, vous pouvez récupérer 1 erreur (avec x == 4 ce sera 2 erreurs récupérables, etc. - l'équation exacte est
nb_error_recoverable == (x-2)
où x est le nombre de répétitions de calcul car vous avez besoin d'au moins 2 calculs d'accord pour récupérer à l'aide du vote majoritaire).L'inconvénient est que vous devez calculer x fois au lieu d'une fois, vous avez donc un coût de calcul supplémentaire, mais la complexité linéaire est si asymptotique que vous ne perdez pas grand-chose pour les avantages que vous gagnez. Un moyen rapide de voter à la majorité consiste à calculer le mode sur un tableau, mais vous pouvez également utiliser un filtre médian.
De plus, si vous voulez vous assurer que les calculs sont effectués correctement, si vous pouvez créer votre propre matériel, vous pouvez construire votre appareil avec x CPU et câbler le système afin que les calculs soient automatiquement dupliqués sur les x CPU avec un vote majoritaire effectué. mécaniquement à la fin (en utilisant des portes ET / OU par exemple). Cela est souvent mis en œuvre dans les avions et les appareils critiques (voir triple redondance modulaire ). De cette façon, vous n'auriez pas de surcharge de calcul (puisque les calculs supplémentaires seront effectués en parallèle), et vous avez une autre couche de protection contre les erreurs logicielles (puisque la duplication des calculs et le vote majoritaire seront gérés directement par le matériel et non par logiciel - qui peut plus facilement être corrompu car un programme est simplement des bits stockés en mémoire ...).
la source
Un point que personne ne semble avoir mentionné. Vous dites que vous développez dans GCC et effectuez une compilation croisée sur ARM. Comment savez-vous que vous n'avez pas de code qui fait des hypothèses sur la RAM libre, la taille entière, la taille du pointeur, combien de temps il faut pour effectuer une certaine opération, combien de temps le système fonctionnera en continu ou diverses choses comme ça? Il s'agit d'un problème très courant.
La réponse est généralement un test unitaire automatisé. Écrivez des faisceaux de test qui exercent le code sur le système de développement, puis exécutez les mêmes faisceaux de test sur le système cible. Recherchez les différences!
Vérifiez également les errata sur votre appareil intégré. Vous pouvez trouver qu'il y a quelque chose à propos de "ne faites pas ça parce que ça va planter, alors activez cette option du compilateur et le compilateur le contournera".
En bref, votre source de plantage la plus probable est les bogues dans votre code. Jusqu'à ce que vous vous soyez assuré que ce n'est pas le cas, ne vous inquiétez pas (encore) des modes de défaillance plus ésotériques.
la source
Vous voulez 3+ machines esclaves avec un maître en dehors de l'environnement de rayonnement. Toutes les E / S passent par le maître qui contient un mécanisme de vote et / ou de nouvelle tentative. Les esclaves doivent chacun avoir un chien de garde matériel et l'appel à les heurter doit être entouré de CRC ou similaires pour réduire la probabilité de heurt involontaire. Le contournement doit être contrôlé par le maître, donc la connexion perdue avec le maître équivaut à redémarrer en quelques secondes.
Un avantage de cette solution est que vous pouvez utiliser la même API pour le maître que pour les esclaves, de sorte que la redondance devient une fonctionnalité transparente.
Edit: D'après les commentaires, je ressens le besoin de clarifier "l'idée du CRC". La possibilité que l'esclave se heurte à son propre chien de garde est proche de zéro si vous entourez la bosse de CRC ou que vous contrôlez les données aléatoires du maître. Ces données aléatoires ne sont envoyées par le maître que lorsque l'esclave examiné est aligné avec les autres. Les données aléatoires et CRC / digest sont immédiatement effacés après chaque bosse. La fréquence de coupure maître-esclave doit être plus du double du délai d'attente du chien de garde. Les données envoyées par le maître sont générées à chaque fois de manière unique.
la source
Que diriez-vous d'exécuter de nombreuses instances de votre application. Si les plantages sont dus à des modifications aléatoires des bits de mémoire, il est probable que certaines de vos instances d'application s'en sortiront et produiront des résultats précis. Il est probablement assez facile (pour quelqu'un ayant des antécédents statistiques) de calculer le nombre d'instances dont vous avez besoin, compte tenu de la probabilité de flop binaire pour obtenir la plus petite erreur globale que vous souhaitez.
la source
Ce que vous demandez est un sujet assez complexe - difficile à répondre. D'autres réponses sont correctes, mais elles ne couvraient qu'une petite partie de tout ce que vous devez faire.
Comme on le voit dans les commentaires , il n'est pas possible de résoudre les problèmes matériels à 100%, mais il est possible avec une forte probabilité de les réduire ou de les attraper en utilisant diverses techniques.
Si j'étais vous, je créerais le logiciel du plus haut niveau d'intégrité de sécurité (SIL-4). Obtenez le document CEI 61513 (pour l'industrie nucléaire) et suivez-le.
la source
Quelqu'un a mentionné l'utilisation de puces plus lentes pour empêcher les ions de retourner les bits aussi facilement. De la même manière, utilisez peut-être un processeur / ram spécialisé qui utilise en fait plusieurs bits pour stocker un seul bit. Fournissant ainsi une tolérance aux pannes matérielles car il est très peu probable que tous les bits soient retournés. Donc 1 = 1111 mais devrait être touché 4 fois pour être retourné. (4 peut être un mauvais nombre car si 2 bits sont retournés, c'est déjà ambigu). Donc, si vous optez pour 8, vous obtenez 8 fois moins de RAM et un temps d'accès plus lent mais une représentation des données beaucoup plus fiable. Vous pourriez probablement faire cela à la fois au niveau logiciel avec un compilateur spécialisé (allouer x quantité d'espace supplémentaire pour tout) ou l'implémentation du langage (écrire des wrappers pour les structures de données qui allouent les choses de cette façon).
la source
Il serait peut-être utile de savoir si cela signifie que le matériel est "conçu pour cet environnement". Comment corrige-t-il et / ou indique-t-il la présence d'erreurs SEU?
Dans un projet lié à l'exploration spatiale, nous avions un MCU personnalisé, qui déclencherait une exception / interruption sur les erreurs SEU, mais avec un certain retard, c'est-à-dire que certains cycles pourraient passer / des instructions seraient exécutées après celle insn qui a provoqué l'exception SEU.
Le cache de données était particulièrement vulnérable, donc un gestionnaire invaliderait la ligne de cache incriminée et redémarrerait le programme. Seulement cela, en raison de la nature imprécise de l'exception, la séquence d'insns dirigée par l'exception levant insn peut ne pas être redémarrable.
Nous avons identifié les séquences dangereuses (non redémarrables) (comme
lw $3, 0x0($2)
, suivies d'un insn, qui modifie$2
et ne dépend pas des données$3
), et j'ai apporté des modifications à GCC, de sorte que de telles séquences ne se produisent pas (par exemple, en dernier recours, en séparant le deux insns par anop
).Juste quelque chose à considérer ...
la source
Si votre matériel tombe en panne, vous pouvez utiliser le stockage mécanique pour le récupérer. Si votre base de code est petite et dispose d'un espace physique, vous pouvez utiliser un magasin de données mécaniques.
Il y aura une surface de matériau qui ne sera pas affectée par le rayonnement. Plusieurs engrenages seront là. Un lecteur mécanique fonctionnera sur tous les engrenages et sera flexible pour monter et descendre. Down signifie qu'il est 0 et up signifie qu'il est 1. De 0 et 1, vous pouvez générer votre base de code.
la source
Utilisez un planificateur cyclique . Cela vous donne la possibilité d'ajouter des temps de maintenance réguliers pour vérifier l'exactitude des données critiques. Le problème le plus souvent rencontré est la corruption de la pile. Si votre logiciel est cyclique, vous pouvez réinitialiser la pile entre les cycles. Ne réutilisez pas les piles pour les appels d'interruption, configurez une pile distincte pour chaque appel d'interruption important.
Semblable au concept Watchdog, il y a des temporisateurs. Démarrez un minuteur matériel avant d'appeler une fonction. Si la fonction ne revient pas avant l'interruption du temporisateur, rechargez la pile et réessayez. S'il échoue toujours après 3/5 essais, vous devez recharger à partir de la ROM.
Divisez votre logiciel en parties et isolez ces parties pour utiliser des zones de mémoire et des temps d'exécution séparés (en particulier dans un environnement de contrôle). Exemple: acquisition de signal, pré-transmission des données, algorithme principal et implémentation / transmission des résultats. Cela signifie qu'un échec dans une partie n'entraînera pas d'échecs dans le reste du programme. Ainsi, pendant que nous réparons l'acquisition du signal, le reste des tâches se poursuit sur les données périmées.
Tout a besoin de CRC. Si vous exécutez en dehors de la RAM, même votre .text a besoin d'un CRC. Vérifiez régulièrement les CRC si vous utilisez un planificateur cyclique. Certains compilateurs (pas GCC) peuvent générer des CRC pour chaque section et certains processeurs ont du matériel dédié pour faire des calculs de CRC, mais je suppose que cela tomberait hors de la portée de votre question. La vérification des CRC invite également le contrôleur ECC sur la mémoire à réparer les erreurs de bit unique avant qu'il ne devienne un problème.
la source
Tout d'abord, concevez votre application en fonction de l'échec . Assurez-vous que dans le cadre d'un fonctionnement à débit normal, il s'attend à se réinitialiser (en fonction de votre application et du type de défaillance, soit légère, soit dure). Cela est difficile à obtenir: les opérations critiques qui nécessitent un certain degré de transactionnalité peuvent devoir être vérifiées et modifiées au niveau de l'assemblage afin qu'une interruption à un point clé ne puisse pas entraîner des commandes externes incohérentes. Échec rapide dès qu'une corruption de mémoire irrécupérable ou un écart de flux de contrôle est détecté. Enregistrez les échecs si possible.
Deuxièmement, si possible, corrigez la corruption et continuez . Cela signifie une somme de contrôle et une correction fréquente des tables (et du code du programme si vous le pouvez); peut-être avant chaque opération majeure ou sur une interruption temporisée, et le stockage des variables dans des structures qui se corrigent automatiquement (encore une fois avant chaque opération majeure ou sur une interruption temporisée, prenez un vote majoritaire de 3 et corrigez s'il s'agit d'une seule déviation). Enregistrez les corrections si possible.
Troisièmement, l' échec du test . Configurez un environnement de test reproductible qui retourne les bits en mémoire de manière aléatoire. Cela vous permettra de reproduire les situations de corruption et aidera à concevoir votre application autour d'eux.
la source
Compte tenu des commentaires de supercat, des tendances des compilateurs modernes et d'autres choses, je serais tenté de retourner dans les temps anciens et d'écrire le code entier en assemblage et en allocations de mémoire statique partout. Pour ce type de fiabilité absolue, je pense que l'assemblage n'entraîne plus une grande différence en pourcentage du coût.
la source
Voici une énorme quantité de réponses, mais je vais essayer de résumer mes idées à ce sujet.
Quelque chose plante ou ne fonctionne pas correctement peut être le résultat de vos propres erreurs - il devrait être facile de le réparer lorsque vous localisez le problème. Mais il y a aussi la possibilité de pannes matérielles - et c'est difficile, sinon impossible à résoudre dans l'ensemble.
Je recommanderais d'abord d'essayer de saisir la situation problématique en vous connectant (pile, registres, appels de fonction) - soit en les enregistrant quelque part dans un fichier, soit en les transmettant directement ("oh non - je plante").
La récupération à partir d'une telle situation d'erreur est soit un redémarrage (si le logiciel est toujours actif et en cours), soit une réinitialisation matérielle (par exemple, des chiens de garde hw). Plus facile à démarrer depuis le premier.
Si le problème est lié au matériel - la journalisation devrait vous aider à identifier dans quel problème d'appel de fonction se produit et qui peut vous donner une connaissance interne de ce qui ne fonctionne pas et où.
De plus, si le code est relativement complexe - il est logique de le «diviser et le conquérir» - ce qui signifie que vous supprimez / désactivez certains appels de fonction là où vous soupçonnez un problème - en désactivant généralement la moitié du code et en activant une autre moitié - vous pouvez obtenir «ça marche» / "ne fonctionne pas" type de décision après quoi vous pouvez vous concentrer sur une autre moitié de code. (Où est le problème)
Si un problème survient après un certain temps - alors un débordement de pile peut être suspecté - alors il est préférable de surveiller les registres de points de pile - s'ils augmentent constamment.
Et si vous parvenez à minimiser complètement votre code jusqu'à ce que le type d'application "hello world" - et il échoue toujours au hasard - alors des problèmes matériels sont attendus - et il doit y avoir une "mise à niveau matérielle" - ce qui signifie inventer un tel cpu / ram / ... -une combinaison matérielle qui tolérerait mieux le rayonnement.
La chose la plus importante est probablement la façon dont vous récupérez vos journaux si la machine est complètement arrêtée / réinitialisée / ne fonctionne pas - probablement la première chose que le bootstap devrait faire - est de revenir à la maison si une situation problématique est découverte.
S'il est également possible dans votre environnement de transmettre un signal et de recevoir une réponse - vous pouvez essayer de construire une sorte d'environnement de débogage à distance en ligne, mais vous devez alors avoir au moins des supports de communication fonctionnant et un processeur / un ram en état de fonctionnement. Et par débogage à distance, je veux dire soit une approche de type stub GDB / gdb, soit votre propre implémentation de ce que vous devez récupérer de votre application (par exemple, télécharger des fichiers journaux, télécharger la pile d'appels, télécharger ram, redémarrer)
la source
J'ai vraiment lu beaucoup de bonnes réponses!
Voici mon 2 centime: construire un modèle statistique de l'anomalie mémoire / registre, en écrivant un logiciel pour vérifier la mémoire ou pour effectuer des comparaisons fréquentes de registres. De plus, créez un émulateur, dans le style d'une machine virtuelle où vous pouvez expérimenter le problème. Je suppose que si vous modifiez la taille de la jonction, la fréquence d'horloge, le fournisseur, le boîtier, etc., vous observerez un comportement différent.
Même la mémoire de notre ordinateur de bureau présente un certain taux d'échec, ce qui ne nuit cependant pas au travail quotidien.
la source