11 votos

Condición de carrera en reposo del microcontrolador

Dado un microcontrolador que está ejecutando 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 el if(has_flag) se evalúa como falso y estamos a punto de ejecutar la instrucción sleep. A la derecha antes de ejecutar la instrucción sleep, recibimos una interrupción. Después de salir de la interrupción, ejecutamos la instrucción sleep.

Esta secuencia de ejecución no es deseable porque:

  • El microcontrolador se puso a dormir en lugar de despertarse y llamar process() .
  • El microcontrolador puede no despertarse nunca si 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 se produzca esta condición de carrera?

Editar

Algunos microcontroladores, como el ATMega, tienen un bit de activación del sueño que impide que se produzca esta condición (gracias a Kvegaoro por señalarlo). JRoberts ofrece un ejemplo de implementación que ejemplifica este comportamiento.

Otros micros, como los PIC18, no tienen este bit, y el problema sigue ocurriendo. Sin embargo, estos micros están diseñados de tal manera que las interrupciones pueden seguir despertando el núcleo independientemente de si el bit de habilitación de interrupción global está activado (gracias a supercat por señalar esto). Para estas arquitecturas, la solución es deshabilitar las interrupciones globales justo antes de entrar en reposo. Si una interrupción se dispara justo antes de ejecutar la instrucción de dormir, el manejador de la interrupción no se ejecutará, el núcleo se despertará, y una vez que las interrupciones globales se vuelvan a habilitar, el manejador de la interrupción se ejecutará. En 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
    }
}

10voto

Darren Newton Puntos 835

Suele haber algún tipo de soporte de hardware para este caso. Por ejemplo, los AVRs sei para activar las interrupciones aplaza la activación hasta que se complete la siguiente instrucción. Con ella 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 habría perdido en el ejemplo se mantendría en este caso hasta que el procesador completara su secuencia de reposo.

3voto

Alex Andronov Puntos 178

En muchos microcontroladores, además de poder habilitar o deshabilitar determinadas causas de interrupción (normalmente dentro de un módulo controlador de interrupciones), hay una bandera maestra dentro del núcleo de la CPU que determina si las peticiones de interrupción serán aceptadas. Muchos microcontroladores saldrán del modo de reposo si una solicitud de interrupción llega al núcleo, independientemente de que el núcleo esté dispuesto a aceptarla o no.

En un diseño de este tipo, un enfoque sencillo para lograr un comportamiento de reposo fiable es hacer que el bucle principal compruebe que se borra una bandera y luego compruebe si conoce alguna razón por la que el procesador debería estar despierto. Cualquier interrupción que se produzca durante ese tiempo y que pueda afectar a alguna de esas razones debería activar la bandera. Si el bucle principal no encuentra ninguna razón para permanecer despierto, y si la bandera no está activada, el bucle principal debería deshabilitar las interrupciones y comprobar la bandera de nuevo [tal vez después de un par de instrucciones NOP si es posible que una interrupción que queda pendiente durante una instrucción de deshabilitar-interrupción pueda ser procesada después de que el operando fetch asociado a la siguiente instrucción ya haya sido realizado]. Si la bandera aún no está activada, entonces pasa a dormir.

En este escenario, una interrupción que se produzca antes de que el bucle principal desactive las interrupciones, activará la bandera antes de la prueba final. Una interrupción que queda pendiente demasiado tarde para ser atendida antes de la instrucción de dormir, impedirá que el procesador se vaya a dormir. Ambas situaciones están bien.

El reposo en salida es a veces un buen modelo a utilizar, pero no todas las aplicaciones se "ajustan" a él. Por ejemplo, un dispositivo con una pantalla LCD de bajo consumo podría programarse más fácilmente con un código parecido:

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 pulsa ningún botón, y no ocurre nada más, no hay razón para que el sistema no entre en reposo durante la ejecución del get_key método. Si bien es posible hacer que las teclas activen una interrupción y gestionar toda la interacción de la interfaz de usuario a través de una máquina de estado, el código como el anterior es a menudo la forma más lógica de manejar los flujos de la interfaz de usuario altamente modales típicos de los pequeños microcontroladores.

1voto

Doltknuckle Puntos 591

Programar el micro para que se despierte con una interrupción.

Los detalles específicos variarán en función del micro que utilices.

Entonces modifica la rutina main():

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X