web-dev-qa-db-fra.com

Compiler une application pour une utilisation dans des environnements hautement radioactifs

Nous compilons une application C/C++ intégrée qui est déployée dans un dispositif blindé dans un environnement bombardé de rayonnement ionisant . 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.

Pouvons-nous apporter des modifications à notre code ou des améliorations à la compilation qui peuvent être apportées pour identifier/corriger les erreurs logicielles et la corruption de mémoire provoquée par des événements uniques ? D'autres développeurs ont-ils réussi à réduire les effets néfastes des erreurs logicielles sur une application de longue durée?

1345
rook

La NASA a un article sur un logiciel durci par rayonnement. Il décrit trois tâches principales:

  1. Contrôle régulier de la mémoire pour les erreurs, puis élimination de ces erreurs,
  2. des mécanismes robustes de récupération d'erreur, et
  3. la possibilité de reconfigurer si quelque chose ne fonctionne plus.

Notez que la fréquence d'analyse de la mémoire doit être assez fréquente pour que les erreurs multibits se produisent rarement, car la plupart de la mémoire ECC peut être restaurée à partir d'erreurs à un bit et non d'erreurs à plusieurs bits.

La reprise sur incident robuste inclut le transfert de flux de contrôle (généralement, le redémarrage d’un processus à un point antérieur à l’erreur), la libération des ressources et la restauration des données.

Leur principale recommandation pour la restauration des données consiste à en éviter le besoin en faisant en sorte que les données intermédiaires soient traitées de manière temporaire, de sorte que tout 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 des techniques particulièrement adaptées aux langages orientés objet tels que C++. Par exemple

  1. ECC basés sur logiciel pour les objets de mémoire contigus
  2. Programmation par contrat : vérification des conditions préalables et postconditions, puis vérification de l'objet pour vérifier qu'il est toujours dans un état valide.

Et, il se trouve que la NASA a utilisé le C++ pour d’importants projets tels que le Mars Rover .

L'abstraction et l'encapsulation de classes C++ ont permis un développement et des tests rapides entre plusieurs projets et développeurs.

Ils ont évité certaines fonctionnalités C++ qui pourraient créer des problèmes:

  1. Exceptions
  2. Modèles
  3. Iostream (pas de console)
  4. Héritage multiple
  5. Surcharge de l'opérateur (autre que new et delete)
  6. Allocation dynamique (utilisé un pool de mémoire dédié et un emplacement new pour éviter toute possibilité de corruption du tas du système).
381
rsjaffe

Voici quelques réflexions et idées:

Utilisez ROM de manière plus créative.

Stockez tout ce que vous pouvez dans ROM. Au lieu de calculer des objets, stockez des tables de consultation dans une mémoire ROM. (Assurez-vous que votre compilateur édite vos tables de recherche dans la section en lecture seule! Imprimez les adresses de mémoire au moment de l'exécution pour le vérifier!) Stockez votre table de vecteurs d'interruption dans la ROM. Bien sûr, effectuez des tests pour vérifier la fiabilité de votre ROM par rapport à votre RAM.

Utilisez votre meilleur RAM pour la pile.

Les UES de la pile sont probablement la source la plus probable des blocages, car c’est là que résident généralement des variables telles que les variables d’index, les variables d’état, les adresses de retour et des pointeurs de différentes sortes.

Implémentez des routines de minuterie-tick et watchdog.

Vous pouvez exécuter une routine de "vérification de l'intégrité" à chaque tick du minuteur, ainsi qu'une routine de surveillance pour gérer le blocage du système. Votre code principal peut également incrémenter périodiquement un compteur pour indiquer les progrès, et la routine de vérification de l'intégrité physique peut garantir que cela se produit.

Implémenter codes de correction d'erreur dans le logiciel.

Vous pouvez ajouter de la 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 radiations plus longtemps, augmentant ainsi le risque d'erreur, vous devez donc envisager le compromis.

N'oubliez pas les caches.

Vérifiez la taille de vos caches de processeur. Les données auxquelles vous avez accédé ou modifié récemment se trouveront probablement dans un cache. Je pense que vous pouvez désactiver au moins une partie des caches (à un coût de performances élevé); Vous devriez essayer ceci pour voir à quel point les caches sont sensibles aux SEU. Si les caches sont plus robustes que RAM, vous pouvez régulièrement lire et réécrire des données critiques pour vous assurer qu'elles restent dans le cache et ramener RAM en ligne.

Utilisez intelligemment les gestionnaires de fautes de page.

Si vous marquez une page mémoire comme non-présente, la CPU émettra une erreur de page lorsque vous tenterez d'y accéder. Vous pouvez créer un gestionnaire de page-fault qui vérifie avant de traiter la demande de lecture. (Les systèmes d'exploitation PC l'utilisent pour charger de manière transparente des pages qui ont été permutées sur un disque.)

Utilise 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; vous savez quelles tables RAM spéciales sont utilisées par la CPU, et vous pouvez concevoir les choses de manière détournée pour réduire les risques.

Utilisez objdump pour examiner le langage Assembly généré et déterminez la quantité de code utilisée par chacune de vos routines.

Si vous utilisez un grand système d'exploitation comme Linux, vous vous posez des problèmes; il y a tellement de complexité et tellement de choses qui ne vont pas.

N'oubliez pas que c'est un jeu de probabilités.

Un commentateur a dit

Chaque routine que vous écrivez pour capturer les erreurs sera sujette à l'échec pour la même cause.

Bien que cela soit vrai, les risques d'erreur dans les (par exemple) 100 octets de code et les données nécessaires au bon fonctionnement d'une routine de contrôle sont beaucoup plus faibles que les risques d'erreur ailleurs. Si votre ROM est assez fiable et que presque tout le code/toutes les données sont réellement dans ROM, vos chances sont encore meilleures.

Utilisez du matériel redondant.

Utilisez au moins deux configurations matérielles identiques 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.

111
Artelius

Vous pouvez également être intéressé par la littérature abondante sur le sujet de la tolérance algorithmique aux pannes. Cela inclut l'ancienne affectation: écrivez une sorte qui trie correctement ses entrées lorsqu'un nombre constant de comparaisons échouera (ou, la version légèrement plus diabolique, lorsque le nombre asymptotique de comparaisons échouées devient log(n) pour les comparaisons n.

L'article de 1984 de Huang et Abraham, intitulé " Tolérance aux fautes fondée sur un algorithme pour les opérations sur les matrices ", est un bon point de départ pour la lecture. Leur idée est vaguement similaire à un calcul crypté homomorphique (mais ce n’est pas vraiment la même chose, car ils essaient de détecter/corriger les erreurs au niveau de l’opération).

Un descendant plus récent de ce document est Bosilca, Delmas, Dongarra et Langou " tolérance de panne basée sur un algorithme appliquée au calcul haute performance ".

96
Eric Towers

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:

  • Utilisez les mesures de sécurité quotidiennes «pain et beurre» qui doivent figurer sur tout système embarqué semi-professionnel: chien de garde interne, détection interne de basse tension, moniteur d'horloge interne. Ces choses ne devraient même pas avoir besoin d'être mentionnées en 2016 et elles sont standard sur presque tous les microcontrôleurs modernes.
  • Si vous disposez d'un MCU axé sur la sécurité et/ou sur l'automobile, il comportera certaines fonctionnalités de surveillance, telles qu'une fenêtre temporelle donnée, à l'intérieur desquelles vous devrez actualiser l'agent de surveillance. Ceci est préférable si vous avez un système temps réel critique.
  • En général, utilisez un MCU adapté à ce type de système et non des fluffs classiques que vous avez reçus dans un paquet de flocons de maïs. De nos jours, presque tous les fabricants de microcontrôleurs disposent de microcontrôleurs spécialisés conçus pour les applications de sécurité (TI, Freescale, Renesas, ST, Infineon, etc.). Celles-ci comportent de nombreuses fonctionnalités de sécurité intégrées, notamment des cœurs à étapes de verrouillage: cela signifie qu'il y a 2 cœurs de processeur exécutant le même code et qu'ils doivent être en accord.
  • IMPORTANT: vous devez vous assurer de l'intégrité des registres internes de la MCU. Tous les registres de contrôle et d'état des périphériques matériels inscriptibles 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 doté des fonctionnalités intégrées «écriture unique» des registres. En outre, vous devez stocker les valeurs par défaut de tous les registres de matériel dans NVM et les copier à intervalles réguliers. Vous pouvez assurer l'intégrité de variables importantes de la même manière.

    Remarque: utilisez toujours une programmation défensive. Cela signifie que vous devez configurer les registres tous dans la MCU et pas uniquement ceux utilisés par l'application. Vous ne voulez pas qu'un périphérique matériel quelconque se réveille soudainement.

  • Il existe toutes sortes de méthodes pour vérifier les erreurs dans RAM ou NVM: sommes de contrôle, "modèles de marche", logiciel ECC, etc. La meilleure solution de nos jours est de ne pas en utiliser, mais d'utiliser un MCU avec -en ECC et contrôles similaires. Parce que cela est complexe dans le logiciel, et la vérification des erreurs en elle-même pourrait donc introduire des bogues et des problèmes inattendus.

  • Utilisez la redondance. Vous pouvez stocker à la fois la mémoire volatile et non volatile dans deux segments "miroir" identiques, qui doivent toujours être équivalents. Une somme de contrôle CRC peut être jointe à chaque segment.
  • Évitez d’utiliser des mémoires externes en dehors de la MCU.
  • Implémentez un gestionnaire d'exception par défaut de routine de service d'interruption/par défaut pour toutes les interruptions/exceptions possibles. Même ceux que vous n'utilisez pas. La routine par défaut ne devrait rien faire d'autre que couper sa propre source d'interruption.
  • 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 et les ignore ensuite de manière sûre.

  • Ne jamais écrire des programmes qui reposent sur un comportement mal spécifié. Il est probable qu'un tel comportement puisse changer radicalement avec des modifications matérielles inattendues causées par des radiations ou des interférences électromagnétiques. Le meilleur moyen de vous assurer que votre programme est exempt de telles conneries consiste à utiliser un standard de codage tel que MISRA, associé à un outil d'analyse statique. Cela aidera également à la programmation défensive et à la suppression des bogues (pourquoi ne voudriez-vous pas détecter les bogues dans n’importe quel type d’application?).
  • IMPORTANT: N'impliquez aucune confiance dans les valeurs par défaut des variables de durée de stockage statique. C’est-à-dire qu’il ne faut pas faire confiance au contenu par défaut de .data ou .bss. Il peut y avoir un laps de temps quelconque entre le point d’initialisation et le moment où la variable est réellement utilisée; il aurait pu y avoir suffisamment de temps pour que la RAM soit corrompue. A la place, écrivez le programme de manière à ce que toutes les variables de ce type 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 à l’étendue du fichier ou sous la forme static, vous ne devez jamais utiliser = pour l’initialiser (sinon, cela ne sert à rien, cela n’a aucun sens, car vous ne pouvez pas compter sur la valeur de toute façon). Toujours le définir au moment de l'exécution, juste avant l'utilisation. S'il est possible de mettre à jour de manière répétée de telles variables à 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 publique "d’installation", que vous pourrez également appeler ultérieurement au moment de l’exécution, directement à partir de l’appelant.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 reposant sur de tels. De nombreux compilateurs ont la possibilité d'ignorer cela, 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 confiance.

    .

  • L'implémentation d'un système de rapport d'erreur/journal d'erreur est toujours utile. 

  • Implementing an error report/error log system is always helpful.
38
Lundin

Il est 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 ils ne savent peut-être pas que le programmeur teste x==42 alors que le compilateur sait qu’il n’ya aucun moyen que x puisse contenir autre chose, car le programmeur le souhaite pour empêcher l'exécution de certains codes avec x ayant une autre valeur, même dans les cas où le seul moyen de conserver cette valeur serait que le système reçoive une sorte de problème électrique.

Déclarer des variables comme étant volatile est souvent utile, mais peut ne pas être une panacée . Il est particulièrement important de noter que le codage sécurisé nécessite souvent que les opérations Dangereuses comportent des verrouillages matériels qui nécessitent plusieurs étapes pour être activés, Et ce code. être écrit en utilisant le motif:

... 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();

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 la prepare_for_activation(), .__, le système peut résister à la plupart des événements de glitch simples et plausibles, même ceux qui corromprait arbitrairement le compteur de programme et la pile. Si Un problème survient juste après un appel à prepare_for_activation(), cela impliquerait Que l'activation aurait été appropriée (puisqu'il n'y a aucune autre raison prepare_for_activation() aurait été appelé avant le problème). Si le Glitch provoque que le code atteigne prepare_for_activation() de manière inappropriée, mais qu'il n'y ait aucun événement de glitch ultérieur, il n'y aurait aucun moyen pour que le code atteigne ensuite trigger_activation() sans avoir passé le contrôle de validation ou appelé en premier cancel_preparations [ si la pile est défaillante, l'exécution peut passer à un emplacement juste avant trigger_activation() après le retour du contexte appelé prepare_for_activation(), mais l'appel à cancel_preparations() aurait eu lieu entre les appels à prepare_for_activation() et à trigger_activation(), rendant ainsi le dernier appel inoffensif.

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 les situations qui pourraient se produire via un mécanisme bien défini et dont les conséquences seraient également bien définies. Un code dont le but est de détecter et de nettoyer après une défaillance peut, dans certains cas, aggraver les choses. Si le compilateur détermine que la tentative de récupération invoque 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.

31
supercat

Ce qui pourrait vous aider 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 fonctionnalité matérielle/logicielle combinée. Le matériel est un simple compteur qui décompte d’un nombre (par exemple 1023) à zéro. TTL ou une autre logique pourrait être utilisée.

Le logiciel a été conçu de manière à ce 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 générale est telle que, dans des circonstances normales, le logiciel empêche le compteur matériel d’atteindre 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 à décompter.

Ce chien de garde s'assure que l'ordinateur connecté est redémarré dans de très nombreux cas d'échec. Je dois avouer que je ne connais pas le matériel capable de remplir une telle fonction sur les ordinateurs actuels. Les interfaces avec le matériel externe sont maintenant beaucoup plus complexes qu’avant.

Un inconvénient inhérent au 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 + le temps de redémarrage. Bien que ce délai soit généralement beaucoup plus court que toute intervention humaine ou externe, l'équipement pris en charge devra pouvoir fonctionner sans contrôle informatique pour cette période.

27
OldFrank

C'est un sujet extrêmement vaste. En gros, vous ne pouvez pas vraiment récupérer de la corruption de mémoire, mais vous pouvez au moins essayer de échouer rapidement . Voici quelques techniques que vous pourriez utiliser:

  • Données de somme de contrôle constantes . Si vous avez des données de configuration qui restent constantes pendant une longue période (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 constatez une non-concordance, il est temps de réinitialiser ou de réinitialiser.

  • stocke les variables avec redondance . Si vous avez une variable importante x, écrivez sa valeur dans x1, x2 et x3 et lisez-la sous la forme (x1 == x2) ? x2 : x3.

  • mettre en œuvre surveillance du flux du programme . XOR est un indicateur global avec une valeur unique dans les fonctions/branches importantes appelées à partir de la boucle principale. L'exécution du programme dans un environnement exempt de radiations 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 déviations.

  • surveille le pointeur de la pile . Au début de la boucle principale, comparez le pointeur de pile avec la valeur attendue. Réinitialiser sur déviation.

27
Dmitry Grigoryev

Puisque vous demandez spécifiquement des solutions logicielles et que vous utilisez le C++, pourquoi ne pas utiliser la surcharge d'opérateur pour créer vos propres types de données sécurisés? Par exemple:

Au lieu d'utiliser uint32_t (et double, int64_t, etc.), créez votre propre SAFE_uint32_t qui contient un multiple (minimum de 3) de uint32_t. Surchargez toutes les opérations que vous voulez effectuer (* + -/<< >> = ==! = Etc), et faites en sorte que les opérations surchargées s'exécutent indépendamment sur chaque valeur interne, autrement dit, ne le faites pas une 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 le mauvais en fonction de la valeur la plus courante. S'il n'y a pas de valeur commune, vous pouvez notifier en toute sécurité qu'il y a une erreur.

De cette manière, peu importe si une corruption survient dans l’ALU, les registres, la RAM ou sur un bus, vous aurez toujours plusieurs tentatives et de très bonnes chances d’attraper 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.

Histoire parallèle: j'ai rencontré un problème similaire, également sur une ancienne puce ARM. Il s’est avéré qu’il s’agissait d’une chaîne d’outils qui utilisait une ancienne version de GCC qui, avec la puce spécifique que nous utilisions, provoquait un bogue dans certains cas Edge qui altéraient (parfois) la transmission de valeurs à des fonctions. Assurez-vous que votre appareil n’a aucun problème avant de le blâmer pour la radio-activité, et oui, c’est parfois un bug du compilateur =)

22
jkflying

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 avoir chacun un chien de garde matériel et l'appel à les cogner doit être entouré de CRC ou similaires, afin de réduire la probabilité de choc involontaire. Les transferts doivent être contrôlés par le maître. Par conséquent, la perte de connexion avec le maître équivaut à un redémarrage dans quelques secondes.

L'un des avantages de cette solution est que vous pouvez utiliser la même API pour le maître et 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 de la CRC". La possibilité que l'esclave bouge son propre chien de garde soit proche de zéro si vous entourez le choc avec du CRC ou si vous effectuez des contrôles de synthèse sur des 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é sur les autres. Les données aléatoires et CRC/digest sont immédiatement effacés après chaque bosse. La fréquence de choc maître-esclave doit être supérieure à double le délai d'attente du chien de garde. Les données envoyées par le maître sont générées de manière unique à chaque fois.

9
Jonas Byström

Un point que personne ne semble avoir mentionné. Vous dites que vous développez dans GCC et que vous effectuez une compilation croisée sur ARM. Comment savez-vous que vous n'avez pas de code qui suppose des hypothèses sur la RAM libre, la taille des entiers, la taille des pointeurs, le temps nécessaire à une opération donnée, la durée de fonctionnement continu du système ou d'autres tâches de ce type? C'est un problème très commun.

La réponse est généralement le test unitaire automatisé. Rédigez 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. Cherchez les différences!

Recherchez également les erreurs sur votre périphérique intégré. Vous constaterez peut-être qu'il y a quelque chose à propos de "ne faites pas ceci parce que ça va planter, alors activez cette option du compilateur et le compilateur contournera".

En bref, les sources de bogues les plus probables sont les bogues de votre code. Tant que vous n'êtes pas sûr que ce n'est pas le cas, ne vous inquiétez pas (encore) des modes de défaillance plus ésotériques.

8
Graham

Pourquoi ne pas exécuter plusieurs instances de votre application? Si les plantages sont dus à des modifications aléatoires des bits de mémoire, il est probable que certaines instances de votre application parviendront à y parvenir et produiront des résultats précis. Il est probablement assez facile (pour une personne possédant des connaissances statistiques) de calculer le nombre d'instances nécessaires pour que la probabilité de perte de bit aboutisse à une erreur aussi petite que vous le souhaitez.

7
ren

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écanique.

 Enter image description here

Il y aura une surface de matériau qui ne sera pas affectée par le rayonnement. Plusieurs vitesses 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 plus haut signifie 1. À partir de 0 et 1, vous pouvez générer votre base de code.

7
Hitul

Il serait peut-être utile de savoir si cela signifie que le matériel doit être "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 être transmis/que les instructions seraient exécutées après celle qui avait provoqué l’exception SEU.

Le cache de données était particulièrement vulnérable. Un gestionnaire invaliderait donc la ligne de cache incriminée et redémarrerait le programme. En raison du caractère imprécis de l’exception, il est possible que la séquence d’insns précédée de l’exception soulevant insn ne puisse pas être redémarrée.

Nous avons identifié les séquences dangereuses (non redémarrables) (telles que lw $3, 0x0($2), suivi d'un insn, qui modifie $2 et ne dépend pas des données sur $3), et j'ai apporté des modifications à GCC afin qu'elles ne se produisent pas (par exemple, en dernier recours). , en séparant les deux insns par un nop).

Juste quelque chose à considérer ...

7
chill

Quelqu'un a mentionné l'utilisation de puces plus lentes pour empêcher les ions de renverser des bits aussi facilement. De la même façon, utilisez peut-être un processeur/ram spécialisé qui utilise plusieurs bits pour stocker un seul bit. Nous fournissons donc une tolérance de panne matérielle, car il serait très improbable que tous les bits soient inversés. Donc, 1 = 1111, mais il faudrait se faire frapper 4 fois pour être réellement inversé. (4 pourrait être un mauvais nombre car si 2 bits sont retournés, c'est déjà ambigu). Donc, si vous choisissez 8, vous obtenez 8 fois moins de RAM et un temps d’accès légèrement 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) et une implémentation linguistique (encapsuleurs d'écriture pour les structures de données qui allouent les choses de cette façon). Ou du matériel spécialisé ayant la même structure logique mais le faisant dans le micrologiciel.

7
Alex C

Ce que vous demandez est un sujet assez complexe - difficile à répondre. Les autres réponses sont acceptables, mais elles ne couvrent qu’une petite partie de toutes les tâches que vous devez faire.

Comme vu dans les commentaires , il n'est pas possible de résoudre les problèmes matériels à 100%, cependant il est possible avec une probabilité élevée de les réduire ou de les résoudre en utilisant diverses techniques.

Si j'étais vous, je créerais le logiciel du plus haut niveau de sécurité { niveau d'intégrité de la sécurité } _ (SIL-4). Obtenez le IEC 61513 (pour l'industrie nucléaire) et suivez-le.

7
BЈовић

Utilisez un planificateur cyclique . Cela vous permet 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 de chaque appel d'interruption important.

Un concept similaire au concept de chien de garde est celui des délais. Démarrer une minuterie matérielle avant d'appeler une fonction. Si la fonction ne revient pas avant que le délai ne soit interrompu, rechargez la pile et réessayez. Si cela échoue toujours après 3/5 tentatives, vous devez recharger à partir de la ROM.

Divisez votre logiciel en plusieurs parties et isolez-les pour utiliser des zones de mémoire et des temps d'exécution distincts (en particulier dans un environnement de contrôle). Exemple: acquisition du signal, pré-acquisition des données, algorithme principal et mise en oeuvre/transmission du résultat. Cela signifie qu'une défaillance dans une partie ne provoquera pas de défaillance dans le reste du programme. Ainsi, pendant que nous réparons l'acquisition du signal, le reste des tâches continue sur des données obsolètes. 

Tout a besoin de CRC. Si vous exécutez en dehors de RAM, même votre texte nécessite un CRC. Vérifiez les CRC régulièrement si vous utilisez un planificateur cyclique. Certains compilateurs (pas GCC) peuvent générer des CRC pour chaque section et certains processeurs ont un matériel dédié pour effectuer les calculs de CRC, mais je suppose que cela ne ferait pas partie de votre question. La vérification des CRC invite également le contrôleur ECC de la mémoire à réparer les erreurs sur un seul bit avant que cela ne devienne un problème.

6
Gerhard

Tout d'abord, concevez votre application en fonction de l'échec. Assurez-vous que, dans le cadre d'un fonctionnement en flux normal, il est prévu de réinitialiser (en fonction de votre application et du type de défaillance, qu'elle soit matérielle ou matérielle). Cela est difficile à obtenir: des opérations critiques nécessitant un certain degré de transactalité peuvent nécessiter une vérification et un ajustement au niveau de l’Assemblée, de sorte qu’une interruption à un point clé ne puisse pas entraîner des commandes externes incohérentes .Fail fast dès que toute unrecoverable corruption de mémoire ou déviation du flux de contrôle est détectée. Consignez les échecs si possible.

Deuxièmement, si possible, corrigez la corruption et continuez. Cela signifie que le contrôle et la fixation des tables constantes (et du code de programme si vous le pouvez) souvent; peut-être avant chaque opération majeure ou lors d'une interruption programmée, et stocker des variables dans des structures qui se corrigent automatiquement (encore une fois avant chaque opération majeure ou lors d'une interruption programmée, prenez un vote majoritaire à partir de 3 et corrigez s'il s'agit d'un seul écart). Consignez les corrections si possible.

Troisièmement, échec du test. Configurez un environnement de test repeatable qui retourne les bits en mémoire de manière aléatoire. Cela vous permettra de reproduire des situations de corruption et vous aidera à concevoir votre application en fonction de celles-ci.

4
MrBigglesworth

Compte tenu des commentaires de Supercat, des tendances des compilateurs modernes, etc., je serais tenté de revenir aux temps anciens et d'écrire le code entier dans Assembly et des allocations de mémoire statique partout. Pour ce type de fiabilité absolue, je pense que l’Assemblée n’engage plus une grande différence de coût par rapport au coût.

3
Joshua

Voici une énorme quantité de réponses, mais je vais essayer de résumer mes idées à ce sujet.

Quelque chose qui tombe en panne ou qui ne fonctionne pas correctement peut résulter de vos propres erreurs - il devrait alors être facile à résoudre lorsque vous localisez le problème. Mais il existe également une possibilité de défaillance matérielle - ce qui est difficile, voire impossible, à résoudre globalement.

Je recommanderais d’abord d’essayer d’attraper la situation qui pose problème en se connectant (pile, registres, appels de fonction) - soit en les enregistrant quelque part dans un fichier, soit en les transmettant d’une manière ou d’une autre directement ("oh non - je tombe en panne").

La récupération d’une telle situation d’erreur est soit un redémarrage (si le logiciel est toujours actif), soit une réinitialisation matérielle (par exemple, des horloges de surveillance matérielles). Plus facile de commencer à partir du premier.

Si le problème est lié au matériel, alors la journalisation devrait vous aider à identifier dans quel problème d'appel de fonction se produit et peut vous donner une connaissance approfondie de ce qui ne fonctionne pas et où.

De même, si le code est relativement complexe - il est logique de le "diviser pour mieux le conquérir" - ce qui signifie que vous supprimez/désactivez certains appels de fonction pour lesquels vous soupçonnez que le problème est - en désactivant généralement la moitié du code et en activant une autre moitié - vous pouvez obtenir un "ça marche"/"ne fonctionne pas" type de décision après laquelle vous pouvez vous concentrer sur une autre moitié du code. (Où est le problème)

Si un problème survient après un certain temps (on peut alors suspecter un dépassement de capacité de la pile), il est préférable de surveiller les registres de points de la pile, s'ils sont en augmentation constante.

Et si vous parvenez à minimiser complètement votre code jusqu'à ce que le type d'application "hello world" - et qu'il échoue toujours de manière aléatoire - des problèmes matériels sont attendus - et il doit y avoir une "mise à niveau matérielle" - ce qui signifie inventer ce type de processeur/... combinaison de matériel qui tolérerait mieux les radiations.

La chose la plus importante est probablement de savoir comment récupérer vos journaux si la machine est complètement arrêtée/réinitialisée/ne fonctionne pas - probablement la première chose à faire pour bootstap - est de rentrer chez soi si une situation problématique est découverte.

Si votre environnement peut également transmettre un signal et recevoir une réponse, vous pouvez essayer de créer une sorte d’environnement de débogage à distance en ligne, mais vous devez disposer au moins de supports de communication en état de fonctionnement et de processeurs/béliers en état de fonctionnement. Et par débogage à distance, je veux dire soit une approche de type souche GDB/gdb, soit votre propre implémentation de ce dont vous avez besoin pour revenir de votre application (par exemple, téléchargez les fichiers journaux, téléchargez la pile d'appels, téléchargez la RAM, redémarrez).

1
TarmoPikaro

J'ai vraiment lu beaucoup de bonnes réponses!

Voici mon 2 cent: 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 des registres. En outre, créez un émulateur, à la manière d'une machine virtuelle, sur lequel 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 a un certain taux d'échec, ce qui n'empêche pas le travail quotidien.

0
user9016329