Mutex vs Spinlock in Linux Kernel Programming: Which One Should You Use?
Part 2 of our free Linux device drivers course — a practical guide to choosing and using mutex_lock, spin_lock, and spin_lock_irqsave correctly
Picking between a mutex and a spinlock is one of the first real design decisions every Linux kernel and device driver developer has to make, and getting it wrong is one of the most common causes of hangs, sluggish drivers, and hard-to-reproduce crashes. This lesson, part of EmbeddedPathashala’s free Linux kernel development course, gives you a clear decision framework plus working, modern kernel code for both locking primitives.
What You Will Learn
Prerequisites
- Part 1 of this course — Critical Sections and Atomicity
- Comfort building and loading kernel modules (
insmod,rmmod,dmesg) - Basic understanding of process context vs interrupt context
Mutex or Spinlock? The Core Question
Both a mutex and a spinlock exist to give one thread exclusive access to a critical section. The difference is entirely about what happens while a thread is waiting:
- A mutex puts the waiting thread to sleep. The CPU is freed up to run something else, and the scheduler wakes the waiter back up once the lock is released.
- A spinlock makes the waiting thread “spin” in a tight loop, repeatedly checking the lock, burning CPU cycles the whole time, until it becomes free.
That single difference drives almost every rule in this lesson.
| Question | If Yes | If No |
|---|---|---|
| Can the caller sleep (process context)? | Mutex is allowed | Mutex forbidden — use spinlock |
| Is the critical section very short? | Spinlock is efficient | Prefer mutex — avoid long spinning |
Might code inside the lock call something that sleeps (e.g. kmalloc(GFP_KERNEL))? |
Use mutex only | Either can work |
Using the Mutex Lock
A mutex in the Linux kernel is declared and used through the API in <linux/mutex.h>. It is the right default choice for process-context code that can afford to sleep while waiting.
#include <linux/mutex.h>
#include <linux/slab.h>
static DEFINE_MUTEX(dev_lock);
static char *shared_buffer;
static ssize_t mydev_write(struct file *filp, const char __user *buf,
size_t len, loff_t *off)
{
char *tmp;
tmp = kmalloc(len, GFP_KERNEL); /* may sleep - fine here */
if (!tmp)
return -ENOMEM;
if (copy_from_user(tmp, buf, len)) {
kfree(tmp);
return -EFAULT;
}
mutex_lock(&dev_lock);
kfree(shared_buffer);
shared_buffer = tmp;
mutex_unlock(&dev_lock);
return len;
}
Key points about mutexes in current kernels:
- Only usable in process context — never from interrupt handlers.
- The same task that locks a mutex must be the one to unlock it.
- A mutex must never be locked recursively by the same task (use
mutex_lock_interruptible()if you need to remain responsive to signals). - Prefer
mutex_lock_interruptible()over plainmutex_lock()in code paths reachable from user space, so a blocked process can still be killed cleanly.
Using the Spinlock
A spinlock, declared via <linux/spinlock.h>, is the right tool when the critical section is short and might be reached from a context that cannot sleep.
#include <linux/spinlock.h>
static DEFINE_SPINLOCK(stats_lock);
static unsigned long packet_count;
static void update_stats(void)
{
unsigned long flags;
spin_lock_irqsave(&stats_lock, flags);
packet_count++;
spin_unlock_irqrestore(&stats_lock, flags);
}
Notice the use of spin_lock_irqsave() instead of the plain spin_lock(). This matters whenever the same lock could be taken both from process context and from an interrupt handler.
Locking and Interrupts: Why irqsave Exists
If process-context code takes a plain spinlock, and then an interrupt fires on the same CPU whose handler tries to take that same lock, you get a deadlock: the interrupt handler spins forever waiting for a lock held by code that can’t run again until the interrupt handler finishes. spin_lock_irqsave() prevents this by disabling local interrupts before taking the lock and restoring their previous state afterward — flags stores exactly what state to restore, since interrupts might already have been off.
| Variant | When to Use |
|---|---|
spin_lock() / spin_unlock() |
Lock never touched by any interrupt handler |
spin_lock_bh() / spin_unlock_bh() |
Lock shared with softirq/tasklet (bottom-half) context |
spin_lock_irqsave() / spin_unlock_irqrestore() |
Lock possibly shared with a hardware interrupt handler |
Common Mistakes and Troubleshooting
| Mistake | Symptom | Fix |
|---|---|---|
| Sleeping while holding a spinlock | System hang, “scheduling while atomic” panic | Switch the critical section to a mutex, or move allocation outside the lock |
| Using plain spin_lock() in a lock also taken by an ISR | Intermittent deadlock, hard to reproduce | Use spin_lock_irqsave()/irqrestore instead |
| Forgetting to unlock on an error return path | Permanent deadlock on next lock attempt | Use a single unlock point via goto, or guard()/cleanup helpers in recent kernels |
| Taking two locks in inconsistent order across code paths | Classic ABBA deadlock | Always acquire multiple locks in the same global order everywhere |
Best Practices
- Default to a mutex unless you have a specific reason (interrupt context, extremely short critical section) to use a spinlock.
- Keep spinlock-protected code as short and predictable as possible — no loops over user-controlled sizes, no blocking calls.
- Always pair lock acquisition and release through every possible return path.
- Enable
CONFIG_PROVE_LOCKING(lockdep) during development — it will flag inconsistent lock ordering and context violations automatically. - Document what data each lock protects directly above its declaration.
Performance Considerations
Spinlocks are cheap when contention is rare and hold times are tiny — there’s no context-switch overhead. But under heavy contention or long hold times, spinning wastes CPU cycles that a mutex would have handed back to the scheduler. Mutexes cost more per acquisition (potential context switch) but scale far better when a lock might be held for anything longer than a few instructions. Profile before assuming; on modern multicore SoCs, lock contention is a very common hidden bottleneck in embedded Linux drivers.
Security Considerations
Incorrect lock handling — missed unlocks, wrong lock ordering, or locks taken in the wrong context — has historically been the root cause of exploitable use-after-free and double-free bugs in kernel drivers. Treat every new lock you add as something that needs the same review rigor as memory management code, not an afterthought.
Real-World Use Case
A typical network driver uses a spinlock (with _irqsave) to protect a small ring-buffer index that both the hardware interrupt handler and the transmit path touch, while using a mutex to protect larger, sleep-tolerant operations like reconfiguring the device through ethtool ioctls. Mixing the two correctly — spinlock for the fast interrupt path, mutex for the slow configuration path — is standard practice across mainline Linux drivers.
Summary — Key Takeaways
- Mutexes put waiters to sleep; spinlocks make waiters busy-loop.
- Use mutexes in process context, especially around code that can itself sleep.
- Use spinlocks for short critical sections, and reach for
spin_lock_irqsave()whenever an interrupt handler might touch the same lock. - Consistent lock ordering across your whole driver prevents deadlocks.
- Lockdep (
CONFIG_PROVE_LOCKING) is your best friend during development.
Conclusion
Choosing between a mutex and a spinlock isn’t a matter of preference — it’s dictated by the execution context your code runs in and how long you need to hold the lock. Get comfortable with the decision table in this lesson, always pair every lock with its unlock on every code path, and lean on lockdep during development. With critical sections, atomicity, mutexes, and spinlocks now under your belt, you have the core synchronization toolkit every Linux kernel and device driver developer relies on daily.
Frequently Asked Questions
Can I use a mutex inside an interrupt handler?
No. Mutexes can put the caller to sleep, and interrupt handlers must never sleep. Use a spinlock with spin_lock_irqsave() instead if the lock might be needed from interrupt context.
What’s the difference between spin_lock and spin_lock_irqsave?
spin_lock() assumes interrupts on the local CPU are already safe to leave enabled. spin_lock_irqsave() additionally disables local interrupts and saves their prior state, which is required whenever an interrupt handler could try to take the same lock.
Why is holding a spinlock too long a problem even outside interrupt handlers?
On a multicore system, every other CPU waiting on that spinlock is burning cycles in a busy loop the entire time it’s held. A long-held spinlock directly wastes CPU capacity across the whole system, unlike a mutex, which frees the CPU for other work while waiting.
What causes an ABBA deadlock and how do I avoid it?
It happens when one code path locks A then B, while another locks B then A; each ends up waiting on a lock the other already holds. Avoid it by always acquiring multiple locks in the exact same order everywhere in your codebase.
Is lockdep something I need in production kernels?
No — CONFIG_PROVE_LOCKING adds overhead and is meant for development and testing kernels, not production builds. Use it heavily while developing and debugging drivers, then build your release kernel without it.
Does this lesson apply to the latest Linux kernel versions?
Yes. The APIs, macros, and recommendations here — DEFINE_MUTEX, DEFINE_SPINLOCK, spin_lock_irqsave, and lockdep — reflect current, actively maintained kernel conventions rather than legacy or deprecated interfaces.
Keep Learning — Free Linux Kernel Development Course
You’ve now covered the core synchronization primitives used across real Linux device drivers. Explore more free lessons on kernel programming, device drivers, and embedded Linux.
Next Lecture → Browse Full Course
2 Comments