Understanding Linux Kernel Mutex Synchronization with Kernel Threads

Introduction
Concurrency is everywhere inside the Linux kernel. Multiple execution contexts may attempt to access the same data simultaneously, leading to race conditions and corrupted state.
In this article, I build a simple kernel module that launches two kernel threads and protects a shared global counter using a mutex. Along the way, I explore:
Kernel threads
Shared resources
Critical sections
Race conditions
Mutex synchronization
mutex_lock_interruptible()Thread lifecycle management
The Problem: Shared Data
Consider a global variable shared by multiple kernel threads:
static int shared_resource = 0;
Both threads increment this variable repeatedly.
shared_resource++;
At first glance this looks harmless, but the operation is actually composed of multiple CPU instructions:
Read value
Increment value
Write value back
If two threads perform these steps simultaneously, updates can be lost.
Understanding Race Conditions
Without synchronization:
Thread-1 reads 5
Thread-2 reads 5
Thread-1 writes 6
Thread-2 writes 6
Expected result:
5 โ 6 โ 7
Actual result:
5 โ 6
One increment disappears.
This is known as a race condition.
Enter the Mutex
Linux provides mutexes to guarantee mutual exclusion.
The module defines a mutex as:
static DEFINE_MUTEX(my_shared_mutex);
A mutex ensures that only one thread can enter a critical section at a time.
Critical Section Protection
The shared counter update is wrapped by the mutex:
if (mutex_lock_interruptible(&my_shared_mutex))
return -EINTR;
shared_resource++;
mutex_unlock(&my_shared_mutex);
This guarantees that concurrent updates occur safely.
Why Use mutex_lock_interruptible()
Linux provides two common mutex acquisition APIs:
| API | Interruptible | Behavior |
|---|---|---|
mutex_lock() |
No | Waits indefinitely |
mutex_lock_interruptible() |
Yes | Can be interrupted by signals |
Example:
mutex_lock_interruptible(&my_shared_mutex);
Unlike mutex_lock(), the interruptible version can abort waiting and return an error if a signal arrives.
Return Values
A successful lock acquisition returns:
0
If interrupted before obtaining the lock:
-EINTR
This allows the thread to exit gracefully rather than waiting forever.
Creating Kernel Threads
The module launches two kernel threads.
thread1 = kthread_run(
my_kthread_func,
"Thread-1",
"kthread_one");
thread2 = kthread_run(
my_kthread_func,
"Thread-2",
"kthread_two");
Each thread repeatedly:
Acquires the mutex
Updates the counter
Releases the mutex
Sleeps briefly
Repeats
Why task_struct Matters
Thread handles are stored as:
static struct task_struct *thread1;
static struct task_struct *thread2;
Every Linux task is represented internally by a task_struct.
These handles allow the module to:
Stop threads
Manage thread lifecycle
Track execution state
Graceful Thread Shutdown
The worker loop uses:
while (!kthread_should_stop())
When the module unloads:
kthread_stop(thread1);
kthread_stop(thread2);
The threads detect the stop request and exit cleanly.
This is the recommended Linux kernel thread shutdown pattern.
Sleeping Locks
Mutexes are sleeping locks.
This means they may block and schedule another task while waiting.
Because of this, mutexes are appropriate in:
Kernel threads
Process context
System call paths
They are not suitable for:
Interrupt handlers
SoftIRQs
Atomic contexts
Sleeping is forbidden in those environments.
Mutex Ownership Rules
A mutex has strict ownership semantics.
Correct:
mutex_lock(&lock);
/* critical section */
mutex_unlock(&lock);
The same thread that acquires the lock must release it.
Violating this rule can trigger kernel warnings and undefined behavior.
Expected Kernel Output
After loading the module:
Initializing Mutex Kthread Module
Thread-1: Secured lock. Shared Resource value = 1
Thread-2: Secured lock. Shared Resource value = 2
Thread-1: Secured lock. Shared Resource value = 3
The counter increases sequentially because only one thread owns the mutex at any given moment.
Key Takeaways
Shared kernel data requires synchronization.
Race conditions occur when multiple threads modify data concurrently.
Mutexes provide mutual exclusion.
mutex_lock_interruptible()allows signal-aware waiting.Mutexes are sleeping locks and should not be used in interrupt context.
Kernel threads are managed using
task_struct.kthread_stop()enables clean thread termination.
Understanding mutexes is one of the foundational steps toward mastering Linux kernel synchronization and building reliable concurrent kernel code.
GitHub Repository: ๐ linux_kernel_mutex Explore the complete source code, build files, and module implementation on GitHub.



