c++ - Compilación de una aplicación para uso en entornos altamente radiactivos.




gcc embedded fault-tolerance (20)

También puede interesarle la abundante literatura sobre el tema de la tolerancia a fallas algorítmica. Esto incluye la asignación anterior: escriba una ordenación que ordene correctamente su entrada cuando fallará un número constante de comparaciones (o, la versión un poco más perversa, cuando el número asintótico de comparaciones fallidas se escalará como log(n) para n comparaciones).

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

Un descendiente más reciente de ese artículo es Bosilca, Delmas, Dongarra y Langou " Tolerancia a fallas basada en algoritmos aplicada a computación de alto rendimiento ".

Estamos compilando una aplicación C / C ++ incrustada 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 lo 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 eventos individuales ? ¿Algún otro desarrollador ha tenido éxito en reducir los efectos dañinos de los errores de software en una aplicación de larga ejecució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 escaneo de la memoria debe ser lo suficientemente frecuente para que los errores de múltiples bits raramente ocurran, ya que la mayoría de la memoria ECC puede recuperarse de errores de un solo bit, no de errores de múltiples bits.

La recuperación de errores robusta incluye la transferencia de flujo de control (normalmente, reiniciar un proceso en un punto anterior al error), la liberación de recursos y la restauración de datos.

Su recomendación principal para la restauración de datos es evitar su necesidad, ya que los datos intermedios se tratan como temporales, de modo que el reinicio antes del error también hace que los datos vuelvan a un estado confiable. Esto suena similar al concepto de "transacciones" en las 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 : verifique las condiciones previas y posteriores, y luego verifique el objeto para verificar que aún se encuentra en un estado válido.

Y, da la casualidad de que la NASA ha usado C ++ para proyectos importantes como el Mars Rover .

La abstracción y la encapsulación de la clase C ++ permitieron un rápido desarrollo y pruebas 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 (distinta de new y delete )
  6. Asignación dinámica (se utilizó una agrupación de memoria dedicada y una new ubicación para evitar la posibilidad de daños en el montón del sistema).

Utilice un planificador cíclico . Esto le brinda la posibilidad 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 reinicializar 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 se interrumpa el tiempo límite, vuelva a cargar la pila y vuelva a intentarlo. Si aún falla después de 3/5 intentos, necesita volver a cargar desde la ROM.

Divida su software en partes y aísle estas partes para usar áreas de memoria y tiempos de ejecución separados (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 estamos reparando la adquisición de la señal, el resto de tareas continúa con datos obsoletos.

Todo necesita CRCs. Si ejecuta fuera de la RAM, incluso su .text necesita un CRC. Compruebe los CRC con regularidad si utiliza un programador cíclico. Algunos compiladores (no GCC) pueden generar CRC para cada sección y algunos procesadores tienen hardware dedicado para realizar los cálculos de CRC, pero supongo que eso quedaría fuera del alcance de su pregunta. La verificació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.


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

En lugar de usar uint32_t (y double , int64_t , etc.), SAFE_uint32_t su propio SAFE_uint32_t que contenga un múltiplo (mínimo de 3) de uint32_t.Sobrecargue todas las operaciones que desea realizar (* + - / << >> = ==! = Etc) y haga que las operaciones sobrecargadas se realicen independientemente en cada valor interno, es decir, no lo haga una vez y copie el resultado. Antes y después, verifique que todos los valores internos coincidan. Si los valores no coinciden, puede actualizar el valor incorrecto con el valor 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 producen daños en la ALU, los registros, la memoria RAM o en un bus, todavía tendrá múltiples intentos y una muy buena posibilidad de detectar errores. Sin embargo, tenga en cuenta que esto solo funciona con las variables que puede reemplazar: el puntero de pila, por ejemplo, aún será 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 de borde que, en ocasiones, corromperí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 actividad de radio, y sí, a veces es un error del compilador =)


¡Realmente he leído muchas respuestas geniales!

Aquí está mi 2 centavo: construya un modelo estadístico de la memoria / anormalidad de registro, escribiendo un software para verificar la memoria o para realizar comparaciones frecuentes de registro. 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 computadora de escritorio tiene una cierta tasa de fallas, que sin embargo no afecta el trabajo diario.


Dados 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 ensamblajes y asignaciones de memoria estática en todas partes. Para este tipo de total confiabilidad, creo que el ensamblaje ya no genera una gran diferencia porcentual del costo.


Un punto que nadie parece haber mencionado. Usted dice que se está desarrollando en GCC y compilación cruzada en ARM. ¿Cómo sabe que no tiene código que haga suposiciones sobre la RAM libre, el tamaño de enteros, el tamaño del puntero, el tiempo que tarda en realizar una determinada operación, el tiempo que durará el sistema continuamente o varias cosas como esa? Este es un problema muy común.

La respuesta suele ser la prueba unitaria automatizada. Escriba los arneses de prueba que ejercen 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 compruebe si hay erratas en su dispositivo integrado. Es posible que haya algo sobre "no hagas esto porque se bloqueará, así que habilita esa opción del compilador y el compilador funcionará a su alrededor".

En resumen, su fuente más probable de fallos son los errores en su código. Hasta que haya asegurado que este no es el caso, no se preocupe (todavía) por los modos de falla más esotéricos.


Alguien mencionó el uso de chips más lentos para evitar que los iones giren las brocas con la misma facilidad. De manera similar, tal vez use un cpu / ram especializado que realmente use múltiples bits para almacenar un solo bit. Por lo tanto, proporcionar una tolerancia a fallos de hardware porque sería muy improbable que todos los bits se invirtieran. Entonces 1 = 1111, pero necesitaría ser golpeado 4 veces para voltearse. (4 podría ser un número malo, ya que si 2 bits se voltean, ya es ambiguo). Entonces, si va con 8, obtiene 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 en el nivel de software con un compilador especializado (asigne x más espacio para todo) o implementación de lenguaje (escriba envoltorios para estructuras de datos que asignen cosas de esta manera).O hardware especializado que tiene la misma estructura lógica pero lo hace en el firmware.


¿Qué hay de ejecutar muchas instancias de su aplicación. Si los bloqueos se deben a cambios aleatorios en los 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 un flop de bits para lograr el error general más pequeño que desee.


Puede ser posible usar C para escribir programas que se comporten de manera robusta en dichos entornos, pero solo si la mayoría de las formas de optimización del compilador están deshabilitadas. Los compiladores optimizados están diseñados para reemplazar muchos patrones de codificación aparentemente redundantes por patrones "más eficientes", y es posible que no tengan 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 sostener otra cosa es porque el programador desea evitar la ejecución de cierto código con x manteniendo algún otro valor, incluso en los casos en que la única forma de mantener ese valor sería si el sistema recibiera algún tipo de falla eléctrica.

Declarar las 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 interbloqueos de hardware que requieran múltiples pasos para activarse, y que el código se escriba usando 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 verificaciones del estado del sistema se repiten después de la prepare_for_activation() , el sistema puede ser robusto frente a casi cualquier evento de fallo prepare_for_activation() plausible, incluso aquellos que dañen arbitrariamente el contador del programa y apilar. Si se produce 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 se haya llamado a prepare_for_activation() antes de la falla). Si la falla hace que el código llegue a prepare_for_activation() inapropiada, pero no hay eventos de falla, no habría manera de que el código alcance la 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 de que se prepare_for_activation() el contexto llamado prepare_for_activation() , pero la llamada a cancel_preparations() habría ocurrido entre las llamadas a prepare_for_activation() y trigger_activation() , por lo que la última llamada sería inofensiva.

Dicho código puede ser seguro en C tradicional, pero no en compiladores de C modernos. Dichos 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 algún mecanismo bien definido y cuyas consecuencias resultantes también estén 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á un comportamiento indefinido, puede inferir que las condiciones que necesitarí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 a 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 varios:

  • Utilice las medidas de seguridad diarias "pan y mantequilla" que deben estar presentes en cualquier sistema integrado semi-profesional: perro guardián interno, 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 orientada hacia la seguridad y / o el automóvil, tendrá ciertas funciones de vigilancia, como una ventana de tiempo determinada, dentro de la cual deberá 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. En la actualidad, casi todos los fabricantes de MCU cuentan con MCU especializadas diseñadas para aplicaciones de seguridad (TI, Freescale, Renesas, ST, Infineon, etc.). Éstas 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 coincidir entre sí.
  • IMPORTANTE: debe garantizar la integridad de los registros internos de MCU. Todos los registros de control y estado de los periféricos de hardware que se pueden escribir pueden estar ubicados en la memoria RAM y, por lo tanto, son vulnerables.

    Para protegerse contra la corrupción de registros, elija un microcontrolador con funciones de "escritura una vez" integradas en los registros. Además, debe almacenar los valores predeterminados de todos los registros de hardware en NVM y copiar esos valores a sus registros a intervalos regulares. Puede garantizar la integridad de las 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 quieres que un periférico de hardware aleatorio se active repentinamente.

  • Hay todo tipo de métodos para verificar errores en la RAM o NVM: sumas de comprobación, "patrones de desplazamiento", 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 cheques similares. Debido a que hacer esto en el software es complejo, y la comprobación de errores en sí misma podría, por lo tanto, introducir errores y problemas inesperados.

  • Usa redundancia. Puede almacenar tanto la memoria volátil como la no volátil en dos segmentos "espejo" idénticos, que siempre deben ser equivalentes. Cada segmento podría tener una suma de comprobación CRC adjunta.
  • Evite el uso de memorias externas fuera de la MCU.
  • Implementar una rutina de servicio de interrupción predeterminada / controlador de excepciones predeterminado para todas las posibles interrupciones / excepciones. Incluso los que no estás usando. La rutina predeterminada no debería hacer nada, excepto apagar su propia fuente de interrupción.
  • Comprender y abrazar 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 la mayor cantidad de errores posibles y luego los ignora de manera segura.

  • Nunca escriba programas que se basen en un comportamiento mal especificado. Es probable que tal comportamiento cambie drásticamente con cambios inesperados en el hardware causados ​​por radiación o EMI. La mejor manera de asegurarse de que su programa esté libre de tanta basura es usar 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 una cantidad de tiempo entre el punto de inicialización y el punto donde realmente se usa la variable, podría haber habido mucho tiempo para que la RAM se corrompa. En su lugar, escriba el programa para que todas las variables de este tipo se configuren desde NVM en tiempo de ejecución, justo antes de la hora en que se usa esa variable por primera vez.

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

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

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

    Esto significa que cualquier biblioteca externa debe verificarse para que no contengan tal confianza.

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

  • Implementar un sistema de informe de errores / registro de errores siempre es útil.

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

Habrá una superficie de material que no será afectada por la radiación. Múltiples engranajes estarán allí. Un lector mecánico se ejecutará 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. Desde 0 y 1 puede generar su base de código.


Tal vez 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 SEU?

En un proyecto relacionado con la exploración del espacio, teníamos una MCU personalizada, que generaría una excepción / interrupción en los errores de la SEU, pero con cierta demora, es decir, algunos ciclos podrían pasar / se ejecutarán instrucciones después de la inserción que causó la excepción de la SEU.

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

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

Solo algo para considerar ...


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

  • datos de la suma de comprobación Si tiene algún dato de configuración que permanezca constante durante mucho tiempo (incluidos los registros de hardware que haya configurado), calcule su suma de comprobación en la inicialización y verifíquelo periódicamente. Cuando vea una falta de coincidencia, es hora de reiniciar o reiniciar.

  • Almacenar variables con redundancia . Si tienes una variable importante x , escribe su valor en x1 , x2 y x3 y léelo 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. Ejecutar el 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. Restablecer si ve desviaciones.

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


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

Algo 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 fallos de hardware, y eso es difícil, si no imposible, de solucionar en general.

Primero recomendaría intentar detectar la situación problemática mediante el registro (pila, registros, llamadas de función), ya sea registrándolos en algún lugar en el archivo o transmitiéndolos de alguna manera directamente ("oh no, estoy fallando").

La recuperación de tal situación de error es reiniciar (si el software aún está activo y pateando) o restablecer el hardware (por ejemplo, perros guardianes de hw). Más fácil empezar 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 la llamada y eso le puede dar 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 en las que sospecha que hay un problema. tipo de decisión "no funciona" después de la cual puede concentrarse en otra mitad del código. (Donde el problema es)

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

Y si logra minimizar al mínimo su código hasta el tipo de aplicación "hola mundo", y sigue fallando al azar, se esperan problemas de hardware, y debe haber una "actualización de hardware", es decir, 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 detuvo / restableció por completo / no funcionó, probablemente lo primero que debería hacer bootstap, es regresar a casa si se encuentra 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 entonces debe tener al menos medios de comunicación funcionando y algún procesador / alguna ramificación en funcionamiento. Y por depuración remota me refiero a un enfoque de código auxiliar GDB / gdb o a su propia implementación de lo que necesita para recuperar su aplicación (por ejemplo, descargar archivos de registro, descargar pila de llamadas, descargar ram, reiniciar)


Lo que pides es un tema bastante complejo, no fácil de responder. Otras respuestas están bien, pero cubrieron solo una pequeña parte de todas las cosas que necesita hacer.

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

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


Quieres 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 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. Los golpes deben ser controlados por el maestro, por lo que la conexión perdida con el maestro equivale a reiniciarse en unos pocos segundos.

Una de las ventajas 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.

Edit: De los comentarios siento la necesidad de aclarar la "idea CRC". La posibilidad de que el esclavo golpee su propio perro guardián es casi nula si rodea el golpe con CRC o realiza un resumen de las comprobaciones aleatorias del maestro. Los datos aleatorios solo se envían desde el maestro cuando el esclavo bajo escrutinio está alineado con los otros. Los datos aleatorios y el 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 de vigilancia. Los datos enviados desde el maestro se generan de forma única cada vez.


Descargo de responsabilidad: no soy un profesional de la radioactividad ni he trabajado 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á un tanto vinculado (mismo problema, diferentes objetivos).

El principal problema con la radioactividad, en mi opinión, es que la radioactividad puede cambiar los bits, por lo que la radioactividad puede alterar cualquier memoria digital . Estos errores suelen denominarse errores blandos , bit rot, etc.

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

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

  • confíe en el antiguo esquema de redundancia , y más específicamente en los códigos de corrección de errores más eficientes (el 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 denomina suma de comprobació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 es correcto antes de hacer algo, y si No, repara los campos. Sin embargo, esta solución no garantiza que su software pueda funcionar (simplemente funcionará correctamente cuando pueda, o dejará de funcionar si no, porque ECC puede decirle si algo está mal y, en este caso, puede detener su software para que pueda no obtener resultados falsos).

  • o puede utilizar estructuras de datos algorítmicas resilientes, lo que garantiza, hasta cierto punto, que su programa aún dará resultados correctos incluso en presencia de errores blandos. 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 flexibilidad está estrechamente vinculado a la estructura, por lo que no es necesario codificar procedimientos adicionales Para comprobar el ECC, y por lo general son mucho más rápidos. Estas estructuras proporcionan una manera de garantizar que su programa funcione bajo cualquier condición, hasta el límite teórico de errores blandos. También puede combinar estas estructuras resilientes con el esquema de redundancia / ECC para seguridad adicional (o codificar sus estructuras de datos más importantes como resilientes, y el resto, los datos prescindibles que puede volver a calcular a partir de 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, nuevo en ingeniería algorítmica y redundancia), le aconsejo leer los siguientes documentos:

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

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

  • Ferraro-Petrillo, U., Grandoni, F., & 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 las obras de Giuseppe F. Italiano (y trabajar a través de las referencias) y el modelo Faulty-RAM (presentado en Finocchi et al. 2005; y Italiano 2008).

/ EDIT: ilustré la prevención / recuperación de errores de software principalmente para la memoria RAM y el almacenamiento de datos, pero no hablé de errores de cálculo (CPU) . Otras respuestas ya apuntan 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 necesite hacer, y almacene el resultado en x variables diferentes (con x> = 3). Luego puedes comparar tus variables x :

  • si todos están de acuerdo, entonces no hay ningún error de cálculo.
  • si no están de acuerdo, entonces 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 un escaneo 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 active el procedimiento a prueba de fallos (reinicio, alerta al usuario, etc.).

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

Así, con voto de la mayoría que está a salvo de la salida dañado, y con redundancia x == 3, puede recuperar 1 error (con x == 4 será 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 recuperar utilizando la mayoría de votos).

El inconveniente es que necesita calcular x veces en lugar de una vez, por lo que tiene un costo de cálculo adicional, pero la complejidad lineal es asintóticamente, por lo 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 adicionalmente de que los cálculos se realizan correctamente, si puede crear 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 una mayoría de votos. Mecánicamente al final (utilizando puertas AND / OR por ejemplo). Esto se implementa a menudo en aviones y dispositivos de misión crítica (ver redundancia modular triple ). De esta manera, no tendría ningún gasto de cómputo (ya que los cálculos adicionales se realizarán en paralelo), y tiene otra capa de protección contra errores blandos (ya que la duplicación del cálculo y el voto mayoritario se administrarán directamente por el hardware y no por software - que puede dañarse más fácilmente ya que un programa es simplemente bits almacenados en la memoria ...).


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

* (Los satélites miniaturizados son mucho más propensos a los 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 hay ningún 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 soporte de hardware. La recuperación (funcional).

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

  1. ... propósito de la recuperación .... Proporcionar la capacidad de actualizar / recompilar / volver a flashear 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 redundantes tantos como quiera, pero en algún momento, todos van a explotar. Entonces, prepara esta característica!

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

    1. capaz de escuchar el comando del sistema externo,
    2. capaz de actualizar el software / firmware actual,
    3. Capaz de monitorear los datos de mantenimiento de la operación básica.
  3. ... copiar ... en alguna parte ... Tener software / firmware redundante en alguna parte.

    1. Usted podría, con o sin hardware redundante, tratar de tener software / firmware redundantes en su ARM uC. Normalmente, esto se hace al tener 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 responden, 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 contacto con el sistema / parte externa responsable de detectar y reparar el error (en el caso de los satélites, generalmente es el Centro de Control de la Misión ( MCC)).

      Estrictamente hablando, sin hardware redundante, la desventaja de hacer esto es que en realidad no puede eliminar todos los puntos únicos de fallas. Como mínimo, seguirá teniendo un único punto de falla, que es el propio interruptor (o, a menudo, el principio 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 será digno de consideración. Más aún, la pieza de código para el cambio sería ciertamente mucho menor que el código para todo el programa, lo que reduce significativamente el riesgo de que se produzca 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 se puede activar 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 corrección / detección de errores del hardware o por un pequeño fragmento de código para la corrección / detección de errores. Es mejor poner dicho código pequeño, múltiple e independiente del software / firmware principal. Su tarea principal es solo de verificación / corrección. Si el circuito / firmware del hardware es confiable (como el hecho de que está más endurecido por radiación que el resto, o que tiene varios circuitos / lógicas), entonces podría considerar realizar una corrección de errores con él. Pero si no lo es, 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 hacer uso de un algoritmo básico de corrección de errores como Hamming / Golay23, ya que pueden implementarse más fácilmente tanto en el circuito como en el software. Pero en última instancia, depende de la capacidad de su equipo. Para la detección de errores, normalmente se utiliza CRC.

  5. ... el hardware que soporta la recuperación Ahora, llega al aspecto más difícil de este problema. 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 ionizante total alcance 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 preocupación de mayor importancia para un dispositivo expuesto a un alto nivel de radiación (como el satélite).

Además de la sugerencia de anticipar el error de 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 que casi se debe tener para evitar señales incompletas / incorrectas recibidas de otro sistema

  2. Filtra en tu lectura de ADC. No use la lectura ADC directamente. Filtre por filtro de mediana, filtro de media o cualquier otro filtro; nunca confíe en el valor de lectura individual. Muestra más, no menos - razonablemente.


C ++ especifica los rangos de los tipos integrales por referencia al estándar C. El estándar C dice:

Para los tipos de enteros sin signo distintos de los caracteres unsigned char , los bits de la representación del objeto se dividirán en dos grupos: bits de valor y bits de relleno (no es necesario que haya ninguno de estos últimos). Si hay N bits de valor, cada bit representará una potencia diferente de 2 entre 1 y 2 N - 1 , de modo que los objetos de ese tipo podrán representar valores de 0 a 2 N - 1 utilizando una representación binaria pura; Esto se conocerá como la representación del valor. Los valores de cualquier bit de relleno no están especificados.

Además, C ++ requiere:

Los enteros sin signo obedecerán las leyes del módulo aritmético 2 n donde n es el número de bits en la representación del valor de ese tamaño particular de entero.

Al juntar todo esto, encontramos que un tipo integral sin signo tiene n bits de valor, representa los valores en el rango [0, 2 n ) y obedece las leyes del módulo aritmético 2 n .







c++ c gcc embedded fault-tolerance