Chapter 3: Mutexes / Mutex Lock

Suyog BuradkarSuyog Buradkar
5 min read

In the Linux kernel, a mutex (short for mutual exclusion) is a synchronization primitive used to protect shared resources from concurrent access by multiple tasks (threads or processes).

Ok lets understand Mutex in following manner ............

  1. Locking and Unlocking:

    • When a task needs to access a shared resource, it locks the mutex.

    • If the mutex is already locked by another task, the current task will go to sleep instead of continuously checking (spinning) for the mutex to become available. This is more efficient than spinlocks, which keep the CPU busy while waiting.

  2. Sleeping and Waking:

    • While a task is waiting for the mutex, it is put into a sleep state by the kernel scheduler, freeing up the CPU to perform other tasks.

    • Once the mutex is unlocked by the task that was holding it, the kernel scheduler wakes up one of the waiting tasks to lock the mutex and proceed with its execution.

  3. Task Ownership:

    • A mutex is owned by the task that locks it, meaning that only the task that locked the mutex can unlock it. This ensures proper synchronization and prevents potential errors caused by improper unlocking.
  4. Context Switching:

    • By allowing tasks to sleep while waiting for the mutex, context switching is utilized to switch between tasks efficiently. This reduces CPU wastage and improves system performance.

Mutex in the Linux kernel is a task-based locking mechanism that allows tasks to sleep while waiting for the lock, ensuring efficient CPU usage and proper synchronization between tasks.

Mutex includes a wait queue to manage tasks that need to sleep while waiting for the mutex to become available.
Here's a breakdown of the struct mutex and its components:

struct mutex {
    atomic_long_t owner;         // Keeps track of the owner of the mutex
    spinlock_t wait_lock;        // Spinlock to protect access to the wait queue
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
    struct optimistic_spin_queue osq;  // Optional: Used for optimizing spinning
#endif
    struct list_head wait_list;  // List of tasks waiting for the mutex
    [...]
};

Here's a breakdown of the key components related to mutexes:

  1. Mutexes and Spinlocks: Mutexes are built using spinlocks. Spinlocks are a simpler locking mechanism used internally by mutexes.

  2. Owner: This represents the process that currently holds (or owns) the lock. Only the owner can release the lock.

  3. Wait List: This is a list where processes that want to acquire the mutex but cannot (because it's already locked) are put to sleep.

  4. Wait Lock: This is a spinlock used to protect the wait list. It ensures that while processes are being added to or removed from the wait list, the list remains consistent and correct, especially in systems with multiple processors.

Now let's understand how mutexes work.

  1. Prerequisites for kernel module compilation on Raspberry Pi

       $ sudo apt update
       $ sudo apt install raspberrypi-linux-headers
    
  2. Sample code (filename: test_mutex.c)

     #include <linux/module.h>
     #include <linux/kernel.h>
     #include <linux/init.h>
     #include <linux/mutex.h>
     #include <linux/slab.h>
     #include <linux/uaccess.h>
    
     static struct mutex my_mutex;
     static char *buffer = NULL;
     static size_t buffer_size = 1024; // Size of allocated buffer
    
     static int test_function(void)
     {
         int ret = 0;
    
         // Allocate memory using kmalloc
         buffer = kmalloc(buffer_size, GFP_KERNEL);
         if (!buffer) {
             pr_info("Failed to allocate memory\n");
             return -ENOMEM;
         }
    
         // Lock mutex
         mutex_lock(&my_mutex);
    
         pr_info("Buffer allocated at: %px\n", buffer);
    
         // Unlock mutex
         mutex_unlock(&my_mutex);
    
         // Free allocated memory
         kfree(buffer);
    
         return ret;
     }
    
     static int __init mutex_module_init(void)
     {
         pr_info("Initializing my module\n");
    
         // Initialize mutex
         mutex_init(&my_mutex);
    
         // Call example function
         test_function();
    
         return 0;
     }
    
     static void __exit mutex_module_exit(void)
     {
         pr_info("Exiting my module\n");
    
         // Clean up mutex
         mutex_destroy(&my_mutex);
     }
    
     module_init(mutex_module_init);
     module_exit(mutex_module_exit);
    
     MODULE_LICENSE("GPL");
     MODULE_AUTHOR("Suyog B <suyogburadkar@gmail.com>");
     MODULE_DESCRIPTION("Kernel module demonstrates mutex locking");
    
  3. Makefile

     pi@SuyogB:~/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock $ cat Makefile
     obj-m += test_mutex.o
    
     all:
             make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
     clean:
             make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    
  4. Kernel module building

     pi@SuyogB:~/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock $ make
     make -C /lib/modules/6.1.21-v8+/build M=/home/pi/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock modules
     make[1]: Entering directory '/usr/src/linux-headers-6.1.21-v8+'
       CC [M]  /home/pi/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock/test_mutex.o
       MODPOST /home/pi/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock/Module.symvers
       CC [M]  /home/pi/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock/test_mutex.mod.o
       LD [M]  /home/pi/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock/test_mutex.ko
     make[1]: Leaving directory '/usr/src/linux-headers-6.1.21-v8+
    
  5. Insert kernel object and check the results

     pi@SuyogB:~/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock $ sudo insmod ./test_mutex.ko
     pi@SuyogB:~/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock $ sudo dmesg | tail
     [ 5354.699880] test_mutex: loading out-of-tree module taints kernel.
     [ 5354.700491] Initializing my module
     [ 5354.700505] Buffer allocated at: ffffff8006c9d800
    
     pi@SuyogB:~ $ sudo rmmod test_mutex
     pi@SuyogB:~ $ dmesg | tail
     [ 5354.699880] test_mutex: loading out-of-tree module taints kernel.
     [ 5354.700491] Initializing my module
     [ 5354.700505] Buffer allocated at: ffffff8006c9d800
     [ 5879.019744] Exiting my module
    

    In the kernel module, a mutex was used to synchronize access to shared resources and ensure thread safety. The module demonstrated this by locking the mutex before performing memory allocation with kmalloc, and unlocking it afterward. This locking mechanism prevents concurrent processes from interfering with each other while accessing or modifying shared data, thereby maintaining data consistency and avoiding race conditions.

  6. Cleaning the module

     pi@SuyogB:~/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock $ make clean
     make -C /lib/modules/6.1.21-v8+/build M=/home/pi/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock clean
     make[1]: Entering directory '/usr/src/linux-headers-6.1.21-v8+'
       CLEAN   /home/pi/linux-kernel-development-for-embedded-systems/Chapter_3_Mutex_Lock/Module.symvers
     make[1]: Leaving directory '/usr/src/linux-headers-6.1.21-v8+'
    

    Github Repo (Chapter 3 Mutex Locks) :

    Chapter 3 Link

    Mutexes address some limitations of spinlocks, such as the problem of wasting CPU cycles while waiting. Unlike spinlocks, mutexes have a wait list for tasks that need to wait for the lock to be released.

    To handle situations where waiting is not desirable, a mechanism called trylock is used, which allows a task to attempt to acquire the lock without sleeping, and we'll explore this in the next chapter.

0
Subscribe to my newsletter

Read articles from Suyog Buradkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Suyog Buradkar
Suyog Buradkar