c++ c gcc embedded fault-tolerance

c++ - Compilar una aplicación para usar en entornos altamente radiactivos



gcc embedded (23)

¡Realmente he leído muchas respuestas geniales!

Aquí está mi 2 centavo: construya un modelo estadístico de la anormalidad de registro / memoria, escribiendo un software para verificar la memoria o realizar comparaciones de registro frecuentes. Además, cree un emulador, al estilo de una máquina virtual donde pueda experimentar con el problema. Supongo que si varía el tamaño de la unión, la frecuencia del reloj, el proveedor, la carcasa, etc., observaría un comportamiento diferente.

Incluso la memoria de nuestra PC de escritorio tiene un cierto índice de falla, que sin embargo no afecta el trabajo diario.

Estamos compilando una aplicación C / C ++ integrada que se implementa en un dispositivo blindado en un entorno bombardeado con radiación ionizante . Estamos utilizando GCC y compilación cruzada para ARM. Cuando se implementa, nuestra aplicación genera algunos datos erróneos y se bloquea con más frecuencia de la que nos gustaría. El hardware está diseñado para este entorno, y nuestra aplicación se ha ejecutado en esta plataforma durante varios años.

¿Hay cambios que podamos hacer en nuestro código, o mejoras en el tiempo de compilación que se puedan hacer para identificar / corregir errores de software y daños en la memoria causados ​​por alteraciones de eventos únicos ? ¿Han tenido éxito otros desarrolladores para reducir los efectos nocivos de los errores de software en una aplicación de larga ejecución?


¿Qué tal si ejecuta muchas instancias de su aplicación? Si los bloqueos se deben a cambios aleatorios de bits de memoria, es probable que algunas de las instancias de su aplicación lo logren y produzcan resultados precisos. Probablemente sea bastante fácil (para alguien con antecedentes estadísticos) calcular cuántas instancias necesita dada la probabilidad de flop de bits para lograr un error general tan pequeño como desee.


Alguien mencionó el uso de chips más lentos para evitar que los iones muevan los bits con la misma facilidad. De manera similar, tal vez use una CPU / RAM especializada que en realidad usa múltiples bits para almacenar un solo bit. Por lo tanto, proporciona una tolerancia a fallas de hardware porque sería muy poco probable que todos los bits se voltearan. Entonces 1 = 1111, pero necesitaría ser golpeado 4 veces para realmente voltearse. (4 podría ser un número incorrecto ya que si se voltean 2 bits ya es ambiguo). Por lo tanto, si elige 8, obtendrá 8 veces menos RAM y una fracción de tiempo de acceso más lento, pero una representación de datos mucho más confiable. Probablemente podría hacer esto tanto a nivel de software con un compilador especializado (asignar x cantidad más espacio para todo) o implementación de lenguaje (envoltorios de escritura para estructuras de datos que asignan cosas de esta manera).O hardware especializado que tiene la misma estructura lógica pero lo hace en el firmware.


Aquí hay una gran cantidad de respuestas, pero intentaré resumir mis ideas sobre esto.

Algo que falla o no funciona correctamente podría ser el resultado de sus propios errores, entonces debería ser fácil de solucionar cuando localice el problema. Pero también existe la posibilidad de fallas de hardware, y eso es difícil, si no imposible, de solucionarlo en general.

En primer lugar, recomendaría tratar de detectar la situación problemática iniciando sesión (apilar, registros, llamadas a funciones), ya sea registrándolas en algún lugar en el archivo o transmitiéndolas de alguna manera directamente ("oh no, estoy fallando").

La recuperación de dicha situación de error es reiniciar (si el software sigue vivo y funcionando) o reiniciar el hardware (por ejemplo, hw watchdogs). Más fácil comenzar desde el primero.

Si el problema está relacionado con el hardware, entonces el registro debería ayudarlo a identificar en qué función se produce el problema de llamada y eso puede brindarle un conocimiento interno de lo que no funciona y dónde.

Además, si el código es relativamente complejo, tiene sentido "dividirlo y conquistarlo", lo que significa que elimina / deshabilita algunas llamadas de función donde sospecha que existe un problema, normalmente deshabilita la mitad del código y habilita otra mitad, puede obtener "funciona" / tipo de decisión "no funciona" después de la cual puede concentrarse en otra mitad del código. (Donde está el problema)

Si el problema ocurre después de un tiempo, entonces se puede sospechar el desbordamiento de la pila, entonces es mejor monitorear los registros de puntos de la pila, si crecen constantemente.

Y si logra minimizar completamente su código hasta el tipo de aplicación "hello world", y todavía falla al azar, entonces se esperan problemas de hardware, y debe haber una "actualización de hardware", lo que significa inventar tal cpu / ram / ... combinación de hardware que toleraría mejor la radiación.

Lo más importante es probablemente cómo recuperar sus registros si la máquina se detiene / reinicia por completo / no funciona, probablemente lo primero que debe hacer bootstap, es regresar a casa si se descubre una situación problemática.

Si es posible en su entorno también transmitir una señal y recibir una respuesta, podría intentar construir algún tipo de entorno de depuración remota en línea, pero luego debe tener al menos medios de comunicación funcionando y algún procesador / algún ram en estado de funcionamiento. Y por depuración remota me refiero a cualquier tipo de enfoque GDB / gdb stub o su propia implementación de lo que necesita recuperar de su aplicación (por ejemplo, descargar archivos de registro, descargar pila de llamadas, descargar ram, reiniciar)


Dado que solicita específicamente soluciones de software y está utilizando C ++, ¿por qué no utilizar la sobrecarga del operador para crear sus propios tipos de datos seguros? Por ejemplo:

En lugar de usar uint32_t (y double , int64_t etc.), SAFE_uint32_t cree el suyo que contenga un múltiplo (mínimo de 3) de uint32_t. Sobrecargue todas las operaciones que desea (* + - / << >> = ==! = Etc.) para realizar, y haga que las operaciones sobrecargadas se realicen independientemente en cada valor interno, es decir, no lo haga una vez y copie el resultado. Tanto antes como después, verifique que todos los valores internos coincidan. Si los valores no coinciden, puede actualizar el incorrecto al valor con el más común. Si no hay un valor más común, puede notificar con seguridad que hay un error.

De esta manera, no importa si se produce corrupción en la ALU, los registros, la RAM o en un bus, aún tendrá múltiples intentos y una muy buena posibilidad de detectar errores. Sin embargo, tenga en cuenta que esto solo funciona para las variables que puede reemplazar: su puntero de pila, por ejemplo, seguirá siendo susceptible.

Una historia paralela: me encontré con un problema similar, también en un viejo chip ARM. Resultó ser una cadena de herramientas que usaba una versión antigua de GCC que, junto con el chip específico que usamos, desencadenó un error en ciertos casos extremos que (a veces) corrompían los valores que se pasaban a las funciones. Asegúrese de que su dispositivo no tenga ningún problema antes de echarle la culpa a la radioactividad, y sí, a veces es un error del compilador =)


Descargo de responsabilidad: no soy un profesional de radiactividad ni trabajé para este tipo de aplicación. Pero trabajé en errores suaves y redundancia para el archivo a largo plazo de datos críticos, que están algo vinculados (mismo problema, objetivos diferentes).

El principal problema con la radioactividad en mi opinión es que la radioactividad puede cambiar bits, por lo que la radioactividad puede / alterará cualquier memoria digital . Estos errores generalmente se denominan errores suaves , rotura de bits, etc.

La pregunta es entonces: ¿cómo calcular de manera confiable cuando su memoria no es confiable?

Para reducir significativamente la tasa de errores blandos (a expensas de la sobrecarga computacional, ya que en su mayoría serán soluciones basadas en software), puede:

  • confíe en el viejo esquema de redundancia y, más específicamente, en los códigos de corrección de errores más eficientes (mismo propósito, pero algoritmos más inteligentes para que pueda recuperar más bits con menos redundancia). Esto a veces (erróneamente) también se llama suma de verificación. Con este tipo de solución, tendrá que almacenar el estado completo de su programa en cualquier momento en una variable / clase maestra (¿o una estructura?), Calcular un ECC y verificar que el ECC sea correcto antes de hacer cualquier cosa, y si No, reparar los campos. Sin embargo, esta solución no garantiza que su software pueda funcionar (simplemente que funcionará correctamente cuando pueda, o deja de funcionar si no, porque ECC puede decirle si algo está mal, y en este caso puede detener su software para que pueda no obtengas resultados falsos).

  • o puede usar estructuras de datos algorítmicos resistentes , lo que garantiza, hasta cierto punto, que su programa aún dará resultados correctos incluso en presencia de errores suaves. Estos algoritmos se pueden ver como una combinación de estructuras algorítmicas comunes con esquemas ECC mezclados de forma nativa, pero esto es mucho más resistente que eso, porque el esquema de resiliencia está estrechamente vinculado a la estructura, por lo que no necesita codificar procedimientos adicionales verificar el ECC, y generalmente son mucho más rápidos. Estas estructuras proporcionan una manera de garantizar que su programa funcionará bajo cualquier condición, hasta el límite teórico de los errores suaves. También puede mezclar estas estructuras resilientes con el esquema de redundancia / ECC para seguridad adicional (o codificar sus estructuras de datos más importantes como resistentes, y el resto, los datos prescindibles que puede recalcular desde las estructuras de datos principales,como estructuras de datos normales con un poco de ECC o un control de paridad que es muy rápido de calcular).

Si está interesado en estructuras de datos resistentes (que es un campo nuevo pero emocionante y nuevo en algoritmos e ingeniería de redundancia), le aconsejo que lea los siguientes documentos:

  • Introducción a las estructuras de datos de algoritmos resilientes por Giuseppe F.Italiano, Universita di Roma "Tor Vergata"

  • Christiano, P., Demaine, ED y Kishore, S. (2011). Estructuras de datos tolerantes a fallos sin pérdidas con sobrecarga aditiva. En algoritmos y estructuras de datos (pp. 243-254). Springer Berlin Heidelberg.

  • Ferraro-Petrillo, U., Grandoni, F. y Italiano, GF (2013). Estructuras de datos resistentes a fallas de memoria: un estudio experimental de diccionarios. Journal of Experimental Algorithmics (JEA), 18, 1-6.

  • Italiano, GF (2010). Algoritmos resistentes y estructuras de datos. En Algoritmos y Complejidad (pp. 13-24). Springer Berlin Heidelberg.

Si está interesado en saber más sobre el campo de las estructuras de datos resistentes, puede consultar los trabajos de Giuseppe F. Italiano (y avanzar a través de las referencias) y el modelo Faulty-RAM (introducido en Finocchi et al. 2005; Finocchi y Italiano 2008).

/ EDITAR: ilustré la prevención / recuperación de errores de software principalmente para la memoria RAM y el almacenamiento de datos, pero no hablé sobre errores de computación (CPU) . Otras respuestas ya apuntaban al uso de transacciones atómicas como en las bases de datos, por lo que propondré otro esquema más simple: redundancia y voto mayoritario .

La idea es que simplemente haga x veces el mismo cálculo para cada cálculo que necesita hacer, y almacene el resultado en x variables diferentes (con x> = 3). Luego puede comparar sus variables x :

  • si todos están de acuerdo, entonces no hay ningún error de cálculo.
  • si no están de acuerdo, puede usar un voto mayoritario para obtener el valor correcto, y dado que esto significa que el cálculo se corrompió parcialmente, también puede activar una exploración de estado del sistema / programa para verificar que el resto esté bien.
  • Si el voto mayoritario no puede determinar un ganador (todos los valores de x son diferentes), entonces es una señal perfecta para que usted active el procedimiento a prueba de fallas (reinicio, alerta al usuario, etc.).

Este esquema de redundancia es muy rápido en comparación con ECC (prácticamente O (1)) y le proporciona una señal clara cuando necesita seguridad . También se garantiza (casi) que el voto mayoritario nunca producirá resultados corruptos y también se recuperará de errores menores de cómputo , porque la probabilidad de que los cálculos x den el mismo resultado es infinitesimal (porque hay una gran cantidad de resultados posibles, es casi imposible Obtenga aleatoriamente 3 veces lo mismo, incluso menos posibilidades si x> 3).

Entonces, con el voto mayoritario, está a salvo de la salida corrupta, y con la redundancia x == 3, puede recuperar 1 error (con x == 4 serán 2 errores recuperables, etc. - la ecuación exacta es nb_error_recoverable == (x-2) donde x es el número de repeticiones de cálculo porque necesita al menos 2 cálculos de acuerdo para recuperarse utilizando el voto mayoritario).

El inconveniente es que necesita calcular x veces en lugar de una vez, por lo que tiene un costo de cálculo adicional, pero su complejidad lineal es tan asintótica que no pierde mucho por los beneficios que obtiene. Una forma rápida de hacer un voto mayoritario es calcular el modo en una matriz, pero también puede usar un filtro de mediana.

Además, si desea asegurarse de que los cálculos se realicen correctamente, si puede hacer su propio hardware, puede construir su dispositivo con x CPU y conectar el sistema para que los cálculos se dupliquen automáticamente en las x CPU con un voto mayoritario hecho mecánicamente al final (usando compuertas AND / OR por ejemplo). Esto a menudo se implementa en aviones y dispositivos de misión crítica (ver redundancia modular triple ). De esta forma, no tendría ninguna sobrecarga computacional (ya que los cálculos adicionales se realizarán en paralelo), y tiene otra capa de protección contra errores suaves (ya que la duplicación de cálculos y el voto mayoritario serán administrados directamente por el hardware y no por software, que puede corromperse más fácilmente ya que un programa es simplemente bits almacenados en la memoria ...).


Desea más de 3 máquinas esclavas con un maestro fuera del entorno de radiación. Todas las E / S pasan a través del maestro que contiene un mecanismo de votación y / o reintento. Los esclavos deben tener un perro guardián de hardware cada uno y la llamada para golpearlos debe estar rodeada de CRC o similares para reducir la probabilidad de golpes involuntarios. El maestro debe controlar los golpes, por lo que la conexión perdida con el maestro equivale a reiniciar en unos segundos.

Una ventaja de esta solución es que puede usar la misma API para el maestro que para los esclavos, por lo que la redundancia se convierte en una característica transparente.

Editar: De los comentarios siento la necesidad de aclarar la "idea de CRC". La posibilidad de que el esclavo golpee su propio perro guardián es casi cero si rodea el golpe con CRC o comprueba las verificaciones de datos aleatorios del maestro. Esos datos aleatorios solo se envían desde el maestro cuando el esclavo bajo escrutinio está alineado con los demás. Los datos aleatorios y CRC / resumen se borran inmediatamente después de cada golpe. La frecuencia de golpe maestro-esclavo debe ser más del double del tiempo de espera del watchdog. Los datos enviados desde el maestro se generan de manera única cada vez.


En primer lugar, diseñe su aplicación en torno al fracaso . Asegúrese de que, como parte de la operación de flujo normal, espere restablecerse (dependiendo de su aplicación y del tipo de falla, ya sea suave o dura). Esto es difícil de perfeccionar: las operaciones críticas que requieren cierto grado de transaccionalidad pueden necesitar ser verificadas y ajustadas a nivel de ensamblaje para que una interrupción en un punto clave no pueda dar como resultado comandos externos inconsistentes. Falla rápidamente tan pronto como se detecte cualquier daño irrecuperable en la memoria o desviación del flujo de control. Registro de fallas si es posible.

En segundo lugar, donde sea posible, corrija la corrupción y continúe . Esto significa suma de verificación y arreglo de tablas constantes (y código de programa si puede) a menudo; tal vez antes de cada operación principal o en una interrupción programada, y almacenando variables en estructuras que se autocorrigen (nuevamente antes de cada operación principal o en una interrupción programada, obtenga un voto mayoritario de 3 y corrija si es una desviación única). Registre correcciones si es posible.

En tercer lugar, falla la prueba . Configure un entorno de prueba repetible que invierta bits en la memoria de forma aleatoria. Esto le permitirá replicar situaciones de corrupción y ayudar a diseñar su aplicación en torno a ellas.


Lo que preguntas es un tema bastante complejo, que no responde fácilmente. Otras respuestas están bien, pero cubrieron solo una pequeña parte de todas las cosas que debe hacer.

Como se ve en los comentarios , no es posible solucionar los problemas de hardware al 100%, sin embargo, es posible con una alta probabilidad reducirlos o atraparlos utilizando diversas técnicas.

Si yo fuera usted, crearía el software del nivel más alto de integridad de seguridad (SIL-4). Obtenga el documento IEC 61513 (para la industria nuclear) y sígalo.


Quizás sería útil saber qué significa que el hardware esté "diseñado para este entorno". ¿Cómo corrige y / o indica la presencia de errores de SEU?

En un proyecto relacionado con la exploración espacial, teníamos una MCU personalizada, que generaría una excepción / interrupción en los errores de SEU, pero con cierto retraso, es decir, algunos ciclos pueden pasar / las instrucciones se ejecutarán después de la información que causó la excepción de SEU.

Particularmente vulnerable era el caché de datos, por lo que un controlador invalidaría la línea de caché infractora y reiniciaría el programa. Solo que, debido a la naturaleza imprecisa de la excepción, la secuencia de inss encabezada por la excepción que genera insn puede no ser reiniciable.

Identificamos las secuencias peligrosas (no reiniciables) (como lw $3, 0x0($2) , seguidas de un insn, que modifica $2 y no depende de los datos $3 ), e hice modificaciones en GCC, por lo que tales secuencias no ocurren (por ejemplo, como último recurso, separando el dos insns por a nop ).

Solo algo a considerar ...


Si su hardware falla, puede usar el almacenamiento mecánico para recuperarlo. Si su base de código es pequeña y tiene espacio físico, puede usar un almacén de datos mecánico.

Habrá una superficie de material que no se verá afectada por la radiación. Múltiples engranajes estarán allí. Un lector mecánico funcionará en todos los engranajes y será flexible para moverse hacia arriba y hacia abajo. Abajo significa que es 0 y arriba significa que es 1. De 0 a 1 puede generar su código base.


Teniendo en cuenta los comentarios de supercat, las tendencias de los compiladores modernos y otras cosas, me sentiría tentado a volver a los tiempos antiguos y escribir todo el código en las asambleas y las asignaciones de memoria estática en todas partes. Para este tipo de fiabilidad absoluta, creo que el ensamblaje ya no incurre en una gran diferencia porcentual del costo.


Un punto que nadie parece haber mencionado. Dices que estás desarrollando en GCC y compilando en ARM. ¿Cómo sabe que no tiene un código que haga suposiciones sobre RAM libre, tamaño entero, tamaño del puntero, cuánto tiempo lleva hacer una determinada operación, cuánto tiempo se ejecutará el sistema continuamente o varias cosas como esa? Este es un problema muy común.

La respuesta suele ser la prueba unitaria automatizada. Escriba arneses de prueba que ejerciten el código en el sistema de desarrollo, luego ejecute los mismos arneses de prueba en el sistema de destino. ¡Busca las diferencias!

También verifique si hay erratas en su dispositivo incorporado. Es posible que haya algo acerca de "no hagas esto porque se bloqueará, así que habilita esa opción del compilador y el compilador lo solucionará".

En resumen, su fuente más probable de fallas son los errores en su código. Hasta que te hayas asegurado de que este no sea el caso, no te preocupes (todavía) por los modos de falla más esotéricos.


Use un planificador cíclico . Esto le brinda la capacidad de agregar tiempos de mantenimiento regulares para verificar la exactitud de los datos críticos. El problema más frecuente es la corrupción de la pila. Si su software es cíclico, puede reiniciar la pila entre ciclos. No reutilice las pilas para las llamadas de interrupción, configure una pila separada de cada llamada de interrupción importante.

Similar al concepto de Watchdog son los temporizadores de fecha límite. Inicie un temporizador de hardware antes de llamar a una función. Si la función no regresa antes de que el temporizador de la fecha límite interrumpa, vuelva a cargar la pila e intente nuevamente. Si aún falla después de 3/5 intentos, necesita recargar desde la ROM.

Divida su software en partes y aísle estas partes para usar áreas de memoria separadas y tiempos de ejecución (especialmente en un entorno de control). Ejemplo: adquisición de señal, datos de preposesión, algoritmo principal e implementación / transmisión de resultados. Esto significa que una falla en una parte no causará fallas en el resto del programa. Entonces, mientras reparamos la adquisición de la señal, el resto de las tareas continúan con datos obsoletos.

Todo necesita CRC. Si ejecuta fuera de RAM, incluso su .text necesita un CRC. Verifique los CRC regularmente si usa un programador cíclico. Algunos compiladores (no GCC) pueden generar CRC para cada sección y algunos procesadores tienen hardware dedicado para hacer cálculos de CRC, pero supongo que eso quedaría fuera del alcance de su pregunta. La comprobación de CRC también solicita al controlador ECC en la memoria que repare los errores de un solo bit antes de que se convierta en un problema.


Aquí hay algunos pensamientos e ideas:

Usa ROM más creativamente.

Almacene todo lo que pueda en ROM. En lugar de calcular cosas, almacene tablas de búsqueda en ROM. (¡Asegúrese de que su compilador envíe sus tablas de búsqueda a la sección de solo lectura! ¡Imprima las direcciones de memoria en tiempo de ejecución para verificar!) Guarde su tabla de vectores de interrupción en la ROM. Por supuesto, ejecute algunas pruebas para ver qué tan confiable es su ROM en comparación con su RAM.

Usa tu mejor RAM para la pila.

Los SEU en la pila son probablemente la fuente más probable de bloqueos, porque es donde viven típicamente variables de índice, variables de estado, direcciones de retorno y punteros de varios tipos.

Implemente las rutinas de temporizador de tic-tac y watchdog.

Puede ejecutar una rutina de "verificación de la cordura" cada vez que se active el temporizador, así como una rutina de vigilancia para manejar el bloqueo del sistema. Su código principal también podría incrementar periódicamente un contador para indicar el progreso, y la rutina de verificación de cordura podría asegurar que esto haya ocurrido.

Implemente error-correcting-codes en el software.

Puede agregar redundancia a sus datos para poder detectar y / o corregir errores. Esto agregará tiempo de procesamiento, lo que podría dejar al procesador expuesto a la radiación durante más tiempo, lo que aumenta la posibilidad de errores, por lo que debe considerar la compensación.

Recuerda los escondites.

Verifique los tamaños de sus cachés de CPU. Los datos a los que ha accedido o modificado recientemente probablemente estarán dentro de un caché. Creo que puede deshabilitar al menos algunas de las cachés (a un alto costo de rendimiento); deberías probar esto para ver qué tan susceptibles son los cachés a los SEU. Si las memorias caché son más resistentes que la RAM, puede leer y volver a escribir regularmente datos críticos para asegurarse de que permanecen en la memoria caché y volver a poner la RAM en línea.

Utilice los manejadores de fallas de página de manera inteligente.

Si marca una página de memoria como no presente, la CPU emitirá un error de página cuando intente acceder a ella. Puede crear un manejador de errores de página que realice algunas comprobaciones antes de atender la solicitud de lectura. (Los sistemas operativos de PC usan esto para cargar de forma transparente las páginas que se han intercambiado al disco).

Use lenguaje ensamblador para cosas críticas (que podrían ser todo).

Con el lenguaje ensamblador, sabes qué hay en los registros y qué hay en la RAM; usted sabe qué tablas especiales de RAM está utilizando la CPU, y puede diseñar cosas de forma indirecta para mantener su riesgo bajo.

Use objdump para ver realmente el lenguaje ensamblador generado y calcule cuánto código ocupa cada una de sus rutinas.

Si está utilizando un gran sistema operativo como Linux, entonces está buscando problemas; hay tanta complejidad y tantas cosas que salen mal.

Recuerda que es un juego de probabilidades.

Un comentarista dijo

Cada rutina que escriba para detectar errores estará sujeta a fallar por la misma causa.

Si bien esto es cierto, las posibilidades de errores en los (digamos) 100 bytes de código y datos necesarios para que una rutina de verificación funcione correctamente es mucho menor que la posibilidad de errores en otros lugares. Si su ROM es bastante confiable y casi todo el código / datos está realmente en ROM, entonces sus probabilidades son aún mejores.

Use hardware redundante.

Use 2 o más configuraciones de hardware idénticas con código idéntico. Si los resultados difieren, se debe activar un reinicio. Con 3 o más dispositivos, puede usar un sistema de "votación" para tratar de identificar cuál ha sido comprometido.


Es posible utilizar C para escribir programas que se comporten de manera sólida en dichos entornos, pero solo si la mayoría de las formas de optimización del compilador están deshabilitadas. Los compiladores de optimización están diseñados para reemplazar muchos patrones de codificación aparentemente redundantes con patrones "más eficientes", y pueden no tener idea de que la razón por la que el programador está probando x==42 cuando el compilador sabe que no hay forma de que x pueda contener algo más es porque el programador quiere evitar la ejecución de cierto código con x manteniendo algún otro valor, incluso en los casos en que la única forma en que podría mantener ese valor sería si el sistema recibiera algún tipo de falla eléctrica.

Declarar variables como volatile menudo es útil, pero puede no ser una panacea. De particular importancia, tenga en cuenta que la codificación segura a menudo requiere que las operaciones peligrosas tengan enclavamientos de hardware que requieren múltiples pasos para activarse, y que el código se escriba utilizando el patrón:

... 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 compilador traduce el código de manera relativamente literal, y si todas las comprobaciones del estado del sistema se repiten después de prepare_for_activation() , el sistema puede ser robusto contra casi cualquier evento de falla única plausible, incluso aquellos que corromperían arbitrariamente el contador del programa y apilar. Si ocurre una falla inmediatamente después de una llamada a prepare_for_activation() , eso implicaría que la activación hubiera sido apropiada (ya que no hay otra razón por la que prepare_for_activation() hubiera sido llamado antes de la falla). Si la falla hace que el código llegue a prepare_for_activation() inapropiada, pero no hay eventos de falla subsiguientes, no habría forma de que el código alcance posteriormente trigger_activation() sin haber pasado por la verificación de validación o llamar a cancel_preparations primero [si la pila falla, la ejecución podría proceder a un punto justo antes de trigger_activation() después del trigger_activation() del contexto que llamó a prepare_for_activation() , pero la llamada a cancel_preparations() habría ocurrido entre las llamadas a prepare_for_activation() y trigger_activation() , lo que hace que la última llamada sea inofensiva.

Tal código puede ser seguro en C tradicional, pero no con los compiladores de C modernos. Tales compiladores pueden ser muy peligrosos en ese tipo de entorno porque agresivos se esfuerzan por incluir solo código que será relevante en situaciones que podrían surgir a través de un mecanismo bien definido y cuyas consecuencias resultantes también estarían bien definidas. El código cuyo propósito sería detectar y limpiar después de fallas puede, en algunos casos, terminar empeorando las cosas. Si el compilador determina que el intento de recuperación en algunos casos invocaría un comportamiento indefinido, puede inferir que las condiciones que requerirían tal recuperación en tales casos no pueden ocurrir, eliminando así el código que los habría verificado.


Escribir código para entornos radiactivos no es realmente diferente de escribir código para cualquier aplicación de misión crítica.

Además de lo que ya se ha mencionado, aquí hay algunos consejos diversos:

  • Utilice las medidas de seguridad cotidianas "pan y mantequilla" que deberían estar presentes en cualquier sistema embebido semiprofesional: vigilancia interna, detección interna de bajo voltaje, monitor de reloj interno. Estas cosas ni siquiera deberían mencionarse en el año 2016 y son estándar en casi todos los microcontroladores modernos.
  • Si tiene una MCU de seguridad y / o orientada a la automoción, tendrá ciertas características de vigilancia, como una ventana de tiempo determinada, dentro de la cual debe actualizar la vigilancia. Esto es preferible si tiene un sistema de tiempo real de misión crítica.
  • En general, use una MCU adecuada para este tipo de sistemas, y no alguna pelusa genérica que recibió en un paquete de copos de maíz. Hoy en día, casi todos los fabricantes de MCU tienen MCU especializadas diseñadas para aplicaciones de seguridad (TI, Freescale, Renesas, ST, Infineon, etc.). Estos tienen muchas características de seguridad integradas, incluidos los núcleos de paso de bloqueo: lo que significa que hay 2 núcleos de CPU que ejecutan el mismo código, y deben estar de acuerdo entre sí.
  • IMPORTANTE: debe garantizar la integridad de los registros MCU internos. Todos los registros de control y estado de los periféricos de hardware que se pueden escribir pueden ubicarse en la memoria RAM y, por lo tanto, son vulnerables.

    Para protegerse contra la corrupción de los registros, elija preferiblemente un microcontrolador con funciones integradas de "escribir una vez" de los registros. Además, debe almacenar los valores predeterminados de todos los registros de hardware en NVM y copiar esos valores en sus registros a intervalos regulares. Puede garantizar la integridad de variables importantes de la misma manera.

    Nota: siempre use programación defensiva. Lo que significa que debe configurar todos los registros en la MCU y no solo los utilizados por la aplicación. No desea que algún periférico de hardware aleatorio se active de repente.

  • Hay todo tipo de métodos para verificar errores en RAM o NVM: sumas de verificación, "patrones de caminata", software ECC, etc. La mejor solución hoy en día es no usar ninguno de estos, sino usar una MCU con ECC incorporado y controles similares Debido a que hacer esto en el software es complejo, y la verificación de errores en sí misma, por lo tanto, podría introducir errores y problemas inesperados.

  • Use redundancia. Puede almacenar memoria volátil y no volátil en dos segmentos "espejo" idénticos, que siempre deben ser equivalentes. Cada segmento podría tener una suma de verificación CRC adjunta.
  • Evite usar memorias externas fuera de la MCU.
  • Implemente una rutina de servicio de interrupción predeterminada / un controlador de excepción predeterminado para todas las posibles interrupciones / excepciones. Incluso los que no estás usando. La rutina predeterminada no debe hacer nada excepto apagar su propia fuente de interrupción.
  • Comprender y adoptar el concepto de programación defensiva. Esto significa que su programa necesita manejar todos los casos posibles, incluso aquellos que no pueden ocurrir en teoría. Examples .

    El firmware de misión crítica de alta calidad detecta tantos errores como sea posible y luego los ignora de manera segura.

  • Nunca escriba programas que se basen en comportamientos mal especificados. Es probable que dicho comportamiento pueda cambiar drásticamente con cambios inesperados de hardware causados ​​por radiación o EMI. La mejor manera de asegurarse de que su programa esté libre de esa basura es utilizar un estándar de codificación como MISRA, junto con una herramienta de análisis estático. Esto también ayudará con la programación defensiva y con la eliminación de errores (¿por qué no querría detectar errores en cualquier tipo de aplicación?).
  • IMPORTANTE: No implemente ninguna dependencia de los valores predeterminados de las variables de duración del almacenamiento estático. Es decir, no confíe en el contenido predeterminado de .data o .bss . Podría haber cualquier cantidad de tiempo entre el punto de inicialización y el punto donde la variable se usa realmente, podría haber pasado mucho tiempo para que la RAM se corrompa. En su lugar, escriba el programa para que todas esas variables se establezcan desde NVM en tiempo de ejecución, justo antes del momento en que dicha variable se usa por primera vez.

    En la práctica, esto significa que si una variable se declara en el ámbito del archivo o como static , nunca debe usar = para inicializarla (o podría hacerlo, pero no tiene sentido, porque de todos modos no puede confiar en el valor). Siempre configúrelo en tiempo de ejecución, justo antes de su uso. Si es posible actualizar repetidamente tales variables desde NVM, entonces hágalo.

    De manera similar en C ++, no confíe en los constructores para las variables de duración del almacenamiento estático. Haga que los constructores llamen a una rutina pública de "configuración", a la que también puede llamar más adelante en tiempo de ejecución, directamente desde la aplicación que llama.

    Si es posible, elimine el código de inicio de "copia" que inicializa .data y .bss (y llama a los constructores de C ++) por completo, de modo que obtenga errores de enlazador si escribe código que se basa en tal. Muchos compiladores tienen la opción de omitir esto, generalmente llamado "arranque mínimo / rápido" o similar.

    Esto significa que las bibliotecas externas deben verificarse para que no contengan dicha dependencia.

  • Implemente y defina un estado seguro para el programa, a donde volverá en caso de errores críticos.

  • La implementación de un informe de errores / sistema de registro de errores siempre es útil.

Este es un tema extremadamente amplio. Básicamente, realmente no puede recuperarse de la corrupción de la memoria, pero al menos puede intentar fallar rápidamente . Aquí hay algunas técnicas que podría usar:

  • datos constantes de suma de comprobación . Si tiene datos de configuración que permanecen constantes durante mucho tiempo (incluidos los registros de hardware que ha configurado), calcule su suma de comprobación en la inicialización y verifíquela periódicamente. Cuando vea una falta de coincidencia, es hora de reiniciar o reiniciar.

  • almacenar variables con redundancia . Si tiene una variable importante x , escriba su valor en x1 , x2 y x3 y x3 como (x1 == x2) ? x2 : x3 (x1 == x2) ? x2 : x3 .

  • implementar el monitoreo del flujo del programa . XOR una bandera global con un valor único en funciones / ramas importantes llamadas desde el bucle principal. La ejecución del programa en un entorno libre de radiación con una cobertura de prueba cercana al 100% debería proporcionarle la lista de valores aceptables de la bandera al final del ciclo. Restablezca si ve desviaciones.

  • supervisar el puntero de la pila . Al comienzo del ciclo principal, compare el puntero de la pila con su valor esperado. Restablecer en desviación.


La NASA tiene un documento sobre software endurecido por radiación . Describe tres tareas principales:

  1. Monitoreo regular de la memoria para detectar errores y luego eliminar esos errores,
  2. mecanismos robustos de recuperación de errores, y
  3. La capacidad de reconfigurar si algo ya no funciona.

Tenga en cuenta que la velocidad de exploración de la memoria debe ser lo suficientemente frecuente como para que raramente se produzcan errores de varios bits, ya que la mayoría de la memoria ECC puede recuperarse de errores de un solo bit, no de errores de varios bits.

La recuperación sólida de errores incluye la transferencia de flujo de control (generalmente reiniciando un proceso en un punto anterior al error), liberación de recursos y restauración de datos.

Su principal recomendación para la restauración de datos es evitar la necesidad de hacerlo, al hacer que los datos intermedios se traten como temporales, de modo que reiniciar antes del error también revierta los datos a un estado confiable. Esto suena similar al concepto de "transacciones" en bases de datos.

Discuten técnicas particularmente adecuadas para lenguajes orientados a objetos como C ++. Por ejemplo

  1. ECC basados ​​en software para objetos de memoria contiguos
  2. Programación por contrato : verificación de precondiciones y postcondiciones, luego verificando el objeto para verificar que todavía está en un estado válido.

Y, simplemente, la NASA ha usado C ++ para proyectos importantes como el Mars Rover .

La abstracción y encapsulación de clase C ++ permitió un rápido desarrollo y prueba entre múltiples proyectos y desarrolladores.

Evitaron ciertas características de C ++ que podrían crear problemas:

  1. Excepciones
  2. Plantillas
  3. Iostream (sin consola)
  4. Herencia múltiple
  5. Sobrecarga del operador (que no sea new y delete )
  6. Asignación dinámica (usó un grupo de memoria dedicado y una ubicación new para evitar la posibilidad de corrupción del montón del sistema).

Lo que podría ayudarte es un watchdog . Los perros guardianes se usaron ampliamente en la informática industrial en la década de 1980. Las fallas de hardware eran mucho más comunes entonces, otra respuesta también se refiere a ese período.

Un perro guardián es una característica combinada de hardware / software. El hardware es un contador simple que cuenta desde un número (digamos 1023) hasta cero. TTL u otra lógica podría ser utilizada.

El software ha sido diseñado de tal manera que una rutina supervisa el funcionamiento correcto de todos los sistemas esenciales. Si esta rutina se completa correctamente = encuentra que la computadora funciona bien, establece el contador nuevamente en 1023.

El diseño general es para que, en circunstancias normales, el software evite que el contador de hardware llegue a cero. En caso de que el contador llegue a cero, el hardware del contador realiza su única tarea y restablece todo el sistema. Desde la perspectiva del contador, cero es igual a 1024 y el contador continúa contando nuevamente.

Este perro guardián asegura que la computadora conectada se reinicie en muchos, muchos casos de falla. Debo admitir que no estoy familiarizado con el hardware que puede realizar dicha función en las computadoras de hoy. Las interfaces con hardware externo ahora son mucho más complejas de lo que solían ser.

Una desventaja inherente del watchdog es que el sistema no está disponible desde el momento en que falla hasta que el contador del watchdog llega a cero + tiempo de reinicio. Si bien ese tiempo es generalmente mucho más corto que cualquier intervención externa o humana, el equipo compatible deberá poder continuar sin el control de la computadora durante ese período de tiempo.


También puede estar interesado en la rica literatura sobre el tema de la tolerancia a fallos algorítmicos. Esto incluye la asignación anterior: escriba una ordenación que ordene correctamente su entrada cuando falle un número constante de comparaciones (o, la versión ligeramente más malvada, cuando el número asintótico de comparaciones fallidas se escala como log(n) para n comparaciones).

Un lugar para comenzar a leer es el artículo de 1984 de Huang y Abraham " Tolerancia a fallos basada en algoritmos para operaciones matriciales ". Su idea es vagamente similar a la computación encriptada homomórfica (pero en realidad no es la misma, ya que están intentando la detección / corrección de errores a nivel de operación).

Un descendiente más reciente de ese artículo es Bosilca, Delmas, Dongarra y " La tolerancia a fallos basada en algoritmos de Langou aplicada a la informática de alto rendimiento ".


Trabajando durante unos 4-5 años con el desarrollo de software / firmware y pruebas de entorno de satélites miniaturizados *, me gustaría compartir mi experiencia aquí.

* (los satélites miniaturizados son mucho más propensos a alteraciones de eventos individuales que los satélites más grandes debido a sus tamaños relativamente pequeños y limitados para sus componentes electrónicos )

Para ser muy conciso y directo: no existe un mecanismo para recuperarse de una situación detectable y errónea por parte del software / firmware sin , al menos, una copia de la versión mínima de trabajo del software / firmware en algún lugar con fines de recuperación , y con el hardware compatible La recuperación (funcional).

Ahora, esta situación normalmente se maneja tanto a nivel de hardware como de software. Aquí, como lo solicite, compartiré lo que podemos hacer en el nivel de software.

  1. ... propósito de recuperación .... Proporcione la capacidad de actualizar / recompilar / actualizar su software / firmware en un entorno real. Esta es una característica casi imprescindible para cualquier software / firmware en un entorno altamente ionizado. Sin esto, podría tener software / hardware redundante tantos como desee, pero en un momento, todos explotarán. ¡Entonces, prepare esta función!

  2. ... versión mínima de trabajo ... Tenga en su código múltiples copias receptivas, versión mínima del software / firmware. Esto es como el modo seguro en Windows. En lugar de tener solo una versión completamente funcional de su software, tenga varias copias de la versión mínima de su software / firmware. La copia mínima generalmente tendrá un tamaño mucho menor que la copia completa y casi siempre tendrá solo las siguientes dos o tres características:

    1. capaz de escuchar comandos desde un sistema externo,
    2. capaz de actualizar el software / firmware actual,
    3. capaz de monitorear los datos de limpieza de la operación básica.
  3. ... copie ... en alguna parte ... Tenga software / firmware redundante en alguna parte.

    1. Podría, con o sin hardware redundante, intentar tener software / firmware redundante en su ARM uC. Esto normalmente se hace teniendo dos o más software / firmware idénticos en direcciones separadas que envían latidos entre sí, pero solo uno estará activo a la vez. Si se sabe que uno o más software / firmware no responde, cambie al otro software / firmware. El beneficio de usar este enfoque es que podemos tener un reemplazo funcional inmediatamente después de que ocurra un error, sin ningún contacto con ningún sistema externo o parte responsable de detectar y reparar el error (en el caso de satélites, generalmente es el Centro de Control de Misión ( MCC)).

      Hablando estrictamente, sin hardware redundante, la desventaja de hacer esto es que en realidad no puede eliminar todos los puntos únicos de fallas. Por lo menos, todavía tendrá un único punto de falla, que es el interruptor en sí (o, a menudo, el comienzo del código). Sin embargo, para un dispositivo limitado por el tamaño en un entorno altamente ionizado (como los satélites pico / femto), la reducción del punto único de fallas a un punto sin hardware adicional aún valdrá la pena considerar. Además, el código para la conmutación ciertamente sería mucho menor que el código para todo el programa, reduciendo significativamente el riesgo de incluir un evento único.

    2. Pero si no está haciendo esto, debe tener al menos una copia en su sistema externo que pueda entrar en contacto con el dispositivo y actualizar el software / firmware (en el caso del satélite, nuevamente es el centro de control de la misión).

    3. También puede tener la copia en su almacenamiento de memoria permanente en su dispositivo que puede activarse para restaurar el software / firmware del sistema en ejecución
  4. ... situación errónea detectable. El error debe ser detectable , generalmente por el circuito de detección / corrección de errores de hardware o por un pequeño código para la detección / corrección de errores. Es mejor poner dicho código pequeño, múltiple e independiente del software / firmware principal. Su tarea principal es solo para verificar / corregir. Si el circuito de hardware / firmware es confiable (como si estuviera más endurecido por la radiación que los restos, o si tiene múltiples circuitos / lógicas), entonces podría considerar hacer una corrección de errores con él. Pero si no es así, es mejor hacerlo como detección de errores. La corrección puede ser por sistema / dispositivo externo. Para la corrección de errores, podría considerar utilizar un algoritmo básico de corrección de errores como Hamming / Golay23, ya que pueden implementarse más fácilmente tanto en el circuito / software. Pero, en última instancia, depende de la capacidad de su equipo. Para la detección de errores, normalmente se utiliza CRC.

  5. ... hardware que soporta la recuperación Ahora, llega al aspecto más difícil en este tema. En última instancia, la recuperación requiere que el hardware responsable de la recuperación sea al menos funcional. Si el hardware se rompe permanentemente (normalmente ocurre después de que su dosis total de ionización alcanza cierto nivel), entonces (lamentablemente) no hay forma de que el software ayude en la recuperación. Por lo tanto, el hardware es, con razón, la principal preocupación para un dispositivo expuesto a un alto nivel de radiación (como un satélite).

Además de la sugerencia para anticipar el error del firmware debido a un solo evento molesto, también me gustaría sugerirle que tenga:

  1. Detección de errores y / o algoritmo de corrección de errores en el protocolo de comunicación entre subsistemas. Este es otro casi imprescindible para evitar señales incompletas / incorrectas recibidas de otro sistema

  2. Filtra tu lectura de ADC. No use la lectura ADC directamente. Filtra por filtro medio, filtro medio o cualquier otro filtro; nunca confíes en un valor de lectura único. Muestra más, no menos, razonablemente.


Esta respuesta asume que le preocupa tener un sistema que funcione correctamente, además de tener un sistema que sea de costo mínimo o rápido; la mayoría de las personas que juegan con cosas radiactivas valoran la corrección / seguridad sobre la velocidad / costo

Varias personas han sugerido cambios de hardware que puede hacer (está bien, ya hay muchas cosas buenas aquí en las respuestas y no tengo la intención de repetirlas), y otras han sugerido redundancia (excelente en principio), pero no creo Alguien ha sugerido cómo podría funcionar esa redundancia en la práctica. ¿Cómo fallas? ¿Cómo sabes cuándo algo ''salió mal''? Muchas tecnologías funcionan sobre la base de que todo funcionará, y el fracaso es, por lo tanto, algo difícil de tratar. Sin embargo, algunas tecnologías informáticas distribuidas diseñadas para la escala esperan fallas (después de todo, con suficiente escala, la falla de un nodo de muchos es inevitable con cualquier MTBF para un solo nodo); puedes aprovechar esto para tu entorno.

Aquí hay algunas ideas:

  • Asegúrese de que todo su hardware se replica n veces (donde n es mayor que 2, y preferiblemente impar), y que cada elemento de hardware puede comunicarse entre sí. Ethernet es una forma obvia de hacerlo, pero hay muchas otras rutas mucho más simples que brindarían una mejor protección (por ejemplo, CAN). Minimice los componentes comunes (incluso las fuentes de alimentación). Esto puede significar muestrear entradas de ADC en varios lugares, por ejemplo.

  • Asegúrese de que el estado de su aplicación esté en un solo lugar, por ejemplo, en una máquina de estados finitos. Esto puede estar completamente basado en RAM, aunque no impide el almacenamiento estable. Por lo tanto, se almacenará en varios lugares.

  • Adoptar un protocolo de quórum para cambios de estado. Ver RAFT por ejemplo. Como está trabajando en C ++, hay bibliotecas bien conocidas para esto. Los cambios en el FSM solo se realizarían cuando la mayoría de los nodos estén de acuerdo. Use una buena biblioteca conocida para la pila de protocolos y el protocolo de quórum en lugar de rodar uno usted mismo, o todo su buen trabajo en redundancia se desperdiciará cuando el protocolo de quórum cuelgue.

  • Asegúrese de que su suma de verificación (por ejemplo, CRC / SHA) su FSM, y almacene la CRC / SHA en la propia FSM (así como la transmisión del mensaje y la suma de verificación de los mensajes mismos). Haga que los nodos verifiquen regularmente su FSM con estas sumas de verificación, los mensajes entrantes de suma de verificación y verifique que su suma de verificación coincida con la suma de verificación del quórum.

  • Construya tantas otras comprobaciones internas en su sistema como sea posible, haciendo que los nodos que detectan su propia falla se reinicien (esto es mejor que continuar trabajando a medias, siempre que tenga suficientes nodos) Intente dejar que se retiren limpiamente del quórum durante el reinicio en caso de que no vuelvan a aparecer. Al reiniciar, pídales que comprueben la imagen del software (y cualquier otra cosa que carguen) y hagan una prueba de RAM completa antes de reintroducirse en el quórum.

  • Use hardware para apoyarlo, pero hágalo con cuidado. Puede obtener RAM ECC, por ejemplo, y leer / escribir regularmente para corregir errores ECC (y entrar en pánico si el error no se puede corregir). Sin embargo (desde la memoria) la RAM estática es mucho más tolerante a la radiación ionizante que la DRAM en primer lugar, por lo que puede ser mejor usar DRAM estática. Vea el primer punto bajo ''cosas que no haría'' también.

Supongamos que tiene un 1% de posibilidades de falla de cualquier nodo en un día, y supongamos que puede hacer que las fallas sean completamente independientes. Con 5 nodos, necesitará tres para fallar en un día, lo que es una probabilidad de .00001%. Con más, bueno, te haces una idea.

Cosas que no haría:

  • Subestime el valor de no tener el problema para comenzar. A menos que el peso sea una preocupación, un gran bloque de metal alrededor de su dispositivo será una solución mucho más barata y confiable que un equipo de programadores. El acoplamiento óptico idéntico de las entradas de EMI es un problema, etc. Lo que sea, intente obtener sus componentes para obtener los mejor calificados contra la radiación ionizante.

  • Tira tus propios algoritmos . La gente ha hecho esto antes. Usa su trabajo. La tolerancia a fallas y los algoritmos distribuidos son difíciles. Use el trabajo de otras personas cuando sea posible.

  • Use configuraciones complicadas del compilador en la ingenua esperanza de que detecte más fallas. Si tiene suerte, puede detectar más fallas. Lo más probable es que use una ruta de código dentro del compilador que haya sido menos probada, particularmente si la ha enrollado usted mismo.

  • Use técnicas que no hayan sido probadas en su entorno. La mayoría de las personas que escriben software de alta disponibilidad tienen que simular modos de falla para verificar que su HA funcione correctamente, y como resultado pierden muchos modos de falla. Usted está en la posición "afortunada" de tener frecuentes fallas bajo demanda. Por lo tanto, pruebe cada técnica y asegúrese de que su aplicación real mejore el MTBF en una cantidad que exceda la complejidad para introducirlo (con la complejidad vienen los errores). Aplique esto especialmente a mis consejos sobre algoritmos de quórum, etc.