Say I have a Ticker with an attached method doTick() in which a variable is read and written to. In my main thread I also read and write this variable. By declaring the variable volatile I can prevent it from being “stale”. But how can I protect the variable from being corrupted? Do I wrap it in a mutex? What is the pattern to protect such global variables when read/written from an ISR?
Hello Claude,
To prevent the ISR from modifying a global (or other) variable while it is being used (written to or read from) in the main or other threads, when such modification could potentially corrupt the code, is to disable interrupts for that short critical section of code (in main or other threads). Mbed most likely doesn’t mention it in the documentation but internally it uses the following pair of functions for that purpose:
...
core_util_critical_section_enter();
// a short section of code protected from being modified by any ISR
core_util_critical_section_exit();
...
However, if the variable fits into one register (it is not longer than 32-bits) then the read or write operation is atomic
. If an atomic operation has started it cannot be interrupted and completes without being corrupted by an operation performed in ISR. So if you cannot afford to disable interrupts even for a very short period of time then try to make the critical section atomic.
Thanks for the reply!
Is your code equivalent to the __disable_irq()/__enable_irq() methods?
What happends to interrupts which would have fired while they are disabled? Are the triggered as soon as they are reenabled? What if several instances of those interrupts would have fire during the disabled time?
Is your code equivalent to the __disable_irq()/__enable_irq() methods?
Basically yes (those functions are called at some point), with some additional “house keeping”. For more details see:
mbed-os/hal/source/mbed_critical_section_api.c
mbed-os/platform/source/mbed_critical.c
What happends to interrupts which would have fired while they are disabled?
When disabled, no interrupts are generated. Hence, those events never happen (they are lost).
Are the triggered as soon as they are reenabled?
Yes, since the moment they are re-enabled.
What if several instances of those interrupts would have fire during the disabled time?
Nothing, there are no interrupts generated when disabled.
However, the situation is not simple when enabled and several instances of interrupts are fired because they could have different priorities, they could become pending, active or inactive. If you are interested in looking deeper into interrupt management and their life cycle then books by Joseph Yiu are a good choice.
thanks again!
One last question just to confirm. Say I have a ticker running at in a 1s interval counting some variable upwards.
I choose to disable interrups briefly to write to a critical variable and then reenable them. During the period where I was writing to the variable the ticker interrupt would have called its attached method.
Will my counter now be missing one count of the variable? The way you describe it I assume it that would be the case.
If so it seems extremely dangerous to disable interrups at all!
EDIT:
What is the best way to solve this situation then:
I must handle every interrupt, so I cannot disable them, but I also have a critical section I must protect against corruption. My desired behaviour is as such:
enterCritical();
foo();
bar();
–INTERRUPT HERE BUT ISR DELAYED–
foo();
bar();
exitCritical();
–ISR CALLED NOW–
You are right. Interrupts shall be disabled only when absolutely necessary and only for a very short period of time!
Apart of disabling interrupts, I can think of two other alternatives to achieve the goal you sketched above.
1 . Wait for an event flag at the begin of a critical section until it gets set inside an ISR;
#define MY_FLAG (1UL << 1);
volatile uint64_t globalVar;
EventFlags event_flags;
void ISR()
{
...
globalVar++; // modify global variable inside ISR
// EventFlags' set method is allowed to be called in ISR context.
event_flags.set(MY_FLAG); // indicate (flag) that now it's save to modify globalVar also elsewhere
...
}
int main()
{
...
// We are about to modify a non atomic variable which can get modified also inside an ISR.
event_flags.wait_all(MY_FLAG); // waiting for MY_FLAG to become set (it blocks this thread)
// Now it's safe to modify the globalVar. It won't collide with ISR.
// begin of critical section
globalVar++;
// end of critical section
...
2 . Defer execution from ISR context to an EventQueue
handler.
An example is available here.
Thank you so much!
This might be helpful
https://os.mbed.com/docs/mbed-os/v6.8/apis/criticalsectionlock.html
Thank you for the link! I have to apologize, I was wrong when I said " Mbed most likely doesn’t mention it in the documentation". Mbed seems to provide a well documented mechanism to access a resource without interruption.
For those who are curious the implementation is as follows:
void CriticalSectionLock::enable()
{
core_util_critical_section_enter();
}
void CriticalSectionLock::disable()
{
core_util_critical_section_exit();
}
I think it still turns off interrupts (but allows nesting).
So using this also fully disables interrupts? So all interrupts that happen during the locked period will never trigger an ISR?
If that really is the case this still seems like a very dangerous thing to do.
My understanding is that they are left pending. When you switch ISRs back on, they should execute.
So which one is true?
Ok, I need to investigate (as I want to know now!). It’s a fairly standard practise to do this as far as I know. Another approach (I’ve seen on other platforms) is to temporarily elevate interrupt priority to max.
Take a read of this:
CriticalSectionLock - API references and tutorials | Mbed OS 6 Documentation
I will do some digging.