论Linux进程线程同步在嵌入式驱动开发中的重要性(基于模拟IIC乱码场景分析)——续集

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。

1 问题

在《论Linux进程/线程同步在嵌入式驱动开发中的重要性(基于模拟IIC乱码场景分析)》中最终采用了互斥锁的方案(方案三)。误码率降低了,但是并没有根治。


2 分析

压力12个小时后,结果表明读写EEPROM时确实还存在误码。明明以GPIO的Port为粒度增加了互斥锁,为什么还是出现误码呢?

2.1 理论分析

正确、快速且万无一失的思路应该是这样的(我这也是马后炮,一开始定位时确实走了一些弯路,静下心来后发现还是不要着急动手,理顺思路最最重要):

–>既然互斥锁在理论分析上是可以完全解决这个问题的,那就是因为锁的使用不当导致的 。

–>锁的使用不当包含两种情况:资源选择不当;临界区选择不当。

–> 首先,排查下锁定的是什么资源呢?是GPIO Port锁资源和相关的内存资源。这里的内存资源指的是在实现GPIO模拟IIC协议时用到的内存(变量)。分析到这里就发现,在资源的选取上确实有些不妥。虽然,在上一篇博客中我们解决了一个资源问题——锁Pin换成了锁Port。但是,从逻辑上来分析,资源是分层的:GPIO Port资源是属于GPIO驱动层的;模拟IIC协议使用到的资源是IIC协议层的。所以,互斥锁也是要分层的,也就是至少需要2把锁,一把锁GPIO Port,一把锁IIC总线。

Tips:当一个线程申请不止一把锁时一定要注意下述问题:当前使用的锁是否支持嵌套;锁的依赖关系;拿锁的顺序。以防止锁不生效或者引起死锁情况。

–>再次,排查临界区。有一个思路一定要转变过来,不是锁决定了临界区在哪儿,而是——资源。为什么这么说呢?因为虽然锁决定了临界区的边界,但资源决定了临界区本身,资源才是核心。所以,排查临界区不是全局搜索锁使用的地方,而是要搜索所有使用该资源的地方。针对本问题就是GPIO 的一个Port。

Tips:资源才是核心。一定要牢记这句话,不管是优先级还是时间片,亦或是锁等等概念,全部是围绕资源展开和衍生出来的,这和人类社会是一样的。举个简单的例子,为什么会有伊拉克战争?因为抢夺石油资源。有了战争,才有战斗机、才有坦克、航母、AK47,才有战略、战术……所有的一切都围绕资源展开,所以,核心是资源。

2.2 实际定位

如果按照上述思路进行操作,可以快速将问题发现并解决。可惜在真实的场景中,我们还是踩了两个小坑。

一个就是我对临界区的认知还停留在锁上面,所以,没有在进行临界区排查时,排查漏掉了一个地方。这个地方非常隐晦,是一个SPI控制器,居然使用了三个GPIO引脚来做片选(老单板遗留问题,代码兼容性没做好),而且并不是在读写函数中操作了这三个引脚,而是在初始化函数中。而恰恰初始化函数又在每次读写的时候被调用了,所以着也影响了问题定位过程中的判断。

Tips:从这个坑中我们可以吸取的经验教训是:

0)提升对概念的认知才能精准把控问题,当然,很多时候出现问题才能提升认知;

1)软件要参与硬件评审,不允许无谓的硬件资源重叠交叉情况存在;

2)在多款设备兼容修改时不要放过任何一个细节;

3)初始化就是初始化函数,初始化原则上只调用一次,避免多次调用,如果例外请更改函数名或添加警告说明。

另外一个坑就是,在定位过程中,我们发现有一个线程在使用GPIO Port时没有拿锁(属于临界区使用错误)。当挂起该线程时,问题确实有所缓解。于是,我们“似乎”掌握了一种好用的调试手段(就是从这里把我们带入了坑中,这个时候其实我已经想着去查找所有使用GPIO的代码了,但是,但是思路还是被打断了)。恰恰就在这个时候,测试人员又提供了一条线索(现在看来如果没有一个清晰的定位思路,线索太多也并非好事):挂起另外一个线程后,该问题就基本不复现了。这就进一步将我们引入了坑里(因为负责人啊、领导啊都来盯这个线程的问题),注意力一下就转移到这个线程上去了,关键这个线程还是别的组负责的线程。什么找人啊,沟通啊,甚至是扯皮啊,老司机们都懂哈~时间就这么一点点被拉长了。

兜了一大圈,最终才找到是“一个SPI控制器使用了三个GPIO引脚做片选,而且没拿锁” 捣的鬼。

Tips:

1)对于上面提到的“好的调试手段”,靠挂起线程来定位的方式,有时候对于定界是有帮助的,但对于定位公共函数或公共模块的问题绝对属于“远水解近渴”的行为。尤其是在线程使用并不规范的场景中(你都不知道哪些函数最终被哪个线程调用了)。

2)有了思路一定要写下来,防止因为加班、熬夜或者领导催促等等因素打乱自己的思路和节奏。


到这里还没结束,大多数人查到了问题就匆匆收尾庆祝了,但是有一个问题还需要深究,这样才能汲取更多的经验。

这个问题就是SPI控制器代码误操作了GPIO引脚怎么就影响到了EEPROM使用的IIC总线呢?

看过这个问题前传的同学还记得这个图吧

img

我查过两个线程相互影响的引脚确实在同一个Port上。我又查看了两个线程的优先级,发现访问“SPI和它绑定的三个引脚”的线程优先级高,而通过访问IIC总线访问EEPROM的线程优先级低。按照“后手优势”的理论,没道理IIC会受到影响啊,受到影响的应该是SPI(当然受到影响也表现不出来,因为那三个GPIO引脚在新板卡上是没有接东西的)。

分析到这里我的认知还局限在单核CPU的分析维度里,而我们用的处理器是8核心的。当我推理受到阻碍的时候,我意识到了这个问题,对,应该就是SMP导致的“后手优势”失效。我去查看了两个线程的亲和性,果然在两个核心上。而此前这两个线程是在同一个核心上的,由于该核业务压力太大,所以做了调整,从而将该问题暴露出来了。

到这里,这个问题才分析圆满。

Tips:
1)使用SMP系统时一定要注意思维不能仅仅局限在单核心的优先级上。因为优先级的概念也是因资源而生,多核心CPU的ALU资源变多了,所以,优先级的概念也要跟着扩充。优先级针对单个ALU资源才有意义。
2)线程的亲和性千万不要随意或经常变动;如果因调整亲和性引发了问题,记得去检查锁的使用情况是否符合SMP的使用规范。

3 解决

分析和定位透彻了,解决从来都不是问题。

解决方案如下:

1.将新平台上SPI附加的控制三个GPIO引脚的代码去除。

2.加两层锁:IIC总线加一层;GPIO驱动加一层。


4 复盘

4.1 定位思路总结

虽然文中一直在以竞态和同步为主线分析和解决问题,但是原始的问题本身是EEPROM读写问题。所以,为了完整性也将EEPROM定位过程中遇到的问题一并总结如下:

1.如果是多核、多进程/线程系统,检查是否加锁了,锁加的对不对。
(1) 锁定的资源划分是否合理
比如GPIO锁定的是引脚(Pin)还是端口(Port)。
(2)锁定的资源临界区选取是否合理
比如要求加锁的位置太靠上层,是否会导致有人忘记加锁。
(3) 是否有些模块忘记加锁
比如压根儿就没加锁。
再比如,一个模块依赖了两个资源,但是只加了一把锁。

2.检查是否配置成开漏模式。
3.检查是否有写保护。
4.检查换页处逻辑处理是否有问题。
5.检查SDA线是否有死锁情况。
6.检查时延问题
(1)检查帧与帧之间的时延是否满足要求。
(2)检查ACK等待时延是否满足要求。
7.确认IIC时序是否正确。

4.2 理论分析与实践相结合

从本例中可以看出,如果理论分析完美无缺确实可以分分钟定位一个“看不见摸不着”的问题。但实际定位问题时会受到心态、环境、时间等因素的影响,往往做不到很完美。因为,完美的理论也需要从实践中得来(俗话说马后炮,哈哈)。但是,马后炮打多了,练熟了,再次遇到这个问题时,就可以分分钟将小强斩杀。这就是所谓的经验。


<完>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿越临界点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值