Pregunta Compilación de una aplicación para su uso en entornos altamente radiactivos


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 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 algún cambio que podamos hacer en nuestro código, o mejoras en el tiempo de compilación que puedan hacerse para identificar / corregir? errores suaves y memoria-corrupción causada por evento único molesta? ¿Algún otro desarrollador tuvo éxito en la reducción de los efectos nocivos de los errores suaves en una aplicación de larga ejecución?


1278
2018-04-24 19:09


origen


Respuestas:


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

* (Los satélites miniaturizados son mucho más propensos a las perturbaciones de un solo evento 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 detectable, erróneo   situación por el software / firmware en sí sin, al menos uno    dupdo de versión mínima de trabajo del software / firmware algun lado para recuperación propósito - y con el hardware que soporta la recuperación (funcional).

Ahora, esta situación normalmente se maneja tanto en el nivel de hardware como de software. Aquí, cuando 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. Esto es un casi imprescindible característica para cualquier software / firmware en un entorno altamente ionizado. Sin esto, tú podría tener el software / hardware redundante tantos como quieras, pero en un punto, todos explotarán. ¡Entonces, prepara esta característica!

  2. ... versión mínima de trabajo ... Tenga respuestas, copias múltiples, versión mínima del software / firmware en su código. Esto es como el modo seguro en Windows. En lugar de tener solo una versión completamente funcional de su software, tenga copias múltiples 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á solamente las siguientes dos o tres características:

    1. capaz de escuchar el comando desde el 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. ... copia ... en alguna parte ... Tener software / firmware redundante en alguna parte.

    1. Podrías, con o sin hardware redundante, intente 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ía latidos del corazón el uno al otro, pero solo uno estará activo a la vez. Si se sabe que uno o más software / firmware no responden, cambie a otro software / firmware. El beneficio de utilizar este enfoque es que podemos tener un reemplazo funcional inmediatamente después de que ocurre un error, sin ningún contacto con el sistema / parte externo responsable de detectar y reparar el error (en el caso de satélite, generalmente es el Centro de Control de Misión ( MCC)).

      Estrictamente hablando, sin hardware redundante, la desventaja de hacer esto es que en realidad no poder eliminar todas punto único de fallas. Por lo menos, todavía tendrás uno punto único de falla, que es el interruptor mismo (o a menudo el comienzo del código). Sin embargo, para un dispositivo limitado por 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 considerarlo. Además, la pieza de código para el cambio sería mucho menor que el código para todo el programa, lo que reduciría significativamente el riesgo de tener Evento Único en él.

    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 el 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 hardware circuito de corrección / detección de errores o mediante un pequeño fragmento de código para la corrección / detección de errores. Lo mejor es poner dicho código pequeño, múltiple y independiente desde el software / firmware principal. Su principal tarea es solamente para verificar / corregir Si el circuito de hardware / firmware es de confianza (por ejemplo, si está más endurecido por radiación que los silencios o tiene múltiples circuitos / lógicas), entonces podría considerar corregir los 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 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 como en el software. Pero finalmente depende de la capacidad de tu equipo. Para la detección de errores, normalmente se usa CRC.

  5. ... hardware que soporta la recuperación Ahora, llega al aspecto más difícil sobre este tema. En última instancia, la recuperación requiere que el hardware que es responsable de la recuperación sea al menos funcional. Si el hardware está permanentemente roto (normalmente ocurre después de su Dosis ionizante total 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 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 anterior de anticipar el error del firmware debido a un evento único molesto, también me gustaría sugerirle que tenga:

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

  2. Filtra en tu lectura de ADC. Hacer no usa la lectura de ADC directamente. Filtrar por filtro mediano, filtro medio o cualquier otro filtro - Nunca confiar en el valor de lectura única. Muestra más, no menos, razonablemente.


729
2018-04-25 02:58



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

  1. Monitoreo regular de la memoria en busca de errores y luego eliminar esos errores,
  2. robustos mecanismos 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 para que los errores de varios bits raramente ocurran, ya que la mayoría ECC la memoria puede recuperarse de errores de un solo bit, no de errores de varios bits.

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

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

Discuten técnicas especialmente 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: verificando condiciones previas y posteriores, luego verificando el objeto para verificar que aún se encuentre en estado válido.

370
2018-04-24 19:32



Aquí hay algunos pensamientos e ideas:

Use ROM de manera más creativa.

Almacena todo lo que puedas en ROM. En lugar de calcular cosas, guarde las tablas de búsqueda en la ROM. (¡Asegúrate de que tu compilador está enviando tus tablas de búsqueda a la sección de solo lectura! Imprime las direcciones de memoria en tiempo de ejecución para verificarlas). Guarda tu tabla de vectores de interrupción en la ROM. Por supuesto, ejecute algunas pruebas para ver qué tan confiable es su ROM comparada con su RAM.

Usa tu mejor RAM para la pila.

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

Implementa las rutinas del temporizador tick y watchdog.

Puede ejecutar una rutina de "control de la cordura" cada vez que el temporizador marque, 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 progreso, y la rutina de control de cordura podría garantizar que esto haya ocurrido.

Implementar códigos de corrección de errores 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 expuesto al procesador a la radiación durante un tiempo más prolongado, lo que aumenta las posibilidades de errores, por lo que debe considerar la compensación.

Recuerda los cachés.

Verifique los tamaños de sus cachés de CPU. Es probable que los datos a los que haya accedido o modificado recientemente estén dentro de un caché. Creo que puede desactivar al menos algunas de las memorias caché (a un gran costo de rendimiento); deberías probar esto para ver qué tan susceptibles son las cachés a las SEU. Si los cachés son más resistentes que la RAM, entonces podría leer y volver a escribir datos críticos con regularidad para asegurarse de que permanecen en la caché y devolver la RAM a la línea.

Use manejadores de falla de página inteligentemente.

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 controlador de falla de página que verifique antes de realizar el servicio de la solicitud de lectura. (Los sistemas operativos de PC lo usan para cargar páginas que se han intercambiado al disco de forma transparente).

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

Con el lenguaje ensamblador, saber qué hay en los registros y qué hay en la RAM; tú saber qué tablas RAM especiales está usando la CPU, y puede diseñar cosas de forma indirecta para reducir el riesgo.

Utilizar objdump para realmente mirar el lenguaje ensamblador generado, y calcular cuánto código ocupa cada una de sus rutinas.

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

Recuerde 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 requeridos 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 todos los códigos / datos están 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, debe reiniciarse. Con 3 o más dispositivos, puede usar un sistema de "votación" para tratar de identificar cuál se ha visto comprometido.


107
2018-04-24 23:11



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

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

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


93
2018-04-24 21:13



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, estos son algunos consejos misceláneos:

  • Use las medidas de seguridad diarias de "pan y mantequilla" que deberían estar presentes en cualquier sistema embebido semiprofesional: perro guardián interno, detector interno de bajo voltaje, monitor interno del reloj. 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 funciones de vigilancia, como una ventana de tiempo determinada, dentro de la cual deberá actualizar el watchdog. Esto es preferible si tiene un sistema de misión crítica en tiempo real.
  • En general, use una MCU adecuada para este tipo de sistemas, y no una pelusa genérica convencional que recibió en un paquete de copos de maíz. Casi todos los fabricantes de MCU hoy en día tienen MCU especializadas diseñadas para aplicaciones de seguridad (TI, Freescale, Renesas, ST, Infineon, etc.). Estos tienen muchas características de seguridad incorporadas, incluidos 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 ellos.
  • 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 se pueden ubicar en la memoria RAM y, por lo tanto, son vulnerables.

    Para protegerse contra las corrupciones de registro, preferiblemente elija un microcontrolador con funciones incorporadas de "escritura única" de los registros. Además, necesita almacenar 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 las variables importantes de la misma manera.

    Nota: siempre use programación defensiva. Lo que significa que tienes que configurar todas se registra en la MCU y no solo en los utilizados por la aplicación. No quiere que un periférico de hardware aleatorio se despierte repentinamente.

  • Hay todo tipo de métodos para detectar errores en RAM o NVM: sumas de comprobación, "patrones de desplazamiento", software ECC, etc. La mejor solución hoy en día es no utilizar ninguno de estos, sino utilizar una MCU con ECC integrado y controles similares. Porque hacer esto en el software es complejo, y la comprobación de errores en sí misma podría introducir errores y problemas inesperados.

  • Use 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 verificación CRC adjunta.
  • Evite usar memorias externas fuera de la MCU.
  • Implemente una rutina predeterminada de servicio de interrupción / manejador de excepciones 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 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. Ejemplos.

    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 dependan de un comportamiento mal especificado. Es probable que dicho comportamiento cambie 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 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 eliminando errores (¿por qué no quieres detectar errores en ningún tipo de aplicación?).
  • IMPORTANTE: No implemente ninguna dependencia de los valores predeterminados de las variables de duración de almacenamiento estáticas. Es decir, no confíe en los contenidos predeterminados del .data o .bss. Puede haber cualquier cantidad de tiempo entre el momento de la inicialización y el momento en que se usa la variable realmente, podría haber habido suficiente tiempo para que la memoria RAM se corrompiera. En su lugar, escriba el programa para que todas esas variables se configuren desde NVM en tiempo de ejecución, justo antes de la hora en que se utiliza dicha variable por primera vez.

    En la práctica, esto significa que si una variable se declara en el alcance del archivo o como staticnunca deberías usar = para inicializarlo (o podrías hacerlo, pero no tiene sentido, porque de todos modos no puedes confiar en el valor). Siempre configúrelo en tiempo de ejecución, justo antes de usarlo. Si es posible actualizar repetidamente tales variables desde NVM, entonces hágalo.

    Del mismo modo, en C ++, no confíe en los constructores para variables de duración de almacenamiento estáticas. 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 de la persona que llama.

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

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

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

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

36
2018-04-27 14:11



Es posible utilizar C para escribir programas que se comporten de forma robusta en dichos entornos, pero solo si la mayoría de las formas de optimización del compilador están desactivadas. Los compiladores de optimización están diseñados para reemplazar muchos patrones de codificación aparentemente redundantes por otros más eficientes, y es posible que no tengan idea de la razón por la cual el programador está probando x==42 cuando el compilador sabe que no hay forma x podría contener cualquier otra cosa es porque el programador quiere evitar la ejecución de cierto código con xmanteniendo 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.

Declarando variables como volatile a menudo es útil, pero puede no ser una panacea. De particular importancia, tenga en cuenta que la codificación segura a menudo requiere que la peligrosa las operaciones tienen enclavamientos de hardware que requieren varios pasos para activarse, y ese código se escribe 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 todos las comprobaciones para el estado del sistema se repiten después de la prepare_for_activation(), el sistema puede ser robusto contra casi cualquier evento plausible de falla única, incluso aquellos que arbitrariamente dañarían el contador y la pila del programa. Si una falla ocurre justo después de una llamada a prepare_for_activation(), eso implicaría esa activación hubiera sido apropiada (ya que no hay otra razón prepare_for_activation() habría sido llamado antes del error). Si el error causa que el código llegue prepare_for_activation() inapropiadamente, pero allí no hay sucesos de falla subsecuentes, no habría forma de que el código sea subsecuentemente alcanzar trigger_activation() sin haber pasado por la comprobación de validación ni haber llamado a cancelar_preparaciones primero [si la pila falla, la ejecución podría pasar a un punto justo antes trigger_activation() después del contexto que llamó prepare_for_activation() regresa, 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 compiladores de C modernos. Tales compiladores pueden ser muy peligrosos en ese tipo de entorno porque agresivos se esfuerzan por incluir únicamente 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 estarían bien definidas. El código cuyo objetivo 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 no definido, puede inferir que las condiciones que requerirían tal recuperación en tales casos no pueden ocurrir, eliminando así el código que las habría verificado.


30
2018-04-25 16:14



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

  • suma de comprobación de datos constantes. 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 veas una discrepancia, es hora de reiniciar o restablecer.

  • 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.

  • implementar 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 darle la lista de valores aceptables de la bandera al final del ciclo. Restablece si ves desviaciones.

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


27
2018-04-25 17:05



Lo que podría ayudarte es un perro guardián. Los perros guardianes se utilizaron 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 hacia abajo 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 monitorea el correcto funcionamiento de todos los sistemas esenciales. Si esta rutina se completa correctamente = encuentra que la computadora funciona correctamente, vuelve a establecer el contador 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, el cero es igual a 1024 y el contador continúa la cuenta regresiva de nuevo.

Este perro guardián asegura que la computadora conectada se reinicia 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 de 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 soportado deberá poder proceder sin control por computadora para ese período de tiempo.


26
2018-04-26 22:41