Chapter 3: Mutexes / Mutex Lock
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 ............
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.
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.
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.
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:
Mutexes and Spinlocks: Mutexes are built using spinlocks. Spinlocks are a simpler locking mechanism used internally by mutexes.
Owner: This represents the process that currently holds (or owns) the lock. Only the owner can release the lock.
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.
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.
Prerequisites for kernel module compilation on Raspberry Pi
$ sudo apt update $ sudo apt install raspberrypi-linux-headers
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");
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
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+
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.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) :
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.
Subscribe to my newsletter
Read articles from Suyog Buradkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by