In this lesson, you will learn how to implement mutexes that prevent priority inversion by safely orchestrating lock contention between hardware interrupts and the main thread.
The Priority Inversion Trap
In real-time systems, a low-priority task holding a mutex can be preempted by a high-priority task. If the high-priority task then tries to acquire that same mutex, it blocks. If a medium-priority task arrives, it can preempt the low-priority task (which holds the lock), effectively blocking the high-priority task indefinitely. This is priority inversion.
When hardware interrupts are involved, the problem intensifies. If an interrupt handler needs a resource held by a low-priority thread, the system deadlocks because the interrupt cannot preempt the thread, but the thread cannot release the lock until it resumes.
Implementing the Guard
To solve this, we use an Interrupt-Safe Mutex. The core idea is to disable interrupts—or at least the specific interrupts that compete for the resource—while the lock is held. In Rust, we achieve this by wrapping the data in a structure that masks interrupts during lock() and restores them in the Drop implementation of the guard.
pub struct CriticalSectionMutex<T> {
data: UnsafeCell<T>,
}
impl<T> CriticalSectionMutex<T> {
pub fn lock(&self) -> MutexGuard<T> {
// Disable interrupts to prevent re-entrancy/preemption
let state = cortex_m::interrupt::disable();
MutexGuard { mutex: self, state }
}
}
impl<'a, T> Drop for MutexGuard<'a, T> {
fn drop(&mut self) {
// Restore interrupt state once the task is finished
unsafe { cortex_m::interrupt::restore(self.state) };
}
}
By ensuring that the critical section is atomic relative to the interrupt controller, we prevent the interrupt handler from ever seeing the data in an inconsistent state. More importantly, we prevent the "infinite stall" scenario because the high-priority interrupt is effectively "wait-free" if we keep the critical section's execution time strictly bounded—a core principle of real-time scheduling.
Always ensure the duration between lock() and the Drop implementation is kept to an absolute minimum to avoid jitter in your time-sensitive interrupt routines.
Key Takeaway: Interrupt-safe mutexes prevent priority inversion and deadlocks by masking interrupts for the duration of a lock, ensuring the holder cannot be preempted by the very entities waiting for the resource.