Combining Mutex and Spinlock in the Same Linux Device Driver-Free Linux Device Driver Training in Hyderabad

Combining Mutex and Spinlock in the Same Linux Device Driver
Free Linux Kernel Programming Course — Kernel Synchronization Series (Kernel 6.x)
Kernel 6.x Updated
Original Code Examples
Free Linux Device Drivers Course

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.

mutex and spinlock linux device driver free linux kernel development course free linux device drivers course free embedded systems course free linux development course
What You Will Learn
Why one driver may need two different lock types The rule: can this critical section sleep? Protecting different struct members with different locks Why copy_to_user() forces a mutex, never a spinlock An original dual-lock driver example (kernel 6.x) Common mutex/spinlock mixing mistakes
Prerequisites

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:

Lock Selection Decision Flow
Can this critical section block/sleep?
(copy_to_user, kmalloc(GFP_KERNEL), mutex_lock, msleep…)
↓ ↓
NO → use spinlock
YES → use mutex

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() and spin_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.

Continue the Free Linux Kernel Development Course

More free Linux device driver and embedded systems lectures are on the way on EmbeddedPathashala.

Browse the Full Course

2 Comments

Leave a Reply

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