Condición de la carrera del sueño del microcontrolador

11

Dado un microcontrolador que ejecuta el siguiente código:

volatile bool has_flag = false;

void interrupt(void) //called when an interrupt is received
{
    clear_interrupt_flag(); //clear interrupt flag
    has_flag = true; //signal that we have an interrupt to process
}

int main()
{
    while(1)
    {
        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

Supongamos que la condición if(has_flag) se evalúa como false y estamos a punto de ejecutar la instrucción de suspensión. Correcto antes de que ejecutemos la instrucción de suspensión, recibimos una interrupción. Después de que dejamos la interrupción, ejecutamos la instrucción de suspensión.

Esta secuencia de ejecución no es deseable porque:

  • El microcontrolador se fue a dormir en lugar de despertarse y llamar a process() .
  • Es posible que el microcontrolador nunca se active si después no se recibe ninguna interrupción.
  • La llamada a process() se pospone hasta la siguiente interrupción.

¿Cómo se puede escribir el código para evitar que ocurra esta condición de carrera?

Editar

Algunos microcontroladores, como en ATMega, tienen un bit de habilitación de reposo que evita que ocurra esta condición (gracias Kvegaoro por señalar esto). JRoberts ofrece una implementación de ejemplo que ejemplifica este comportamiento.

Otros micros, como en PIC18s, no tienen este bit, y el problema continúa. Sin embargo, estos micros están diseñados de tal manera que las interrupciones aún pueden activar el núcleo independientemente de si el bit de habilitación de interrupción global está establecido (gracias a Supercat por señalarlo). Para tales arquitecturas, la solución es deshabilitar las interrupciones globales justo antes de irse a dormir. Si se dispara una interrupción justo antes de ejecutar la instrucción de suspensión, el controlador de interrupciones no se ejecutará, el núcleo se activará y, una vez que se vuelvan a habilitar las interrupciones globales, se ejecutará el controlador de interrupciones. En el pseudocódigo, la implementación se vería así:

int main()
{
    while(1)
    {
        //clear global interrupt enable bit.
        //if the flag tested below is not set, then we enter
        //sleep with the global interrupt bit cleared, which is
        //the intended behavior.
        disable_global_interrupts();

        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            enable_global_interrupts();  //set global interrupt enable bit.

            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}
    
pregunta TRISAbits

3 respuestas

9

Por lo general, hay algún tipo de soporte de hardware para este caso. Por ejemplo, la instrucción sei de los AVR para habilitar los desvíos de activación de interrupciones hasta que se complete la siguiente instrucción. Con ello se puede hacer:

forever,
   interrupts off;
   if has_flag,
      interrupts on;
      process interrupt;
   else,
      interrupts-on-and-sleep;    # won't be interrupted
   end
end

La interrupción que se hubiera perdido en el ejemplo en este caso se mantendría hasta que el procesador complete su secuencia de suspensión.

    
respondido por el JRobert
3

En muchos microcontroladores, además de poder habilitar o deshabilitar determinadas causas de interrupción (generalmente dentro de un módulo de controlador de interrupción), hay un indicador maestro dentro del núcleo de la CPU que determina si se aceptarán las solicitudes de interrupción. Muchos microcontroladores abandonarán el modo de suspensión si una solicitud de interrupción llega al núcleo, independientemente de que el núcleo esté dispuesto a aceptarlo o no.

En un diseño de este tipo, un enfoque simple para lograr un comportamiento de sueño confiable es hacer que el bucle principal verifique una bandera y luego verifique si conoce alguna razón por la que el procesador deba estar despierto. Cualquier interrupción que ocurra durante ese tiempo que pueda afectar cualquiera de esas razones debe establecer la bandera. Si el bucle principal no encontró ninguna causa para permanecer despierto, y si el indicador no está establecido, el bucle principal debe desactivar las interrupciones y volver a verificar el indicador [quizás después de un par de instrucciones NOP si es posible que haya una interrupción que esté pendiente. durante una instrucción de inhabilitar-interrumpir puede procesarse después de que se haya realizado la búsqueda del operando asociada con la siguiente instrucción]. Si la bandera aún no está establecida, ve a dormir.

En este escenario, una interrupción que se produce antes de que el bucle principal desactive las interrupciones establecerá el indicador antes de la prueba final. Una interrupción que está pendiente demasiado tarde para ser reparada antes de las instrucciones de suspensión evitará que el procesador entre en suspensión. Ambas situaciones están bien.

A veces, el modo de espera para salir es un buen modelo para usar, pero no todas las aplicaciones realmente lo "ajustan". Por ejemplo, un dispositivo con una pantalla LCD de bajo consumo de energía podría programarse más fácilmente con un código similar al siguiente:

void select_view_user(int default_user)
{
  int current_user;
  int ch;
  current_user = default_user;
  do
  {
    lcd_printf(0, "User %d");
    lcd_printf(1, ...whatever... );
    get_key();
    if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
    if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
    if (key_hit(KEY_ENTER)) view_user(current_user);
  } while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}

Si no se presiona ningún botón y no pasa nada más, no hay razón por la que el sistema no deba ir a dormir durante la ejecución del método get_key . Si bien es posible que las teclas desencadenen una interrupción y gestionen toda la interacción de la interfaz de usuario a través de la máquina de estados, el código como el anterior es a menudo la forma más lógica de manejar flujos de interfaz de usuario altamente modales típicos de pequeños microcontroladores.

    
respondido por el supercat
1

Programe el micro para que se active en caso de interrupción.

Los detalles específicos variarán según el micro que esté utilizando.

Luego modifica la rutina principal ():

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}
    
respondido por el jwygralak67

Lea otras preguntas en las etiquetas