Mutex in Linux Kernel – Linux Device Driver Tutorial Part 22

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 spinlockread-write spinlockProcfsWorkqueueCompletionSoftirq, 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.

  1. Kthread Tutorial in Linux Kernel

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.

  1. 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?
  2. 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?
  3. 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.

  1. In the SPI example, CAR = SPI, Person = Threads, Blast = Software Crash/Software may get the wrong data.
  2. In the LED display example,  CAR = LED Display, Person = Threads, and Blast = Display will show some unwanted junk.
  3. 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

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:

  1. Static Method
  2. 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.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
STM32F7 Bootloader TutorialsRaspberry PI Pico Tutorials
STM32F103 Bootloader TutorialsRT-Thread RTOS Tutorials
Zephyr RTOS Tutorials - STM32Zephyr RTOS Tutorials - ESP32
AUTOSAR TutorialsUDS Protocol Tutorials
Product ReviewsSTM32 MikroC Bootloader Tutorial
VHDL Tutorials
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Table of Contents