Directrices disponibles para el procesamiento controlado por interrupciones

¿Existen pautas disponibles para la cantidad de código que se debe tener en una sección crítica del procesamiento impulsado por interrupciones?

Mi regla general personal es que la parte crítica (es decir, entre deshabilitar interrupciones y habilitar interrupciones) de cualquier procesamiento controlado por interrupción no debe tener más de una docena de líneas de código (incluidas las de cualquier función/biblioteca/macro llamada ), y que el procesamiento debe ser lo más lineal posible.

Esperaría algo como:

disable_interrupts
if error_condition:
   set_error_flag
else:
   small_data_transfer
   set_ready_flag
enable_interrupts

Sin embargo, me han pedido que analice un proyecto existente en el que algunas de las interrupciones tienen controladores en los que los gráficos de llamadas no caben en una hoja de A1 (todas en la sección crítica).

Para aclarar, este es un proyecto "bare metal" sin OS/Scheduler implementado, y todos los controladores de interrupciones comienzan deshabilitando todas las interrupciones, es decir, los autores originales consideraron que cada operación en cada ISR es crítica (incluso la limpieza está dentro de una sección de activación/desactivación). Hay una multitud de interrupciones y dependencias de hardware pero, por lo que puedo ver, algunos de los ISR ejecutarán miles de líneas dentro de la sección "crítica".

Sé que esto es un problema y realmente necesita ser reescrito por completo, pero no puedo encontrar ningún estándar, incluido MISRA, que pueda señalar en lugar de simplemente decir " en mi experiencia ", para convencer a la gerencia del proyecto que debo señalar. algunos estándares aceptados.

Entonces, de ahí mi pregunta: ¿alguien puede indicarme algún estándar o guía que pueda usar para respaldar mi experiencia? (O, por supuesto, estoy totalmente equivocado).

¿Solo tiene una única prioridad de interrupción? Sé que he realizado algunos proyectos en los que toda la aplicación vivía en el controlador de interrupciones de baja prioridad. Básicamente, actuaba como un bucle principal activado por interrupción. Suponiendo que tenga suficientes niveles de prioridad de interrupción, no veo por qué ese tipo de estructura es un problema.
Otro comentario sería que he escrito algunos decodificadores de protocolo que eran bastante grandes, pero la ruta de ejecución real a través del decodificador por interrupción era corta. Creo que un gráfico de ruta de llamada para eso sería desproporcionadamente grande, y más bien tergiversaría la eficiencia real de la interrupción.
Su terminología es confusa. Por un lado, parece estar hablando principalmente de la ISR (rutina de servicio de interrupción). Pero también sigue mencionando "sección crítica", que normalmente es un término reservado para aquellas secciones de código que no son ISR durante las cuales las interrupciones están deshabilitadas, lo que impide que se ejecute ISR. ¿Puedes aclarar un poco tu pregunta?
@DaveTweed: la pregunta se expandió arriba, pero básicamente en este contexto estoy usando "Crítico" para indicar partes de la ISR donde todas las demás interrupciones están deshabilitadas.
Nunca pongas un printf() en una interrupción... es una función relativamente grande.
Entonces tiene un sistema que permite interrupciones anidadas, y parece que los diseñadores del código existente no planearon esto con mucho cuidado. Suena como un desastre. Si las secciones críticas en las interrupciones de baja prioridad son largas, la respuesta del sistema a las interrupciones de alta prioridad presumiblemente más importantes se retrasará. Me sentiría tentado a dejar de lado el código existente y crear mi propia arquitectura de alto nivel para el sistema general basada solo en los requisitos externos. Solo entonces volvería al código existente para ver qué partes podrían ser reutilizables.
@DaveTweed: Muy cierto, sin embargo, necesito más que evidencia anacdotal para persuadir a la gerencia de que es probable que el sistema existente nunca brinde el nivel de confiabilidad que están buscando.
Todo lo que obtenga de la web, incluido este sitio, será visto como "anecdótico" por la administración. La única forma de obtener los datos concretos que necesitan es someter a prueba la implementación existente para demostrar los errores.
Esperaba obtener algunas referencias a especificaciones o estudios a los que pudiera referirme, las pruebas de estrés son un problema ya que no hay otro equipo de prueba que no sea el sistema implementado (malas noticias, lo sé), y la implementación para la prueba no es una práctica. opción ya que se implementa en una ubicación muy desafiante.
Bien, si esto es único y actualmente funciona bien, ¿por qué estamos teniendo esta conversación? "Si no está roto, no lo arregles".
Porque está roto, ya que falla intermitentemente bajo una carga moderada, y me han pedido que lo arregle, ¡rápido! El hecho de que ha llevado alrededor de 8 años-hombre ponerlo en este estado.... Además, los departamentos de marketing tienen la costumbre de vender productos únicos, como estoy seguro de que muchos de nosotros hemos experimentado.
8 años hombre? Eso es un desastre caro :)

Respuestas (4)

En general, la distribución del tiempo de CPU entre el código ISR y el código no ISR depende mucho de la aplicación.

Si la mayor parte del trabajo se realiza en código que no es ISR, entonces sí, por lo general desea mantener los ISR lo más cortos posible (pero no más cortos, parafraseando una cita famosa).

Sin embargo, he visto (y creado) aplicaciones DSP en tiempo real en las que el 90% del trabajo se realiza en el ISR, y el código que no es ISR solo maneja tareas que no son críticas en cuanto al tiempo, como configuración, estado y interfaz de usuario.

Cualquiera de los enfoques es perfectamente válido en sistemas profundamente integrados.

La pauta general es que las secciones críticas deben ser lo más cortas posible.

Lo que realmente debe hacer es tener en cuenta la longitud de la sección crítica cuando verifique la capacidad de programación del conjunto de tareas. Hacer eso depende de cómo programe las tareas, pero creo que podría ser tan simple como agregar el tiempo de ejecución de la sección crítica al tiempo de ejecución de cada tarea de menor prioridad.

Desafortunadamente, este es un proyecto completo, sin OS/Scheduler y sin sistema de gestión de prioridades; todo está en secciones críticas, ¡incluso la limpieza!
No necesita un sistema operativo, un programador o un sistema de gestión de prioridades para realizar el análisis de capacidad de programación, se puede realizar en cualquier conjunto de tareas que utilicen prioridad. Tal vez la pregunta que debe hacerse sobre la longitud de las secciones críticas es... ¿a quién le importa? ¿Se rompe algo si las secciones críticas son demasiado largas?
Sí, Joe: bloqueos prolongados para romper cosas, ya que las otras interrupciones se bloquean (incluso durante la limpieza), se ignoran y los mensajes se corrompen o se pierden.
¡Entonces lo que necesita es un análisis de planificabilidad! Debe asegurarse de que todas las tareas responderán en el tiempo deseado.
Y ese, por supuesto, es uno de los problemas con los ISR largos y complejos: que a menudo no es práctico realizar dicho análisis en una escala de tiempo razonable debido a la complejidad.

Creo que debe considerar el tiempo de ejecución del código ininterrumpible con el tiempo de respuesta más rápido (latencia) y la variabilidad de ese tiempo (inestabilidad) que necesita del microcontrolador. Puede que no sea intuitivo qué es eso; puede ser algo que es 'urgente' pero no 'importante'.

Por ejemplo, un controlador que diseñé hace algunos años necesitaba manejar entradas RS-232, actualizar la salida usando el algoritmo de control, controlar el ADC de doble pendiente, escanear una pantalla LED y un teclado (incluido el antirrebote), escribir en una EEPROM lenta, y para controlar un beeper electromagnético (y probablemente un par de otras cosas que he olvidado). Resultó que el tiempo de respuesta más crítico fue la interrupción periódica del pitido (cada 250 useg), seguida de la pantalla LED, principalmente por razones estéticas. Entonces, si el 10% de fluctuación en el conmutador de pitido era aceptable, eso es 25 useg de código ininterrumpido (que no eran muchas instrucciones, pero suficientes para hacer que ciertas cosas fueran atómicas). Ese procesador en particular admitía interrupciones anidadas, por lo que IIRC I terminó haciendo que el código de interrupción periódica volviera a entrar después de la primera sección.

Si está aplazando las interrupciones pendientes durante largas secciones de código, puede estar bien, si sus requisitos de latencia y fluctuación son bastante relajados en relación con el tiempo de ejecución de las "partes críticas".

Mi enfoque habitual es minimizar el tiempo de ejecución del código en los ISR y volver lo más rápido posible (tal vez rellenando cosas como tokens de eventos de entrada rebotados en colas circulares, establecer banderas y salir). Por ejemplo, si es necesario ejecutar un algoritmo de control complejo cada 200 mseg con un jitter máximo de 20 mseg, configuraría un indicador cada 200 mseg en la interrupción del temporizador y sondearía ese indicador fuera del ISR.

Además de cumplir con los requisitos de diseño, este enfoque mejora la confiabilidad.

En cuanto a encontrar algún tipo de directriz que establezca esto... sugiero mirar algunas de las directrices de software críticas para la seguridad. Es posible que no lo encuentre declarado directamente, pero puede inferirlo de otras declaraciones. UL1998 IEC 60730, etc. Este documento de la FAA establece:

Las interrupciones y su efecto sobre los datos deben recibir atención especial en áreas críticas para la seguridad. El análisis debe verificar que las interrupciones y las rutinas de manejo de interrupciones no alteren los elementos de datos críticos utilizados por otras rutinas.

y

De particular interés para la seguridad es garantizar la integridad de los datos críticos de seguridad para que no se modifiquen o sobrescriban inadvertidamente. Por ejemplo, verifique si el procesamiento de interrupciones interfiere con los datos críticos de seguridad.

Ambos se vuelven más difíciles a medida que aumenta el tamaño del código de interrupción.

Las interrupciones frecuentes cortas (en relación con la latencia y la fluctuación máximas) se pueden analizar como una disminución efectiva de la velocidad del procesador en lugar de tener que lidiar con las peores situaciones acumuladas de interrupciones largas en los momentos más críticos, lo que también aboga por una ejecución rápida como código simple dentro de ISR.

Para mí, evitar las interrupciones de anidamiento no parece tan malo o incluso absurdo. De hecho, las interrupciones anidadas pueden causar más problemas de los que resuelven.

Me refiero específicamente a la memoria utilizada para guardar/restaurar contexto al entrar/salir del ISR. Comúnmente, en arquitecturas simples, cada ISR guardará todo el contexto requerido del código que interrumpió, por ejemplo, en la pila del procesador. Al desarrollar el software, es necesario tener en cuenta esta memoria.

Si, por ejemplo, ISR A requiere 10 bytes de contexto para guardar/restaurar, ISR B necesita 15 e ISR C necesita 12 bytes, el cálculo/estimación simplemente es:

En cualquier lugar durante la ejecución de secciones no críticas, cualquiera de los ISR puede activarse. Ahora, si no se permite anidar los ISR , la memoria máxima requerida para el contexto es max( 10, 15, 12 )= 15 bytes. Si cada uno de los ISR puede interrumpir cualquier otro de esos 3 ISR, el peor de los casos será 10 + 15 + 12= 37 bytes de contexto que deben almacenarse al mismo tiempo . Entonces, en cada instante durante la ejecución (de código no atómico), debe haber al menos suficiente memoria disponible para esos 37 bytes de contexto, frente a 15 en el caso de no anidamiento.

Por supuesto, cuantos más ISR se unan al "juego de anidamiento", más visible se vuelve el problema.

Si tiene varias tareas que se ejecutan simultáneamente y los ISR guardan el contexto en la pila actual de cualquier código que estén interrumpiendo, debe tener en cuenta el tamaño de contexto del peor de los casos por tarea .

Este argumento, por supuesto, depende de la arquitectura HW que esté utilizando. Puede que no sea aplicable si el controlador de interrupción/HW es más avanzado y tiene características especiales para manejar el cambio de contexto hacia/desde ISR.

Aparte de eso, los ISR complejos tienden a requerir más datos de contexto para guardar/restaurar que los simples, por lo que generalmente se recomiendan ISR pequeños y concisos porque brindan múltiples beneficios:

  1. tiempo de ejecución rápido, posiblemente dando
    1. secciones críticas más cortas y por lo tanto
    2. menos nerviosismo
  2. contexto más pequeño, resultando en
    1. menos uso de memoria en el peor de los casos y (depende de la arquitectura)
    2. incluso menos tiempo de ejecución (al no tener que transferir tanta información de estado de A a B a A)

Al final, si todos los ISR se reducen al mínimo, como cuando solo se establece una bandera, la pregunta de si permitir o no anidar ISR puede responderse claramente a favor de no anidar porque el beneficio de anidar ISR se desvanece.