Skip to main content

Command Palette

Search for a command to run...

Linux Kernel Mutexes: Safe Synchronization with Kernel Threads

Updated
7 min readView as Markdown
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:

  1. Acquire the mutex.

  2. Increment the shared counter.

  3. Print the updated value.

  4. Sleep briefly.

  5. Release the mutex.

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