Mutex vs Spinlock in the Linux Kernel: Which Lock to Use When-Free Linux Device Drivers Training

Mutex vs Spinlock in the Linux Kernel: Which Lock to Use When
Free Linux kernel development course — synchronization primitives explained simply
Kernel Version
6.12+
Level
Beginner to Intermediate
Reading Time
~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.

What You Will Learn
How a mutex works
How a spinlock works
Sleeping vs busy-waiting
Atomic context rules
Interrupt-safe spinlock variants
PREEMPT_RT and raw_spinlock_t
Decision checklist

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?

Mutex vs Spinlock — What Happens While Waiting
Mutex (contended)
Thread is put to sleep
Scheduler runs something else
Woken up once the lock is free
Spinlock (contended)
CPU keeps checking the lock in a tight loop
No context switch happens
Proceeds the instant the lock is released

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

VariantUse 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?

Quick Decision Path
Can this code run from interrupt/atomic context?
↓ yes → use a spinlock   |   ↓ no, continue
Is the critical section short (a handful of instructions)?
↓ yes → spinlock is fine   |   ↓ no, continue
Does the critical section need to sleep, allocate memory (GFP_KERNEL), or call blocking functions?
↓ yes → use a mutex

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 TypeStandard KernelPREEMPT_RT Kernel (6.12+)
spinlock_tBusy-waits, non-preemptibleBecomes a sleeping, priority-inheriting lock
raw_spinlock_tBusy-waits, non-preemptibleStill busy-waits, non-preemptible
mutexSleeping lockSleeping 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

Character Device Read/Write Path

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.

Interrupt Handler Updating Shared State

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.

Timer Callback Updating a Small Flag

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

MistakeSymptomFix
Calling mutex_lock() inside an interrupt handler“Scheduling while atomic” / system freezeUse a spinlock instead in that context
Holding a spinlock across a long or blocking operationOther CPUs stall, latency spikesKeep the critical section minimal, switch to a mutex if it must sleep
Using plain spin_lock() when an ISR shares the lockRare interrupt-context deadlock under loadUse spin_lock_irqsave()/spin_unlock_irqrestore()
Forgetting to match lock/unlock on every return pathPermanent lock held, later hangUse 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

Leave a Reply

Your email address will not be published. Required fields are marked *