This article is a continuation of the Series on Linux Device Drivers and carries the discussion on Linux device drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand.
In this article, we are going to focus on Mutex in Linux Kernel. Here we will learn how to effectively use Mutex in Linux device drivers with practical examples and step-by-step implementation. We will Understand the importance of synchronization and avoid race conditions in your kernel-level programming.
You can also read spinlock, read-write spinlock, Procfs, Workqueue, Completion, Softirq, and threaded IRQ in the Linux device driver.
Prerequisites
In the example section, I have used Kthread to explain Mutex. If you don’t know what is Kthread and How to use it, then I would recommend you check the below article first.
Introduction
Before getting to know about Mutex, let’s take an analogy first.
Let us assume there are four people and we have a car designed to accommodate only one person at any instance of time. But, if more than one person tries to enter the car, a bomb will set off an explosion! (Quite a fancy car manufactured by EmbeTronicX!! lol ).
Now that the four doors of the car are open. So, there are possible chances that more than one person can enter at the same time. If that happens, then the car may blast.
Now how we can solve this issue? Yes, correct. We can provide a key for the car. So, the person who wants to enter the car must have access to the key. If they don’t have keys, they have to wait until that key is available.
Example Problems
Let’s correlate the analogy above to what happens in our software. Let’s explore situations like these through examples.
- You have one SPI connection. What if one thread wants to write something into that SPI device and another thread wants to read from that SPI device at the same time?
- You have one LED display. What if one thread is writing data at a different position of Display and another thread is writing different data at a different position of Display at the same time?
- You have one Linked List. What if one thread wants to insert something into the list and another one wants to delete something on the same Linked List at the same time?
In all the scenarios above, the problem encountered is the same. At any given point two threads are accessing a single resource. Now we will relate the above scenarios to our car example.
- In the SPI example, CAR = SPI, Person = Threads, Blast = Software Crash/Software may get the wrong data.
- In the LED display example, CAR = LED Display, Person = Threads, and Blast = Display will show some unwanted junk.
- In the Linked List example, CAR = Linked List, Person = Threads, Blast = Software Crash/Software may get the wrong data.
The cases above are termed Race Conditions.
Race Condition
A race condition occurs when two or more threads can access shared data and they try to change it at the same time. Because the thread scheduling algorithm can swap between threads at any time, we don’t know the order in which the threads will attempt to access the shared data.
Therefore, the result of the change in data is dependent on the thread scheduling algorithm, i.e. both threads are “racing” to access/change the data.
To avoid race conditions, we have many ways like Semaphore, Spinlock, and Mutex. In this tutorial, we will concentrate on Mutex.
Mutex
A mutex is a mutual exclusion lock. Only one thread can hold the lock.
A mutex can be used to prevent the simultaneous execution of a block of code by multiple threads that are running in single or multiple processes.
Mutex is used as a synchronization primitive in situations where a resource has to be shared by multiple threads simultaneously.
A mutex has ownership. The thread that locks a Mutex must also unlock it.
So whenever you are accessing a shared resource that time first we lock the mutex and then access the shared resource. When we are finished with that shared resource then we unlock the Mutex.
I hope you got some idea about Mutex. Now, let us look at Mutex in the Linux Kernel.
Mutex in Linux Kernel
Today most major operating systems employ multitasking. Multitasking is where multiple threads can execute in parallel, thereby, utilizing the CPU in an optimum way. Even though, multitasking is useful, if not implemented cautiously can lead to concurrency issues (Race condition), which can be very difficult to handle.
The actual mutex type (minus debugging fields) is quite simple:
struct mutex { atomic_t count; spinlock_t wait_lock; struct list_head wait_list; };
We will be using this structure for Mutex in the Linux kernel. Refer to Linux/include/linux/mutex.h
Initializing Mutex
We can initialize Mutex in two ways:
- Static Method
- Dynamic Method
1. Static Method:
This method will be useful while using global Mutex. This macro is defined below.
DEFINE_MUTEX(name)
This call defines and initializes a mutex. Refer to Linux/include/linux/mutex.h
2. Dynamic Method:
This method will be useful for per-object mutexes when the mutex is just a field in a heap-allocated object. This macro is defined below.
mutex_init(struct mutex *lock);
Argument:
struct mutex *lock
– the mutex to be initialized.
This call initializes already allocated mutex. Initialize the mutex to the unlocked state.
It is not allowed to initialize an already locked mutex.
Example:
struct mutex etx_mutex; mutex_init(&etx_mutex);
Mutex Lock
Once a mutex has been initialized, it can be locked by any one of them explained below.
mutex_lock
This is used to lock/acquire the mutex exclusively for the current task. If the mutex is not available, the current task will sleep until it acquires the Mutex.
The mutex must, later on, be released by the same task that acquired it. Recursive locking is not allowed. The task may not exit without first unlocking the mutex.
Also, kernel memory where the mutex resides must not be freed with the mutex still locked. The mutex must first be initialized (or statically defined) before it can be locked. memset
-ing the mutex to 0 is not allowed.
void mutex_lock(struct mutex *lock);
Argument:
struct mutex *lock
– the mutex to be acquired
mutex_lock_interruptible
Locks the mutex like mutex_lock
, and returns 0 if the mutex has been acquired or sleeps until the mutex becomes available. If a signal arrives while waiting for the lock then this function returns -EINTR
.
int mutex_lock_interruptible(struct mutex *lock);
Argument:
struct mutex *lock
– the mutex to be acquired
mutex_trylock
This will try to acquire the mutex, without waiting (will attempt to obtain the lock, but will not sleep). Returns 1 if the mutex has been acquired successfully, and 0 on contention.
int mutex_trylock(struct mutex *lock);
Argument:
struct mutex *lock
– the mutex to be acquired
This function must not be used in an interrupt context. The mutex must be released by the same task that acquired it.
Mutex Unlock
This is used to unlock/release a mutex that has been locked by a task previously.
This function must not be used in an interrupt context. Unlocking of a not locked mutex is not allowed.
void mutex_unlock(struct mutex *lock);
Argument:
struct mutex *lock
– the mutex to be released
Mutex Status
This function is used to check whether mutex has been locked or not.
int mutex_is_locked(struct mutex *lock);
Argument:
struct mutex *lock
– the mutex to check the status.
Returns 1 if the mutex is locked, 0 if unlocked.
Mutex in Linux kernel – Example Programming
This code snippet explains how to create two threads that access a global variable (etx_gloabl_variable
). So before accessing the variable, it should lock the mutex. After that, it will release the mutex.
Driver Source Code
[Get the source code from GitHub]
/***************************************************************************//** * \file driver.c * * \details Simple Linux device driver (mutex) * * \author EmbeTronicX * * \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+ * *******************************************************************************/ #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include<linux/slab.h> //kmalloc() #include<linux/uaccess.h> //copy_to/from_user() #include <linux/kthread.h> //kernel threads #include <linux/sched.h> //task_struct #include <linux/delay.h> #include <linux/mutex.h> #include <linux/err.h> struct mutex etx_mutex; unsigned long etx_global_variable = 0; dev_t dev = 0; static struct class *dev_class; static struct cdev etx_cdev; static int __init etx_driver_init(void); static void __exit etx_driver_exit(void); static struct task_struct *etx_thread1; static struct task_struct *etx_thread2; /*************** Driver functions **********************/ static int etx_open(struct inode *inode, struct file *file); static int etx_release(struct inode *inode, struct file *file); static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off); static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off); /******************************************************/ int thread_function1(void *pv); int thread_function2(void *pv); /* ** Thread function 1 */ int thread_function1(void *pv) { while(!kthread_should_stop()) { mutex_lock(&etx_mutex); etx_global_variable++; pr_info("In EmbeTronicX Thread Function1 %lu\n", etx_global_variable); mutex_unlock(&etx_mutex); msleep(1000); } return 0; } /* ** Thread function 2 */ int thread_function2(void *pv) { while(!kthread_should_stop()) { mutex_lock(&etx_mutex); etx_global_variable++; pr_info("In EmbeTronicX Thread Function2 %lu\n", etx_global_variable); mutex_unlock(&etx_mutex); msleep(1000); } return 0; } //File operation structure static struct file_operations fops = { .owner = THIS_MODULE, .read = etx_read, .write = etx_write, .open = etx_open, .release = etx_release, }; /* ** This function will be called when we open the Device file */ static int etx_open(struct inode *inode, struct file *file) { pr_info("Device File Opened...!!!\n"); return 0; } /* ** This function will be called when we close the Device file */ static int etx_release(struct inode *inode, struct file *file) { pr_info("Device File Closed...!!!\n"); return 0; } /* ** This function will be called when we read the Device file */ static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { pr_info("Read function\n"); return 0; } /* ** This function will be called when we write the Device file */ static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) { pr_info("Write Function\n"); return len; } /* ** Module Init function */ static int __init etx_driver_init(void) { /*Allocating Major number*/ if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){ pr_info("Cannot allocate major number\n"); return -1; } pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev)); /*Creating cdev structure*/ cdev_init(&etx_cdev,&fops); /*Adding character device to the system*/ if((cdev_add(&etx_cdev,dev,1)) < 0){ pr_info("Cannot add the device to the system\n"); goto r_class; } /*Creating struct class*/ if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){ pr_info("Cannot create the struct class\n"); goto r_class; } /*Creating device*/ if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){ pr_info("Cannot create the Device \n"); goto r_device; } mutex_init(&etx_mutex); /* Creating Thread 1 */ etx_thread1 = kthread_run(thread_function1,NULL,"eTx Thread1"); if(etx_thread1) { pr_err("Kthread1 Created Successfully...\n"); } else { pr_err("Cannot create kthread1\n"); goto r_device; } /* Creating Thread 2 */ etx_thread2 = kthread_run(thread_function2,NULL,"eTx Thread2"); if(etx_thread2) { pr_err("Kthread2 Created Successfully...\n"); } else { pr_err("Cannot create kthread2\n"); goto r_device; } pr_info("Device Driver Insert...Done!!!\n"); return 0; r_device: class_destroy(dev_class); r_class: unregister_chrdev_region(dev,1); cdev_del(&etx_cdev); return -1; } /* ** Module exit function */ static void __exit etx_driver_exit(void) { kthread_stop(etx_thread1); kthread_stop(etx_thread2); device_destroy(dev_class,dev); class_destroy(dev_class); cdev_del(&etx_cdev); unregister_chrdev_region(dev, 1); pr_info("Device Driver Remove...Done!!\n"); } module_init(etx_driver_init); module_exit(etx_driver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <[email protected]>"); MODULE_DESCRIPTION("A simple device driver - Mutex"); MODULE_VERSION("1.17");
MakeFile
obj-m += driver.o KDIR = /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(shell pwd) modules clean: make -C $(KDIR) M=$(shell pwd) clean
In this example, thread_function1
and thread_function2
are synchronized using the mutex.
In our next tutorial, we will discuss spinlock in the Linux device driver. You can also read the read-write spinlock.
Please find the other Linux device driver tutorials here.
You can also read the below tutorials.

Embedded Software | Firmware | Linux Devic Deriver | RTOS
Hi, I’m SLR. I am a tech blogger and an Embedded Engineer. I am always eager to learn and explore tech-related concepts. And also, I wanted to share my knowledge with everyone in a more straightforward way with easy practical examples. I strongly believe that learning by doing is more powerful than just learning by reading. I love to do experiments. If you want to help or support me on my journey, consider sharing my articles, or Buy me a Coffee! Thank you for reading my blog! Happy learning!