Welcome back. In our last lesson, we established the critical boundary between user space and kernel space, and saw how system calls provide a controlled mechanism for applications to request privileged kernel services.
This brings us to a crucial question: what happens when multiple threads of execution—perhaps from different processes on separate CPU cores, or even an interrupt handler—are running inside the kernel at the same time and need to access the same piece of data? Without protection, this would lead to chaos.
This lesson directly addresses one of the most fundamental interview topics in kernel and low-level systems programming: differentiating kernel synchronization primitives: spinlocks vs. mutexes. You will learn which to use for short, non-sleeping critical sections versus longer, sleepable ones. Mastering this distinction is essential for writing correct and efficient kernel code.
1. The Problem: Concurrency and Race Conditions
In a modern multitasking OS on a multi-core system, several things can be happening "at once" within the kernel:
- Symmetric Multiprocessing (SMP): Code can be executing on multiple CPU cores simultaneously.
- Kernel Preemption: A higher-priority task can interrupt a lower-priority task even when the latter is executing in kernel mode.
- Interrupts: A hardware interrupt can occur at any time, pausing the current code and executing an Interrupt Service Routine (ISR).
When these concurrent threads of execution try to access and modify a shared resource (like a global variable, a hardware register, or a shared data structure), a race condition can occur. The final state of the resource depends on the unpredictable order of execution.
To prevent this, we must protect the code that accesses the shared resource. This protected region is called a critical section. The goal is to ensure mutual exclusion—only one thread can be inside the critical section at a time. The tools we use to enforce this are called locking primitives.

2. Spinlocks: Fast, Busy-Waiting Locks
A spinlock is the most basic locking mechanism. Its operation is simple: if the lock is available, you acquire it and enter the critical section. If it's already held by another thread, your CPU will "spin" in a tight loop, repeatedly checking the lock until it becomes available. This is a form of busy-waiting.
How Spinlocks Work
This busy-wait loop isn't a simple while (lock == 1);. That would introduce a race condition. Instead, spinlocks rely on a single, atomic hardware instruction that can both check and set a value in memory without interruption.
The following video provides an excellent explanation of a spinlock's implementation from first principles, showing why a naive approach fails and how an atomic instruction (like amoswap on RISC-V) solves the problem.
The video 'xv6 Kernel-4: Spinlocks' demonstrates the fundamental mechanics of a spinlock. It clearly explains the race condition in a simple check-then-set implementation and how atomic hardware instructions provide the necessary guarantee for mutual exclusion.
Watch from 02:15 to 05:11. Focus on understanding the flaw in the naive acquire function and how the atomic memory swap instruction resolves it by combining the check and set operations into a single, indivisible step.
Context and Rules for Spinlocks
Because a spinning CPU is consuming 100% of its core's execution time doing no productive work, spinlocks are only suitable for very short critical sections.
The most important rule for spinlocks is: Code holding a spinlock must never sleep.
Why? If a task holding a spinlock goes to sleep, it might never wake up to release the lock. Other CPUs trying to acquire the same lock would spin forever, leading to a system freeze. This means you cannot call any function that might sleep (like kmalloc with GFP_KERNEL or copy_from_user) while holding a spinlock.
This "no-sleeping" rule makes spinlocks the only choice for atomic contexts, such as:
- Interrupt handlers (ISRs)
- Softirqs and tasklets
Spinlocks and Interrupts: The _irqsave Variant
A common and dangerous deadlock scenario occurs when data is shared between normal process context code and an ISR.
- A task on CPU 0 acquires a spinlock
L. - A hardware interrupt occurs on CPU 0, preempting the task.
- The ISR for that interrupt tries to acquire the same spinlock
L.
The system is now deadlocked. The ISR will spin forever waiting for L, but the task that holds L can't run to release it until the ISR finishes.
To solve this, the kernel provides special spinlock variants. The safest and most common is spin_lock_irqsave().
To learn about this critical pattern, please read the following sections.
The Linux Kernel Locking API and Shared Objects
This article, 'The Linux Kernel Locking API and Shared Objects', gives a clear, practical guide to spinlocks in the Linux kernel.
Read the sections 'Spinlocks' and the sub-section that details the '_irq' and '_irqsave' variants. Pay close attention to the deadlock scenario it describes and understand why spin_lock_irqsave is the robust solution.
In summary, spin_lock_irqsave() does three things:
- Saves the current state of the interrupts on the local CPU.
- Disables interrupts on the local CPU.
- Acquires the spinlock.
Its counterpart, spin_unlock_irqrestore(), releases the lock and restores the interrupt state to whatever it was before the lock was taken.
3. Mutexes: Slower, Sleeping Locks
A mutex (short for mutual exclusion) serves the same purpose as a spinlock but behaves very differently when contested. If a task tries to acquire a mutex that is already held, it doesn't spin. Instead, the kernel puts the task to sleep and adds it to a wait queue for that mutex. The CPU is then free to schedule and run other tasks. When the mutex is eventually released, it wakes up a waiting task, which then becomes eligible to run again.
How Mutexes Work
A mutex is a more complex structure than a spinlock. As you'll see in the resources, a Linux mutex internally contains a spinlock_t wait_lock to protect its own list_head wait_list.
The 'C Linux Master Sheet' provides a concise breakdown of the struct mutex and the rules for its use.
Read the 'Mutex' section. Review the 'Contents of mutex Linux Kernel structure' to see how it's built from other components (like a spinlock and a wait list). Then, focus on the list of rules and the critical 'Where not to use mutex' section.
Context and Rules for Mutexes
The ability to sleep makes mutexes suitable for:
- Longer critical sections, where busy-waiting would be wasteful.
- Critical sections that might need to sleep, for example, to allocate memory or wait for I/O.
The most important rule for mutexes is the inverse of the spinlock rule: Mutexes can only be used in process context, where it is safe to sleep. You must never use a mutex in an atomic context like an interrupt handler.
The kernel offers different functions to acquire a mutex:
mutex_lock(struct mutex *lock): Puts the task into an uninterruptible sleep if it has to wait.mutex_lock_interruptible(struct mutex *lock): Puts the task into an interruptible sleep. The wait can be cut short by a signal. This is generally preferred.mutex_trylock(struct mutex *lock): Attempts to acquire the lock but returns immediately without sleeping if it's not available.
The following video demonstrates the use of mutex_lock and mutex_lock_interruptible in a practical character device driver example.
Linux kernel synchronization technique | Semaphore, Mutex | Linux kernel online training course.
This video, 'Linux kernel synchronization technique', walks through a kernel module that uses a mutex to protect a shared resource between read and write operations. It's a good practical demonstration of the API.
Watch the following segments: (21:03 - 28:28) Introduction to critical sections and the need for mutexes. (34:46 - 44:00) Walkthrough of a character driver using mutex_lock to protect a global variable. (1:01:50 - 1:05:30) Explanation of the difference between mutex_lock and mutex_lock_interruptible with a demonstration of how a signal can interrupt the latter.
4. The Deciding Factor: Spinlock vs. Mutex
In an interview, you will almost certainly be asked to choose between these two primitives for a given scenario. The choice boils down to a few key questions.
| Feature / Question | Spinlock | Mutex |
|---|---|---|
| Contention Behavior | Busy-waits (spins) | Sleeps (blocks) |
| Allowed Context | Process & Atomic (ISRs) | Process context only |
| Can the critical section sleep? | No, absolutely not. | Yes, it can. |
| Typical Critical Section Duration | Very short | Can be longer |
| Performance Overhead | Low (if no contention) | Higher (context switch) |
| Interrupts | Must be disabled on local CPU if shared with ISR (_irqsave) |
Not used in interrupt context |
Interview-Style Decision Scenarios
-
Scenario 1: You are writing a network driver. The interrupt handler needs to update a counter in a shared
statsstructure. What lock do you use to protect the structure?- Answer: A spinlock (
spin_lock_irqsave). The code is running in an interrupt handler (atomic context), where sleeping is forbidden. A mutex is not an option.
- Answer: A spinlock (
-
Scenario 2: Your driver's
ioctlhandler needs to reconfigure a hardware device. The process involves writing to multiple registers and then allocating a new buffer withkmalloc(..., GFP_KERNEL)based on the new configuration. What lock do you use?- Answer: A mutex. The call to
kmallocwith theGFP_KERNELflag may sleep if memory is not immediately available. Holding a spinlock across a function that might sleep would deadlock the system.
- Answer: A mutex. The call to
-
Scenario 3: You have a critical section that is just a few assembly instructions. Should you use a spinlock or a mutex?
- Answer: A spinlock. The overhead of a context switch (putting a task to sleep and waking it up later) is far greater than the cost of spinning for a few dozen CPU cycles. For very short critical sections, a spinlock is much more performant.
Conclusion
You have now analyzed the two most fundamental synchronization primitives in the Linux kernel. The ability to articulate their differences and choose the correct one for a given context is a hallmark of a competent systems programmer.
Key Takeaways:
- Spinlocks are for short, non-sleeping critical sections, especially in atomic contexts like interrupt handlers. They use busy-waiting.
- Mutexes are for potentially longer, sleepable critical sections that run only in process context. They put waiting tasks to sleep.
- The most critical question is: "Can the context or the critical section code sleep?" If the answer is yes, you must use a mutex. If no, a spinlock is your tool.
- When protecting data shared with an interrupt handler, you must use
spin_lock_irqsave()to prevent deadlocks by disabling interrupts on the local CPU.
Preview of the Next Lesson:
We've established the importance of spin_lock_irqsave. In the next lesson, we will solidify this concept by focusing entirely on its application. You will learn to apply IRQ-safe spinlock variants to protect data shared between process context and interrupt context, a pattern you will encounter constantly in device driver development.