Mutex vs Spinlock in the Linux Kernel (Kernel 6.x Guide)-Free Linux Device Driver Course Online

← Previous Lecture | Next Lecture →

Mutex vs Spinlock in the Linux Kernel (Kernel 6.x Guide)
Free Linux Kernel Programming Course — Understand the Real Difference Before You Write Your Own Driver Locking Code
Chapter: Kernel Synchronization
Level: Beginner – Intermediate
Kernel: 6.x

Every driver author following a free linux device drivers course eventually asks the same question: “should I use a mutex or a spinlock here?” The two APIs look almost identical to call, but they behave completely differently under the hood. Picking the wrong one can silently hurt performance, or worse, deadlock your system. In this lecture of our free linux kernel development course we explain the real conceptual difference on modern kernel 6.x, with original code you can build and test yourself.

mutex vs spinlock linux linux kernel locking spinlock tutorial kernel mutex example free linux kernel development course free embedded systems course

What You Will Learn

  • Sleep vs spin: the core difference
  • Basic mutex API on kernel 6.x
  • Basic spinlock API on kernel 6.x
  • Why spinlocks matter only on multicore (SMP) systems
  • The real cost of a mutex: context switches
  • How modern kernels optimize mutex waiting (adaptive spinning)

Prerequisites

This lecture assumes you understand what a critical section is and why concurrent kernel code needs protection, along with the deadlock and livelock concepts from the previous lecture in this chapter. No specific hardware is required; any kernel 6.x build (native or VM) with loadable module support is enough to try the examples.

The Core Idea: What Happens While You Wait

Picture three threads — call them T1, T2, and T3 — all racing to enter the same critical section protected by one lock. Only one of them can win and proceed; the other two become “losers” for now. The single most important question in kernel locking is this: what do the losers do while they wait? The answer to that question is the entire difference between a mutex and a spinlock.

Three Threads, One Lock — Who Waits, and How?
Threads T1, T2, T3 all call lock() at roughly the same time. Exactly one thread (say T2) becomes the OWNER and enters the critical section. T1 and T3 are the LOSERS. With a MUTEX: T1 and T3 go to SLEEP. The scheduler removes them from the CPU entirely until T2 calls unlock(). With a SPINLOCK: T1 and T3 keep RUNNING, spinning in a tight loop, checking again and again whether the lock has been released.

Mutex: The Sleeping Lock

With a mutex, a losing thread is put to sleep. Internally this means the kernel calls into the scheduler, the thread is taken off the CPU, and something else useful runs in its place. When the owner calls mutex_unlock(), the sleeping thread(s) are woken back up and compete for the lock again. Because of this sleep/wake behaviour, mutexes (along with semaphores) are sometimes called “sleeplocks.”

A minimal, original mutex example, updated for kernel 6.x API (no deprecated calls):

#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>

struct my_device_data {
    struct mutex lock;
    int counter;
};

static struct my_device_data *dev_data;

static void safe_increment(void)
{
    mutex_lock(&dev_data->lock);
    dev_data->counter++;
    mutex_unlock(&dev_data->lock);
}

static int __init mutex_demo_init(void)
{
    dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
    if (!dev_data)
        return -ENOMEM;

    mutex_init(&dev_data->lock);
    safe_increment();
    pr_info("mutex_demo: counter = %d\n", dev_data->counter);
    return 0;
}

static void __exit mutex_demo_exit(void)
{
    mutex_destroy(&dev_data->lock);
    kfree(dev_data);
}

module_init(mutex_demo_init);
module_exit(mutex_demo_exit);
MODULE_LICENSE("GPL");

Because a mutex can put the caller to sleep, it is a blocking call. That means you must never call mutex_lock() from a context that cannot sleep — most importantly, never from interrupt context (hardirq) or from any code path holding a spinlock.

Spinlock: The Busy-Waiting Lock

A spinlock never puts anyone to sleep. A losing thread simply loops, repeatedly checking whether the lock has become free, conceptually similar to while (locked) ;. This only makes sense on a multicore (SMP) system: while the owner thread runs the critical section on one CPU, the losing threads spin on other CPUs, and as soon as the lock is released one of them grabs it almost instantly, with none of the scheduling overhead a mutex would need.

On modern ARM64 and x86_64 kernel 6.x builds, the actual implementation is far from a naive busy loop. It is a highly optimized, architecture-specific ticket or queued spinlock, and on ARM it commonly uses the WFE (wait-for-event) instruction so the “spinning” CPU can sit in a low-power state instead of burning full clock cycles — but conceptually, it is still “wait right here, actively, until the lock opens.”

#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/slab.h>

struct fast_path_data {
    spinlock_t lock;
    int counter;
};

static struct fast_path_data *fp_data;

static void fast_increment(void)
{
    unsigned long flags;

    spin_lock_irqsave(&fp_data->lock, flags);
    fp_data->counter++;
    spin_unlock_irqrestore(&fp_data->lock, flags);
}

static int __init spinlock_demo_init(void)
{
    fp_data = kzalloc(sizeof(*fp_data), GFP_KERNEL);
    if (!fp_data)
        return -ENOMEM;

    spin_lock_init(&fp_data->lock);
    fast_increment();
    pr_info("spinlock_demo: counter = %d\n", fp_data->counter);
    return 0;
}

static void __exit spinlock_demo_exit(void)
{
    kfree(fp_data);
}

module_init(spinlock_demo_init);
module_exit(spinlock_demo_exit);
MODULE_LICENSE("GPL");

Notice the spin_lock_irqsave() / spin_unlock_irqrestore() pair. This variant also disables local interrupts while the lock is held, which is exactly the tool that prevents the process-context-vs-interrupt-context deadlock we covered in the previous lecture, whenever a lock can be taken from both contexts.

Why Mutexes Cost More: The Real Price Is Context Switches

The reason kernel developers care so much about this choice is cost. For a mutex to make a losing thread sleep, the kernel must call schedule(), which triggers a full context switch off the CPU. When the owner later unlocks, the sleeping thread must be woken and context-switched back onto a CPU. That is a minimum of two context switches just for one lock/unlock round trip under contention — and context switches are one of the more expensive operations a CPU performs, since they involve saving and restoring register state and often disturb the CPU cache.

A spinlock avoids all of that scheduling overhead entirely. If the critical section is short, spinning for a few extra cycles is far cheaper than paying for two context switches, which is exactly why spinlocks are the default choice for short, fast critical sections, and mutexes are the default choice when the critical section might take a while or might itself need to sleep (for example, if it allocates memory with GFP_KERNEL or calls a function that can block).

Mutex vs Spinlock — Quick Comparison
Property Mutex Spinlock
Loser behaviour Sleeps (removed from CPU) Spins (stays on CPU)
Can be used in interrupt context? No Yes (with irqsave variant)
Cost under contention Two context switches CPU cycles spent spinning
Best for Longer or sleep-capable critical sections Short, fast critical sections
Meaningful on UP (single-core)? Yes Mostly a no-op/preempt-disable on true UP

A Modern Kernel 6.x Detail: Adaptive Mutex Spinning

One nuance worth knowing if you are working on current kernels: a plain mutex is not purely a “go to sleep immediately” lock anymore. Since the CONFIG_MUTEX_SPIN_ON_OWNER optimization (enabled by default on SMP kernel 6.x builds with a suitable architecture), a waiting thread will first optimistically spin for a short time if the current lock owner is actively running on another CPU, betting that the critical section will finish soon. Only if that bet does not pay off does the thread fall back to the classic sleep-and-wake path. This blurs the line a little in practice, but the conceptual model — “mutex prioritizes sleeping under real contention, spinlock never sleeps” — still holds and is what you should design around.

FAQ

Q1. Can I call mutex_lock() inside an interrupt handler?
No. Mutexes can sleep, and interrupt handlers must never sleep. Use a spinlock (with the irqsave variant if the same lock is also used in process context).

Q2. Is a spinlock ever a bad choice?
Yes, if the critical section is long or can sleep (memory allocation with GFP_KERNEL, taking another sleeping lock, calling something that blocks). Holding a spinlock for too long wastes CPU cycles on every waiting core and can even trigger a “soft lockup” watchdog warning.

Q3. Do spinlocks matter on a single-core (UP) system?
Not in the same way. On a true single-CPU system there is no “other CPU” to spin on, so the spinlock functions mainly just disable preemption (and interrupts, for the irqsave variant) to protect the critical section.

Q4. What is the minimum cost of a contended mutex lock/unlock?
At least two context switches: one to put the loser thread to sleep, and one to wake it back up and reschedule it.

Q5. Does kernel 6.x still support both lock types the same way?
Yes, both struct mutex and spinlock_t remain first-class, actively maintained primitives in kernel 6.x; the core API shown here is stable.

Q6. What is adaptive mutex spinning?
A kernel 6.x SMP optimization where a thread waiting on a mutex first spins briefly if the owner is currently running on another CPU, avoiding a full sleep/wake cycle if the lock is released quickly.

Q7. How do I decide which lock to use in my own driver?
As a starting rule of thumb: if the code can sleep or the section is not trivially short, use a mutex; if the code must run in interrupt context or the section is very short and fast, use a spinlock. We will build a full decision guide in the next lecture.

Continue this free Linux kernel programming course with the next lecture, where we build a complete, practical decision guide for choosing between mutexes, spinlocks, and other Linux synchronization primitives.

Next Lecture → Back to Course Index

← Previous Lecture | Next Lecture →

2 Comments

Leave a Reply

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