ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

一、实验基础说明

1.1、互斥体简介

[1.2 本次实验设计思路](#1.2 本次实验设计思路)

二、硬件原理分析(看过之前博客的可以忽略)

三、实验程序编写

[3.1 互斥体 LED 驱动代码(mutex.c)](#3.1 互斥体 LED 驱动代码(mutex.c))

[3.2.1、设备结构体定义(28-39 行)](#3.2.1、设备结构体定义(28-39 行))

[3.2.2、设备打开函数(50-63 行)](#3.2.2、设备打开函数(50-63 行))

[3.2.3、设备写操作函数(86-107 行)](#3.2.3、设备写操作函数(86-107 行))

[3.2.4、设备释放函数(114-122 行)](#3.2.4、设备释放函数(114-122 行))

[3.2.5驱动入口函数(138-201 行)](#3.2.5驱动入口函数(138-201 行))

[3.2.6、驱动出口函数(208-216 行)](#3.2.6、驱动出口函数(208-216 行))

3.3、测试APP编写

四、运行测试

[4.1、编译驱动程序和测试 APP](#4.1、编译驱动程序和测试 APP)

4.2、运行测试

总结


前言

前几期博客我们分别用原子操作、自旋锁、信号量实现了 LED 设备的互斥访问,但信号量本质是 "同步原语",仅靠二值信号量实现互斥略显 "大材小用"。Linux 内核专门提供了互斥体为 "互斥访问" 量身定制的同步机制,语法更简洁、语义更明确,是进程上下文互斥的首选方案。


一、实验基础说明

1.1、互斥体简介

前面讲信号量的时候提到,把信号量值设为1,就能实现互斥访问(一次只能一个线程访问资源)。但Linux内核专门提供了一个更专业的互斥机制------互斥体

简单说:互斥体就是"专门用来做互斥"的工具,比二值信号量更轻量、更规范,是驱动开发中实现互斥访问的首选

学过FreeRTOS的小伙伴肯定清楚,互斥体的核心就是"一次只能一个持有者",和二值信号量类似,但有几个关键区别:

  • 互斥体不能递归上锁/解锁:线程已经持有互斥体,再去申请同一个,直接死锁(自己等自己释放)。

  • 互斥体的持有者必须释放:谁上锁,谁解锁,不能由其他线程释放(信号量可以)。

  • 互斥体只能用于线程上下文:和信号量一样,会导致线程休眠,不能用于中断

💡 生活类比:卫生间的门锁(互斥体),一次只能一个人用,必须自己开门、自己关门,不能替别人关门,也不能自己锁门后再锁一次。

Linux内核用 struct mutex 结构体表示互斥体(定义在 <include/linux/mutex.h>),省略条件编译后结构如下:

复制代码
struct mutex {
    /* 1: 未上锁, 0: 已上锁, 负数: 已上锁且有等待线程 */
    atomic_t count;
    spinlock_t wait_lock; // 自旋锁,保护等待队列
};

👉 互斥体使用注意事项(必看!避免踩坑):

  1. 不能用于中断上下文:会导致休眠,中断无法休眠,只能用自旋锁。

  2. 临界区可以调用休眠函数:和信号量一样,因为线程可以休眠,所以允许调用copy_from_user、msleep等函数。

  3. 不能递归操作:同一线程不能多次上锁,否则死锁。

  4. 严格互斥:一次只能一个线程持有,适合需要"独占资源"的场景(比如设备的打开/关闭操作)。

1.2 本次实验设计思路

互斥体的使用逻辑与二值信号量高度相似,但 API 更简洁:

初始化互斥体,代表 LED 设备初始为空闲状态;

open函数调用mutex_lock_interruptible获取互斥体:

成功:独占 LED 设备;

失败:进程休眠,等待其他进程释放;

release函数调用mutex_unlock释放互斥体,唤醒休眠进程;

设备树、硬件、测试 APP 完全复用信号量实验代码,仅替换同步机制为互斥体。

实验在此次博客基础上实现:

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(信号量实现 LED 设备互斥访问)--- Ubuntu20.04信号量实验-CSDN博客

二、硬件原理分析(看过之前博客的可以忽略)

从图中可以看出,LED0 接到了 GPIO_3 上,GPIO_3 就是 GPIO1_IO03,当 GPIO1_IO03 输出低电平 (0) 的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平 (1) 的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03 的输出电平,输出 0 就亮,输出 1 就灭。

三、实验程序编写

本次实验基于上一节自旋锁的 LED 驱动修改,仅替换同步机制为信号量,硬件和设备树无需修改。把 semaphore.c改成mutex.c文件。

3.1 互斥体 LED 驱动代码(mutex.c)

复制代码
  1 #include <linux/types.h>
  2 #include <linux/kernel.h>
  3 #include <linux/delay.h>
  4 #include <linux/ide.h>
  5 #include <linux/init.h>
  6 #include <linux/module.h>
  7 #include <linux/errno.h>
  8 #include <linux/gpio.h>
  9 #include <linux/cdev.h>
 10 #include <linux/device.h>
 11 #include <linux/of.h>
 12 #include <linux/of_address.h>
 13 #include <linux/of_gpio.h>
 14 #include <linux/semaphore.h>
 15 #include <asm/mach/map.h>
 16 #include <asm/uaccess.h>
 17 #include <asm/io.h>
 18 /***************************************************************
 19 描述	   	: 互斥体实验,使用互斥体来实现对实现设备的互斥访问
 20 其他	   	: 无
 21 ***************************************************************/
 22 #define GPIOLED_CNT			1		  	/* 设备号个数 */
 23 #define GPIOLED_NAME		"gpioled"	/* 名字 */
 24 #define LEDOFF 				0			/* 关灯 */
 25 #define LEDON 				1			/* 开灯 */
 26 
 27 
 28 /* gpioled设备结构体 */
 29 struct gpioled_dev{
 30 	dev_t devid;			/* 设备号 	 */
 31 	struct cdev cdev;		/* cdev 	*/
 32 	struct class *class;	/* 类 		*/
 33 	struct device *device;	/* 设备 	 */
 34 	int major;				/* 主设备号	  */
 35 	int minor;				/* 次设备号   */
 36 	struct device_node	*nd; /* 设备节点 */
 37 	int led_gpio;			/* led所使用的GPIO编号		*/
 38 	struct mutex lock;		/* 互斥体:核心同步成员 */
 39 };
 40 
 41 struct gpioled_dev gpioled;	/* led设备 */
 42 
 43 /*
 44  * @description		: 打开设备
 45  * @param - inode 	: 传递给驱动的inode
 46  * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 47  * 					  一般在open的时候将private_data指向设备结构体。
 48  * @return 			: 0 成功;其他 失败
 49  */
 50 static int led_open(struct inode *inode, struct file *filp)
 51 {
 52 	filp->private_data = &gpioled; /* 设置私有数据 */
 53 
 54 	/* 获取互斥体,可以被信号打断 */
 55 	if (mutex_lock_interruptible(&gpioled.lock)) {
 56 		return -ERESTARTSYS;
 57 	}
 58 #if 0
 59 	mutex_lock(&gpioled.lock);	/* 不能被信号打断 */
 60 #endif
 61 
 62 	return 0;
 63 }
 64 
 65 /*
 66  * @description		: 从设备读取数据 
 67  * @param - filp 	: 要打开的设备文件(文件描述符)
 68  * @param - buf 	: 返回给用户空间的数据缓冲区
 69  * @param - cnt 	: 要读取的数据长度
 70  * @param - offt 	: 相对于文件首地址的偏移
 71  * @return 			: 读取的字节数,如果为负值,表示读取失败
 72  */
 73 static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
 74 {
 75 	return 0;
 76 }
 77 
 78 /*
 79  * @description		: 向设备写数据 
 80  * @param - filp 	: 设备文件,表示打开的文件描述符
 81  * @param - buf 	: 要写给设备写入的数据
 82  * @param - cnt 	: 要写入的数据长度
 83  * @param - offt 	: 相对于文件首地址的偏移
 84  * @return 			: 写入的字节数,如果为负值,表示写入失败
 85  */
 86 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
 87 {
 88 	int retvalue;
 89 	unsigned char databuf[1];
 90 	unsigned char ledstat;
 91 	struct gpioled_dev *dev = filp->private_data;
 92 
 93 	retvalue = copy_from_user(databuf, buf, cnt);
 94 	if(retvalue < 0) {
 95 		printk("kernel write failed!\r\n");
 96 		return -EFAULT;
 97 	}
 98 
 99 	ledstat = databuf[0];		/* 获取状态值 */
100 
101 	if(ledstat == LEDON) {	
102 		gpio_set_value(dev->led_gpio, 0);	/* 打开LED灯 */
103 	} else if(ledstat == LEDOFF) {
104 		gpio_set_value(dev->led_gpio, 1);	/* 关闭LED灯 */
105 	}
106 	return 0;
107 }
108 
109 /*
110  * @description		: 关闭/释放设备
111  * @param - filp 	: 要关闭的设备文件(文件描述符)
112  * @return 			: 0 成功;其他 失败
113  */
114 static int led_release(struct inode *inode, struct file *filp)
115 {
116 	struct gpioled_dev *dev = filp->private_data;
117 
118 	/* 释放互斥锁 */
119 	mutex_unlock(&dev->lock);
120 
121 	return 0;
122 }
123 
124 /* 设备操作函数 */
125 static struct file_operations gpioled_fops = {
126 	.owner = THIS_MODULE,
127 	.open = led_open,
128 	.read = led_read,
129 	.write = led_write,
130 	.release = 	led_release,
131 };
132 
133 /*
134  * @description	: 驱动入口函数
135  * @param 		: 无
136  * @return 		: 无
137  */
138 static int __init led_init(void)
139 {
140 	int ret = 0;
141 
142 	/* 初始化互斥体 */
143 	mutex_init(&gpioled.lock);
144 	
145 	/* 设置LED所使用的GPIO */
146 	/* 1、获取设备节点:gpioled */
147 	gpioled.nd = of_find_node_by_path("/gpioled");
148 	if(gpioled.nd == NULL) {
149 		printk("gpioled node not find!\r\n");
150 		return -EINVAL;
151 	} else {
152 		printk("gpioled node find!\r\n");
153 	}
154 
155 	/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
156 	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
157 	if(gpioled.led_gpio < 0) {
158 		printk("can't get led-gpio");
159 		return -EINVAL;
160 	}
161 	printk("led-gpio num = %d\r\n", gpioled.led_gpio);
162 
163 	/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
164 	ret = gpio_direction_output(gpioled.led_gpio, 1);
165 	if(ret < 0) {
166 		printk("can't set gpio!\r\n");
167 	}
168 
169 	/* 注册字符设备驱动 */
170 	/* 1、创建设备号 */
171 	if (gpioled.major) {		/*  定义了设备号 */
172 		gpioled.devid = MKDEV(gpioled.major, 0);
173 		register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
174 	} else {						/* 没有定义设备号 */
175 		alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);	/* 申请设备号 */
176 		gpioled.major = MAJOR(gpioled.devid);	/* 获取分配号的主设备号 */
177 		gpioled.minor = MINOR(gpioled.devid);	/* 获取分配号的次设备号 */
178 	}
179 	printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);	
180 	
181 	/* 2、初始化cdev */
182 	gpioled.cdev.owner = THIS_MODULE;
183 	cdev_init(&gpioled.cdev, &gpioled_fops);
184 	
185 	/* 3、添加一个cdev */
186 	cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
187 
188 	/* 4、创建类 */
189 	gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
190 	if (IS_ERR(gpioled.class)) {
191 		return PTR_ERR(gpioled.class);
192 	}
193 
194 	/* 5、创建设备 */
195 	gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
196 	if (IS_ERR(gpioled.device)) {
197 		return PTR_ERR(gpioled.device);
198 	}
199 	
200 	return 0;
201 }
202 
203 /*
204  * @description	: 驱动出口函数
205  * @param 		: 无
206  * @return 		: 无
207  */
208 static void __exit led_exit(void)
209 {
210 	/* 注销字符设备驱动 */
211 	cdev_del(&gpioled.cdev);/*  删除cdev */
212 	unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
213 
214 	device_destroy(gpioled.class, gpioled.devid);
215 	class_destroy(gpioled.class);
216 }
217 
218 module_init(led_init);
219 module_exit(led_exit);
220 MODULE_LICENSE("GPL");
221 MODULE_AUTHOR("duan");

3.2、驱动代码分段解析

3.2.1、设备结构体定义(28-39 行)

复制代码
28 /* gpioled设备结构体 */
29 struct gpioled_dev{
30 	dev_t devid;			/* 设备号 	 */
31 	struct cdev cdev;		/* cdev 	*/
32 	struct class *class;	/* 类 		*/
33 	struct device *device;	/* 设备 	 */
34 	int major;				/* 主设备号	  */
35 	int minor;				/* 次设备号   */
36 	struct device_node	*nd; /* 设备节点 */
37 	int led_gpio;			/* led所使用的GPIO编号		*/
38 	struct mutex lock;		/* 互斥体:核心同步成员 */
39 };

核心修改点(38 行):

用struct mutex lock替代原信号量成员,是实现互斥访问的核心载体;

其余成员为字符设备驱动标准成员:负责设备号管理、GPIO 解析、设备节点创建等基础功能;

互斥体无需计数值:天然为 "二值" 设计,初始状态为 "可用",无需像信号量那样设置sema_init(&sem, 1)

3.2.2、设备打开函数(50-63 行)

复制代码
50 static int led_open(struct inode *inode, struct file *filp)
51 {
52 	filp->private_data = &gpioled; /* 设置私有数据 */
53 
54 	/* 获取互斥体,可以被信号打断 */
55 	if (mutex_lock_interruptible(&gpioled.lock)) {
56 		return -ERESTARTSYS;
57 	}
58 #if 0
59 	mutex_lock(&gpioled.lock);	/* 不能被信号打断 */
60 #endif
61 
62 	return 0;
63 }

52 行:将设备结构体指针赋值给filp->private_data,后续write/release函数可通过该指针访问设备结构体;

55-57 行(核心):调用mutex_lock_interruptible获取互斥体:

成功:互斥体被当前进程持有,其他进程无法获取,实现 LED 独占访问;

失败:进程进入可中断休眠状态(可被kill、Ctrl+C等信号打断),返回-ERESTARTSYS,避免进程僵死;

59 行(注释):mutex_lock为 "不可中断获取",进程休眠时无法被信号唤醒,易导致死锁,实际开发中严禁使用;

3.2.3、设备写操作函数(86-107 行)

复制代码
86 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
87 {
88 	int retvalue;
89 	unsigned char databuf[1];
90 	unsigned char ledstat;
91 	struct gpioled_dev *dev = filp->private_data;
92 
93 	retvalue = copy_from_user(databuf, buf, cnt);
94 	if(retvalue < 0) {
95 		printk("kernel write failed!\r\n");
96 		return -EFAULT;
97 	}
98 
99 	ledstat = databuf[0];		/* 获取状态值 */
100 
101 	if(ledstat == LEDON) {	
102 		gpio_set_value(dev->led_gpio, 0);	/* 打开LED灯 */
103 	} else if(ledstat == LEDOFF) {
104 		gpio_set_value(dev->led_gpio, 1);	/* 关闭LED灯 */
105 	}
106 	return 0;
107 }

91 行:从filp->private_data取出设备结构体指针;

93 行:copy_from_user将用户空间的控制指令(LED 亮 / 灭)拷贝到内核空间,是内核与用户空间数据交互的标准函数;

101-105 行:根据用户指令控制 GPIO 电平,实现 LED 亮灭;

无需额外同步:因open函数已通过互斥体保证只有一个进程能进入,天然实现互斥访问。

3.2.4、设备释放函数(114-122 行)

复制代码
114 static int led_release(struct inode *inode, struct file *filp)
115 {
116 	struct gpioled_dev *dev = filp->private_data;
117 
118 	/* 释放互斥锁 */
119 	mutex_unlock(&dev->lock);
120 
121 	return 0;
122 }

119 行(核心):调用mutex_unlock释放互斥体:

互斥体计数值恢复为 "可用";

内核自动唤醒等待该互斥体的休眠进程,使其获取互斥体并继续执行;

对比信号量:mutex_unlock ≈ up,但互斥体禁止 "跨进程释放"(必须由持有者释放),降低死锁风险。

3.2.5驱动入口函数(138-201 行)

复制代码
138 static int __init led_init(void)
139 {
140 	int ret = 0;
141 
142 	/* 初始化互斥体 */
143 	mutex_init(&gpioled.lock);
144 	
145 	/* 设置LED所使用的GPIO */
146 	/* 1、获取设备节点:gpioled */
147 	gpioled.nd = of_find_node_by_path("/gpioled");
148 	if(gpioled.nd == NULL) {
149 		printk("gpioled node not find!\r\n");
150 		return -EINVAL;
151 	} else {
152 		printk("gpioled node find!\r\n");
153 	}
154 
155 	/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
156 	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
157 	if(gpioled.led_gpio < 0) {
158 		printk("can't get led-gpio");
159 		return -EINVAL;
160 	}
161 	printk("led-gpio num = %d\r\n", gpioled.led_gpio);
162 
163 	/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
164 	ret = gpio_direction_output(gpioled.led_gpio, 1);
165 	if(ret < 0) {
166 		printk("can't set gpio!\r\n");
167 	}
168 
169 	/* 注册字符设备驱动 */
170 	/* 1、创建设备号 */
171 	if (gpioled.major) {		/*  定义了设备号 */
172 		gpioled.devid = MKDEV(gpioled.major, 0);
173 		register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
174 	} else {						/* 没有定义设备号 */
175 		alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);	/* 申请设备号 */
176 		gpioled.major = MAJOR(gpioled.devid);	/* 获取分配号的主设备号 */
177 		gpioled.minor = MINOR(gpioled.devid);	/* 获取分配号的次设备号 */
178 	}
179 	printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);	
180 	
181 	/* 2、初始化cdev */
182 	gpioled.cdev.owner = THIS_MODULE;
183 	cdev_init(&gpioled.cdev, &gpioled_fops);
184 	
185 	/* 3、添加一个cdev */
186 	cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
187 
188 	/* 4、创建类 */
189 	gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
190 	if (IS_ERR(gpioled.class)) {
191 		return PTR_ERR(gpioled.class);
192 	}
193 
194 	/* 5、创建设备 */
195 	gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
196 	if (IS_ERR(gpioled.device)) {
197 		return PTR_ERR(gpioled.device);
198 	}
199 	
200 	return 0;
201 }

143 行(核心):

mutex_init(&gpioled.lock)初始化互斥体,无参数(互斥体天然为互斥设计,无需设置计数值);

147-167 行:设备树解析流程:

of_find_node_by_path获取 LED 设备节点;

of_get_named_gpio解析设备树中led-gpio属性,得到 GPIO 编号;

gpio_direction_output配置 GPIO 为输出模式,默认高电平(LED 熄灭);

169-198 行:字符设备驱动标准注册流程:

创建设备号(静态注册 / 动态申请);

初始化cdev并添加到内核;

创建类和设备节点,最终在/dev目录生成gpioled设备文件。

3.2.6、驱动出口函数(208-216 行)

复制代码
208 static void __exit led_exit(void)
209 {
210 	/* 注销字符设备驱动 */
211 	cdev_del(&gpioled.cdev);/*  删除cdev */
212 	unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
213 
214 	device_destroy(gpioled.class, gpioled.devid);
215 	class_destroy(gpioled.class);
216 }

驱动卸载时的资源释放流程,与入口函数注册流程反向;

互斥体无需手动释放:模块卸载时内核会自动清理互斥体资源。

3.3、测试APP编写

直接复用原子操作实验的atomicApp.c,仅重命名为mutexApp.c,核心逻辑不变:

复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: mutexApp.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: 互斥体测试APP,测试信号量能不能实现一次
			只允许一个应用程序使用LED。
其他	   	: 无
使用方法	 :./mutexApp /dev/gpioled  0 关闭LED灯
		 	 ./mutexApp /dev/gpioled  1 打开LED灯
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/

#define LEDOFF 	0
#define LEDON 	1

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char cnt = 0;
	unsigned char databuf[1];
	
	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开beep驱动 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 */

	/* 向/dev/gpioled文件写入数据 */
	retvalue = write(fd, databuf, sizeof(databuf));
	if(retvalue < 0){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	/* 模拟占用25S LED */
	while(1) {
		sleep(5);
		cnt++;
		printf("App running times:%d\r\n", cnt);
		if(cnt >= 5) break;
	}

	printf("App running finished!\r\n");
	retvalue = close(fd); /* 关闭文件 */
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}
  1. 向 LED 驱动发送亮 / 灭控制指令;
  2. 模拟对 LED 设备的长时间占用(25 秒),验证在占用期间其他应用无法打开设备;
  3. 占用结束后释放设备,验证其他应用可正常访问。
代码段 功能说明
if(argc != 3) 参数校验:必须传入设备路径+控制指令(如./mutexApp /dev/gpioled 1),否则提示用法错误
fd = open(filename, O_RDWR) 打开/dev/gpioled设备文件:- 若当前无其他进程占用,驱动open函数获取互斥体成功,返回文件描述符;- 若已有进程占用,当前进程会休眠,直到前一个进程释放互斥体
databuf[0] = atoi(argv[2]) 将用户传入的字符串指令("0"/"1")转为数字,作为控制 LED 的参数
write(fd, databuf, sizeof(databuf)) 向驱动写入控制指令,驱动write函数接收后控制 GPIO 电平
while(1) { sleep(5); cnt++; ... } 核心测试逻辑:- 每 5 秒打印一次运行次数,总共运行 25 秒;- 这段时间内,当前进程一直持有互斥体,其他进程无法打开 LED 设备;- 模拟 "长时间占用硬件资源" 的真实场景
close(fd) 关闭设备文件:触发驱动release函数,释放互斥体,其他进程可获取并访问 LED

四、运行测试

4.1、编译驱动程序和测试 APP

编写 Makefile 文件,本次实验的 Makefile 文件和之前的led实验基本一样,只是将 obj-m 变量的值改为mutex.o,Makefile 内容如下所示:

复制代码
KERNELDIR := /home/duan/linux/linux-imx-rel_imx_4.1.15_2.1.1_ga_alientek_v2.2
CURRENT_PATH := $(shell pwd)

obj-m := mutex.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第 4 行,设置 obj-m 变量的值为mutex.o。

输入如下命令编译出驱动模块文件:

复制代码
make -j32

编译成功以后就会生成一个名为"mutex.ko"的驱动模块文件。

编译测试 APP

输入如下命令编译测试试mutexApp.c这个测试程序:

复制代码
arm-linux-gnueabihf-gcc mutexApp.c-o  mutexApp

4.2、运行测试

将上一小节编译出来的mutex.ko 和 mutexApp这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中。

复制代码
sudo cp mutex.ko/home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f

sudo cp  mutexApp /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f

进入到目录 lib/modules/4.1.15 中,输入如下命令加载mutex.ko驱动模块:

复制代码
depmod //第一次加载驱动的时候需要运行此命令
modprobe mutex.ko//加载驱动

驱动加载成功以后会在终端中输出一些信息,如下图所示:

驱动加载成功以后就可以使用mutexApp 软件测试驱动是否工作正常。方便和上一期博客信号量实验是一样的。
如果要卸载驱动的话输入如下命令即可:

复制代码
rmmod mutex.ko

总结

互斥体是 Linux 驱动中处理进程间互斥的 "最优解",本期博客完成了驱动 + APP 实战。

相关推荐
16Miku2 小时前
OpenClaw-Linux+飞书官方Plugin安装指南
linux·ai·飞书·agent·openclaw·feishu
安庆平.Я2 小时前
STM32——FreeRTOS - 移植
stm32·单片机·嵌入式硬件
the sun342 小时前
打通嵌入式与 Linux:USB 转串口通信实战
linux·运维·服务器
XMYX-03 小时前
Zabbix 7.0 自定义 Linux 监控模板(Agent Active版,支持CPU/内存/磁盘/网卡自动发现)
linux·zabbix
进击切图仔3 小时前
linux 虚拟文件系统简介和详解
linux·运维·服务器
HIZYUAN3 小时前
AG32 MCU可以替代STM32+CPLD吗(一)
stm32·单片机·嵌入式硬件
皮皮哎哟4 小时前
嵌入式数据库从入门到精通
linux·数据库·sqlite3·sqlite3_open
Lester_11014 小时前
STM32 定时器驱动电机时,定时器编码器输入通道引脚模式为什么设置为输出开漏,不应该是输入模式吗
stm32·单片机·嵌入式硬件