5.5 The Core Challenge: ISRs and Tasks Synchronization
Understanding the Problem: Shared Data and Race Conditions
When a hardware interrupt occurs, the CPU immediately stops executing the current task and jumps to the ISR. This can happen at any time, even in the middle of a single line of C code that takes multiple machine instructions to execute. If the ISR and the task both access the same global variable, the system is vulnerable to a race condition.
A race condition is an undesirable situation that occurs when the outcome of a process depends on the uncontrollable sequence of events. In our case, it's a bug that occurs when the timing of the interrupt corrupts shared data.
A Classic Example of a Race Condition:
Imagine a global variable volatile int counter = 0;
.
-
An ISR, triggered by a timer, is programmed to increment the counter:
counter++
;. -
A task in the main application is programmed to decrement it:
counter--
;.
Let's trace a potential failure scenario. Assume counter
is currently 10
.
-
Task Executes: The task reads the value of
counter
(10) into a CPU register. -
Task Calculates: The CPU calculates the new value,
10 - 1 = 9
. -
CONTEXT SWITCH (INTERRUPT): Before the task can write the value
9
back to thecounter
variable in memory, a hardware interrupt occurs! -
ISR Executes: The ISR runs. It reads the value of
counter
from memory (which is still 10). -
ISR Calculates: The ISR calculates
10 + 1 = 11
. -
ISR Writes: The ISR writes the value
11
back to the counter variable in memory. -
ISR Finishes: The interrupt is complete, and the CPU returns control to the task, restoring its state exactly where it left off.
-
Task Resumes: The task is completely unaware that it was interrupted. Its next step is to write its calculated value (
9
) back to thecounter
variable. -
Corruption: The value
11
that the ISR correctly calculated is now overwritten with9
. The increment operation has been completely lost.
This is the fundamental problem of concurrency: protecting shared resources from uncontrolled, simultaneous access.
The ISR Context and ...FromISR()
Functions
To solve the synchronization problem, we need tools to manage access to shared data. However, as we learned in Part 2, ISRs operate in a special interrupt context. They are not tasks and are not managed by the RTOS scheduler. This leads to a critical rule: an ISR can never block.
If an ISR tried to wait for a resource (like calling xQueueSend()
and the queue was full), it would effectively block. But since the ISR is not a task, the scheduler has no other context to switch to. The entire system would freeze, leading to a crash.
To solve this, FreeRTOS provides a special set of ISR-safe functions that are non-blocking. You can recognize them by their
...FromISR()
suffix.
-
xQueueSend()
->xQueueSendFromISR()
-
xSemaphoreGive()
->xSemaphoreGiveFromISR()
These functions include an optional parameter, pxHigherPriorityTaskWoken
. An ISR uses this parameter to inform the RTOS if its action (e.g., giving a semaphore) has unblocked a task that has a higher priority than the task that was originally interrupted. If so, the RTOS can perform an immediate context switch to the higher-priority task as soon as the ISR finishes, ensuring the system remains as responsive as possible.
No comments to display
No comments to display