Skip to main content

Command Palette

Search for a command to run...

Understanding Linux Kernel Mutex Synchronization with Kernel Threads

Updated
โ€ข4 min read
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:

  1. Read value

  2. Increment value

  3. 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.