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