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