天天看点

i.MX6ULL终结者Linux并发与竞争实验信号量实验

文章目录

    • 1 编写驱动程序
    • 2 编写应用测试程序
    • 3 运行测试

信号量可以导致休眠,因此信号量保护的临界区没有运行时间限制,可以在驱动的 open 函数申请信号量,然后在release 函数中释放信号量。但是信号量不能用在中断中,本节实验我们不会在中断中使用信号量。

1 编写驱动程序

本实验例程路径:i.MX6UL终结者光盘资料/06_Linux驱动例程/06_gpioled_semaphore

创建gpioled_semaphore.c文件,因为和前面的实验驱动文件部分是相同不用修改的,下面是部分内容:

1 #include <linux/types.h>
  2 #include <linux/kernel.h>
......
 17 #include <linux/semaphore.h>
 18 
 19 #define GPIOLED_CNT 1 /* 设备号个数 */
 20 #define GPIOLED_NAME "gpioled" /* 名字 */
 21 #define LEDOFF 0 /* 关灯 */
 22 #define LEDON 1 /* 开灯 */
 23 
 24 /* gpioled 设备结构体 */
 25 struct gpioled_dev{
 26         dev_t devid; /* 设备号 */
 27         struct cdev cdev; /* cdev */
 28         struct class *class; /* 类 */
 29         struct device *device; /* 设备 */
 30         int major; /* 主设备号 */
 31         int minor; /* 次设备号 */
 32         struct device_node *nd; /* 设备节点 */
 33         int led_gpio; /* led 所使用的 GPIO 编号 */
 34         struct semaphore sem; /* 信号量 */
 35 };
 36 
 37 struct gpioled_dev gpioled; /* led 设备 */
 38 
 39 /*
 40  * @description : 打开设备
 41  * @param – inode : 传递给驱动的 inode
 42  * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
 43  * 一般在 open 的时候将 private_data 指向设备结构体。
 44  * @return : 0 成功;其他 失败
 45  */
 46 static int led_open(struct inode *inode, struct file *filp)
 47 {
 48         filp->private_data = &gpioled; /* 设置私有数据 */
 49 
 50         /* 获取信号量,进入休眠状态的进程可以被信号打断 */
 51         if (down_interruptible(&gpioled.sem)) {
 52                 return -ERESTARTSYS;
 53         }
 54 #if 0
 55         down(&gpioled.sem); /* 不能被信号打断 */
 56 #endif
 57 
 58         return 0;
 59 }
......
 
105 /*
106  * @description : 关闭/释放设备
107  * @param – filp : 要关闭的设备文件(文件描述符)
108  * @return : 0 成功;其他 失败
109  */
110 static int led_release(struct inode *inode, struct file *filp)
111 {
112         struct gpioled_dev *dev = filp->private_data;
113 
114         up(&dev->sem); /* 释放信号量,信号量值加 1 */
115 
116         return 0;
117 }
118 
119 /* 设备操作函数 */
120 static struct file_operations gpioled_fops = {
121         .owner = THIS_MODULE,
122         .open = led_open,
123         .read = led_read,
124         .write = led_write,
125         .release = led_release,
126 };
127 
128 /*
129  * @description : 驱动入口函数
130  * @param : 无
131  * @return : 无
132  */
133 static int __init led_init(void)
134 {
135         int ret = 0;
136 
137         /* 初始化信号量 */
138         sema_init(&gpioled.sem, 1);
......
198         return 0;
199 }
200 /*
201  * @description : 驱动出口函数
202  * @param : 无
203  * @return : 无
204  */
205 static void __exit led_exit(void)
206 {
207         /* 注销字符设备驱动 */
208         cdev_del(&gpioled.cdev);/* 删除 cdev */
209         unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
210 
211         device_destroy(gpioled.class, gpioled.devid);
212         class_destroy(gpioled.class);
213 }
214 
215 module_init(led_init);
216 module_exit(led_exit);
217 MODULE_LICENSE("GPL");
218 MODULE_AUTHOR("topeet");
           

第 17 行,要使用信号量必须添加<linux/semaphore.h>头文件。

第 34 行,在设备结构体中添加一个信号量成员变量 sem。

第 46~59行,在 open函数中申请信号量,可以使用 down 函数,也可以使用 down_interruptible函数。如果信号量值大于等于 1 就表示可用,那么应用程序就会开始使用 LED 灯。如果信号量值为 0 就表示应用程序不能使用 LED 灯,此时应用程序就会进入到休眠状态。等到信号量值大于 1 的时候应用程序就会唤醒,申请信号量,获取 LED 灯使用权。

第 114 行,在 release 函数中调用 up 函数释放信号量,这样其他因为没有得到信号量而进入休眠状态的应用程序就会唤醒,获取信号量。

第 138 行,在驱动入口函数中调用 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 灯。

2 编写应用测试程序

应用测试程序使用38.1.2中的应用测试程序即可。

3 运行测试

1、编译驱动程序

添加Makefile文件,修改obj-m的值为 gpioled_semaphore.o,内容如下:

KERNELDIR := /home/topeet/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := gpioled_semaphore.o

build: kernel_modules

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

首先我们在终端输入两个命令(设置两个环境变量):

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
           

然后使用“make”命令进行编译,生成gpioled_semaphore.ko驱动模块文件。

2、运行测试

将编译好的gpioled_semaphore.ko驱动模块文件拷贝到/lib/modules/4.1.15目录下(检查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话需要自行创建一下。开发板中使用的是光盘资料里面提供的busybox文件系统,光盘资料的“i.MX6UL终结者光盘资料\08_开发板系统镜像\03_文件系统镜像\01_Busybox文件系统”目录下)。输入下面命令加载模块:

depmod
modprobe gpioled_semaphore
           

驱动模块加载成功如图 3.1所示:

i.MX6ULL终结者Linux并发与竞争实验信号量实验

图 3.1

驱动模块加载成功后,使用gpioled_atomic_test应用测试程序进行测试,测试流程和原子变量测试一样。使用下面的命令打开LED灯:

./gpioled_atomic_test /dev/gpioled 1 &

然后紧接着输入LED灯关闭命令:

./gpioled_atomic_test /dev/gpioled 0 &

注意两个命令都是运行在后台,第一条命令先获取到信号量,因此可以操作 LED 灯,将LED 灯打开,并且占有 25S。第二条命令因为获取信号量失败而进入休眠状态,等待第一条命令运行完毕并释放信号量以后才拥有 LED 灯使用权,将 LED 灯关闭,运行结果如图 39.3.3.2所示:

i.MX6ULL终结者Linux并发与竞争实验信号量实验

图 3.2

可以看到有两次运行结果。

卸载模块命令:

rmmod gpioled_semaphore

i.MX6ULL终结者Linux并发与竞争实验信号量实验