Skip to content

aj333git/linux_kernel_mutex4

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Linux Kernel Mutex Driver Demonstration

A Linux kernel module demonstrating how mutexes protect shared writable data in a device driver.

Unlike trivial counter examples, this module models a miniature driver by encapsulating device state inside a Driver Context. Three kernel threads concurrently access the same shared state, allowing you to observe critical sections, mutual exclusion, and lock contention exactly as they appear in real Linux drivers.

The objective of this project is not to teach multithreading itself, but to demonstrate how Linux mutexes synchronize concurrent access to shared driver resources.


Learning Objectives

After completing this project, you should understand:

  • Why device drivers require synchronization
  • What a Driver Context is
  • What Shared Writable Data means
  • Why race conditions occur
  • What a Critical Section is
  • How a mutex guarantees mutual exclusion
  • Why even reading shared mutable data often requires locking
  • How multiple kernel threads safely share one driver context

Program Architecture

The entire module revolves around a single shared Driver Context.

                    Driver Context
          +-------------------------------+
          | struct mutex lock             |
          | tx_packets                    |
          | rx_packets                    |
          | device_enabled                |
          | device_name                   |
          +---------------+---------------+
                          |
          -----------------------------------------
          |                  |                    |
          |                  |                    |
          ▼                  ▼                    ▼
      TX Thread          RX Thread         Stats Thread
          |                  |                    |
          +------------------+--------------------+
                             |
                      Linux Mutex
                             |
                     Critical Sections
                             |
                  Shared Writable Data

Every thread operates on the same driver context.

The mutex guarantees that only one thread at a time can access the shared driver state.


Overall Design Philosophy

This module intentionally follows the same architectural pattern used by production Linux drivers.

Instead of creating several unrelated global variables, all driver state is grouped into a single structure.

             Driver
                │
                ▼
        Driver Context (ctx)
                │
     -------------------------
     │          │            │
     ▼          ▼            ▼
 TX Thread   RX Thread   Stats Thread

This approach provides:

  • better encapsulation
  • easier maintenance
  • cleaner synchronization
  • scalable driver design

As drivers become larger, storing all state inside a context structure becomes essential.


Driver Context

What is a Driver Context?

A Driver Context is a structure that stores all runtime information belonging to a driver.

Think of it as the driver's private memory.

Instead of scattering variables throughout the module, everything is grouped together.

+----------------------------------+
| Driver Context                   |
|----------------------------------|
| mutex lock                       |
| tx_packets                       |
| rx_packets                       |
| device_enabled                   |
| device_name                      |
+----------------------------------+

The context represents the complete state of the simulated device.

Every worker thread receives access to exactly the same object.

               ctx
                ▲
      ┌─────────┼─────────┐
      │         │         │
      │         │         │
     TX        RX      Stats

This single object becomes the central resource of the driver.


Why Use a Driver Context?

Without a context structure, a driver quickly becomes difficult to maintain.

Poor design:

global_tx_packets
global_rx_packets
global_enabled
global_mutex
global_name

Better design:

struct driver_context
{
    ...
};

Advantages include:

  • Improved organization
  • Easier synchronization
  • Better scalability
  • Cleaner initialization
  • Simpler cleanup
  • Common production practice

Nearly every modern Linux driver maintains some form of private driver context.


Shared Writable Data

Inside the Driver Context are several variables whose values change while the driver is running.

These variables are called Shared Writable Data.

Examples include:

  • tx_packets
  • rx_packets
  • device_enabled

These variables are:

  • shared by multiple threads
  • modified during execution
  • accessed repeatedly
               Driver Context
                     │
                     ▼
      +-------------------------------+
      | tx_packets                    |
      | rx_packets                    |
      | device_enabled                |
      +-------------------------------+
               ▲      ▲      ▲
               │      │      │
              TX     RX    Stats

Every thread can potentially access the same memory.

This immediately introduces the possibility of concurrent access.


Why "Shared"?

Because multiple execution contexts reference the same object.

        TX
         │
         │
         ▼
      Driver Context
         ▲
         │
        RX
         ▲
         │
      Stats

There is only one copy of the data.

All threads operate on that single copy.


Why "Writable"?

The values change during execution.

Example:

tx_packets

0

↓

1

↓

2

↓

3

↓

4

Similarly,

rx_packets

0

↓

1

↓

2

↓

3

Since these values continuously change, they require synchronization.


Why Shared Writable Data Needs Protection

Whenever multiple threads modify the same variable simultaneously, the result becomes unpredictable.

Imagine two CPUs incrementing the same counter.

Current value

tx_packets = 5

CPU 1

Read 5

↓

Increment

↓

Write 6

CPU 2

Read 5

↓

Increment

↓

Write 6

Expected result

7

Actual result

6

One increment disappears.

This phenomenon is called a Race Condition.


Critical Section

A Critical Section is the portion of code that accesses shared writable data.

Only one thread should execute that region at a time.

mutex_lock()

    Access Shared Data

mutex_unlock()

Everything between the lock and unlock belongs to the critical section.


Visualizing a Critical Section

Acquire Mutex
      │
      ▼

+-----------------------------+
| Update Shared Driver State  |
| Read Shared Driver State    |
| Modify Shared Driver State  |
+-----------------------------+

      │
      ▼

Release Mutex

The mutex forms a protective boundary around the shared resource.


Why Reads Also Need Protection

A common misconception is that only writes require locking.

Consider the statistics thread.

It only prints information.

However, while it is reading:

tx_packets

another thread may be updating that value.

The result could be inconsistent or partially updated state.

Therefore production drivers commonly protect both reads and writes of shared mutable data.

             Shared Data

             Read
               ▲
               │
               │
Write ◄────────┼────────► Read
               │
               ▼
             Update

        All protected
        by one mutex

Relationship Between the Core Concepts

The module revolves around four ideas.

             Driver Context
                    │
                    ▼
        +-------------------------+
        | Shared Writable Data    |
        +-------------------------+
                    ▲
                    │
            Protected By
                    │
               Linux Mutex
                    │
                    ▼
           Critical Sections
                    ▲
                    │
        Accessed By Multiple Threads

Everything else in the program is built on top of these concepts.


Mental Model

Whenever reading Linux driver code, remember the following sequence.

Driver Context

        │

        ▼

Shared Writable Data

        │

        ▼

Critical Sections

        │

        ▼

Linux Mutex

        │

        ▼

Safe Concurrent Access

If you understand this flow, you understand the architectural foundation of synchronization in Linux device drivers.


Key Takeaways

  • A Driver Context stores the complete runtime state of a driver.
  • Shared Writable Data can be accessed by multiple execution contexts.
  • Concurrent access without synchronization leads to race conditions.
  • A Critical Section is the code that accesses shared mutable state.
  • A Linux mutex ensures that only one thread executes a critical section at any given time.
  • Grouping shared state inside a Driver Context and protecting it with a mutex is the standard synchronization pattern used throughout Linux kernel drivers.

Mutex APIs

The Linux kernel provides a family of mutex APIs that allow sleeping synchronization between execution contexts. Unlike spinlocks, mutexes are designed for longer critical sections where the owner may voluntarily sleep.

This driver demonstrates the complete mutex life cycle.

mutex_init()

        │

        ▼

mutex_lock()
or
mutex_lock_interruptible()

        │

        ▼

Critical Section

        │

        ▼

mutex_unlock()

        │

        ▼

mutex_destroy()

mutex_init()

A mutex must be initialized before any thread attempts to acquire it.

Driver Context

+----------------------+
| struct mutex lock    |
+----------------------+

        │

        ▼

mutex_init(&ctx->lock);

Initialization places the mutex into an unlocked state, making it ready for concurrent use.

Purpose

  • Initializes the mutex object.
  • Prepares the synchronization primitive.
  • Typically performed during driver initialization.

mutex_lock()

mutex_lock() acquires exclusive ownership of the mutex.

If another thread already owns the mutex, the calling thread sleeps until the mutex becomes available.

Thread

      │

      ▼

mutex_lock()

      │

      ▼

Critical Section

      │

      ▼

mutex_unlock()

Characteristics:

  • Sleeping lock
  • Blocks until available
  • Cannot be used in interrupt context
  • Suitable for long critical sections

mutex_lock_interruptible()

This function behaves similarly to mutex_lock() but allows the sleeping task to be interrupted by a signal.

mutex_lock_interruptible()

        │

        ├──────── Success
        │
        ▼

Critical Section

        │

        ▼

mutex_unlock()


OR


Interrupted

        │

        ▼

Return -EINTR

This API is useful when a thread should remain responsive while waiting for a mutex.

Advantages:

  • Interruptible wait
  • Better responsiveness
  • Common in blocking kernel paths

mutex_unlock()

Releases ownership of the mutex.

Acquire

↓

Critical Section

↓

Release

Once released, another waiting thread may immediately acquire the mutex.

Owner

TX Thread

↓

mutex_unlock()

↓

RX Thread acquires mutex

mutex_destroy()

Destroys the mutex object.

Although many kernel mutexes require no explicit destruction, calling mutex_destroy() is considered good practice for dynamically allocated mutexes and helps debug kernels detect misuse.

Module Exit

        │

        ▼

Stop Threads

        │

        ▼

mutex_destroy()

        │

        ▼

Free Driver Context

TX, RX and Stats Threads

The module launches three independent kernel threads.

                 Driver Context

                        ▲
        ┌───────────────┼───────────────┐
        │               │               │
        ▼               ▼               ▼

   TX Thread      RX Thread      Stats Thread

Each thread performs a different task while sharing the same driver state.


TX Thread

The TX thread simulates packet transmission.

Responsibilities:

  • Acquire mutex
  • Update transmit counter
  • Simulate transmission delay
  • Release mutex
TX Thread

Acquire Mutex

↓

Increment TX Counter

↓

Simulate Work

↓

Release Mutex

Because the TX counter belongs to shared driver state, every update occurs inside a critical section.


RX Thread

The RX thread simulates packet reception.

Instead of using mutex_lock(), it demonstrates the interruptible version.

RX Thread

Acquire Mutex
(Interruptible)

↓

Increment RX Counter

↓

Simulate Work

↓

Release Mutex

This highlights that Linux offers multiple ways to wait for a mutex depending on driver requirements.


Statistics Thread

The Statistics thread periodically reads the driver state.

Unlike the TX and RX threads, it performs no updates.

Instead, it reads:

  • Device name
  • Device status
  • TX packets
  • RX packets
Stats Thread

Acquire Mutex

↓

Read Shared State

↓

Display Statistics

↓

Release Mutex

Although this thread only performs reads, it still acquires the mutex.


Why Lock During Reads?

Many beginners assume that only writers require synchronization.

Consider the following situation.

TX Thread

tx_packets++

             │
             │
             ▼

Stats Thread

Read tx_packets

If the statistics thread reads the value while another thread is modifying it, the resulting state may be inconsistent.

For this reason, Linux drivers commonly protect both reads and writes of shared mutable state.


Thread Interaction

All three threads operate independently.

             Driver Context

                  ▲

       ┌──────────┼──────────┐
       │          │          │

      TX         RX       Stats

       │          │          │

       └──────────┼──────────┘

                  ▼

             Same Mutex

Only one thread may enter the critical section at any instant.


Race Conditions

A race condition occurs whenever multiple execution contexts access shared writable data without proper synchronization.

Imagine the TX counter currently equals:

tx_packets = 5

Both CPUs execute simultaneously.

CPU 1

Read 5

↓

Increment

↓

Write 6
CPU 2

Read 5

↓

Increment

↓

Write 6

Expected result:

7

Actual result:

6

One increment is lost.

The outcome depends entirely on execution timing.

This makes race conditions:

  • difficult to reproduce
  • difficult to debug
  • highly nondeterministic

How the Mutex Prevents the Race

Instead of allowing simultaneous access, the mutex serializes execution.

CPU 1

Acquire Mutex

↓

Increment Counter

↓

Release Mutex

Only after CPU 1 finishes can another thread proceed.

CPU 2

Waiting...

↓

Acquire Mutex

↓

Increment Counter

↓

Release Mutex

Since updates occur sequentially, every increment is preserved.

5

↓

6

↓

7

No update is lost.


Synchronization Flow

The synchronization model implemented by this driver can be summarized as follows.

Kernel Thread

        │

        ▼

Acquire Mutex

        │

        ▼

Access Shared Driver Context

        │

        ▼

Update / Read Shared Data

        │

        ▼

Release Mutex

Regardless of which thread executes, every access follows exactly the same synchronization pattern.


Mental Model

The worker threads themselves are not the primary focus of this project.

They simply generate concurrent access.

The real objective is to demonstrate how one mutex protects one shared driver context.

        Three Kernel Threads

      TX      RX      Stats

          │     │     │

          └─────┼─────┘

                ▼

        Linux Kernel Mutex

                ▼

        Driver Context (ctx)

                ▼

     Shared Writable Driver State

Whenever multiple execution contexts share mutable state, Linux kernel drivers protect that state using synchronization primitives such as mutexes.


Key Takeaways

  • mutex_init() prepares a mutex for use.
  • mutex_lock() acquires exclusive ownership and sleeps if necessary.
  • mutex_lock_interruptible() allows the wait to be interrupted by signals.
  • mutex_unlock() releases ownership so another thread may proceed.
  • mutex_destroy() cleans up dynamically initialized mutexes.
  • TX, RX and Statistics threads all operate on the same Driver Context.
  • The worker threads exist solely to generate concurrent access.
  • A race condition occurs when shared writable data is accessed without synchronization.
  • A mutex serializes access to shared state, guaranteeing mutual exclusion and preserving data consistency.

Lock Contention

One of the primary goals of this demonstration is to visualize lock contention.

Lock contention occurs when multiple execution contexts attempt to acquire the same mutex simultaneously, but only one thread is allowed to own it.

Since a mutex provides mutual exclusion, every other thread must wait until the current owner releases the lock.


Why Does Contention Occur?

In this driver, all three worker threads operate on the same Driver Context.

                    Driver Context
             +-------------------------+
             | struct mutex lock       |
             | tx_packets              |
             | rx_packets              |
             | device_enabled          |
             | device_name             |
             +-----------+-------------+
                         |
        ---------------------------------------
        |                 |                  |
        ▼                 ▼                  ▼
     TX Thread        RX Thread       Stats Thread

Because all three threads access the same shared mutable state, they all require the same mutex.

Only one thread can own the mutex at any moment.


Visualizing Lock Contention

Suppose the TX thread acquires the mutex first.

                ctx->lock

                 Owner
                   │
                  TX
                   │
        -------------------------
        Waiting : RX
        Waiting : Stats

The TX thread enters the critical section while the RX and Statistics threads remain blocked.

This waiting state is known as lock contention.


Timeline of Lock Contention

Time ------------------------------------------------------------>

TX Thread

Acquire Lock
      │
      ▼
Update TX Counter
      │
      ▼
Sleep (1 second)
      │
      ▼
Release Lock

---------------------------------------------------------------

RX Thread

Attempt Lock
      │
      ▼
Waiting...
Waiting...
Waiting...
      │
      ▼
Acquire Lock
      │
      ▼
Release Lock

---------------------------------------------------------------

Stats Thread

Attempt Lock
      │
      ▼
Waiting...
Waiting...
Waiting...
Waiting...
      │
      ▼
Acquire Lock
      │
      ▼
Release Lock

Although all three threads execute concurrently, only one thread executes the critical section at a time.


Is Lock Contention Bad?

Not necessarily.

Some contention is completely normal.

Whenever multiple execution contexts protect the same shared resource, some waiting is expected.

The problem occurs when contention becomes excessive.

High contention means:

  • Threads spend more time waiting.
  • CPU resources are underutilized.
  • Overall throughput decreases.
  • Concurrency is reduced.

Light vs Heavy Contention

Light Contention

Thread A

Lock
Work
Unlock

---------------------

Thread B

Wait
Lock
Unlock

The waiting time is extremely small.

This is usually acceptable.


Heavy Contention

Thread A

Lock

Long Operation

Sleep

Unlock

------------------------

Thread B

Waiting...

------------------------

Thread C

Waiting...

------------------------

Thread D

Waiting...

Many threads remain blocked while one thread owns the mutex.

This significantly reduces concurrency.


Sleeping While Holding a Mutex

One of the most important characteristics of a Linux mutex is that the owner is allowed to sleep while holding it.

Unlike spinlocks, mutexes are specifically designed for sleeping synchronization.

The TX worker intentionally demonstrates this behavior.

Acquire Mutex

↓

Update Shared Data

↓

Sleep

↓

Release Mutex

During the sleep period, the TX thread still owns the mutex.

No other thread can enter the critical section.


Why Is Sleeping Allowed?

A mutex is a sleeping lock.

When a thread sleeps while holding the mutex:

  • The scheduler suspends the thread.
  • Waiting threads are also put to sleep.
  • No CPU cycles are wasted spinning.

This makes mutexes ideal for longer operations.


Mutex vs Spinlock

Feature Mutex Spinlock
Sleeping while holding lock Yes No
Waiting thread sleeps Yes No
Suitable for long operations Yes No
Suitable for interrupt context No Yes

This distinction is fundamental in Linux kernel synchronization.


Why Does This Demo Sleep?

The purpose is educational.

Without the sleep, the mutex would only be held for a few microseconds.

Acquire Lock

↓

Increment Counter

↓

Release Lock

The RX and Statistics threads would almost never be observed waiting.

Contention would be nearly invisible.


Instead, the demo intentionally performs:

Acquire Lock

↓

Increment Counter

↓

Sleep (1 second)

↓

Release Lock

The mutex remains owned for one full second.

During that period, the other threads attempt to acquire the mutex and become blocked.

This makes lock contention easy to observe.


Important Clarification

The sleep itself does not create contention.

Contention occurs because:

  • one thread owns the mutex
  • other threads attempt to acquire the same mutex

The longer the owner keeps the mutex, the greater the probability that other threads will be forced to wait.


Production Driver vs Demonstration Driver

This project intentionally prioritizes learning over performance.


Demonstration Driver

The TX thread sleeps while holding the mutex.

Advantages:

  • Easy to visualize synchronization.
  • Easy to observe waiting threads.
  • Easy to understand mutex ownership.
  • Excellent teaching example.

Disadvantages:

  • Long critical section.
  • High lock contention.
  • Poor scalability.

Production Driver

A production-quality driver follows a different philosophy.

The mutex should protect only the shared resource.

Everything else should execute outside the critical section.

Conceptually:

Acquire Mutex

↓

Update Shared Data

↓

Release Mutex

↓

Sleep

↓

Continue Processing

This minimizes waiting time.

Other threads can immediately acquire the mutex after the shared data has been updated.


General Kernel Programming Rule

A mutex should protect only the code that requires exclusive access.

Keep critical sections:

  • small
  • efficient
  • predictable

Long critical sections increase contention and reduce concurrency.


Why Even Readers Acquire the Mutex

The Statistics thread never modifies the counters.

It only reads them.

Even so, it still acquires the mutex.

Why?

Because another thread may update the shared data while the reader is accessing it.

Without synchronization, the reader could observe inconsistent state.

Production Linux drivers therefore commonly protect both readers and writers whenever they access shared mutable data.


Complete Synchronization Model

The synchronization strategy of this module can be summarized as follows.

                 Driver Context
        +-----------------------------+
        | Shared Writable Data        |
        +-------------+---------------+
                      ▲
                      │
                Protected By
                      │
                 Linux Mutex
                      ▲
                      │
      ----------------------------------------
      |                 |                    |
      ▼                 ▼                    ▼
 TX Thread         RX Thread          Stats Thread

Every thread follows exactly the same synchronization model.

Acquire mutex.

Access shared driver state.

Release mutex.


Interview Takeaways

This project covers several concepts frequently discussed during Linux kernel and device driver interviews.


What does a mutex protect?

A mutex protects shared writable data from concurrent access.


What is a Driver Context?

A Driver Context stores the complete runtime state of a device driver.


What is a Critical Section?

The code that accesses shared mutable data.

Only one thread should execute it at a time.


What is Lock Contention?

Multiple threads attempting to acquire the same mutex simultaneously.


Can a mutex sleep?

Yes.

Mutexes are sleeping locks.


Can a spinlock sleep?

No.

Sleeping while holding a spinlock is a serious kernel programming error.


Why use mutex_lock_interruptible()?

It allows a sleeping task waiting for the mutex to be interrupted by a signal.


Why keep critical sections short?

To reduce lock contention and improve concurrency.


Why lock reads?

Because shared mutable data may change while another thread is reading it.


Why use a Driver Context?

It groups all driver state into a single structure, making synchronization simpler and improving maintainability.


Key Takeaways

  • Lock contention occurs when multiple threads compete for the same mutex.
  • Only one thread can execute a critical section protected by a mutex.
  • Linux mutexes are sleeping locks.
  • Sleeping while holding a mutex is legal but increases contention.
  • This module intentionally sleeps while holding the mutex to make contention easy to observe.
  • Production drivers generally keep critical sections as short as possible.
  • Both reads and writes of shared mutable data should be synchronized.
  • The worker threads are simply a mechanism to create concurrent access.
  • The true purpose of this project is to demonstrate how a single Linux mutex protects a shared Driver Context, ensuring safe concurrent access to shared writable data.

About

process and thread

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors