Linux Kernel Mutexes: Safe Synchronization with Kernel Threads

Modern Linux kernel development is fundamentally about correctness under concurrency. Whether we're writing a character driver, platform driver, network subsystem, or filesystem code, multiple execution contexts often access the same shared data simultaneously. Without proper synchronization, race conditions can corrupt kernel state and produce unpredictable behavior.
One of the most widely used synchronization primitives in Linux is the mutex. Unlike spinlocks, mutexes are sleeping locks, making them ideal for protecting shared resources in process context where blocking is allowed.
In this article, I'll build a Linux kernel module that demonstrates mutex synchronization using two competing kernel threads while exploring the design principles behind mutexes.
Why Do We Need Mutexes?
Consider two kernel threads incrementing the same shared counter.
Without synchronization, both threads may read the same value before either writes it back, causing one update to be lost.
This classic race condition leads to inconsistent program behavior.
A mutex ensures that only one thread can enter the critical section at a time, preserving data integrity.
Project Overview
The module demonstrates:
Linux kernel threads (
kthread_run())Shared resource protection
Dynamic memory allocation
Static mutex initialization
Dynamic mutex initialization
Critical sections
Safe synchronization
Module initialization and cleanup
The shared driver context contains both the protected data and the mutex.
struct demo_context {
int shared_counter;
struct mutex lock;
};
The counter is accessed by two independent kernel threads, both competing for the same mutex.
Static vs Dynamic Mutex Initialization
Linux provides two ways to initialize a mutex.
Static Initialization
Static mutexes are initialized automatically before the module starts executing.
static DEFINE_MUTEX(global_mutex);
This approach is ideal for global or module-wide locks whose lifetime matches the module.
Advantages
No initialization call required
Simple and efficient
Lifetime equals module lifetime
Dynamic Initialization
When mutexes are embedded inside dynamically allocated objects, they must be initialized explicitly.
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
mutex_init(&ctx->lock);
This is the preferred approach for driver-specific objects allocated at runtime.
Advantages
Works with dynamically allocated structures
Common in device drivers
Flexible for multiple device instances
Creating Kernel Threads
The module launches two worker threads.
thread1 = kthread_run(worker, (void *)1, "mutex_thread1");
thread2 = kthread_run(worker, (void *)2, "mutex_thread2");
Both execute the same worker function and continuously compete for the mutex.
Entering the Critical Section
The critical section begins when the mutex is acquired.
mutex_lock(&ctx->lock);
ctx->shared_counter++;
mutex_unlock(&ctx->lock);
If another thread already owns the mutex, the current thread is automatically put to sleep until the lock becomes available.
Unlike busy waiting, sleeping conserves CPU resources and improves overall system efficiency.
Sleeping Inside a Mutex
One interesting aspect of this example is that the worker intentionally sleeps while holding the mutex.
msleep(1000);
This demonstrates an important property of Linux mutexes:
Sleeping is allowed while holding a mutex.
Sleeping is forbidden while holding a spinlock.
Although sleeping inside a mutex is legal, real production code should keep critical sections as short as possible to minimize lock contention.
Understanding the Worker Thread
Each worker repeatedly performs the following sequence:
Acquire the mutex.
Increment the shared counter.
Print the updated value.
Sleep briefly.
Release the mutex.
Sleep outside the critical section.
This repeated competition clearly demonstrates serialized access to shared data.
Expected Kernel Output
A typical execution looks like this:
===== Mutex Demo Loaded =====
Static mutex locked once
Thread 1 acquired mutex
Thread 1 Counter=1
Thread 1 releasing mutex
Thread 2 acquired mutex
Thread 2 Counter=2
Thread 2 releasing mutex
Notice that the counter always increases sequentially.
There is never simultaneous access to the protected resource.
Important Mutex APIs
| API | Purpose |
|---|---|
DEFINE_MUTEX() |
Static initialization |
mutex_init() |
Dynamic initialization |
mutex_lock() |
Acquire mutex |
mutex_unlock() |
Release mutex |
kthread_run() |
Create kernel thread |
kthread_stop() |
Stop kernel thread |
msleep() |
Sleep in process context |
Essential Mutex Rules
1. Only One Owner
Only one task may own a mutex at any moment.
Any additional thread attempting to acquire it will sleep until it becomes available.
2. Owner Must Unlock
The thread that acquires the mutex must also release it.
Unlocking a mutex from another thread leads to undefined behavior.
3. Recursive Locking Is Not Allowed
Attempting to acquire the same mutex twice from the same thread causes a deadlock.
Avoid recursive locking unless a different synchronization primitive is specifically designed for it.
4. Mutexes May Sleep
Because mutex_lock() may block, mutexes are valid only in process context.
They are commonly used inside:
Kernel threads
System calls
Character device operations
Driver read/write methods
5. Never Use Mutexes in Atomic Context
Mutexes should never be used inside:
Interrupt handlers
SoftIRQs
Tasklets
Timers
Other atomic contexts
Blocking is not permitted in these execution contexts.
6. Keep Critical Sections Small
The protected region should contain only the minimum required operations.
Smaller critical sections improve scalability, reduce contention, and increase overall system responsiveness.
7. Follow Consistent Lock Ordering
When multiple locks are required, always acquire them in the same order throughout the codebase.
Consistent ordering is one of the simplest and most effective techniques for preventing deadlocks.
Module Lifecycle
Module Initialization
During module loading, the following sequence occurs:
Allocate driver context
Initialize dynamic mutex
Demonstrate static mutex
Launch two kernel threads
Module Cleanup
During module removal:
Stop both kernel threads
Free allocated memory
Exit cleanly
Proper cleanup ensures that no kernel resources are leaked.
Where Are Mutexes Used?
Mutexes appear throughout the Linux kernel and are commonly used in:
Character device drivers
Platform drivers
USB drivers
PCI drivers
I2C drivers
SPI drivers
Filesystems
Network drivers
Virtual device drivers
Embedded Linux systems
Typical protected resources include:
Device state
Hardware registers
Linked lists
Queues
Buffers
Configuration structures
Statistics
Driver context objects
Final Thoughts
Mutexes are among the most important synchronization primitives in Linux kernel development. Understanding when to use them—and equally important, when not to use them—is fundamental for writing reliable kernel modules and production-quality device drivers.
This demonstration illustrates the complete lifecycle of mutex usage, from initialization and lock acquisition to protecting shared resources and performing clean module shutdown. Once these fundamentals are mastered, I can confidently move on to advanced synchronization mechanisms such as semaphores, completions, wait queues, spinlocks, reader-writer locks, RCU, and lock-free programming techniques.
GitHub Repository: 👉 linux_kernel_mutex3 Explore the complete source code, build files, and module implementation on GitHub.



