1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html 4)正点原子官方B站:https://space.bilibili.com/394620890 5)正点原子STM32MP157技术交流群:691905614
在上一章中我们学习了Linux下的并发与竞争,并且学习了四种常用的处理并发和竞争的机制:原子操作、自旋锁、信号量和互斥体。本章我们就通过四个实验来学习如何在驱动中使用这四种机制。
28.1 原子操作实验 本实验对应的例程路径为:开发板光盘 1、程序源码2、Linux驱动例程7_atomic。 本例程我们在第二十五章的gpioled.c文件基础上完成。在本节使用中我们使用原子操作来实现对LED这个设备的互斥访问,也就是一次只允许一个应用程序可以使用LED灯。 28.1.1 实验程序编写 1、修改设备树文件 因为本章实验是在第二十五章实验的基础上完成的,因此不需要对设备树做任何的修改。 2、LED驱动修改 本节实验在第二十五章实验驱动文件gpioled.c的基础上修改而来。新建名为“7_atomic”的文件夹,然后在7_atomic文件夹里面创建vscode工程,工作区命名为“atomic”。将5_gpioled实验中的gpioled.c复制到7_atomic文件夹中,并且重命名为atomic.c。本节实验重点就是使用atomic来实现一次只能允许一个应用访问LED,所以我们只需要在atomic.c文件源码的基础上加上添加atomic相关代码即可,完成以后的atomic.c文件内容如下所示:
示例代码28.1.1.1 atomic.c文件代码段
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #include
15 #include
16 #include
17 /***************************************************************
18 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
19 文件名 : gpioled.c
20 作者 : 正点原子Linux团队
21 版本 : V1.0
22 描述 : 原子操作实验,使用原子变量来实现对实现设备的互斥访问。
23 其他 : 无
24 论坛 : www.openedv.com
25 日志 : 初版V1.0 2021/1/4 正点原子Linux团队创建
26 ***************************************************************/
27 #define GPIOLED_CNT 1 /* 设备号个数 */
28 #define GPIOLED_NAME "gpioled" /* 名字 */
29 #define LEDOFF 0 /* 关灯 */
30 #define LEDON 1 /* 开灯 */
31
32 /* gpioled设备结构体 */
33 struct gpioled_dev{
34 dev_t devid; /* 设备号 */
35 struct cdev cdev; /* cdev */
36 struct class *class; /* 类 */
37 struct device *device; /* 设备 */
38 int major; /* 主设备号 */
39 int minor; /* 次设备号 */
40 struct device_node *nd; /* 设备节点 */
41 int led_gpio; /* led所使用的GPIO编号 */
42 atomic_t lock; /* 原子变量 */
43 };
44
45 static struct gpioled_dev gpioled; /* led设备 */
46
47
48 /*
49 * @description : 打开设备
50 * @param – inode : 传递给驱动的inode
51 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
52 * 一般在open的时候将private_data指向设备结构体。
53 * @return : 0 成功;其他 失败
54 */
55 static int led_open(struct inode *inode, struct file *filp)
56 {
57 /* 通过判断原子变量的值来检查LED有没有被别的应用使用 */
58 if (!atomic_dec_and_test(&gpioled.lock)) {
59 atomic_inc(&gpioled.lock);/* 小于0的话就加1,使其原子变量等于0 */
60 return -EBUSY; /* LED被使用,返回忙 */
61 }
62
63 filp->private_data = &gpioled; /* 设置私有数据 */
64 return 0;
65 }
66
67 /*
68 * @description : 从设备读取数据
69 * @param - filp : 要打开的设备文件(文件描述符)
70 * @param - buf : 返回给用户空间的数据缓冲区
71 * @param - cnt : 要读取的数据长度
72 * @param - offt : 相对于文件首地址的偏移
73 * @return : 读取的字节数,如果为负值,表示读取失败
74 */
75 static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
76 {
77 return 0;
78 }
79
80 /*
81 * @description : 向设备写数据
82 * @param - filp : 设备文件,表示打开的文件描述符
83 * @param - buf : 要写给设备写入的数据
84 * @param - cnt : 要写入的数据长度
85 * @param - offt : 相对于文件首地址的偏移
86 * @return : 写入的字节数,如果为负值,表示写入失败
87 */
88 static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
89 {
90 int retvalue;
91 unsigned char databuf[1];
92 unsigned char ledstat;
93 struct gpioled_dev *dev = filp->private_data;
94
95 retvalue = copy_from_user(databuf, buf, cnt);
96 if(retvalue led_gpio, 0); /* 打开LED灯 */
105 } else if(ledstat == LEDOFF) {
106 gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
107 }
108 return 0;
109 }
110
111 /*
112 * @description : 关闭/释放设备
113 * @param – filp : 要关闭的设备文件(文件描述符)
114 * @return : 0 成功;其他 失败
115 */
116 static int led_release(struct inode *inode, struct file *filp)
117 {
118 struct gpioled_dev *dev = filp->private_data;
119
120 /* 关闭驱动文件的时候释放原子变量 */
121 atomic_inc(&dev->lock);
122
123 return 0;
124 }
125
126 /* 设备操作函数 */
127 static struct file_operations gpioled_fops = {
128 .owner = THIS_MODULE,
129 .open = led_open,
130 .read = led_read,
131 .write = led_write,
132 .release = led_release,
133 };
134
135 /*
136 * @description : 驱动出口函数
137 * @param : 无
138 * @return : 无
139 */
140 static int __init led_init(void)
141 {
142 int ret = 0;
143 const char *str;
144
145 /* 1、初始化原子变量 */
146 gpioled.lock = (atomic_t)ATOMIC_INIT(0);
147
148 /* 2、原子变量初始值为1 */
149 atomic_set(&gpioled.lock, 1);
150
151 /* 设置LED所使用的GPIO */
152 /* 1、获取设备节点:gpioled */
153 gpioled.nd = of_find_node_by_path("/gpioled");
154 if(gpioled.nd == NULL) {
155 printk("gpioled node not find!\r\n");
156 return -EINVAL;
157 }
158
159 /* 2.读取status属性 */
160 ret = of_property_read_string(gpioled.nd, "status", &str);
161 if(ret private_data;
123
124 up(&dev->sem); /* 释放信号量,信号量count值加1 */
125 return 0;
126 }
127
128 /* 设备操作函数 */
129 static struct file_operations gpioled_fops = {
130 .owner = THIS_MODULE,
131 .open = led_open,
132 .read = led_read,
133 .write = led_write,
134 .release = led_release,
135 };
136
137 /*
138 * @description : 驱动出口函数
139 * @param : 无
140 * @return : 无
141 */
142 static int __init led_init(void)
143 {
144 int ret = 0;
145 const char *str;
146
147 /* 初始化信号量 */
148 sema_init(&gpioled.sem, 1);
......
250 }
251
252 /*
253 * @description : 驱动出口函数
254 * @param : 无
255 * @return : 无
256 */
257 static void __exit led_exit(void)
258 {
259 /* 注销字符设备驱动 */
260 cdev_del(&gpioled.cdev); /* 删除cdev */
261 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
262 device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */
263 class_destroy(gpioled.class); /* 注销类 */
264 gpio_free(gpioled.led_gpio); /* 释放GPIO */
265 }
266
267 module_init(led_init);
268 module_exit(led_exit);
269 MODULE_LICENSE("GPL");
270 MODULE_AUTHOR("ALIENTEK");
271 MODULE_INFO(intree, "Y");
第14行,要使用信号量必须添加头文件。 第43行,在设备结构体中添加一个信号量成员变量sem。 第60~66行,在open函数中申请信号量,可以使用down函数,也可以使用down_interruptible函数。如果信号量值大于等于1就表示可用,那么应用程序就会开始使用LED灯。如果信号量值为0就表示应用程序不能使用LED灯,此时应用程序就会进入到休眠状态。等到信号量值大于1的时候应用程序就会唤醒,申请信号量,获取LED灯使用权。 第124行,在release函数中调用up函数释放信号量,这样其他因为没有得到信号量而进入休眠状态的应用程序就会唤醒,获取信号量。 第148行,在驱动入口函数中调用sema_init函数初始化信号量sem的值为1,相当于sem是个二值信号量。 总结一下,当信号量sem为1的时候表示LED灯还没有被使用,如果应用程序A要使用LED灯,先调用open函数打开/dev/gpioled,这个时候会获取信号量sem,获取成功以后sem的值减1变为0。如果此时应用程序B也要使用LED灯,调用open函数打开/dev/gpioled就会因为信号量无效(值为0)而进入休眠状态。当应用程序A运行完毕,调用close函数关闭/dev/gpioled的时候就会释放信号量sem,此时信号量sem的值就会加1,变为1。信号量sem再次有效,表示其他应用程序可以使用LED灯了,此时在休眠状态的应用程序A就会获取到信号量sem,获取成功以后就开始使用LED灯。 3、编写测试APP 测试APP使用28.1.1小节中的atomicApp.c即可,将7_atomic中的atomicApp.c文件到本例程中,并将atomicApp.c重命名为semaApp.c即可。 28.3.2 运行测试 1、编译驱动程序 编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为semaphore.o,Makefile内容如下所示:
示例代码28.1.2.1 Makefile文件
1 KERNELDIR := /home/zuozhongkai/linux/my_linux/linux-5.4.31
......
4 obj-m := semaphore.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,设置obj-m变量的值为semaphore.o。 输入如下命令编译出驱动模块文件: make -j32 编译成功以后就会生成一个名为“semaphore.ko”的驱动模块文件。 2、编译测试APP 输入如下命令编译测试semaApp.c这个测试程序: arm-none-linux-gnueabihf-gcc semaApp.c -o semaApp 编译成功以后就会生成semaApp这个应用程序。 3、运行测试 将上一小节编译出来的semaphore.ko和semaApp这两个文件拷贝到rootfs/lib/modules/5.4.31目录中,重启开发板,进入到目录lib/modules/5.4.31中,输入如下命令加载semaphore.ko驱动模块: depmod //第一次加载驱动的时候需要运行此命令 modprobe semaphore.ko //加载驱动 驱动加载成功以后就可以使用semaApp软件测试驱动是否工作正常,测试方法和28.1.2小节中一样,先输入如下命令让semaApp软件模拟占用25S的LED灯: ./ semaApp /dev/gpioled 1& //打开LED灯 紧接着再输入如下命令关闭LED灯: ./ semaApp /dev/gpioled 0& //关闭LED灯 注意两个命令都是运行在后台,第一条命令先获取到信号量,因此可以操作LED灯,将LED灯打开,并且占有25S。第二条命令因为获取信号量失败而进入休眠状态,等待第一条命令运行完毕并释放信号量以后才拥有LED灯使用权,将LED灯关闭,运行结果如图28.3.2.1所示:
图28.3.2.1 命令运行过程 如果要卸载驱动的话输入如下命令即可: rmmod semaphore.ko 28.4 互斥体实验 前面我们使用原子操作、自旋锁和信号量实现了对LED灯的互斥访问,但是最适合互斥的就是互斥体mutex了。本节我们来学习一下如何使用mutex实现对LED灯的互斥访问。 28.4.1 实验程序编写 1、修改设备树文件 本章实验是在上一节实验的基础上完成的,同样不需要对设备树做任何的修改。 2、LED驱动修改 本节实验在第上一节实验驱动文件semaphore.c的基础上修改而来。新建名为“10_mutex”的文件夹,然后在10_mutex文件夹里面创建vscode工程,工作区命名为“mutex”。将9_semaphore实验中的semaphore.c复制到10_mutex文件夹中,并且重命名为mutex.c。将原来使用到信号量的地方换为mutex即可,其他的内容基本不变,完成以后的mutex.c文件内容如下所示(有省略):
示例代码28.4.1.1 mutex.c文件代码
1 #include
......
18 /***************************************************************
19 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
20 文件名 : gpioled.c
21 作者 : 正点原子Linux团队
22 版本 : V1.0
23 描述 : 互斥体实验,使用互斥体来实现对实现设备的互斥访问。
24 其他 : 无
25 论坛 : www.openedv.com
26 日志 : 初版V1.0 2021/1/4 正点原子Linux团队创建
27 ***************************************************************/
28 #define GPIOLED_CNT 1 /* 设备号个数 */
29 #define GPIOLED_NAME "gpioled" /* 名字 */
30 #define LEDOFF 0 /* 关灯 */
31 #define LEDON 1 /* 开灯 */
32
33 /* gpioled设备结构体 */
34 struct gpioled_dev{
35 dev_t devid; /* 设备号 */
36 struct cdev cdev; /* cdev */
37 struct class *class; /* 类 */
38 struct device *device; /* 设备 */
39 int major; /* 主设备号 */
40 int minor; /* 次设备号 */
41 struct device_node *nd; /* 设备节点 */
42 int led_gpio; /* led所使用的GPIO编号 */
43 struct mutex lock; /* 互斥体 */
44 };
45
46 static struct gpioled_dev gpioled; /* led设备 */
47
48
49 /*
50 * @description : 打开设备
51 * @param – inode : 传递给驱动的inode
52 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
53 * 一般在open的时候将private_data指向设备结构体。
54 * @return : 0 成功;其他 失败
55 */
56 static int led_open(struct inode *inode, struct file *filp)
57 {
58 filp->private_data = &gpioled; /* 设置私有数据 */
59
60 /* 获取互斥体,可以被信号打断 */
61 if (mutex_lock_interruptible(&gpioled.lock)) {
62 return -ERESTARTSYS;
63 }
64 #if 0
65 mutex_lock(&gpioled.lock); /* 不能被信号打断 */
66 #endif
67
68 return 0;
69 }
......
120 static int led_release(struct inode *inode, struct file *filp)
121 {
122 struct gpioled_dev *dev = filp->private_data;
123
124 /* 释放互斥锁 */
125 mutex_unlock(&dev->lock);
126 return 0;
127 }
128
129 /* 设备操作函数 */
130 static struct file_operations gpioled_fops = {
131 .owner = THIS_MODULE,
132 .open = led_open,
133 .read = led_read,
134 .write = led_write,
135 .release = led_release,
136 };
137
138 /*
139 * @description : 驱动出口函数
140 * @param : 无
141 * @return : 无
142 */
143 static int __init led_init(void)
144 {
145 int ret = 0;
146 const char *str;
147
148 /* 初始化互斥体 */
149 mutex_init(&gpioled.lock);
......
251 }
252
253 /*
254 * @description : 驱动出口函数
255 * @param : 无
256 * @return : 无
257 */
258 static void __exit led_exit(void)
259 {
260 /* 注销字符设备驱动 */
261 cdev_del(&gpioled.cdev); /* 删除cdev */
262 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
263 device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */
264 class_destroy(gpioled.class); /* 注销类 */
265 gpio_free(gpioled.led_gpio); /* 释放GPIO */
266 }
267
268 module_init(led_init);
269 module_exit(led_exit);
270 MODULE_LICENSE("GPL");
271 MODULE_AUTHOR("ALIENTEK");
272 MODULE_INFO(intree, "Y");
第43行,定义互斥体lock。 第60~66行,在open函数中调用mutex_lock_interruptible或者mutex_lock获取mutex,成功的话就表示可以使用LED灯,失败的话就会进入休眠状态,和信号量一样。 第125行,在release函数中调用mutex_unlock函数释放mutex,这样其他应用程序就可以获取mutex了。 第149行,在驱动入口函数中调用mutex_init初始化mutex。 互斥体和二值信号量类似,只不过互斥体是专门用于互斥访问的。 3、编写测试APP 测试APP使用28.1.1小节中的atomicApp.c即可,将7_atomic中的atomicApp.c文件到本例程中,并将atomicApp.c重命名为mutexApp.c即可。 28.4.2 运行测试 1、编译驱动程序 编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为mutex.o,Makefile内容如下所示:
示例代码28.4.2.1 Makefile文件
1 KERNELDIR := /home/zuozhongkai/linux/my_linux/linux-5.4.31
......
4 obj-m := mutex.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,设置obj-m变量的值为mutex.o。
输入如下命令编译出驱动模块文件:
make -j32 编译成功以后就会生成一个名为“mutex.ko”的驱动模块文件。 2、编译测试APP 输入如下命令编译测试mutexApp.c这个测试程序: arm-none-linux-gnueabihf-gcc mutexApp.c -o mutexApp 编译成功以后就会生成mutexApp这个应用程序。 3、运行测试 将上一小节编译出来的mutex.ko和mutexApp这两个文件拷贝到rootfs/lib/modules/5.4.31目录中,重启开发板,进入到目录lib/modules/5.4.31中,输入如下命令加载mutex.ko驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe mutex.ko //加载驱动
驱动加载成功以后就可以使用mutexApp软件测试驱动是否工作正常,测试方法和28.3.2中测试信号量的方法一样。 如果要卸载驱动的话输入如下命令即可: rmmod mutex.ko