c++ - Compiler une application pour une utilisation dans des environnements hautement radioactifs




gcc embedded fault-tolerance (20)

I've really read a lot of great answers!

Voici mes 2 cents: construire un modèle statistique de l'anomalie de mémoire / registre, en écrivant un logiciel pour vérifier la mémoire ou pour effectuer des comparaisons de registre fréquentes. En outre, créez un émulateur, dans le style d'une machine virtuelle où vous pouvez expérimenter avec le problème. Je suppose que si vous changez la taille de jonction, la fréquence d'horloge, le fournisseur, le boîtier, etc. observeraient un comportement différent.

Même notre mémoire de PC de bureau a un certain taux d'échec, qui cependant n'altère pas le travail quotidien.

Nous compilons une application C / C ++ embarquée qui est déployée dans un dispositif 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 à la compilation qui peuvent être faites pour identifier / corriger les erreurs logiques et la corruption de la mémoire causée par des événements uniques ? Est-ce que d'autres développeurs ont réussi à réduire les effets néfastes des erreurs logicielles sur une application de longue durée?


One point no-one seems to have mentioned. You say you're developing in GCC and cross-compiling onto ARM. How do you know that you don't have code which makes assumptions about free RAM, integer size, pointer size, how long it takes to do a certain operation, how long the system will run for continuously, or various stuff like that? This is a very common problem.

The answer is usually automated unit testing. Write test harnesses which exercise the code on the development system, then run the same test harnesses on the target system. Look for differences!

Also check for errata on your embedded device. You may find there's something about "don't do this because it'll crash, so enable that compiler option and the compiler will work around it".

In short, your most likely source of crashes is bugs in your code. Until you've made pretty damn sure this isn't the case, don't worry (yet) about more esoteric failure modes.


Voici quelques pensées et idées:

Utilisez la ROM de manière plus créative.

Rangez tout ce que vous pouvez en ROM. Au lieu de calculer des choses, stockez les tables de consultation dans la ROM. (Assurez-vous que votre compilateur sort vos tables de consultation dans la section en lecture seule! Imprimez les adresses de la mémoire à l'exécution pour vérifier!) Stockez votre table de vecteurs d'interruption dans la ROM. Bien sûr, effectuez des tests pour voir à quel point votre ROM est fiable par rapport à votre RAM.

Utilisez votre meilleure RAM pour la pile.

Les UES dans la pile sont probablement la source la plus probable de plantages, car c'est là que vivent généralement les variables d'index, les variables d'état, les adresses de retour et les pointeurs de différentes sortes.

Implémentez les routines timer-tick et watchdog timer.

Vous pouvez exécuter une routine "vérification de cohérence" à chaque coche de la minuterie, ainsi qu'une routine de surveillance pour gérer le verrouillage du système. Votre code principal pourrait également incrémenter périodiquement un compteur pour indiquer la progression, et la routine de vérification de la santé mentale pourrait garantir que cela s'est produit.

Implémenter des error-correcting-codes dans le logiciel.

Vous pouvez ajouter de la redondance à vos données pour pouvoir détecter et / ou corriger les erreurs. Cela ajoutera du temps de traitement, ce qui pourrait exposer le processeur à des radiations plus longtemps, ce qui augmentera le risque d'erreurs. Vous devez donc envisager le compromis.

Rappelez-vous les caches.

Vérifiez la taille de vos caches CPU. Les données que vous avez récemment consultées ou modifiées seront probablement dans un cache. Je crois que vous pouvez désactiver au moins une partie des caches (à un coût de performance élevé); Vous devriez essayer ceci pour voir à quel point les caches sont sensibles aux SEU. Si les caches sont plus robustes que la RAM, vous pouvez lire et réécrire régulièrement 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 de mémoire comme non présente, la CPU émet une erreur de page lorsque vous essayez d'y accéder. Vous pouvez créer un gestionnaire d'erreurs de page qui effectue une vérification avant de traiter 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 (ce qui pourrait être tout).

Avec le langage d'assemblage, vous savez ce qu'il y a dans les registres et ce qui est dans la RAM; Vous savez quelles tables RAM spéciales sont utilisées par le processeur et vous pouvez concevoir les choses de manière détournée pour réduire les risques.

Utilisez objdump pour examiner le langage assembleur généré et déterminer le objdump de code de chacune de vos routines.

Si vous utilisez un gros système d'exploitation comme Linux, vous demandez des problèmes; il y a tellement de complexité et tant de choses qui vont mal.

Rappelez-vous que c'est un jeu de probabilités.

Un commentateur a dit

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

Tandis que ceci est vrai, les chances d'erreurs dans le (disons) 100 octets de code et de données requis pour qu'une routine de contrôle fonctionne correctement est beaucoup plus petite que le risque d'erreurs ailleurs. Si votre ROM est assez fiable et presque tout le code / données est réellement dans ROM, 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 lequel a été compromis.


Vous pouvez également être intéressé par la riche littérature sur le sujet de la tolérance aux fautes algorithmique. Cela inclut l'ancienne affectation: écrire une sorte qui trie correctement son entrée lorsqu'un nombre constant de comparaisons échouera (ou, la version un peu plus mauvaise, lorsque le nombre asymptotique de comparaisons échouées sera log(n) pour n comparaisons).

Un endroit pour commencer à lire est le document de 1984 de Huang et Abraham " Algorithm-Based Fault Tolerance for Matrix Operations ". Leur idée est vaguement similaire au calcul crypté homomorphique (mais ce n'est pas vraiment la même chose, puisqu'ils tentent la détection / correction d'erreur au niveau de l'opération).

Un descendant plus récent de cet article est la « tolérance aux fautes basée sur l'algorithme appliquée par Bosilca, Delmas, Dongarra et Langou au calcul haute performance ».


Use a cyclic scheduler . This gives you the ability to add regular maintenance times to check the correctness of critical data. The biggest problem often is corruption of the stack. If your software is cyclical you can reinitialise the stack between cycles. Do not reuse the stacks for interrupt calls, setup a separate stack of each important interrupt call.

Similar to the Watchdog concept is deadline timers.Start a hardware timer before calling a function. If the function does not return before the deadline reload the stack and try again. If it still fails after 3/5 tries you need reload from ROM.

Slit your software into parts and isolate these parts (Especially in a control environment). Example: signal acquisition, prepossessing data, main algorithm and result implementation/transmission. This means a failure in one part will not cause failures through the rest of the program.

Everything needs CRCs. If you execute out of RAM even your .text needs a CRC. Check the CRCs regularly if you using a cyclical scheduler. Some compilers (not GCC) can generate CRCs for each section and some processors have dedicated hardware to do CRC calculations, but I guess that would fall out side of the scope of your question.


Someone mentioned using slower chips to prevent ions from flipping bits as easily. In a similar fashion perhaps use a specialized cpu/ram that actually uses multiple bits to store a single bit. Thus providing a hardware fault tolerance because it would be very unlikely that all of the bits would get flipped. So 1 = 1111 but would need to get hit 4 times to actually flipped. (4 might be a bad number since if 2 bits get flipped its already ambiguous). So if you go with 8, you get 8 times less ram and some fraction slower access time but a much more reliable data representation. You could probably do this both on the software level with a specialized compiler(allocate x amount more space for everything) or language implementation (write wrappers for data structures that allocate things this way). Or specialized hardware that has the same logical structure but does this in the firmware.


Cette réponse suppose que vous êtes soucieux d'avoir un système qui fonctionne correctement, en plus d'avoir un système qui est un coût minimum ou rapide; la plupart des gens qui jouent avec des choses radioactives apprécient l'exactitude / la sécurité par rapport à la vitesse / au coût

Plusieurs personnes ont suggéré des changements de matériel que vous pouvez faire (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), et d'autres ont suggéré la redondance (excellent en principe), mais je ne pense pas n'importe qui a suggéré comment cette redondance pourrait fonctionner dans la pratique. Comment échouez-vous? Comment savez-vous quand quelque chose a «mal tourné»? De nombreuses technologies fonctionnent sur la base de tout ce qui fonctionne, et l'échec est donc une chose délicate à gérer. Cependant, certaines technologies de calcul distribué conçues pour l'échelle attendent une défaillance (après tout, avec une échelle suffisante, la défaillance d'un nœud de plusieurs est inévitable avec tout 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 chaque autre élément matériel. Ethernet est un moyen évident de le faire, mais il existe de nombreuses autres routes beaucoup plus simples qui donneraient une meilleure protection (par exemple CAN). Minimiser les composants communs (même les alimentations). Cela peut vouloir dire échantillonner des entrées ADC à plusieurs endroits, par exemple.

  • Assurez-vous que l'état de votre application est dans un seul endroit, par exemple dans une machine à états finis. Cela peut être entièrement basé sur la RAM, mais n'exclut pas le stockage stable. Il sera ainsi stocké à plusieurs endroits.

  • Adopter 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 seront effectué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 faire un vous-même, sinon tout votre bon travail sur la redondance sera gaspillé lorsque le protocole de quorum se bloque.

  • Assurez-vous de la somme de contrôle (par exemple CRC / SHA) votre FSM, et stockez le CRC / SHA dans le FSM lui-même (ainsi que la transmission dans le message, et la vérification des messages eux-mêmes). Obtenez les noeuds pour vérifier leur FSM régulièrement contre cette somme de contrôle, somme de contrôle des messages entrants, et vérifiez leur somme de contrôle correspond à la somme de contrôle du quorum.

  • Construisez autant de vérifications internes que possible sur votre système, en faisant redémarrer les nœuds qui détectent leur propre défaillance (cela vaut mieux que de continuer à travailler à moitié si vous avez suffisamment de nœuds). Essayez de les laisser se retirer proprement du quorum pendant le redémarrage au cas où ils ne reviendraient pas. Au redémarrage, donnez-leur la somme de contrôle de l'image logicielle (et toute autre chose qu'ils chargent) et faites un test de RAM complet avant de se réintroduire dans le quorum.

  • Utilisez le matériel pour vous soutenir, mais faites-le avec soin. Vous pouvez obtenir de 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 (de mémoire) la RAM statique est beaucoup plus tolérante aux rayonnements ionisants que la mémoire DRAM en premier lieu, donc il peut être préférable d'utiliser la DRAM statique à la place. Voir aussi 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 prétendons que vous pouvez rendre les échecs totalement indépendants. Avec 5 nœuds, vous aurez besoin de trois pour échouer en un jour, ce qui représente une probabilité de .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 une préoccupation, un gros bloc de métal autour de votre appareil va être une solution beaucoup moins chère et plus fiable qu'une équipe de programmeurs peut imaginer. Idem le couplage optique des entrées d'EMI est un problème, etc. Quoi qu'il en soit, essayez de vous approvisionner pour trouver les composants les mieux notés contre les rayonnements ionisants.

  • Lancez vos propres algorithmes . Les gens ont déjà fait ça avant. Utilisez leur travail. La tolérance aux pannes et les algorithmes distribués sont difficiles. Utilisez le travail des autres si possible.

  • Utilisez des paramètres de compilation compliqués dans l'espoir naïf que vous 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 qui n'ont pas été 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 que leur HA fonctionne correctement, ce qui leur fait manquer de nombreux modes de défaillance. Vous êtes dans la position «chanceuse» d'avoir des échecs fréquents sur demande. Donc tester chaque technique, et s'assurer que son application réelle améliore le MTBF d'un montant qui dépasse la complexité de l'introduire (avec la complexité des bugs). Surtout appliquez cela à mes conseils sur les algorithmes de quorum, etc.


Given supercat's comments, the tendencies of modern compilers, and other things, I'd be tempted to go back to the ancient days and write the whole code in assembly and static memory allocations everywhere. For this kind of utter reliability I think assembly no longer incurs a large percentage difference of the cost.


Il peut être possible d'utiliser C pour écrire des programmes qui se comportent de manière robuste dans de tels environnements, mais seulement si la plupart des formes d'optimisation du compilateur sont désactivées. Les compilateurs d'optimisation sont conçus pour remplacer plusieurs modèles de codage apparemment redondants par des modèles "plus efficaces", et n'ont aucune idée de la raison pour laquelle le programmeur teste x==42 lorsque le compilateur sait qu'il est impossible de tenir quoi que ce soit d'autre. le programmeur veut empêcher l'exécution de certain code avec x tenant une autre valeur - même dans les cas où la seule façon dont il pourrait tenir cette valeur serait si le système a reçu une sorte de pépin électrique.

Déclarer les variables comme 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 aient des verrouillages matériels nécessitant plusieurs étapes d'activation, et que le code soit écrit à l'aide du modèle:

... 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 manière relativement littérale, et si toutes les vérifications de l'état du système sont répétées après prepare_for_activation() , le système peut être robuste contre presque tous les événements plausibles, même ceux qui corrompraient arbitrairement le compteur de programme. empiler. 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 pas d'autre raison prepare_for_activation() laquelle prepare_for_activation() aurait été appelée avant le pépin). Si le glitch amène le code à atteindre prepare_for_activation() manière inappropriée, mais qu'il n'y a pas d'évènement glitch, il ne trigger_activation() atteindre trigger_activation() sans avoir passé le contrôle de validation ou appelé cancel_preparations en premier [si la pile pépille, l'exécution peut se dérouler 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 ce dernier 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 des 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 serait de détecter et de nettoyer après des échecs peut, dans certains cas, aggraver la situation. 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.


If your hardware fails then you can use mechanical storage to recover it. If your code base is small and have some physical space then you can use a mechanical data store.

There will be a surface of material which will not be affected by radiation. Multiple gears will be there. A mechanical reader will run on all the gears and will be flexible to move up and down. Down means it is 0 and up means it is 1. From 0 and 1 you can generate your code base.


Disclaimer: Je ne suis pas un professionnel de la radioactivité et je n'ai pas travaillé pour ce genre d'application. Mais j'ai travaillé sur les erreurs logicielles et la redondance pour l'archivage à long terme des données critiques, ce qui est quelque peu lié (même problème, objectifs différents).

Le principal problème avec la radioactivité à mon avis est que la radioactivité peut changer de bits, ainsi la radioactivité peut altérer / altérer n'importe quelle mémoire numérique . Ces erreurs sont généralement appelées erreurs souples , pourriture des bits, etc.

La question est alors: comment calculer de manière fiable quand votre mémoire n'est pas fiable?

Pour réduire significativement le taux d'erreurs logicielles (au détriment de la surcharge informatique, car il s'agira principalement de solutions logicielles), vous pouvez:

  • Comptez sur le bon vieux schéma de redondance , et plus particulièrement sur les codes de correction d'erreurs les plus efficaces (même but, mais algorithmes plus intelligents pour récupérer plus de bits avec moins de redondance). C'est parfois (à tort) aussi appelé checksumming. Avec ce type de solution, vous devrez stocker l'état complet de votre programme à tout moment dans une variable / classe maître (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 peut, ou il ne fonctionnera pas, 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'obtenez pas de faux résultats).

  • ou vous pouvez utiliser des structures de données algorithmiques résilientes , qui garantissent, jusqu'à un certain point, 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, parce que le schéma de résilience est étroitement lié à la structure, de sorte que vous n'avez pas besoin d'encoder des procédures supplémentaires pour vérifier l'ECC, et ils sont généralement beaucoup plus rapides. Ces structures fournissent un moyen de s'assurer que votre programme fonctionnera dans n'importe quelle condition, 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 plus de sécurité (ou coder vos structures de données les plus importantes comme résilientes, et le reste, les données non récupérables que vous pouvez recalculer à partir des structures de données principales). un peu d'ECC ou un contrôle de parité qui est très rapide à calculer).

Si vous êtes intéressé par les structures de données résilientes (ce qui est un 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 d'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 additive. Dans Algorithmes et structures de données (pp. 243-254). Springer Berlin Heidelberg.

  • Ferraro-Petrillo, U., Grandoni, F., & Italiano, GF (2013). Data structures resilient to memory faults: an experimental study of dictionaries. Journal of Experimental Algorithmics (JEA), 18, 1-6.

  • Italiano, GF (2010). Resilient algorithms and data structures. In Algorithms and Complexity (pp. 13-24). Springer Berlin Heidelberg.

If you are interested in knowing more about the field of resilient data structures, you can checkout the works of Giuseppe F. Italiano (and work your way through the refs) and the Faulty-RAM model (introduced in Finocchi et al. 2005; Finocchi and Italiano 2008).

/EDIT: I illustrated the prevention/recovery from soft-errors mainly for RAM memory and data storage, but I didn't talk about computation (CPU) errors . Other answers already pointed at using atomic transactions like in databases, so I will propose another, simpler scheme: redundancy and majority vote .

The idea is that you simply do x times the same computation for each computation you need to do, and store the result in x different variables (with x >= 3). You can then compare your x variables :

  • if they all agree, then there's no computation error at all.
  • if they disagree, then you can use a majority vote to get the correct value, and since this means the computation was partially corrupted, you can also trigger a system/program state scan to check that the rest is ok.
  • if the majority vote cannot determine a winner (all x values are different), then it's a perfect signal for you to trigger the failsafe procedure (reboot, raise an alert to user, etc.).

This redundancy scheme is very fast compared to ECC (practically O(1)) and it provides you with a clear signal when you need to failsafe . The majority vote is also (almost) guaranteed to never produce corrupted output and also to recover from minor computation errors , because the probability that x computations give the same output is infinitesimal (because there is a huge amount of possible outputs, it's almost impossible to randomly get 3 times the same, even less chances if x > 3).

So with majority vote you are safe from corrupted output, and with redundancy x == 3, you can recover 1 error (with x == 4 it will be 2 errors recoverable, etc. -- the exact equation is nb_error_recoverable == (x-2) where x is the number of calculation repetitions because you need at least 2 agreeing calculations to recover using the majority vote).

The drawback is that you need to compute x times instead of once, so you have an additional computation cost, but's linear complexity so asymptotically you don't lose much for the benefits you gain. A fast way to do a majority vote is to compute the mode on an array, but you can also use a median filter.

Also, if you want to make extra sure the calculations are conducted correctly, if you can make your own hardware you can construct your device with x CPUs, and wire the system so that calculations are automatically duplicated across the x CPUs with a majority vote done mechanically at the end (using AND/OR gates for example). This is often implemented in airplanes and mission-critical devices (see triple modular redundancy ). This way, you would not have any computational overhead (since the additional calculations will be done in parallel), and you have another layer of protection from soft errors (since the calculation duplication and majority vote will be managed directly by the hardware and not by software -- which can more easily get corrupted since a program is simply bits stored in memory...).


What you ask is quite complex topic - not easily answerable. Other answers are ok, but they covered just a small part of all the things you need to do.

As seen in comments , it is not possible to fix hardware problems 100%, however it is possible with high probabily to reduce or catch them using various techniques.

If I was you, I would create the software of the highest Safety integrity level level (SIL-4). Get the IEC 61513 document (for the nuclear industry) and follow it.


Here are huge amount of replies, but I'll try to sum up my ideas about this.

Something crashes or does not work correctly could be result of your own mistakes - then it should be easily to fix when you locate the problem. But there is also possibility of hardware failures - and that's difficult if not impossible to fix in overall.

I would recommend first to try to catch the problematic situation by logging (stack, registers, function calls) - either by logging them somewhere into file, or transmitting them somehow directly ("oh no - I'm crashing").

Recovery from such error situation is either reboot (if software is still alive and kicking) or hardware reset (eg hw watchdogs). Easier to start from first one.

If problem is hardware related - then logging should help you to identify in which function call problem occurs and that can give you inside knowledge of what is not working and where.

Also if code is relatively complex - it makes sense to "divide and conquer" it - meaning you remove / disable some function calls where you suspect problem is - typically disabling half of code and enabling another half - you can get "does work" / "does not work" kind of decision after which you can focus into another half of code. (Where problem is)

If problem occurs after some time - then can be suspected - then it's better to monitor stack point registers - if they constantly grows.

And if you manage to fully minimize your code until "hello world" kind of application - and it's still failing randomly - then hardware problems are expected - and there needs to be "hardware upgrade" - meaning invent such cpu / ram / ... -hardware combination which would tolerate radiation better.

Most important thing is probably how you get your logs back if machine fully stopped / resetted / does not work - probably first thing bootstap should do - is a head back home if problematic situation is entcovered.

If it's possible in your environment also to transmit a signal and receive response - you could try out to construct some sort of online remote debugging environment, but then you must have at least of communication media working and some processor/ some ram in working state. And by remote debugging I mean either GDB / gdb stub kind of approach or your own implementation of what you need to get back from your application (eg download log files, download call stack, download ram, restart)


La NASA a un document sur les logiciels durcis aux rayonnements . Il décrit trois tâches principales:

  1. Surveillance régulière de la mémoire pour détecter les erreurs, puis éliminer ces erreurs,
  2. mécanismes robustes de récupération d'erreur, et
  3. la possibilité de reconfigurer si quelque chose ne fonctionne plus.

Notez que le taux de balayage de la mémoire doit être suffisamment fréquent pour que les erreurs multi-bits se produisent rarement, car la plupart des mémoires ECC peuvent récupérer à partir d'erreurs à un seul bit, et non d'erreurs à plusieurs bits.

La reprise d'erreur robuste inclut le transfert de flux de contrôle (généralement le redémarrage d'un processus à un point situé avant l'erreur), la libération de ressources et la restauration de données.

Leur principale recommandation pour la restauration de données est d'en éviter le besoin, en faisant en sorte que les données intermédiaires soient traitées comme temporaires, de sorte qu'un redémarrage avant l'erreur ramène également les données à un état fiable. Cela ressemble à la notion 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 des logiciels pour des objets 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.

C'est un sujet extrêmement large. Fondamentalement, vous ne pouvez pas vraiment récupérer de la corruption de la 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 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 voyez une discordance, il est temps de réinitialiser ou de réinitialiser.

  • stocker les variables avec redondance . Si vous avez une variable importante x , écrivez sa valeur en x1 , x2 et x3 et lisez-la en tant que (x1 == x2) ? x2 : x3 (x1 == x2) ? x2 : x3 .

  • mettre en œuvre la surveillance du flux de programme . XOR un drapeau global avec une valeur unique dans les fonctions / branches importantes appelées de la boucle principale. L'exécution du programme dans un environnement sans rayonnement avec une couverture de test de près de 100% devrait vous donner la liste des valeurs acceptables du drapeau à la fin du cycle. Réinitialiser si vous voyez des écarts.

  • surveiller le pointeur de la pile . Au début de la boucle principale, comparez le pointeur de pile avec sa valeur attendue. Réinitialiser sur l'écart.


Ce qui pourrait vous aider est un watchdog . Les chiens de garde ont été largement utilisés dans l'informatique industrielle dans les années 1980. Les pannes matérielles étaient alors beaucoup plus fréquentes - 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 (disons 1023) à zéro. TTL ou autre logique pourrait être utilisé.

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 = 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. Dans le cas où 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 à compter à nouveau.

Ce chien de garde s'assure que l'ordinateur connecté est redémarré dans un grand nombre de cas d'échec. Je dois admettre que je ne suis pas familier avec le matériel qui est capable d'effectuer une telle fonction sur les ordinateurs d'aujourd'hui. Les interfaces avec le matériel externe sont maintenant beaucoup plus complexes qu'elles ne l'étaient 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 chien de garde atteigne le temps de redémarrage zéro +. Bien que cette durée soit généralement beaucoup plus courte que toute intervention externe ou humaine, l'équipement pris en charge devra être en mesure de procéder sans contrôle informatique pour cette période.


You want 3+ slave machines with a master outside the radiation environment. All I/O passes through the master which contains a vote and/or retry mechanism. The slaves must have a hardware watchdog each and the call to bump them should be surrounded by CRCs or the like to reduce the probability of involuntary bumping. Bumping should be controlled by the master, so lost connection with master equals reboot within a few seconds.

One advantage of this solution is that you can use the same API to the master as to the slaves, so redundancy becomes a transparent feature.

Edit: From the comments I feel the need to clarify the "CRC idea." The possibilty of the slave bumping it's own watchdog is close to zero if you surround the bump with CRC or digest checks on random data from the master. That random data is only sent from master when the slave under scrutiny is aligned with the others. The random data and CRC/digest are immediately cleared after each bump. The master-slave bump frequency should be more than double the watchdog timeout. The data sent from the master is uniquely generated every time.


Perhaps it would help to know does it mean for the hardware to be "designed for this environment". How does it correct and/or indicates the presence of SEU errors ?

At one space exploration related project, we had a custom MCU, which would raise an exception/interrupt on SEU errors, but with some delay, ie some cycles may pass/instructions be executed after the one insn which caused the SEU exception.

Particularly vulnerable was the data cache, so a handler would invalidate the offending cache line and restart the program. Only that, due to the imprecise nature of the exception, the sequence of insns headed by the exception raising insn may not be restartable.

We identified the hazardous (not restartable) sequences (like lw $3, 0x0($2) , followed by an insn, which modifies $2 and is not data-dependent on $3 ), and I made modifications to GCC, so such sequences do not occur (eg as a last resort, separating the two insns by a nop ).

Just something to consider ...


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 devraient être présentes sur tout système embarqué semi-professionnel: chien de garde interne, détecteur de basse tension interne, moniteur d'horloge interne. Ces choses ne devraient même pas être mentionnées en 2016 et elles sont standard sur presque tous les microcontrôleurs modernes.
  • Si vous avez un MCU de sécurité et / ou orienté vers l'automobile, il aura certaines fonctions de surveillance, telles qu'une fenêtre temporelle donnée, à l'intérieur desquelles vous devez actualiser le chien de garde. Ceci est préférable si vous avez un système temps réel critique.
  • En général, utilisez un microcontrôleur adapté à ce type de système, et non une peluche générique que vous avez reçue dans un paquet de flocons de maïs. Presque tous les fabricants de microcontrôleurs ont des microcontrôleurs spécialisés conçus pour des applications de sécurité (TI, Freescale, Renesas, ST, Infineon, etc.). Ceux-ci ont beaucoup de fonctionnalités de sécurité intégrées, y compris les cœurs de verrouillage: ce qui signifie qu'il y a 2 cœurs de processeur exécutant le même code, et qu'ils doivent être en accord les uns avec les autres.
  • IMPORTANT: Vous devez vous assurer de l'intégrité des registres MCU internes. Tous les registres de contrôle et d'état des périphériques matériels qui sont inscriptibles peuvent être situés dans la mémoire RAM, et sont donc vulnérables.

    Pour vous protéger contre les corruptions de registre, de préférence, choisissez un microcontrôleur avec des fonctions intégrées "en écriture seule" des registres. En outre, vous devez stocker les valeurs par défaut de tous les registres matériels dans NVM et copier ces valeurs sur vos registres à intervalles réguliers. Vous pouvez assurer 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 du 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: checksums, "modèles de marche", ECC logiciel, etc. La meilleure solution est de ne pas en utiliser, mais d'utiliser un MCU avec ECC intégré et contrôles similaires. Parce que faire cela dans un logiciel est complexe, et la vérification d'erreur 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. Chaque segment peut avoir une somme de contrôle CRC attachée.
  • Évitez d'utiliser des mémoires externes à l'extérieur de la MCU.
  • Implémentez une routine de service d'interruption par défaut / un gestionnaire d'exceptions 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 sauf fermer 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. Examples

    Le microprogramme de haute qualité critique détecte autant d'erreurs que possible, puis les ignore de manière sûre.

  • N'écrivez jamais de programmes qui reposent sur un comportement mal spécifié. Il est probable qu'un tel comportement puisse changer radicalement avec des changements matériels inattendus causés par des radiations ou des interférences électromagnétiques. La meilleure façon de s'assurer que votre programme est libre de telles conneries est d'utiliser une norme de codage comme MISRA, avec un outil d'analyse statique. Cela aidera également à la programmation défensive et à l'élimination des bogues (pourquoi ne voudriez-vous pas détecter les bogues dans n'importe quel type d'application?).
  • IMPORTANT: n'implémentez pas les valeurs par défaut des variables de durée de stockage statique. C'est-à-dire, ne faites pas confiance au contenu par défaut du .data ou .bss . Il pourrait y avoir un laps de temps entre le moment de l'initialisation et le moment où la variable est réellement utilisée, il y aurait eu beaucoup de temps pour que la RAM soit corrompue. Au lieu de cela, écrivez le programme afin que toutes ces variables soient définies à partir de NVM en cours d'exécution, juste avant l'heure à laquelle 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 static , vous ne devriez jamais utiliser = pour l'initialiser (ou vous pourriez, mais cela ne sert à rien, car vous ne pouvez pas compter sur la valeur de toute façon). Réglez-le toujours en cours d'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 comptez pas sur les constructeurs pour les variables de durée de stockage statique. Demandez au (x) constructeur (s) d'appeler une routine "d'installation" publique, que vous pourrez également appeler plus tard en cours d'exécution, directement à partir de l'application de l'appelant.

    Si possible, supprimez le code de démarrage "copy-down" qui initialise .data et .bss (et appelle les constructeurs C ++) entièrement, afin que vous obteniez des erreurs de l'éditeur de liens si vous écrivez du code en s'appuyant dessus. 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 dépendance.

  • Implémentez et définissez un état de sécurité pour le programme, à l'endroit où vous allez revenir en cas d'erreurs critiques.

  • La mise en œuvre d'un rapport d'erreur / système de journal des erreurs est toujours utile.

vecteur est un modèle

donc le type de template avec int a un typedef membre appelé size_type . x est défini comme une variable de ce type.





c++ c gcc embedded fault-tolerance