• Linux学习第19天:Linux并发与竞争实例: 没有规矩不成方圆


    Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长


            先说点题外话,上周参加行业年会,停更了一周。接下来的周五就要开启国庆中秋双节模式,所以有的时候,尤其是工作以后想要好好的学习点东西,是多么的难。并且学习必须是一个连续的过程。大家可能会有这样的体会,本来做研发的过程中,突然临时领导分配其他的工作,并且要立马去做的。当集中几天精力完成了领导的任务再回头去看原来手里的工作时,发现有很多东西都记不太清了。这也是我很头疼的一个问题,大家有好的建议或是处理方法可以评论区给我留言。


            本篇笔记主要是学习并发和竞争的实例,主要内容包括原子操作、自旋锁操作、信号量和互斥体

            本笔记的思维导图很简单,如下:

    一、原子操作

            本节要实现的功能是利用原子操作来实现对LED设备的互斥操作。

    1. 32 /* gpioled 设备结构体 */
    2. 33 struct gpioled_dev{
    3. 34 dev_t devid; /* 设备号 */
    4. 35 struct cdev cdev; /* cdev */
    5. 36 struct class *class; /* 类 */
    6. 37 struct device *device; /* 设备 */
    7. 38 int major; /* 主设备号 */
    8. 39 int minor; /* 次设备号 */
    9. 40 struct device_node *nd; /* 设备节点 */
    10. 41 int led_gpio; /* led 所使用的 GPIO 编号 */
    11. 42 atomic_t lock; /* 原子变量 */
    12. 43 };

            第 42 行,原子变量 lock,用来实现一次只能允许一个应用访问 LED 灯, led_init 驱动入口函数会将 lock 的值设置为 1。

    1. 54 static int led_open(struct inode *inode, struct file *filp)
    2. 55 {
    3. 56 /* 通过判断原子变量的值来检查 LED 有没有被别的应用使用 */
    4. 57 if (!atomic_dec_and_test(&gpioled.lock)) {
    5. 58 atomic_inc(&gpioled.lock);/* 小于 0 的话就加 1,使其原子变量等于 0 */
    6. 59 return -EBUSY; /* LED 被使用,返回忙 */
    7. 60 }
    8. 61
    9. 62 filp->private_data = &gpioled; /* 设置私有数据 */
    10. 63 return 0;
    11. 64 }

            第 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。

            上面一段说的可能有点绕,总的意思就是要想要先申请

    1. 111 * @description : 关闭/释放设备
    2. 112 * @param – filp : 要关闭的设备文件(文件描述符)
    3. 113 * @return : 0 成功;其他 失败
    4. 114 */
    5. 115 static int led_release(struct inode *inode, struct file *filp)
    6. 116 {
    7. 117 struct gpioled_dev *dev = filp->private_data;
    8. 118
    9. 119 /* 关闭驱动文件的时候释放原子变量 */
    10. 120 atomic_inc(&dev->lock);
    11. 121 return 0;
    12. 122 }

            第 120 行, LED 灯使用完毕,应用程序调用 close 函数关闭的驱动文件, led_release 函数执行,调用 atomic_inc 释放 lcok,也就是将 lock 加 1。

    1. 142 /* 初始化原子变量 */
    2. 143 atomic_set(&gpioled.lock, 1); /* 原子变量初始值为 1 */

            第 143 行,初始化原子变量 lock,初始值设置为 1,这样每次就只允许一个应用使用 LED
    灯。

            在测试的APP中有如下程序:

    1. 62 /* 模拟占用 25S LED */
    2. 63 while(1) {
    3. 64 sleep(5);
    4. 65 cnt++;
    5. 66 printf("App running times:%d\r\n", cnt);
    6. 67 if(cnt >= 5) break;
    7. 68 }

            第 63~68 行的模拟占用 25 秒 LED 的代码。测试 APP 在获取到 LED 灯驱动的使用权以后会使用 25S,在使用的这段时间如果有其他的应用也去获取 LED 灯使用权的话肯定会失败!

    二、自旋锁

            自旋锁保护的临界区要尽可能的短。

            考虑驱动的兼容性,合理的选择 API 函数。

    1. 33 /* gpioled 设备结构体 */
    2. 34 struct gpioled_dev{
    3. 35 dev_t devid; /* 设备号 */
    4. 36 struct cdev cdev; /* cdev */
    5. 37 struct class *class; /* 类 */
    6. 38 struct device *device; /* 设备 */
    7. 39 int major; /* 主设备号 */
    8. 40 int minor; /* 次设备号 */
    9. 41 struct device_node *nd; /* 设备节点 */
    10. 42 int led_gpio; /* led 所使用的 GPIO 编号 */
    11. 43 int dev_stats; /* 设备状态, 0,设备未使用;>0,设备已经被使用 */
    12. 44 spinlock_t lock; /* 自旋锁 */
    13. 45 };

            第 43 行, dev_stats 表示设备状态,如果为 0 的话表示设备还没有被使用,如果大于 0 的
    话就表示设备已经被使用了。第 44 行,定义自旋锁变量 lock。

    1. 56 static int led_open(struct inode *inode, struct file *filp)
    2. 57 {
    3. 58 unsigned long flags;
    4. 59 filp->private_data = &gpioled; /* 设置私有数据 */
    5. 60
    6. 61 spin_lock_irqsave(&gpioled.lock, flags); /* 上锁 */
    7. 62 if (gpioled.dev_stats) { /* 如果设备被使用了 */
    8. 63 spin_unlock_irqrestore(&gpioled.lock, flags); /* 解锁 */
    9. 64 return -EBUSY;
    10. 65 }
    11. 66 gpioled.dev_stats++; /* 如果设备没有打开,那么就标记已经打开了 */
    12. 67 spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */
    13. 68
    14. 69 return 0;
    15. 70 }

            第 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。

    1. 121 static int led_release(struct inode *inode, struct file *filp)
    2. 122 {
    3. 123 unsigned long flags;
    4. 124 struct gpioled_dev *dev = filp->private_data;
    5. 125
    6. 126 /* 关闭驱动文件的时候将 dev_stats 减 1 */
    7. 127 spin_lock_irqsave(&dev->lock, flags); /* 上锁 */
    8. 128 if (dev->dev_stats) {
    9. 129 dev->dev_stats--;
    10. 130 }
    11. 131 spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */
    12. 132
    13. 133 return 0;
    14. 134 }

            第 126~131 行,在 release 函数中将 dev_stats 减 1,表示设备被释放了,可以被其他的应用程序使用。将 dev_stats 减 1 的时候需要自旋锁对其进行保护。

    1. 150 static int __init led_init(void)
    2. 151 {
    3. 152 int ret = 0;
    4. 153
    5. 154 /* 初始化自旋锁 */
    6. 155 spin_lock_init(&gpioled.lock);
    7. ......
    8. 212 return 0;
    9. 213 }

            第 155 行,在驱动入口函数 led_init 中调用 spin_lock_init 函数初始化自旋锁。


    三、信号量

            使用信号量实现了一次只能有一个应用程序访问 LED 灯,信号量可以导致休眠,因此信号量保护的临界区没有运行时间限制,可以在驱动的 open 函数申请信号量,然后在release 函数中释放信号量。但是信号量不能用在中断中。

    14 #include 
    1. 33 /* gpioled 设备结构体 */
    2. 34 struct gpioled_dev{
    3. 35 dev_t devid; /* 设备号 */
    4. 36 struct cdev cdev; /* cdev */
    5. 37 struct class *class; /* 类 */
    6. 38 struct device *device; /* 设备 */
    7. 39 int major; /* 主设备号 */
    8. 40 int minor; /* 次设备号 */
    9. 41 struct device_node *nd; /* 设备节点 */
    10. 42 int led_gpio; /* led 所使用的 GPIO 编号 */
    11. 43 struct semaphore sem; /* 信号量 */
    12. 44 };

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

    1. 48 /*
    2. 49 * @description : 打开设备
    3. 50 * @param – inode : 传递给驱动的 inode
    4. 51 * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
    5. 52 * 一般在 open 的时候将 private_data 指向设备结构体。
    6. 53 * @return : 0 成功;其他 失败
    7. 54 */
    8. 55 static int led_open(struct inode *inode, struct file *filp)
    9. 56 {
    10. 57 filp->private_data = &gpioled; /* 设置私有数据 */
    11. 58
    12. 59 /* 获取信号量,进入休眠状态的进程可以被信号打断 */
    13. 60 if (down_interruptible(&gpioled.sem)) {
    14. 61 return -ERESTARTSYS;
    15. 62 }
    16. 63 #if 0
    17. 64 down(&gpioled.sem); /* 不能被信号打断 */
    18. 65 #endif
    19. 66
    20. 67 return 0;
    21. 68 }

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

    1. 114 /*
    2. 115 * @description : 关闭/释放设备
    3. 116 * @param – filp : 要关闭的设备文件(文件描述符)
    4. 117 * @return : 0 成功;其他 失败
    5. 118 */
    6. 119 static int led_release(struct inode *inode, struct file *filp)
    7. 120 {
    8. 121 struct gpioled_dev *dev = filp->private_data;
    9. 122
    10. 123 up(&dev->sem); /* 释放信号量,信号量值加 1 */
    11. 124
    12. 125 return 0;
    13. 126 }

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

    1. 142 static int __init led_init(void)
    2. 143 {
    3. 144 int ret = 0;
    4. 145
    5. 146 /* 初始化信号量 */
    6. 147 sema_init(&gpioled.sem, 1);
    7. ......
    8. 204 return 0;
    9. 205 }

            第 147 行,在驱动入口函数中调用 sema_init 函数初始化信号量 sem 的值为 1,相当于 sem
    是个二值信号量。

    四、互斥体

    1. 33 /* gpioled 设备结构体 */
    2. 34 struct gpioled_dev{
    3. 35 dev_t devid; /* 设备号 */
    4. 36 struct cdev cdev; /* cdev */
    5. 37 struct class *class; /* 类 */
    6. 38 struct device *device; /* 设备 */
    7. 39 int major; /* 主设备号 */
    8. 40 int minor; /* 次设备号 */
    9. 41 struct device_node *nd; /* 设备节点 */
    10. 42 int led_gpio; /* led 所使用的 GPIO 编号*/
    11. 43 struct mutex lock; /* 互斥体 */
    12. 44 };

            第 43 行,定义互斥体 lock。

    1. 55 static int led_open(struct inode *inode, struct file *filp)
    2. 56 {
    3. 57 filp->private_data = &gpioled; /* 设置私有数据 */
    4. 58
    5. 59 /* 获取互斥体,可以被信号打断 */
    6. 60 if (mutex_lock_interruptible(&gpioled.lock)) {
    7. 61 return -ERESTARTSYS;
    8. 62 }
    9. 63 #if 0
    10. 64 mutex_lock(&gpioled.lock); /* 不能被信号打断 */
    11. 65 #endif
    12. 66
    13. 67 return 0;
    14. 68 }

            第 60~65 行,在 open 函数中调用 mutex_lock_interruptible 或者 mutex_lock 获取 mutex,成功的话就表示可以使用 LED 灯,失败的话就会进入休眠状态,和信号量一样。

    1. 119 static int led_release(struct inode *inode, struct file *filp)
    2. 120 {
    3. 121 struct gpioled_dev *dev = filp->private_data;
    4. 122
    5. 123 /* 释放互斥锁 */
    6. 124 mutex_unlock(&dev->lock);
    7. 125
    8. 126 return 0;
    9. 127 }

            第 124 行,在 release 函数中调用 mutex_unlock 函数释放 mutex,这样其他应用程序就可以
    获取 mutex 了。

    1. 143 static int __init led_init(void)
    2. 144 {
    3. 145 int ret = 0;
    4. 146
    5. 147 /* 初始化互斥体 */
    6. 148 mutex_init(&gpioled.lock);
    7. ......
    8. 205 return 0;
    9. 206 }

            第 148 行,在驱动入口函数中调用 mutex_init 初始化 mutex。

    五、总结

            本片笔记主要是巩固上次课学习的并发和竞争相关内容,主要包括原子操作、自旋锁、信号量和互斥体。


    本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

  • 相关阅读:
    虚拟机macos安装brew、llvm并使用cmake构建项目
    Linux高负载排查最佳实践
    老卫带你学---leetcode刷题(221. 最大正方形)
    Linux线程池和其他锁
    Linux搭建elasticsearch-7.8.0单机版本
    国标GB28181协议客户端开发(三)查询和实时视频画面
    移远EC600U-CN开发板 day03
    SFI立昌Ultra Low Capacitance方案与应用
    数据库表的基本操作
    java计算机毕业设计空闲教室查询系统MyBatis+系统+LW文档+源码+调试部署
  • 原文地址:https://blog.csdn.net/jiage987450/article/details/133279749