Overview
Mutexes (mutual exclusion locks) are synchronization primitives provided by the Linux kernel to hendle concurrent accesss to shared resources. Unlike semaphores which allow multiple concurrent access, a mutex ensures that only one thread can hold the lock at any given time.
This article demonstrates how to integrate mutex synchronization into a character device driver for GPIO LED control.
Mutex Implementation in a GPIO LED Driver
Project Setup
This implementation builds upon an existing semaphore-based project. To create the new project structure:
mkdir 8_mutex
cp ./8_semaphore/* ./8_mutex/ -rf
cp ./8_semaphore/.vscode/ ./8_mutex/ -rf
cd ./8_mutex
make clean
mv semaphore.c mutex.c
Update the Makefile to reference the new source file:
obj-m := mutex.o
Driver Implementation
The following code demonstrates mutex usage with in a GPIO LED driver:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/mutex.h>
#define LED_DISABLE 0
#define LED_ENABLE 1
#define DEVICE_NAME "gpioled"
#define DEVICE_COUNT 1
/* Device structure */
struct led_device {
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class* class;
struct device* dev;
struct device_node* device_node;
int gpio_num;
struct mutex mtx;
};
static struct led_device led_dev;
static int led_open(struct inode *inode, struct file *filp)
{
mutex_lock(&led_dev.mtx);
filp->private_data = &led_dev;
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret;
unsigned char rx_buf[2] = {0};
ret = copy_from_user(rx_buf, buf, count);
if (ret != 0) {
printk("copy_from_user failed\n");
return ret;
}
if (rx_buf[0] == LED_ENABLE) {
gpio_set_value(led_dev.gpio_num, 0);
} else if (rx_buf[0] == LED_DISABLE) {
gpio_set_value(led_dev.gpio_num, 1);
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
struct led_device* dev = (struct led_device*)(filp->private_data);
mutex_unlock(&dev->mtx);
return 0;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
static int __init led_init(void)
{
int ret;
mutex_init(&led_dev.mtx);
led_dev.major = 0;
if (led_dev.major) {
led_dev.devid = MKDEV(led_dev.major, 0);
ret = register_chrdev_region(led_dev.devid, DEVICE_COUNT, DEVICE_NAME);
} else {
ret = alloc_chrdev_region(&led_dev.devid, 0, DEVICE_COUNT, DEVICE_NAME);
led_dev.major = MAJOR(led_dev.devid);
led_dev.minor = MINOR(led_dev.devid);
}
printk("Major: %d, Minor: %d\n", led_dev.major, led_dev.minor);
if (ret < 0) {
printk("Failed to register device number\n");
goto alloc_devid_failed;
}
led_dev.cdev.owner = THIS_MODULE;
cdev_init(&led_dev.cdev, &led_fops);
ret = cdev_add(&led_dev.cdev, led_dev.devid, DEVICE_COUNT);
if (ret < 0) {
printk("Failed to add cdev\n");
goto cdev_add_failed;
}
led_dev.class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(led_dev.class)) {
ret = PTR_ERR(led_dev.class);
goto class_create_failed;
}
led_dev.dev = device_create(led_dev.class, NULL, led_dev.devid, NULL, DEVICE_NAME);
if (IS_ERR(led_dev.dev)) {
ret = PTR_ERR(led_dev.dev);
goto dev_create_failed;
}
led_dev.device_node = of_find_node_by_path("/gpioled");
if (!led_dev.device_node) {
printk("Failed to find device node\n");
goto find_node_failed;
}
led_dev.gpio_num = of_get_named_gpio(led_dev.device_node, "led-gpio", 0);
if (led_dev.gpio_num < 0) {
printk("Failed to get GPIO number\n");
goto find_node_failed;
}
ret = gpio_request(led_dev.gpio_num, DEVICE_NAME);
if (ret != 0) {
printk("Failed to request GPIO\n");
goto find_node_failed;
}
ret = gpio_direction_output(led_dev.gpio_num, 1);
if (ret != 0) {
printk("Failed to set GPIO direction\n");
goto gpio_dir_failed;
}
return 0;
gpio_dir_failed:
gpio_free(led_dev.gpio_num);
find_node_failed:
device_destroy(led_dev.class, led_dev.devid);
dev_create_failed:
class_destroy(led_dev.class);
class_create_failed:
cdev_del(&led_dev.cdev);
cdev_add_failed:
unregister_chrdev_region(led_dev.devid, DEVICE_COUNT);
alloc_devid_failed:
return ret;
}
static void __exit led_exit(void)
{
gpio_set_value(led_dev.gpio_num, 1);
cdev_del(&led_dev.cdev);
unregister_chrdev_region(led_dev.devid, DEVICE_COUNT);
device_destroy(led_dev.class, led_dev.devid);
class_destroy(led_dev.class);
gpio_free(led_dev.gpio_num);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
Building the Module
Compile the driver from the project directory:
make
The build process invokes the kernel build system and produces a loadable kernel module. Successful compilation indicates the mutex synchronization has been properly integrated into the driver.
Key Mutex Operations
| Operation | Description |
|---|---|
mutex_init() |
Initializes the mutex before device registration |
mutex_lock() |
Acquires the lock in open() - blocks if already held |
mutex_unlock() |
Releases the lock in release() - enables next access |
The mutex guarantees that only one process can access the LED hardware at a time, preventing race conditions when multiple applications attempt simultaneous control.
Important Considerations
- Single acquirer: Unlike semaphores, a mutex strictly enforces that the same thread must release the lock
- Non-recursive: The Linux kernel mutex does not support recursive locking; attempting to lock twice from the same context causes deadlock
- Speed: Mutexes are optimized for the common case where no contention exists, making them more efficient than semaphores for simple exclusion scenarios