6.12+
Beginner to Intermediate
~13 min
Once you understand locking guidelines and how deadlocks happen, the next practical question every driver author faces is: mutex vs spinlock — which one do I actually reach for in this code path? Both protect a critical section, both are easy to call, and yet picking the wrong one can cause a system hang or wreck your driver’s performance. This lecture, part of our free Linux device drivers course, walks through the real difference between the two, with simple original examples updated for kernel 6.12 and later.
Prerequisites
- Comfortable with basic kernel module structure (module_init/module_exit)
- Read our earlier lecture on Linux kernel locking guidelines and deadlocks (recommended, not mandatory)
- Understand, at a high level, what “process context” and “interrupt context” mean in the kernel
The Core Difference: Sleep or Spin
Every discussion of mutex vs spinlock in the Linux kernel comes down to one question: what should a thread do while it waits for a lock it cannot get right now?
A mutex lets the CPU do something useful with the waiting thread’s time slice, which is great — but the wake-up path costs real microseconds of scheduling latency. A spinlock burns CPU cycles while waiting, which is wasteful if the wait is long, but it avoids the scheduling overhead entirely, which matters when the critical section is extremely short.
Rule 1: A Mutex Can Sleep — So It Cannot Be Used Everywhere
Because a blocked mutex puts the calling thread to sleep, a mutex can only ever be used in process context, where sleeping is legal. You must never try to acquire a mutex from an interrupt handler, a softirq, a tasklet, or any other atomic context — doing so is a kernel bug, and on a debug-enabled kernel it will trigger a “scheduling while atomic” warning.
#include <linux/mutex.h>
static DEFINE_MUTEX(driver_data_lock);
static void update_shared_counter(int *counter, int delta)
{
mutex_lock(&driver_data_lock);
*counter += delta;
mutex_unlock(&driver_data_lock);
}
This pattern is perfect for code that runs from a system call, an ioctl handler, or any other process-context path where a brief sleep while waiting is completely acceptable.
Rule 2: A Spinlock Never Sleeps — So It Is Safe in Atomic Context
A spinlock never puts the caller to sleep, which is exactly why it is safe to use inside interrupt handlers and other atomic contexts where sleeping is forbidden. The trade-off is that the code holding a spinlock must be short, since every other CPU trying to take that same lock is actively burning cycles until it is released.
#include <linux/spinlock.h>
static DEFINE_SPINLOCK(ring_buffer_lock);
static void push_to_ring_buffer(int value)
{
unsigned long flags;
spin_lock_irqsave(&ring_buffer_lock, flags);
/* short critical section only */
ring_buffer_write(value);
spin_unlock_irqrestore(&ring_buffer_lock, flags);
}
Notice the irqsave/irqrestore variant here. This is the correct choice whenever the same lock could also be taken from an interrupt handler, since it disables local interrupts for the duration of the critical section — directly preventing the interrupt-context deadlock pattern discussed in our locking guidelines lecture.
Spinlock Variants at a Glance
| Variant | Use When |
|---|---|
spin_lock() / spin_unlock() | Lock is never touched by an interrupt handler |
spin_lock_irq() / spin_unlock_irq() | You already know interrupts are enabled going in |
spin_lock_irqsave() / spin_unlock_irqrestore() | Interrupt state is unknown, or the lock is shared with an ISR — safest general default |
spin_lock_bh() / spin_unlock_bh() | Lock is shared with a softirq/tasklet (bottom half) but not a hardirq |
Decision Checklist: Mutex or Spinlock?
How PREEMPT_RT (Kernel 6.12+) Changes This Picture
Kernel 6.12 mainlined the PREEMPT_RT patchset, and this genuinely affects the mutex vs spinlock decision. On a PREEMPT_RT-enabled kernel, an ordinary spinlock_t is internally converted into a sleeping, priority-inheritance-aware lock, very similar in behavior to a mutex, so that a low-priority holder cannot block a high-priority real-time task indefinitely. Only raw_spinlock_t retains the traditional non-preemptible, busy-wait behavior on an RT kernel, and it is reserved for the small amount of code that genuinely must never be preempted, such as certain low-level scheduler or interrupt-controller internals.
| Lock Type | Standard Kernel | PREEMPT_RT Kernel (6.12+) |
|---|---|---|
spinlock_t | Busy-waits, non-preemptible | Becomes a sleeping, priority-inheriting lock |
raw_spinlock_t | Busy-waits, non-preemptible | Still busy-waits, non-preemptible |
mutex | Sleeping lock | Sleeping lock, priority-inheriting (rt_mutex based) |
For most driver authors targeting general-purpose Linux, ordinary spinlock_t and mutex usage as shown above is still correct. The raw_spinlock_t distinction only becomes relevant when you are writing code intended to run correctly on real-time targets, such as industrial control or robotics platforms built on PREEMPT_RT.
Real-World Use Cases
File operation callbacks like read() and write() run in process context and can safely sleep, so a mutex is the natural fit for protecting a shared device buffer accessed through these calls.
An interrupt service routine that increments a packet counter shared with the rest of the driver must use a spinlock with the irqsave variant, since it cannot sleep and may race with process-context code touching the same counter.
A softirq-context timer callback that flips a small status flag shared with a tasklet is a good fit for spin_lock_bh(), avoiding both sleep and unnecessary interrupt disabling.
Common Mistakes and Troubleshooting
| Mistake | Symptom | Fix |
|---|---|---|
| Calling mutex_lock() inside an interrupt handler | “Scheduling while atomic” / system freeze | Use a spinlock instead in that context |
| Holding a spinlock across a long or blocking operation | Other CPUs stall, latency spikes | Keep the critical section minimal, switch to a mutex if it must sleep |
| Using plain spin_lock() when an ISR shares the lock | Rare interrupt-context deadlock under load | Use spin_lock_irqsave()/spin_unlock_irqrestore() |
| Forgetting to match lock/unlock on every return path | Permanent lock held, later hang | Use goto-based cleanup or guard patterns consistently |
Best Practices
- Default to a mutex in process context unless you have a specific reason not to
- Default to spin_lock_irqsave() whenever a lock might be shared with an interrupt handler
- Never call anything that can sleep while holding a spinlock
- Keep spinlock-protected critical sections as small as a handful of instructions
- On RT targets, only reach for raw_spinlock_t when you have confirmed the code truly cannot tolerate preemption
Performance and Security Considerations
Performance: Spinlocks are cheaper for very short, frequently-taken critical sections on multi-core systems because they avoid context-switch overhead; mutexes are cheaper overall when contention is common and waits can be longer, since they free the CPU instead of burning cycles.
Security: A driver that mistakenly uses a spinlock around a long or unbounded operation can be pushed into a denial-of-service state by an attacker who can trigger that path repeatedly from multiple CPUs — another reason critical section length deserves careful review in any driver exposed to external input.
Summary / Key Takeaways
- A mutex sleeps when contended; a spinlock busy-waits
- Mutexes are process-context only; spinlocks are safe in atomic/interrupt context
- Use spin_lock_irqsave() whenever an interrupt handler might share the same lock
- On PREEMPT_RT kernels (6.12+), ordinary spinlock_t behaves like a sleeping, priority-inheriting lock, while raw_spinlock_t preserves classic busy-wait behavior
- Keep spinlock critical sections short; use a mutex if the code needs to sleep or block
Conclusion
The mutex vs spinlock decision is really a question about context and duration: can this code sleep, and how long will it hold the lock? Get those two answers right and the choice practically makes itself. Combine that with the lock-ordering discipline from the previous lecture, and you already have the foundation most kernel synchronization bugs never get past. Keep following this free Linux kernel development course for more hands-on lectures on kernel internals and device driver development.
Frequently Asked Questions
Q1. Can I use a mutex inside an interrupt handler?
No. A mutex can sleep, and sleeping is not allowed in interrupt context. Use a spinlock instead.
Q2. Why does a spinlock waste CPU cycles?
Because a waiting CPU keeps checking the lock in a tight loop instead of being scheduled away, which is intentional to avoid the overhead of a context switch for very short waits.
Q3. When should I use spin_lock_irqsave() instead of plain spin_lock()?
Whenever the same lock could also be taken from an interrupt handler, since irqsave also disables local interrupts and prevents interrupt-context deadlocks.
Q4. Does PREEMPT_RT eliminate the need for spinlocks?
No, it changes how ordinary spinlock_t behaves internally (making it sleep-capable), but raw_spinlock_t is still available for code that must remain truly non-preemptible.
Q5. Is a mutex always slower than a spinlock?
Not necessarily — for short critical sections with light contention a spinlock is typically faster, but under heavy or long contention a mutex is more efficient since it frees the CPU instead of busy-waiting.
Q6. What happens if I hold a spinlock and call a function that sleeps?
This is a serious bug that can hang the kernel; debug kernels will typically flag it with a “scheduling while atomic” warning.
Q7. Are these rules still accurate for the newest kernel releases?
Yes, the mutex and spinlock APIs are stable, long-standing kernel interfaces; this lecture reflects kernel 6.12+ behavior including the mainlined PREEMPT_RT changes.

2 Comments