搜索
bottom↓
回复: 0

【正点原子Linux连载】第四十八章Linux并发与竞争实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南

[复制链接]

出0入234汤圆

发表于 2020-7-3 10:17:35 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-26 11:32 编辑

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html  
4)对正点原子Linux感兴趣的同学可以加群讨论:
876919289 QQ群头像.png
5)关注正点原子公众号,获取最新资料

100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png



第四十八章Linux并发与竞争实验

        
        在上一章中我们学习了Linux下的并发与竞争,并且学习了四种常用的处理并发和竞争的机制:原子操作、自旋锁、信号量和互斥体。本章我们就通过四个实验来学习如何在驱动中使用这四种机制。

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

        第42行,原子变量lock,用来实现一次只能允许一个应用访问LED灯,led_init驱动入口函数会将lock的值设置为1。
        第57~60行,每次调用open函数打开驱动设备的时候先申请lock,如果申请成功的话就表示LED灯还没有被其他的应用使用,如果申请失败就表示LED灯正在被其他的应用程序使用。每次打开驱动设备的时候先使用atomic_dec_and_test函数将lock减1,如果atomic_dec_and_test函数返回值为真就表示lock当前值为0,说明设备可以使用。如果atomic_dec_and_test函数返回值为假,就表示lock当前值为负数(lock值默认是1),lock值为负数的可能性只有一个,那就是其他设备正在使用LED。其他设备正在使用LED灯,那么就只能退出了,在退出之前调用函数atomic_inc将lock加1,因为此时lock的值被减成了负数,必须要对其加1,将lock的值变为0。
        第120行,LED灯使用完毕,应用程序调用close函数关闭的驱动文件,led_release函数执行,调用atomic_inc释放lcok,也就是将lock加1。
        第143行,初始化原子变量lock,初始值设置为1,这样每次就只允许一个应用使用LED灯。
3、编写测试APP
        新建名为atomicApp.c的测试APP,在里面输入如下所示内容:
示例代码48.1.1.2 atomicApp.c文件代码
  1. 1  #include "stdio.h"
  2. 2  #include "unistd.h"
  3. 3  #include "sys/types.h"
  4. 4  #include "sys/stat.h"
  5. 5  #include "fcntl.h"
  6. 6  #include "stdlib.h"
  7. 7  #include "string.h"
  8. 8/***************************************************************
  9. 9  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
  10. 10文件名                : atomicApp.c
  11. 11作者        : 左忠凯
  12. 12 版本        : V1.0
  13. 13描述        : 原子变量测试APP,测试原子变量能不能实现一次
  14. 14        只允许一个应用程序使用LED。
  15. 15其他        : 无
  16. 16使用方法                :./atomicApp /dev/gpioled  0 关闭LED灯
  17. 17          ./atomicApp /dev/gpioled  1 打开LED灯
  18. 18论坛        : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
  19. 19日志        : 初版V1.0 2019/1/30 左忠凯创建
  20. 20 ***************************************************************/
  21. 21
  22. 22 #define LEDOFF           0
  23. 23 #define LEDON            1
  24. 24
  25. 25/*
  26. 26  * @description          : main主程序
  27. 27  * @param - argc          : argv数组元素个数
  28. 28  * @param - argv         : 具体参数
  29. 29  * @return                : 0 成功;其他失败
  30. 30  */
  31. 31int main(int argc,char*argv[])
  32. 32{
  33. 33        int fd, retvalue;
  34. 34        char*filename;
  35. 35        unsignedchar cnt =0;
  36. 36        unsignedchar databuf[1];
  37. 37
  38. 38                if(argc !=3){
  39. 39        printf("Error Usage!\r\n");
  40. 40        return-1;
  41. 41        }
  42. 42
  43. 43        filename = argv[1];
  44. 44
  45. 45        /* 打开beep驱动 */
  46. 46        fd = open(filename, O_RDWR);
  47. 47        if(fd <0){
  48. 48        printf("file %s open failed!\r\n", argv[1]);
  49. 49        return-1;
  50. 50        }
  51. 51
  52. 52        databuf[0]= atoi(argv[2]);/* 要执行的操作:打开或关闭 */
  53. 53
  54. 54        /* 向/dev/gpioled文件写入数据 */
  55. 55        retvalue = write(fd, databuf,sizeof(databuf));
  56. 56        if(retvalue <0){
  57. 57        printf("LED Control Failed!\r\n");
  58. 58        close(fd);
  59. 59        return-1;
  60. 60        }
  61. 61
  62. 62        /* 模拟占用25S LED */
  63. 63        while(1){
  64. 64        sleep(5);
  65. 65        cnt++;
  66. 66        printf("App running times:%d\r\n", cnt);
  67. 67        if(cnt >=5)break;
  68. 68        }
  69. 69
  70. 70        printf("App running finished!");
  71. 71        retvalue = close(fd);/* 关闭文件 */
  72. 72        if(retvalue <0){
  73. 73        printf("file %s close failed!\r\n", argv[1]);
  74. 74        return-1;
  75. 75        }
  76. 76        return0;
  77. 77}
复制代码

        atomicApp.c中的内容就是在第四十五章的ledAPP.c的基础上修改而来的,重点是加入了第63~68行的模拟占用25秒LED的代码。测试APP在获取到LED灯驱动的使用权以后会使用25S,在使用的这段时间如果有其他的应用也去获取LED灯使用权的话肯定会失败!
48.1.2 运行测试
        1、编译驱动程序
        编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为atomic.o,Makefile内容如下所示:
示例代码48.1.2.1 Makefile文件
  1. 1  KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
  2. ......
  3. 4  obj-m := atomic.o
  4. ......
  5. 11 clean:
  6. 12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码

        第4行,设置obj-m变量的值为atomic.o。
        输入如下命令编译出驱动模块文件:
make-j32
        编译成功以后就会生成一个名为“atomic.ko”的驱动模块文件。
        2、编译测试APP
        输入如下命令编译测试atomicApp.c这个测试程序:
  1. arm-linux-gnueabihf-gcc atomicApp.c -o atomicApp
复制代码

        编译成功以后就会生成atomicApp这个应用程序。
3、运行测试
        将上一小节编译出来的atomic.ko和atomicApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载atomic.ko驱动模块:
  1. depmod                                //第一次加载驱动的时候需要运行此命令
  2. modprobe atomic.ko        //加载驱动
复制代码

驱动加载成功以后就可以使用atomicApp软件来测试驱动是否工作正常,输入如下命令以后台运行模式打开LED灯,“&”表示在后台运行atomicApp这个软件:
  1. ./atomicApp /dev/gpioled 1&                //打开LED灯
复制代码

        输入上述命令以后观察开发板上的红色LED灯是否点亮,然后每隔5秒都会输出一行“App running times ”,如图48.1.2.1所示:
image002.jpg

图48.1.2.1 打开LED灯

        从图48.1.2.1可以看出,atomicApp运行正常,输出了“App running times:1”和“App running times:2”,这就是模拟25S占用,说明atomicApp这个软件正在使用LED灯。此时再输入如下命令关闭LED灯:
  1. ./atomicApp  /dev/gpioled 0        //关闭LED灯
复制代码

        输入上述命令以后会发现如图48.1.2.2所示输入信息:
image004.jpg

图48.1.2.2 关闭LED灯

        从图48.1.2.2可以看出,打开/dev/gpioled失败!原因是在图48.1.2.1中运行的atomicAPP软件正在占用/dev/gpioled,如果再次运行atomicApp软件去操作/dev/gpioled肯定会失败。必须等待图48.1.2.1中的atomicApp运行结束,也就是25S结束以后其他软件才能去操作/dev/gpioled。这个就是采用原子变量实现一次只能有一个应用程序访问LED灯。
如果要卸载驱动的话输入如下命令即可:
  1. rmmodatomic.ko
复制代码

48.2 自旋锁实验
        上一节我们使用原子变量实现了一次只能有一个应用程序访问LED灯,本节我们使用自旋锁来实现此功能。在使用自旋锁之前,先回顾一下自旋锁的使用注意事项:
        ①、自旋锁保护的临界区要尽可能的短,因此在open函数中申请自旋锁,然后在release函数中释放自旋锁的方法就不可取。我们可以使用一个变量来表示设备的使用情况,如果设备被使用了那么变量就加一,设备被释放以后变量就减1,我们只需要使用自旋锁保护这个变量即可。
        ②、考虑驱动的兼容性,合理的选择API函数。
        综上所述,在本节例程中,我们通过定义一个变量dev_stats表示设备的使用情况,dev_stats为0的时候表示设备没有被使用,dev_stats大于0的时候表示设备被使用。驱动open函数中先判断dev_stats是否为0,也就是判断设备是否可用,如果为0的话就使用设备,并且将dev_stats加1,表示设备被使用了。使用完以后在release函数中将dev_stats减1,表示设备没有被使用了。因此真正实现设备互斥访问的是变量dev_stats,但是我们要使用自旋锁对dev_stats来做保护。
48.2.1 实验程序编写
1、修改设备树文件
        本章实验是在上一节实验的基础上完成的,同样不需要对设备树做任何的修改。
        2、LED驱动修改
本节实验在第上一节实验驱动文件atomic.c的基础上修改而来。新建名为“8_spinlock”的文件夹,然后在8_spinlock文件夹里面创建vscode工程,工作区命名为“spinlock”。将7_atomic实验中的atomic.c复制到8_spinlock文件夹中,并且重命名为spinlock.c。将原来使用atomic的地方换为spinlock即可,其他代码不需要修改,完成以后的spinlock.c文件内容如下所示(有省略):
示例代码48.2.1.1 spinlock.c文件代码
  1. 1   #include <linux/types.h>
  2. 2   #include <linux/kernel.h>
  3. 3   #include <linux/delay.h>
  4. 4   #include <linux/ide.h>
  5. 5   #include <linux/init.h>
  6. ......
  7. 17/***************************************************************
  8. 18  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
  9. 19文件名                : spinlock.c
  10. 20作者        : 左忠凯
  11. 21版本        : V1.0
  12. 22描述        : 自旋锁实验,使用自旋锁来实现对实现设备的互斥访问
  13. 23其他        : 无
  14. 24论坛        : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
  15. 25日志        : 初版V1.0 2019/7/18 左忠凯创建
  16. 26  ***************************************************************/
  17. 27  #define GPIOLED_CNT                     1        /* 设备号个数        */
  18. 28  #define GPIOLED_NAME                "gpioled"        /* 名字                */
  19. 29  #define LEDOFF                      0        /* 关灯                */
  20. 30  #define LEDON                       1        /* 开灯                */
  21. 31
  22. 32
  23. 33/* gpioled设备结构体 */
  24. 34struct gpioled_dev{
  25. 35      dev_t devid;        /* 设备号                                */
  26. 36struct cdev cdev;        /* cdev                                     */
  27. 37struct class *class;        /* 类                                        */
  28. 38struct device *device;        /* 设备                                        */
  29. 39int major;        /* 主设备号                                */
  30. 40int minor;        /* 次设备号                                */
  31. 41struct device_node  *nd;        /* 设备节点                                        */
  32. 42int led_gpio;        /* led所使用的GPIO编号        */
  33. 43int dev_stats;/* 设备状态,0,设备未使用;>0,设备已经被使用 */
  34. 44      spinlock_t lock;        /* 自旋锁                                        */
  35. 45};
  36. 46
  37. 47struct gpioled_dev gpioled;        /* led设备                                */
  38. 48
  39. 49/*
  40. 50   * @description          : 打开设备
  41. 51   * @param – inode        : 传递给驱动的inode
  42. 52   * @param - filp         : 设备文件,file结构体有个叫做private_data的成员变量
  43. 53   *                    一般在open的时候将private_data指向设备结构体。
  44. 54   * @return                : 0 成功;其他失败
  45. 55   */
  46. 56staticint led_open(struct inode *inode,struct file *filp)
  47. 57{
  48. 58unsignedlong flags;
  49. 59      filp->private_data =&gpioled;        /* 设置私有数据 */
  50. 60
  51. 61      spin_lock_irqsave(&gpioled.lock, flags);        /* 上锁                        */
  52. 62if(gpioled.dev_stats){        /* 如果设备被使用了        */
  53. 63          spin_unlock_irqrestore(&gpioled.lock, flags);        /* 解锁 */
  54. 64return-EBUSY;
  55. 65}
  56. 66      gpioled.dev_stats++;/* 如果设备没有打开,那么就标记已经打开了 */
  57. 67      spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */
  58. 68
  59. 69return0;
  60. 70}
  61. ......
  62. 116/*
  63. 117  * @description         : 关闭/释放设备
  64. 118  * @param – filp        : 要关闭的设备文件(文件描述符)
  65. 119  * @return               : 0 成功;其他失败
  66. 120  */
  67. 121staticint led_release(struct inode *inode,struct file *filp)
  68. 122{
  69. 123unsignedlong flags;
  70. 124struct gpioled_dev *dev = filp->private_data;
  71. 125
  72. 126/* 关闭驱动文件的时候将dev_stats减1 */
  73. 127     spin_lock_irqsave(&dev->lock, flags);/* 上锁 */
  74. 128if(dev->dev_stats){
  75. 129         dev->dev_stats--;
  76. 130}
  77. 131     spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */
  78. 132
  79. 133return0;
  80. 134}
  81. 135
  82. 136/* 设备操作函数 */
  83. 137staticstruct file_operations gpioled_fops ={
  84. 138.owner = THIS_MODULE,
  85. 139.open = led_open,
  86. 140.read = led_read,
  87. 141.write = led_write,
  88. 142.release =  led_release,
  89. 143};
  90. 144
  91. 145/*
  92. 146  * @description        : 驱动入口函数
  93. 147  * @param               : 无
  94. 148  * @return              : 无
  95. 149  */
  96. 150staticint __init led_init(void)
  97. 151{
  98. 152int ret =0;
  99. 153
  100. 154/*  初始化自旋锁 */
  101. 155     spin_lock_init(&gpioled.lock);
  102. ......
  103. 212return0;
  104. 213}
  105. 214
  106. 215/*
  107. 216  * @description         : 驱动出口函数
  108. 217  * @param               : 无
  109. 218  * @return              : 无
  110. 219  */
  111. 220staticvoid __exit led_exit(void)
  112. 221{
  113. 222/* 注销字符设备驱动 */
  114. 223     cdev_del(&gpioled.cdev);/*  删除cdev */
  115. 224     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
  116. 225
  117. 226     device_destroy(gpioled.class, gpioled.devid);
  118. 227     class_destroy(gpioled.class);
  119. 228}
  120. 229
  121. 230 module_init(led_init);
  122. 231 module_exit(led_exit);
  123. 232 MODULE_LICENSE("GPL");
  124. 233 MODULE_AUTHOR("zuozhongkai");
复制代码

第43行,dev_stats表示设备状态,如果为0的话表示设备还没有被使用,如果大于0的话就表示设备已经被使用了。
第44行,定义自旋锁变量lock。
第61~67行,使用自旋锁实现对设备的互斥访问,第61行调用spin_lock_irqsave函数获取锁,为了考虑到驱动兼容性,这里并没有使用spin_lock函数来获取锁。第62行判断dev_stats是否大于0,如果是的话表示设备已经被使用了,那么就调用spin_unlock_irqrestore函数释放锁,并且返回-EBUSY。如果设备没有被使用的话就在第66行将dev_stats加1,表示设备要被使用了,然后调用spin_unlock_irqrestore函数释放锁。自旋锁的工作就是保护dev_stats变量,真正实现对设备互斥访问的是dev_stats。
        第126~131行,在release函数中将dev_stats减1,表示设备被释放了,可以被其他的应用程序使用。将dev_stats减1的时候需要自旋锁对其进行保护。
        第155行,在驱动入口函数led_init中调用spin_lock_init函数初始化自旋锁。
3、编写测试APP
        测试APP使用48.1.1小节中的atomicApp.c即可,将7_atomic中的atomicApp.c文件到本例程中,并将atomicApp.c重命名为spinlockApp.c即可。
48.2.2 运行测试
        1、编译驱动程序
        编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为spinlock.o,Makefile内容如下所示:
示例代码48.2.2.1 Makefile文件
  1. 1  KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
  2. ......
  3. 4  obj-m := spinlock.o
  4. ......
  5. 11 clean:
  6. 12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码

        第4行,设置obj-m变量的值为spinlock.o。
        输入如下命令编译出驱动模块文件:
make-j32
        编译成功以后就会生成一个名为“spinlock.ko”的驱动模块文件。
        2、编译测试APP
        输入如下命令编译测试spinlockApp.c这个测试程序:
  1. arm-linux-gnueabihf-gcc spinlockApp.c -o spinlockApp
复制代码

        编译成功以后就会生成spinlockApp这个应用程序。
3、运行测试
        将上一小节编译出来的spinlock.ko和spinlockApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载spinlock.ko驱动模块:
  1. depmod                                //第一次加载驱动的时候需要运行此命令
  2. modprobe spinlock.ko        //加载驱动
复制代码

驱动加载成功以后就可以使用spinlockApp软件测试驱动是否工作正常,测试方法和48.1.2小节中一样,先输入如下命令让spinlockAPP软件模拟占用25S的LED灯:
  1. ./atomicApp /dev/gpioled 1&                //打开LED灯
  2. 紧接着再输入如下命令关闭LED灯:
  3. ./atomicApp  /dev/gpioled 0                //关闭LED灯
复制代码

        看一下能不能关闭LED灯,驱动正常工作的话并不会马上关闭LED灯,会提示你“file /dev/gpioled open failed!”,必须等待第一个atomicApp软件运行完成(25S计时结束)才可以再次操作LED灯。
如果要卸载驱动的话输入如下命令即可:
  1. rmmodspinlock.ko
复制代码

48.3 信号量实验
        本节我们来使用信号量实现了一次只能有一个应用程序访问LED灯,信号量可以导致休眠,因此信号量保护的临界区没有运行时间限制,可以在驱动的open函数申请信号量,然后在release函数中释放信号量。但是信号量不能用在中断中,本节实验我们不会在中断中使用信号量。
48.3.1 实验程序编写
1、修改设备树文件
        本章实验是在上一节实验的基础上完成的,同样不需要对设备树做任何的修改。
        2、LED驱动修改
本节实验在第上一节实验驱动文件spinlock.c的基础上修改而来。新建名为“9_semaphore”的文件夹,然后在9_semaphore文件夹里面创建vscode工程,工作区命名为“semaphore”。将8_spinlock实验中的spinlock.c复制到9_semaphore文件夹中,并且重命名为semaphore.c。将原来使用到自旋锁的地方换为信号量即可,其他的内容基本不变,完成以后的semaphore.c文件内容如下所示(有省略):
示例代码48.3.1.1 semaphore.c文件代码
  1. 1   #include <linux/types.h>
  2. ......
  3. 14  #include <linux/semaphore.h>
  4. 15  #include <asm/mach/map.h>
  5. 16  #include <asm/uaccess.h>
  6. 17  #include <asm/io.h>
  7. 18/***************************************************************
  8. 19  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
  9. 20文件名                : semaphore.c
  10. 21作者        : 左忠凯
  11. 22版本        : V1.0
  12. 23描述        : 信号量实验,使用信号量来实现对实现设备的互斥访问
  13. 24其他        : 无
  14. 25论坛        : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
  15. 26日志        : 初版V1.0 2019/7/18 左忠凯创建
  16. 27  ***************************************************************/
  17. 28  #define GPIOLED_CNT            1        /* 设备号个数        */
  18. 29  #define GPIOLED_NAME           "gpioled"        /* 名字                */
  19. 30  #define LEDOFF                   0        /* 关灯                */
  20. 31  #define LEDON                    1        /* 开灯                */
  21. 32
  22. 33/* gpioled设备结构体 */
  23. 34struct gpioled_dev{
  24. 35      dev_t devid;        /* 设备号                        */
  25. 36struct cdev cdev;        /* cdev                             */
  26. 37struct class *class;        /* 类                                        */
  27. 38struct device *device;        /* 设备                                        */
  28. 39int major;        /* 主设备号                                */
  29. 40int minor;        /* 次设备号                                */
  30. 41struct device_node  *nd;        /* 设备节点                                        */
  31. 42int led_gpio;        /* led所使用的GPIO编号        */
  32. 43struct semaphore sem;        /* 信号量                                        */
  33. 44};
  34. 45
  35. 46struct gpioled_dev gpioled;/* led设备 */
  36. 47
  37. 48/*
  38. 49   * @description        : 打开设备
  39. 50   * @param – inode        : 传递给驱动的inode
  40. 51   * @param - filp         : 设备文件,file结构体有个叫做private_data的成员变量
  41. 52   *                    一般在open的时候将private_data指向设备结构体。
  42. 53   * @return                : 0 成功;其他失败
  43. 54   */
  44. 55staticint led_open(struct inode *inode,struct file *filp)
  45. 56{
  46. 57      filp->private_data =&gpioled;/* 设置私有数据 */
  47. 58
  48. 59/* 获取信号量,进入休眠状态的进程可以被信号打断 */
  49. 60if(down_interruptible(&gpioled.sem)){
  50. 61return-ERESTARTSYS;
  51. 62}
  52. 63  #if0
  53. 64      down(&gpioled.sem);/* 不能被信号打断 */
  54. 65  #endif
  55. 66
  56. 67return0;
  57. 68}
  58. ......
  59. 114/*
  60. 115  * @description          : 关闭/释放设备
  61. 116  * @param – filp        : 要关闭的设备文件(文件描述符)
  62. 117  * @return               : 0 成功;其他失败
  63. 118  */
  64. 119staticint led_release(struct inode *inode,struct file *filp)
  65. 120{
  66. 121struct gpioled_dev *dev = filp->private_data;
  67. 122
  68. 123     up(&dev->sem);/* 释放信号量,信号量值加1 */
  69. 124
  70. 125return0;
  71. 126}
  72. 127
  73. 128/* 设备操作函数 */
  74. 129staticstruct file_operations gpioled_fops ={
  75. 130.owner = THIS_MODULE,
  76. 131.open = led_open,
  77. 132.read = led_read,
  78. 133.write = led_write,
  79. 134.release =  led_release,
  80. 135};
  81. 136
  82. 137/*
  83. 138  * @description        : 驱动入口函数
  84. 139  * @param               : 无
  85. 140  * @return              : 无
  86. 141  */
  87. 142staticint __init led_init(void)
  88. 143{
  89. 144int ret =0;
  90. 145
  91. 146/* 初始化信号量 */
  92. 147     sema_init(&gpioled.sem,1);
  93. ......
  94. 204return0;
  95. 205}
  96. 206
  97. 207/*
  98. 208  * @description        : 驱动出口函数
  99. 209  * @param               : 无
  100. 210  * @return              : 无
  101. 211  */
  102. 212staticvoid __exit led_exit(void)
  103. 213{
  104. 214/* 注销字符设备驱动 */
  105. 215     cdev_del(&gpioled.cdev);/*  删除cdev */
  106. 216     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
  107. 217
  108. 218     device_destroy(gpioled.class, gpioled.devid);
  109. 219     class_destroy(gpioled.class);
  110. 220}
  111. 221
  112. 222 module_init(led_init);
  113. 223 module_exit(led_exit);
  114. 224 MODULE_LICENSE("GPL");
  115. 225 MODULE_AUTHOR("zuozhongkai");
复制代码

        第14行,要使用信号量必须添加<linux/semaphore.h>头文件。
        第43行,在设备结构体中添加一个信号量成员变量sem。
        第60~65行,在open函数中申请信号量,可以使用down函数,也可以使用down_interruptible函数。如果信号量值大于1就表示可用,那么应用程序就会开始使用LED灯。如果信号量值为0就表示应用程序不能使用LED灯,此时应用程序就会进入到休眠状态。等到信号量值大于1的时候应用程序就会唤醒,申请信号量,获取LED灯使用权。
        第123行,在release函数中调用up函数释放信号量,这样其他因为没有得到信号量而进入休眠状态的应用程序就会唤醒,获取信号量。
        第147行,在驱动入口函数中调用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使用48.1.1小节中的atomicApp.c即可,将7_atomic中的atomicApp.c文件到本例程中,并将atomicApp.c重命名为semaApp.c即可。
48.3.2 运行测试
        1、编译驱动程序
        编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为semaphore.o,Makefile内容如下所示:
示例代码48.3.2.1 Makefile文件
  1. 1  KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
  2. ......
  3. 4  obj-m :=semaphore.o
  4. ......
  5. 11 clean:
  6. 12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码

        第4行,设置obj-m变量的值为semaphore.o。
        输入如下命令编译出驱动模块文件:
make-j32
        编译成功以后就会生成一个名为“semaphore.ko”的驱动模块文件。
        2、编译测试APP
        输入如下命令编译测试semaApp.c这个测试程序:
  1. arm-linux-gnueabihf-gcc semaApp.c -o semaApp
复制代码

        编译成功以后就会生成semaApp这个应用程序。
3、运行测试
        将上一小节编译出来的semaphore.ko和semaApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载semaphore.ko驱动模块:
  1. depmod                                //第一次加载驱动的时候需要运行此命令
  2. modprobe semaphore.ko        //加载驱动
复制代码

驱动加载成功以后就可以使用semaApp软件测试驱动是否工作正常,测试方法和48.1.2小节中一样,先输入如下命令让semaApp软件模拟占用25S的LED灯:
  1. ./ semaApp /dev/gpioled 1&                //打开LED灯
  2. 紧接着再输入如下命令关闭LED灯:
  3. ./ semaApp /dev/gpioled 0&                //关闭LED灯
复制代码

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

图48.3.2.1 命令运行过程

如果要卸载驱动的话输入如下命令即可:
  1. rmmodsemaphore.ko
复制代码

48.4 互斥体实验
        前面我们使用原子操作、自旋锁和信号量实现了对LED灯的互斥访问,但是最适合互斥的就是互斥体mutex了。本节我们来学习一下如何使用mutex实现对LED灯的互斥访问。
48.4.1 实验程序编写
1、修改设备树文件
        本章实验是在上一节实验的基础上完成的,同样不需要对设备树做任何的修改。
        2、LED驱动修改
本节实验在第上一节实验驱动文件semaphore.c的基础上修改而来。新建名为“10_mutex”的文件夹,然后在10_mutex文件夹里面创建vscode工程,工作区命名为“mutex”。将9_semaphore实验中的semaphore.c复制到10_mutex文件夹中,并且重命名为mutex.c。将原来使用到信号量的地方换为mutex即可,其他的内容基本不变,完成以后的mutex.c文件内容如下所示(有省略):
示例代码48.4.1.1 mutex.c文件代码
  1. 1   #include <linux/types.h>
  2. ......
  3. 17  #include <asm/io.h>
  4. 18/***************************************************************
  5. 19  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
  6. 20文件名        : mutex.c
  7. 21作者        : 左忠凯
  8. 22版本        : V1.0
  9. 23描述        : 互斥体实验,使用互斥体来实现对实现设备的互斥访问
  10. 24其他        : 无
  11. 25 论坛        : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
  12. 26日志        : 初版V1.0 2019/7/18 左忠凯创建
  13. 27  ***************************************************************/
  14. 28  #define GPIOLED_CNT               1        /* 设备号个数        */
  15. 29  #define GPIOLED_NAME                "gpioled"        /* 名字                */
  16. 30  #define LEDOFF                      0        /* 关灯                */
  17. 31  #define LEDON                       1        /* 开灯                */
  18. 32
  19. 33/* gpioled设备结构体 */
  20. 34struct gpioled_dev{
  21. 35      dev_t devid;        /* 设备号                        */
  22. 36struct cdev cdev;        /* cdev                             */
  23. 37struct class *class;        /* 类                                */
  24. 38struct device *device;        /* 设备                                */
  25. 39int major;        /* 主设备号                        */
  26. 40int minor;        /* 次设备号                        */
  27. 41struct device_node  *nd;        /* 设备节点                                */
  28. 42int led_gpio;        /* led所使用的GPIO编号*/
  29. 43struct mutex lock;        /* 互斥体                                */
  30. 44};
  31. 45
  32. 46struct gpioled_dev gpioled;/* led设备 */
  33. 47
  34. 48/*
  35. 49   * @description         : 打开设备
  36. 50   * @param – inode        : 传递给驱动的inode
  37. 51   * @param - filp         : 设备文件,file结构体有个叫做private_data的成员变量
  38. 52   *                    一般在open的时候将private_data指向设备结构体。
  39. 53   * @return              : 0 成功;其他失败
  40. 54   */
  41. 55staticint led_open(struct inode *inode,struct file *filp)
  42. 56{
  43. 57      filp->private_data =&gpioled;/* 设置私有数据 */
  44. 58
  45. 59/* 获取互斥体,可以被信号打断 */
  46. 60if(mutex_lock_interruptible(&gpioled.lock)){
  47. 61return-ERESTARTSYS;
  48. 62}
  49. 63  #if0
  50. 64      mutex_lock(&gpioled.lock);/* 不能被信号打断 */
  51. 65  #endif
  52. 66
  53. 67return0;
  54. 68}
  55. ......
  56. 114/*
  57. 115  * @description         : 关闭/释放设备
  58. 116  * @param – filp        : 要关闭的设备文件(文件描述符)
  59. 117  * @return               : 0 成功;其他失败
  60. 118  */
  61. 119staticint led_release(struct inode *inode,struct file *filp)
  62. 120{
  63. 121struct gpioled_dev *dev = filp->private_data;
  64. 122
  65. 123/* 释放互斥锁 */
  66. 124     mutex_unlock(&dev->lock);
  67. 125
  68. 126return0;
  69. 127}
  70. 128
  71. 129/* 设备操作函数 */
  72. 130staticstruct file_operations gpioled_fops ={
  73. 131.owner = THIS_MODULE,
  74. 132.open = led_open,
  75. 133.read = led_read,
  76. 134.write = led_write,
  77. 135.release =  led_release,
  78. 136};
  79. 137
  80. 138/*
  81. 139  * @description        : 驱动入口函数
  82. 140  * @param                     : 无
  83. 141  * @return                    : 无
  84. 142  */
  85. 143staticint __init led_init(void)
  86. 144{
  87. 145int ret =0;
  88. 146
  89. 147/* 初始化互斥体 */
  90. 148     mutex_init(&gpioled.lock);
  91. ......
  92. 205return0;
  93. 206}
  94. ......
  95. 223 module_init(led_init);
  96. 224 module_exit(led_exit);
  97. 225 MODULE_LICENSE("GPL");
  98. 226 MODULE_AUTHOR("zuozhongkai");
复制代码

        第43行,定义互斥体lock。
        第60~65行,在open函数中调用mutex_lock_interruptible或者mutex_lock获取mutex,成功的话就表示可以使用LED灯,失败的话就会进入休眠状态,和信号量一样。
        第124行,在release函数中调用mutex_unlock函数释放mutex,这样其他应用程序就可以获取mutex了。
        第148行,在驱动入口函数中调用mutex_init初始化mutex。
        互斥体和二值信号量类似,只不过互斥体是专门用于互斥访问的。
3、编写测试APP
        测试APP使用48.1.1小节中的atomicApp.c即可,将7_atomic中的atomicApp.c文件到本例程中,并将atomicApp.c重命名为mutexApp.c即可。
48.4.2 运行测试
        1、编译驱动程序
        编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为mutex.o,Makefile内容如下所示:
示例代码48.4.2.1 Makefile文件
  1. 1  KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
  2. ......
  3. 4  obj-m :=mutex.o
  4. ......
  5. 11 clean:
  6. 12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码

        第4行,设置obj-m变量的值为mutex.o。
        输入如下命令编译出驱动模块文件:
  1. make-j32
复制代码

        编译成功以后就会生成一个名为“mutex.ko”的驱动模块文件。
        2、编译测试APP
        输入如下命令编译测试mutexApp.c这个测试程序:
  1. arm-linux-gnueabihf-gcc mutexApp.c -o mutexApp
复制代码

        编译成功以后就会生成mutexApp这个应用程序。
3、运行测试
        将上一小节编译出来的mutex.ko和mutexApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载mutex.ko驱动模块:
  1. depmod                                //第一次加载驱动的时候需要运行此命令
  2. modprobe mutex.ko        //加载驱动
复制代码

驱动加载成功以后就可以使用mutexApp软件测试驱动是否工作正常,测试方法和48.3.2中测试信号量的方法一样。
如果要卸载驱动的话输入如下命令即可:
  1. rmmodmutex.ko
复制代码

回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-20 02:40

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表