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 if(has_flag)
condición se evalúa como falsa y estamos a punto de ejecutar la instrucción de suspensión. Justo antes de ejecutar la instrucción de suspensión, recibimos una interrupción. Después de dejar 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
process()
. - Es posible que el microcontrolador nunca se active si no se recibe ninguna interrupción.
- La llamada a
process()
se pospone hasta la próxima interrupción.
¿Cómo se puede escribir el código para evitar que ocurra esta condición de carrera?
Editar
Algunos microcontroladores, como el ATMega, tienen un bit de habilitación de suspensión 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 los PIC18, no tienen este bit y el problema aún ocurre. Sin embargo, estos micros están diseñados de tal manera que las interrupciones aún pueden despertar el núcleo independientemente de si el bit de habilitación de interrupción global está configurado (gracias supercat por señalar esto). Para tales arquitecturas, la solución es deshabilitar las interrupciones globales justo antes de ir a dormir. Si se dispara una interrupción justo antes de ejecutar la instrucción de suspensión, el controlador de interrupción no se ejecutará, el núcleo se activará y, una vez que se vuelvan a habilitar las interrupciones globales, se ejecutará el controlador de interrupción. 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
}
}
interrupt_flag
como un int
, y lo incrementaría cada vez que haya una interrupción. Luego cambie if(has_flag)
a while (interrupts_count)
y luego duerma. No obstante, la interrupción podría ocurrir después de que haya salido del ciclo while. Si esto es un problema, ¿el procesamiento en la interrupción misma?