In this free Linux kernel development course lecture we solve a very practical problem:
a single driver often has to protect more than one kind of critical section, and not every critical
section behaves the same way. Some sections only touch plain integers and never block. Other sections
call functions like copy_to_user() that can put the calling task to sleep. Using a
mutex and spinlock in the same Linux device driver — each protecting the right
kind of section — is the correct, modern (kernel 6.x) way to handle this. By the end of this
lecture you will know exactly when to reach for a spinlock and when a mutex is mandatory.
Before this lecture, you should already be comfortable with:
- Basic character/misc device driver structure (open, read, write)
- The spinlock API:
spin_lock_init(),spin_lock(),spin_unlock() - The mutex API:
mutex_init(),mutex_lock(),mutex_unlock() - The idea of a “critical section” and why concurrent access to shared data is dangerous
If any of these feel shaky, revisit the earlier lectures in this Kernel Synchronization series first.
Why Would a Driver Need Both a Mutex and a Spinlock?
A real driver rarely protects just one piece of data with just one kind of access pattern. Picture a driver that maintains a shared context structure holding: (a) two small counters that are incremented and decremented on every open/close, and (b) a secret buffer that gets copied out to user space on every read. These two pieces of data have completely different locking needs, and that difference is the whole point of this lecture.
The counters in (a) are updated with a couple of arithmetic operations — fast, and guaranteed to never
sleep. The secret buffer in (b) is sent to user space with copy_to_user(), an operation that can
trigger a page fault and cause the current task to be put to sleep while the page is faulted in. Protecting both
kinds of section with a single lock type is a mistake in one direction or the other, as the next section explains.
The One Rule That Decides Your Lock Type
Every time you write a critical section, ask exactly one question:
(copy_to_user, kmalloc(GFP_KERNEL), mutex_lock, msleep…)
That’s it. Not “which lock feels faster” or “which one the previous developer used” — purely whether the
code inside the critical section can ever cause the CPU to give up control via schedule(). Spinlocks
spin the CPU in a tight loop, which is fine for a handful of instructions but catastrophic if the “handful of
instructions” secretly includes a blocking call — you can end up spinning forever waiting for a task that
is itself waiting for your spinlock to be released.
Why copy_to_user() Specifically Needs a Mutex
copy_to_user() and copy_from_user() touch a user-space virtual address. If that page
is not currently resident (swapped out, or not yet mapped/COW’d in), the kernel takes a page fault, and resolving
a page fault can involve I/O and therefore sleeping. From the driver’s point of view this is invisible —
there’s no explicit schedule() call in your code — but the possibility is always there. That
hidden possibility is exactly why the rule in the previous section says “can sleep”, not “does sleep every time”.
A spinlock held across such a call is a latent bug that may work fine for months and then hang a CPU in
production.
| Struct Member | Operation on it | Correct Lock | Reason |
|---|---|---|---|
| open_count (int) | increment/decrement | spinlock | Pure arithmetic, never blocks |
| secret buffer | length read before copy_to_user() | mutex | copy_to_user() can sleep on a page fault |
| device state flags | set/clear inside an ISR or softirq | spinlock (irq variant) | Mutexes cannot be used in interrupt context |
Original Example: A Driver Context Struct Guarded by Two Locks
Below is an original demo (written for this course, not taken from any book) built and tested against a
kernel 6.x tree. It maintains a small context structure with a fast counter guarded by a spinlock, and a
message buffer whose read path is guarded by a mutex because it uses copy_to_user().
struct ep_ctx {
struct mutex rw_mutex; /* guards the message buffer + copy_to_user path */
spinlock_t cnt_lock; /* guards the fast open/close counter, never sleeps */
int open_count;
char msgbuf[128];
size_t msglen;
};
static struct ep_ctx *ctx;
static int ep_open(struct inode *inode, struct file *filp)
{
/* Fast, non-blocking update -> spinlock is correct here */
spin_lock(&ctx->cnt_lock);
ctx->open_count++;
spin_unlock(&ctx->cnt_lock);
return 0;
}
static ssize_t ep_read(struct file *filp, char __user *ubuf,
size_t count, loff_t *off)
{
ssize_t ret;
size_t len;
/* This section eventually calls copy_to_user() -> must use mutex */
if (mutex_lock_interruptible(&ctx->rw_mutex))
return -ERESTARTSYS;
len = min(count, ctx->msglen);
if (copy_to_user(ubuf, ctx->msgbuf, len)) {
mutex_unlock(&ctx->rw_mutex);
return -EFAULT;
}
ret = len;
mutex_unlock(&ctx->rw_mutex);
return ret;
}
static int ep_init_ctx(void)
{
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
mutex_init(&ctx->rw_mutex);
spin_lock_init(&ctx->cnt_lock);
strscpy(ctx->msgbuf, "hello from ep_ctx", sizeof(ctx->msgbuf));
ctx->msglen = strlen(ctx->msgbuf);
return 0;
}
Notice the two locks never protect the same field, and neither critical section calls a blocking function while holding the spinlock. That separation is the entire design pattern.
Common Mistakes When Mixing Mutex and Spinlock
- Using a spinlock around copy_to_user()/copy_from_user() — the single most common and most dangerous mistake covered in this lecture.
- Forgetting to initialize one of the two locks — always call both
mutex_init()andspin_lock_init()in your driver’s init path. - Taking the mutex from interrupt/softirq context — mutexes can sleep and must never be acquired from atomic context; use a spinlock (with the irq-safe variant) there instead.
- Holding the mutex far longer than necessary, e.g. doing unrelated work while still holding the lock, increasing contention.
- Locking at the wrong granularity — one giant lock for the whole structure removes the benefit of separating fast and slow paths.
Best Practices
- Document, next to each struct member, which lock protects it.
- Keep spinlock-protected sections as short as a handful of instructions.
- Prefer
mutex_lock_interruptible()in paths reachable from user space so a blocked task can still be killed with a signal. - Never call any GFP_KERNEL allocation, copy_to_user/copy_from_user, or another sleeping mutex_lock while holding a spinlock.
Performance Considerations
Spinlocks are cheap when held briefly because there’s no context switch overhead, but every CPU spinning on
a busy lock wastes cycles. Mutexes cost more per acquisition (potential sleep/wake, and on kernel 6.x an
adaptive spin phase via CONFIG_MUTEX_SPIN_ON_OWNER) but scale much better for anything that can
legitimately take a while. Choosing correctly per critical section, as shown above, gives you the best of
both: fast paths stay fast, slow paths stay safe.
Security Considerations
A spinlock held across a blocking call is not just a performance bug — on a single-core or low-core-count embedded system it can hang the entire machine, which is a denial-of-service condition. Careful lock-type selection is therefore also a robustness/security concern for any driver shipped in a product.
Summary / Key Takeaways
- One driver can, and often should, use both a mutex and a spinlock — for different sections.
- The decision rule is simple: if the section can ever sleep, use a mutex; otherwise a spinlock is fine.
copy_to_user()/copy_from_user()can sleep on a page fault, so they always require a mutex (or an equivalent sleep-safe lock), never a spinlock.- Document which lock guards which field to avoid accidental cross-locking bugs.
Conclusion
Mixing lock types inside one driver is not a hack — it’s the correct, idiomatic approach used all over
the mainline Linux kernel. The mental model is simple once internalised: fast, guaranteed non-blocking updates
get a spinlock; anything that touches user space, allocates with GFP_KERNEL, or otherwise can
sleep gets a mutex. In the next lecture of this free Linux kernel development course, we take this a step
further and deliberately break the rule to see, in a real debug session, exactly what happens when a driver
sleeps while holding a spinlock.
Frequently Asked Questions
Q1. Can I just use a mutex everywhere and skip spinlocks entirely?
Not if any part of your driver runs in interrupt or softirq context — mutexes can sleep and are illegal there. Even in process context, mutexes cost more per short, hot-path update than a spinlock.
Q2. Can I hold a mutex and a spinlock at the same time?
Yes, as long as you always take the mutex first and the spinlock second (never the reverse), and you release the spinlock before doing anything that could sleep.
Q3. Is it safe to call copy_to_user() while holding a spinlock, “just this once”?
No. Even if it appears to work during testing, the underlying page-fault path can sleep, and this bug can surface unpredictably in production. The next lecture demonstrates exactly this failure.
Q4. Do these rules still apply on the latest kernel 6.x releases?
Yes. The mutex and spinlock APIs used here are unchanged in behaviour on modern kernel 6.x trees; only some internal implementation details (like adaptive mutex spinning) have improved.
Q5. What happens if I forget to call mutex_init() or spin_lock_init()?
The lock’s internal state is uninitialized, so locking behaviour is undefined — you may see silent corruption, a crash, or a lockdep warning at boot if lock debugging is enabled.
Q6. How small should a spinlock-protected critical section be?
As small as possible — ideally just a few arithmetic or pointer operations. If you find yourself reaching for anything that could allocate memory, sleep, or call into another subsystem, move that logic outside the spinlock or switch to a mutex.
Q7. Is this pattern relevant outside of character device drivers?
Yes — any kernel module that has both a hot, non-blocking path and a slower path involving user space or allocation benefits from this same mutex-plus-spinlock split.
More free Linux device driver and embedded systems lectures are on the way on EmbeddedPathashala.
Browse the Full Course
2 Comments