Compilar una aplicación para usar en entornos altamente radiactivos

1456

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?

torre
fuente
186
¿Están cambiando los valores en la memoria o están cambiando los valores en el procesador? Si el hardware está diseñado para el entorno, el software debe ejecutarse como si se ejecutara en un entorno no radiactivo.
Thomas Matthews
3
Si es posible, debe configurar un sistema de registro que almacene eventos en la memoria no volátil que sea resistente a la radiación. Almacene suficiente información para poder rastrear el evento y encontrar fácilmente la causa raíz.
Thomas Matthews
2
@Thomas Matthews Toda la memoria tiene una tasa de error FIT, y los fabricantes de hardware hacen muchas promesas. La mayoría de los problemas probablemente son causados ​​por la modificación de RAM de SEU en tiempo de ejecución.
torre
99
Esta es una solución combinada de hardware / software, pero sé que Texas Instruments (y probablemente otros) fabrica chips integrados para aplicaciones críticas de seguridad que consisten en dos núcleos duplicados, que se ejecutan en bloque, medio ciclo fuera de fase. Hay interrupciones especiales y acciones de reinicio que se realizan cuando el hardware detecta algo diferente entre los núcleos, para que pueda recuperarse de los errores. Creo que TI los marca como procesadores de seguridad "Hércules".
mbrig
55
Motores robustos redundantes, algunos engranajes, ejes y trinquetes. Reemplace anualmente o con mayor frecuencia según lo requieran las dosis. No realmente, mi primera pregunta con este tipo de problemas siempre ha sido, ¿realmente necesitas tanto software allí? Sea lo más análogo posible.
jwdonahue

Respuestas:

814

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. ... finalidad la 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. Como mínimo, 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 podría 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 que está más endurecido por la radiación que los restos, o que tiene múltiples circuitos / lógicas), entonces puede 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 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 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 , no utilizar el ADC leer directamente. Filtra por filtro medio, filtro medio o cualquier otro filtro; nunca confíes en un solo valor de lectura. Muestra más, no menos, razonablemente.

Ian
fuente
401

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 ocurran 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 de errores robusta 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 newy delete)
  6. Asignación dinámica (se utiliza un grupo de memoria dedicado y ubicación newpara evitar la posibilidad de corrupción del montón del sistema).
rsjaffe
fuente
28
Esto en realidad suena como algo en lo que un lenguaje puro sería bueno. Como los valores nunca cambian, si están dañados, puede volver a la definición original (que es lo que se supone que debe ser), y no hará lo mismo dos veces accidentalmente (debido a la falta de efectos secundarios).
PyRulez
20
RAII es una mala idea, porque no puede depender de que funcione correctamente o incluso en absoluto. Podría dañar aleatoriamente sus datos, etc. Realmente desea tanta inmutabilidad como sea posible, además de mecanismos de corrección de errores. Es mucho más fácil tirar las cosas rotas que intentar repararlas de alguna manera (¿cómo sabe exactamente lo suficiente para volver al estado anterior correcto?). Sin embargo, es probable que desee usar un lenguaje bastante estúpido para esto: las optimizaciones pueden doler más de lo que ayudan.
Luaan
67
@PyRulez: Los lenguajes puros son una abstracción, el hardware no es puro. Los compiladores son bastante buenos para ocultar la diferencia. Si su programa tiene un valor que lógicamente ya no debería usar después del paso X, el compilador puede sobrescribirlo con un valor que se calcula en el paso X + 1. Pero esto significa que no puedes regresar. Más formalmente, los posibles estados de un programa en un lenguaje puro forman un gráfico acíclico, lo que significa que dos estados son equivalentes y pueden fusionarse cuando los estados accesibles desde ambos son equivalentes. Esta fusión destruye la diferencia en los caminos que conducen a esos estados.
MSalters
2
@Vorac: según la presentación, la preocupación con las plantillas de C ++ es la hinchazón de código.
jww
3
@DeerSpotter El problema exacto es mucho más grande que eso. La ionización puede dañar partes de su programa de vigilancia en ejecución. Entonces necesitarás un observador de un observador, luego - observador de un observador de un observador y así sucesivamente ...
Agnius Vasiliauskas
116

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 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-tic y watchdog

Puede ejecutar una rutina de "verificación de 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 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 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 permanezca en la memoria caché y vuelva 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 RAM especiales está utilizando la CPU, y puede diseñar cosas de forma indirecta para mantener su riesgo bajo.

Utilícelo objdumppara 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.

Artelius
fuente
14
Hoy en día, hay ECC disponible a través del hardware, lo que ahorra el tiempo de procesamiento. El primer paso sería elegir un microcontrolador con ECC incorporado.
Lundin
23
En algún lugar en el fondo de mi mente hay una referencia al hardware de vuelo de la aviónica (¿tal vez el transbordador espacial?) Donde la arquitectura redundante fue diseñada explícitamente para no ser idéntica (y por diferentes equipos). Hacerlo mitiga la posibilidad de un error sistémico en el diseño de hardware / software, reduciendo la posibilidad de que todos los sistemas de votación se bloqueen al mismo tiempo cuando se enfrentan con las mismas entradas.
Peter M
8
@PeterM: AFAIK que también reclama el software de vuelo para el Boeing 777: tres versiones de tres equipos en tres lenguajes de programación.
Restablece a Monica - M. Schröder el
77
@DanEsparza RAM generalmente tiene un condensador (DRAM) o algunos transistores en retroalimentación (SRAM) que almacenan datos. Un evento de radiación puede cargar / descargar espuriosamente el condensador, o cambiar la señal en el circuito de retroalimentación. ROM normalmente no necesita la capacidad de ser escrito (al menos sin circunstancias especiales y / o voltajes más altos) y, por lo tanto, puede ser inherentemente más estable en el nivel físico.
nanofarad
77
@DanEsparza: Existen múltiples tipos de memorias ROM. Si la "ROM" se emula, es decir, mediante eeprom o flash de solo lectura a 5v pero programable a 10v, entonces esa "ROM" aún es propensa a la ionización. Quizás solo menos que otros. Sin embargo, hay cosas buenas y duras como Mask ROM o PROM basado en fusibles que creo que necesitaría una cantidad de radiación realmente seria para comenzar a fallar. Sin embargo, no sé si todavía se fabrican.
quetzalcoatl
105

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 clasificació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 las ncomparaciones).

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 detectar / corregir errores a nivel de operación).

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

Eric Towers
fuente
55
Realmente me gusta tu respuesta. Este es un enfoque de software más genérico para la integridad de los datos, y se utilizará una solución de tolerancia a fallas basada en algoritmos en nuestro producto final. ¡Gracias!
torre
41

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:

  • Use 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 interrupciones predeterminada / un controlador 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 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. 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 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 los contenidos predeterminados de .datao .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 alcance 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 "copiado" que se inicializa .datay .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.
Lundin
fuente
Una forma de tratar que los booleanos se corrompan (como en su enlace de ejemplo) podría ser TRUEigualarlos para 0xffffffffluego usarlos POPCNTcon un umbral.
wizzwizz4
@ wizzwizz4 Dado que el valor 0xff es el valor predeterminado de la celda flash no programada, parece una mala idea.
Lundin
%01010101010101010101010101010101, XOR entonces POPCNT?
wizzwizz4
1
@ wizzwizz4 O simplemente el valor 0x1, como lo requiere el estándar C.
Lundin
1
@ wizzwizz4 ¿Por qué utiliza algunos o todos los métodos mencionados anteriormente (ECC, CRC, etc.)? De lo contrario, el rayo cósmico podría .textcambiar un solo bit en su sección, cambiando un código de operación o similar.
Lundin
34

Es 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 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==42cuando el compilador sabe que no hay forma de que xpueda contener algo más es porque el programador quiere evitar la ejecución de cierto código con la xretención de 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 a volatilemenudo 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 para el estado del sistema se repiten después del prepare_for_activation(), el sistema puede ser robusto contra casi cualquier evento de falla única plausible, incluso aquellos que corromperían arbitrariamente el contador y la pila del programa. 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 prepare_for_activation()que se haya llamado antes de la falla). Si la falla causa que el código llegue de manera prepare_for_activation()inapropiada, pero no hay eventos de falla subsiguientes, no habría forma de que el código llegue posteriormente trigger_activation()sin haber pasado por la verificación de validación o llamando a cancel_preparations primero [si la pila falla, la ejecución podría proceder a un punto justo antestrigger_activation()después del contexto que llamó prepare_for_activation()devuelve, 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.

Super gato
fuente
66
Hablando de manera realista, ¿cuántos compiladores modernos hay que no ofrecen -O0o un cambio equivalente? GCC hará muchas cosas extrañas si le das permiso , pero si le pides que no las haga, generalmente también puede ser bastante literal.
Leushenko
24
Lo siento, pero esta idea es fundamentalmente peligrosa. Deshabilitar optimizaciones produce un programa más lento. O, en otras palabras, necesita una CPU más rápida. Como sucede, las CPU más rápidas son más rápidas porque las cargas en sus puertas de transistores son más pequeñas. Esto los hace mucho más susceptibles a la radiación. La mejor estrategia es utilizar un chip grande y lento donde un solo fotón tiene muchas menos probabilidades de volcarse un poco y recuperar la velocidad -O2.
MSalters
27
Una razón secundaria por la cual -O0es una mala idea es porque emite instrucciones mucho más inútiles. Ejemplo: una llamada no en línea contiene instrucciones para guardar registros, realizar la llamada, restaurar registros. Todo esto puede fallar. Una instrucción que no está allí no puede fallar.
MSalters
15
Otra razón por la cual -O0es una mala idea: tiende a almacenar variables en la memoria en lugar de en un registro. Ahora no es seguro que la memoria sea más susceptible a los SEU, pero los datos en vuelo son más susceptibles que los datos en reposo. El movimiento de datos inútil debe evitarse, y -O2ayuda allí.
MSalters
99
@MSalters: Lo importante no es que los datos sean inmunes a las interrupciones, sino que el sistema pueda manejar las interrupciones de una manera que cumpla con los requisitos. En muchos compiladores, deshabilitar todas las optimizaciones produce un código que realiza un número excesivo de movimientos de registro a registro, lo cual es malo, pero almacenar variables en la memoria es más seguro desde el punto de vista de la recuperación que mantenerlas en los registros. Si uno tiene dos variables en la memoria que se supone que obedecen a alguna condición (por ejemplo, v1=v2+0xCAFEBABEy todas las actualizaciones de las dos variables se realizan ...
supercat
29

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 usted tiene una variable importante x, escriba su valor en x1, x2y x3y leerlo como (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. 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.

Dmitry Grigoryev
fuente
27

Lo que podría ayudarte es un perro guardián . 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.

OldFrank
fuente
99
Los perros guardianes de contador binario con circuitos integrados estándar TTL son una solución de los años ochenta. No hagas eso. Hoy en día, no existe un solo MCU en el mercado sin circuitos de vigilancia integrados. Todo lo que necesita verificar es si el perro guardián incorporado tiene una fuente de reloj individual (buena, probablemente el caso) o si hereda su reloj del reloj del sistema (malo).
Lundin
1
O implemente el perro guardián en una FPGA: ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20130013486.pdf
nos
2
Todavía se usa ampliamente en procesadores integrados, por cierto.
Graham
55
@ Peter Mortensen Por favor, detenga su juerga de edición en cada respuesta a esta pregunta. Esto no es Wikipedia, y esos enlaces no son útiles (y estoy seguro de que todos saben cómo encontrar Wikipedia de todos modos ...). Muchas de sus ediciones son incorrectas porque no conoce el tema. Estoy haciendo reversiones en tus ediciones incorrectas a medida que las encuentro. No estás convirtiendo este hilo mejor, sino peor. Deja de editar.
Lundin
Jack Ganssle tiene un buen artículo sobre perros guardianes: ganssle.com/watchdogs.htm
Igor Skochinsky
23

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 (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 repita nveces (donde nsea ​​mayor que 2, y preferiblemente impar), y que cada elemento de hardware pueda 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 realizar una suma de verificación (p. Ej., CRC / SHA) en su FSM, y almacene el CRC / SHA en la propia FSM (así como en la transmisión del mensaje y la suma de verificación de los mensajes mismos). Haga que los nodos verifiquen su FSM regularmente 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.

abligh
fuente
2
Ethernet probablemente no sea una gran idea para usar en aplicaciones de misión crítica. Tampoco lo es I2C, fuera de la propia PCB. Algo resistente como CAN sería mucho más adecuado.
Lundin
1
@Lundin Fair point, aunque cualquier cosa conectada ópticamente (incluido Ethernet) debería estar bien.
abligh
1
Los medios físicos no son tanto la razón por la cual Ethernet no es adecuada, sino la falta de comportamiento determinista en tiempo real. Aunque supongo que hoy en día también hay formas de proporcionar Ethernet algo confiable, simplemente lo agrupo junto con la electrónica comercial / de juguete por viejo hábito.
Lundin
1
@Lundin es un punto justo, pero como sugiero usarlo para ejecutar RAFT, habrá un comportamiento en tiempo real (teóricamente) no determinista en el algoritmo de todos modos (por ejemplo, elecciones simultáneas de líderes que resulten en una elección de repetición similar a CSMA / DISCOS COMPACTOS). Si se necesita un comportamiento estricto en tiempo real, podría decirse que mi respuesta tiene más problemas que Ethernet (y tenga en cuenta que al principio de mi respuesta dije que 'correcto' probablemente sea a expensas de 'rápido' a menudo). Sin embargo, incorporé tu punto sobre CAN.
abligh
1
@Lundin: Ningún sistema que implique aspectos asincrónicos puede ser completamente no determinista. Creo que el peor de los casos de Ethernet puede limitarse en ausencia de interrupciones de hardware si los protocolos de software se configuran de manera adecuada y los dispositivos tienen ID únicos y hay un límite conocido para la cantidad de dispositivos (cuantos más dispositivos, mayor el peor número de reintentos).
supercat
23

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_tetc.), SAFE_uint32_tcree el suyo que contenga un múltiplo (mínimo de 3) de uint32_t. Sobrecargue todas las operaciones que desee (* + - / << >> = ==! = 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 =)

jkflying
fuente
1
Algunas de estas sugerencias tienen algo a lo largo de un 'cordura cheque de varios bits' mentalidad similar para detectar la corrupción, me gusta mucho éste con la sugerencia de los tipos de datos personalizados de seguridad crítica más aunque
WearyWanderer
2
Hay sistemas en el mundo donde cada nodo redundante fue diseñado y desarrollado por diferentes equipos, con un árbitro para asegurarse de que no se decidieron accidentalmente por las mismas soluciones. De esa manera, no todos tienen el mismo error y transitorios similares no manifiestan modos de falla similares.
jwdonahue
16

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, diferentes objetivos).

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 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 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 pueden verse 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 para 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 una seguridad adicional (o codificar sus estructuras de datos más importantes como resistentes, y el resto, los datos prescindibles que puede volver a calcular desde las estructuras de datos principales,

Si está interesado en estructuras de datos resistentes (que es un campo nuevo, pero emocionante, 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é de errores de computación (CPU) . Otras respuestas ya apuntaban al uso de transacciones atómicas como en 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 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 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 (debido a que 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 acordados 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 manera, 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 ...).

gaborous
fuente
9

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 de forma continua o varias cosas por el estilo? 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. Puede encontrar que hay algo acerca de "no haga esto porque se bloqueará, así que habilite 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.

Graham
fuente
1
De hecho, en ninguna parte de la prueba de la pregunta, el autor menciona que se encontró que la aplicación funciona bien fuera del entorno radiactivo.
Marc.2377
9

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 doble del tiempo de espera del watchdog. Los datos enviados desde el maestro se generan de manera única cada vez.

Jonas Byström
fuente
77
Estoy tratando de comprender un escenario en el que puedas tener un maestro fuera del entorno de radiación, capaz de comunicarte de manera confiable con los esclavos dentro del entorno de radiación, donde no puedas simplemente poner a los esclavos fuera del entorno de radiación.
fostandy
1
@fostandy: Los esclavos miden o controlan utilizando equipos que necesitan un controlador. Digamos un contador geiger. El maestro no necesita una comunicación confiable debido a la redundancia del esclavo.
Jonas Byström
44
Introducir un maestro no significará automáticamente una mayor seguridad. Si el esclavo x se ha vuelto loco debido a la corrupción de la memoria, de modo que se dice repetidamente a sí mismo "el maestro está aquí, el maestro está contento", entonces ninguna cantidad de CRC u órdenes ladradas por el maestro lo guardarán. Tendría que darle al maestro la posibilidad de cortar el poder de ese esclavo. Y si tiene un error de causa común, agregar más esclavos no aumentará la seguridad. También tenga en cuenta que la cantidad de errores de software y la cantidad de cosas que pueden romperse aumentan con la complejidad.
Lundin
55
Dicho esto, por supuesto que sería bueno "subcontratar" la mayor parte del programa a un lugar menos expuesto, manteniendo la electrónica dentro del entorno radiactivo lo más simple posible, si tiene esa opción.
Lundin
7

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

ren
fuente
2
Seguramente, un sistema integrado preferiría las capturas críticas de seguridad en una instancia de una aplicación robusta que simplemente disparar varias instancias, aumentar los requisitos de hardware y, en cierta medida, esperar suerte ciega de que al menos una instancia pase bien. Tengo la idea y es válida, pero me inclino más hacia las sugerencias que no dependen de la fuerza bruta
WearyWanderer
7

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.

BЈовић
fuente
11
O más bien, lea los requisitos técnicos e implemente los que tengan sentido. Una gran parte de los estándares SIL no tiene sentido, si los sigue dogmáticamente, terminará con productos peligrosos e inseguros. La certificación SIL hoy se trata principalmente de producir una tonelada de documentación y luego sobornar a una casa de prueba. El nivel SIL no dice nada sobre la seguridad real del sistema. En cambio, querrá centrarse en las medidas técnicas de seguridad reales. Hay algunos muy buenos en los documentos SIL, y hay algunos sin sentido.
Lundin
7

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). Entonces, si elige 8, obtendrá 8 veces menos ram y un tiempo de acceso un poco 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).

Alex C
fuente
7

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 $2y 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 ...

frío
fuente
7

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

Ingrese la descripción de la imagen aquí

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 base de código.

Hitul
fuente
2
Quizás un medio óptico como un CD-ROM cumpliría con esta definición. Tendría la ventaja adicional de una gran capacidad.
Wossname
2
Sí, será similar, pero el CD-ROM usará menos, pero este será un sistema completamente mecánico.
Hitul
77
Me pregunto si hay una razón por la cual no usan lectores de tarjetas perforadas en el espacio.
Soren
3
@Soren La velocidad y el espacio físico pueden ser una razón.
Hitul
5

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

Gerhard
fuente
4

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 ensamblado para que una interrupción en un punto clave no pueda dar como resultado comandos externos inconsistentes. Fallo rápido 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.

MrBigglesworth
fuente
3

Teniendo en cuenta los comentarios de supercat, las tendencias de los compiladores modernos y otras cosas, estaría tentado a volver a los tiempos antiguos y escribir todo el código en asambleas y 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.

Joshua
fuente
Soy un gran admirador del lenguaje ensamblador (como puede ver en mis respuestas a otras preguntas), pero no creo que sea una buena respuesta. Es bastante posible saber qué esperar del compilador para la mayoría del código C (en términos de valores que viven en registros frente a memoria), y siempre puede verificar que sea lo que esperaba. Escribir a mano un proyecto grande en ASM es solo una tonelada de trabajo adicional, incluso si tiene desarrolladores que se sienten muy cómodos escribiendo ARM ASM. Tal vez si desea hacer cosas como calcular el mismo resultado 3 veces, escribir algunas funciones en asm tiene sentido. (los compiladores lo eliminarán)
Peter Cordes
De lo contrario, el mayor riesgo que debe equilibrarse con la actualización del compilador puede dejarle cambios inesperados.
Joshua
1

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 (pila, registros, llamadas a funciones), ya sea registrándolas en algún lugar en el archivo o transmitiéndolas de alguna manera directamente ("oh no, me estoy bloqueando").

La recuperación de dicha situación de error es reiniciar (si el software aún está activo 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 está funcionando 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 al mínimo 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)

TarmoPikaro
fuente
Lo sentimos, pero la pregunta es sobre el entorno radiactivo donde ocurrirán fallas de hardware. Su respuesta es sobre la optimización general del software y cómo encontrar errores. Pero en esta situación, los errores no son producidos por errores
jeb
Sí, también puede culpar a la gravedad de la tierra, las optimizaciones del compilador, la biblioteca de terceros, el entorno radiactivo, etc. ¿Pero estás seguro de que no son tus propios errores? :-) A menos que se pruebe, no lo creo. He ejecutado alguna vez una actualización de firmware y una situación de apagado de prueba: mi software sobrevivió a todas las situaciones de apagado solo después de que solucioné todos mis propios errores. (Más de 4000 apagados durante la noche) Pero es difícil creer que haya errores en algunos casos. Especialmente cuando hablamos de corrupción de memoria.
TarmoPikaro
0

¡Realmente he leído muchas respuestas geniales!

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


fuente