<
Linux kernel locking is the next essential skill after atomic operations, and it is a core topic in any free Linux kernel development course, free Linux device drivers course, or free embedded systems course. While atomic operations protect a single variable, real driver code often needs to update several related fields together, safely, across CPUs and interrupt handlers. That’s exactly what locks are for. This tutorial builds the concept of locking from first principles, using clear diagrams instead of dense theory.
Prerequisites
- Basic C programming and pointer handling
- Familiarity with kernel modules (build, insert, remove)
- Ideally, you’ve read the previous lecture on atomic operations and critical sections in this free Linux kernel development course
What Does Linux Kernel Locking Actually Do?
A lock guarantees that exactly one thread of execution can be “inside” a critical section at any given moment. Every other thread that wants to enter must wait until the current owner releases the lock. This turns a section of code that would otherwise run concurrently on multiple CPUs into something that behaves as if it runs one-at-a-time — serialized.
Once CPU 0 calls unlock(), CPU 1 (or whichever CPU was waiting) becomes the new owner and proceeds. This waiting behavior is the heart of every kernel lock — the difference between lock types is mainly about how a CPU waits.
Spinlocks vs Mutexes in Linux Kernel Locking
The Linux kernel offers several lock types, but the two you will use constantly as a beginner are the spinlock and the mutex.
| Aspect | Spinlock | Mutex |
|---|---|---|
| How it waits | Busy-waits (“spins”) in a tight loop | Puts the waiting task to sleep |
| Safe in interrupt context? | Yes | No — never sleep in interrupt context |
| Typical critical section length | Very short | Can be longer |
| CPU usage while waiting | High (actively burns cycles) | Low (CPU is freed for other tasks) |
| Common use case | Interrupt handlers, very short updates | Process-context code, longer operations, code that may sleep |
GFP_KERNEL, or calls a function that can block), you must use a mutex, not a spinlock. Sleeping while holding a spinlock can hang or crash the system.
A Practical Spinlock Example
Here’s how a driver protects a shared structure using a spinlock:
#include <linux/spinlock.h>
struct mydev_data {
int pending_requests;
u32 last_status;
};
static struct mydev_data dev_data;
static DEFINE_SPINLOCK(dev_data_lock);
static void mydev_update_status(u32 status)
{
unsigned long flags;
spin_lock_irqsave(&dev_data_lock, flags);
dev_data.pending_requests--;
dev_data.last_status = status;
spin_unlock_irqrestore(&dev_data_lock, flags);
}
spin_lock_irqsave() both takes the lock and disables local interrupts, saving their previous state into flags. This is essential when the same data can also be touched from an interrupt handler — without disabling interrupts, the CPU holding the lock could be interrupted and deadlock against itself.
A Practical Mutex Example
For process-context code that may allocate memory or otherwise sleep, prefer a mutex:
#include <linux/mutex.h>
static DEFINE_MUTEX(config_lock);
static char *config_buffer;
static int mydev_update_config(const char *new_config, size_t len)
{
char *tmp = kstrdup(new_config, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
mutex_lock(&config_lock);
kfree(config_buffer);
config_buffer = tmp;
mutex_unlock(&config_lock);
return 0;
}
Checking Lock Contention on a Running System
The kernel provides lock-debugging and profiling facilities you can enable while learning. For example, enabling lock statistics and viewing them:
# Enable lock stats (kernel must be built with CONFIG_LOCK_STAT)
echo 1 > /proc/sys/kernel/lock_stat
# View collected lock statistics
cat /proc/lock_stat
This is a great way to see, on a real system, which locks in the kernel are most contended — a useful habit to build early in a free Linux kernel development course.
Real-World Use Cases for Locking
- Character and block device drivers protecting shared buffers between read/write calls from multiple processes
- Network drivers protecting transmit/receive ring buffers shared between process context and interrupt handlers
- Filesystem code protecting inode and metadata updates
- Power management code protecting device state transitions
Common Locking Mistakes (and Deadlocks)
| Mistake | Consequence | Fix |
|---|---|---|
| Using a spinlock around code that sleeps | System hang or “scheduling while atomic” bug | Use a mutex for code paths that can sleep |
| Taking the same lock twice on the same CPU | Self-deadlock | Restructure code so nested calls don’t re-acquire the same lock |
| Acquiring locks A then B in one path, and B then A in another | Classic AB-BA deadlock between two CPUs | Always acquire locks in the same global order |
Forgetting _irqsave/_irqrestore when data is also touched by an interrupt handler | Deadlock if an interrupt fires on the same CPU while the lock is held | Use the IRQ-safe spinlock variants whenever interrupt context is involved |
Best Practices for Linux Kernel Locking
- Keep critical sections as short as possible — do allocation and heavy work outside the lock
- Always acquire multiple locks in a consistent, documented order
- Prefer the narrowest lock type that fits the job (spinlock for short, non-sleeping sections; mutex otherwise)
- Use lock validators such as lockdep during development to catch ordering bugs early
Performance Considerations
Every lock introduces some overhead and potential contention. Spinlocks are cheap when held briefly but waste CPU cycles under contention, since waiting CPUs spin instead of doing other work. Mutexes avoid wasting CPU while waiting but carry the extra cost of a context switch when a task has to sleep and be woken up. Choosing the right lock type — and keeping critical sections short — has a direct, measurable impact on driver throughput and system responsiveness.
Security Considerations
Incorrect locking is a frequent root cause of kernel security vulnerabilities, particularly race conditions that lead to use-after-free or double-free bugs when reference-counted objects are freed while another CPU still assumes exclusive access. Careful, consistent locking around object lifetime management is just as much a security practice as it is a correctness practice.
Summary and Key Takeaways
- A lock serializes access to a critical section so only one CPU is “inside” it at a time
- Spinlocks busy-wait and are safe in interrupt context; mutexes sleep and must never be used in interrupt context
- Use
_irqsave/_irqrestorespinlock variants whenever an interrupt handler can touch the same data - Keep critical sections short, and acquire multiple locks in a consistent order to avoid deadlocks
Conclusion
Linux kernel locking is where atomic operations meet real-world driver design. Once you’re comfortable choosing between a spinlock and a mutex, and structuring critical sections correctly, you have the core synchronization skills expected in any free Linux kernel development course, free Linux device drivers course, or free embedded systems course. From here, the natural next steps are reader/writer locks, RCU (Read-Copy-Update), and completion variables — all built on the same fundamental idea introduced in this lecture.
Frequently Asked Questions
No. Mutexes can sleep, and interrupt handlers must never sleep. Use a spinlock (with the IRQ-safe variant) for data shared with interrupt context.
Other CPUs waiting on that lock burn CPU cycles spinning, which can noticeably hurt system performance and, on real-time kernels, cause latency spikes.
A situation where two or more CPUs each wait forever for a lock the other is holding, usually caused by acquiring multiple locks in inconsistent order.
Only for single-variable updates. As soon as you need to update more than one related field together consistently, you need a lock.
Lockdep, the kernel’s built-in lock validator, is the standard tool for detecting potential deadlocks and incorrect lock usage patterns.
This lecture is part of EmbeddedPathashala’s free Linux kernel development course, which continues into reader/writer locks, RCU, and completion variables.
Next up: reader/writer locks and an introduction to RCU in the Linux kernel.
Next Lecture Back to Course Index
3 Comments