【干货】同步与互斥的失败例子
发布日期:2021-06-30 18:47:01 浏览次数:3 分类:技术文章

本文共 2978 字,大约阅读时间需要 9 分钟。

韦东山老师最新录制的驱动大全之<<同步与互斥>>收费视频已经在淘宝上架销售 ,一共7节,良心价29元,同时已经同步到CSDN , 51CTO , 电子发烧友,腾讯课堂等平台。

本文是其中一节《同步与互斥的失败例子》视频配套文档,

《同步与互斥的失败例子》免费试看版(2分钟,完整版7分46秒):

《同步与互斥的失败例子》完整版 购买链接:

https://item.taobao.com/item.htm?id=620987021249  (复制到浏览器打开)

光看文档可能难度稍大,建议结合视频进行学习;gg完毕,开始干货。

1.2 同步与互斥的失败例子

注意:本节在GIT上没有源码。

GIT地址:

git clone https://e.coding.net/weidongshan/01_all_series_quickstart.git

一句话理解同步与互斥:我等你用完厕所,我再用厕所。什么叫同步?就是条件不允许,我要等等。什么是互斥?你我早起都要用厕所,谁先抢到谁先用,中途不被打扰。

同步与互斥经常放在一起讲,是因为它们之间的关系很大,“互斥”操作可以使用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?有时候看代码更容易理解,伪代码如下:

01 void  抢厕所(void)02 {03    if (有人在用) 我眯一会;04    用厕所;05    喂,醒醒,有人要用厕所吗;06 }

假设有A、B两人早起抢厕所,A先行一步占用了;B慢了一步,于是就眯一会;当A用完后叫醒B,B也就愉快地上厕所了。

在这个过程中,A、B是互斥地访问“厕所”,“厕所”被称之为临界资源。我们使用了“休眠-唤醒”的同步机制实现了“临界资源”的“互斥访问”。

上面是一个有“味道”的例子,回到程序员的世界,一个驱动程序同时只能有一个APP使用,怎么实现?

1.2.1 失败例子1

01 static int valid = 1;0203 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file)04 {05      if (!valid)06      {07              return -EBUSY;08      }09      else10      {11              valid = 0;12      }1314      return 0; //成功15 }1617 static int gpio_key_drv_close (struct inode *node, struct file *file)18 {19      valid = 1;20      return 0;21 }22

看第5行,我们使用一个全局变量valid来实现互斥访问。这有问题吗?很大概率没问题,但是并非万无一失。

注意:编写驱动程序时,要有系统的概念,程序A调用驱动程序时,它可能被程序B打断,程序B也去调用这个驱动程序。下图是一个例子,程序A在调用驱动程序的中途被程序B抢占了CPU资源:

程序A执行到第11行之前,被程序B抢占了,这时valid尚未被改成0;程序B调用gpio_key_drv_open时,发现valid等于1,所以成功返回0;当程序A继续从第11行执行时,它最终也成功返回0;这样程序A、B都成功打开了驱动程序。

注意:在内核态,程序A不是主动去休眠、主动放弃CPU资源;而是被优先级更高的程序B抢占了,这种行为被称为“preempt”(抢占)。

1.2.2 失败例子2

上面的例子是不是第5行到第11行的时间跨度大长了?再优化一下程序行不行?代码如下:

01 static int valid = 1;0203 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file)04 {05      if (--valid)06      {07              valid++;08              return -EBUSY;09      }10      return 0;11 }1213 static int gpio_key_drv_close (struct inode *node, struct file *file)14 {15      valid = 1;16      return 0;17 }18

第5行先减1再判断,这样可以更大概率地避免问题,但是还是不能确保万无一失。对数据的修改分为3步:读出来、修改、写进去。请看下图:

进程A在读出valid时发现它是1,减1后为0,这时if不成立;但是修改后的值尚未写回内存;假设这时被程序B抢占,程序B读出valid仍为1,减1后为0,这时if不成立,最后成功返回;轮到A继续执行,它把0值写到valid变量,最后也成功返回。这样程序A、B都成功打开了驱动程序。

1.2.3 失败例子3

前面2个例子,都是在修改valid的过程中被别的进程抢占了,那么在修改valid的时候直接关中断不就可以了吗?

01 static int valid = 1;0203 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file)04 {05       unsigned long flags;06       raw_local_irq_save(flags); // 关中断07      if (--valid)08      {09              valid++;10              raw_local_irq_restore(flags);  // 恢复之前的状态11              return -EBUSY;12      }13       raw_local_irq_restore(flags);          // 恢复之前的状态14      return 0;15 }1617 static int gpio_key_drv_close (struct inode *node, struct file *file)18 {19      valid = 1;20      return 0;21 }

第06行直接关中断,这样别的线程、中断都不能来打扰本线程了,在它读取、修改valid变量的过程中无人打扰。没有问题了?

对于单CPU核的系统上述代码是没问题的;但是对于SMP系统,你只能关闭当前CPU核的中断,别的CPU核还可以运行程序,它们也可以来执行这个函数,同样导致问题,如下图:

假设CPU 0上进程A、CPU1上进程B同时运行到上图中读出valid的地方,它们同时发现valid都是1,减减后都等于0,在第07行判断条件都不成立,所以在第14行都可以返回0,都可以成功打开驱动。

推荐阅读:

    

    

    

嵌入式Linux

微信扫描二维码,关注我的公众号 

转载地址:https://linus.blog.csdn.net/article/details/108633338 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:用于MCU,基于FreeRTOS的micro(轻量级)ROS
下一篇:Linux内存管理slub分配器

发表评论

最新留言

做的很好,不错不错
[***.243.131.199]2024年04月09日 02时59分48秒