专栏算法工具链记录一次"Can_have_no_input"诊断问题分析过程

记录一次"Can_have_no_input"诊断问题分析过程

Oops2024-03-27
107
0

1. 问题背景

Description

如上图为某项目中SPI数据通路示意图,MCU通过SPI每隔10ms给J5发送一帧数据,J5侧SPI驱动接收到数据后,会按地平线定义的SPI协议将数据缓存在内核空间,上层应用程序(vechleio)通过libspihal.so动态库将SPI数据从内核空间读到用户空间,最后通过zmq将数据发送给其它进程。

问题现象
在系统运行的过程中,会偶现"Can_have_no_input"诊断错误,该诊断错误的含义表示vechleio进程在100ms内没有接收到MCU发送的SPI数据,从而导致数据接收超时,进而报出该诊断错误。

2. 问题分析

从SPI的数据通路上来看,如果出现上层接收数据超时,可能的原因有如下几点:

  • MCU本身数据发送慢

  • MCU数据发送正常,但J5的SPI驱动接收数据慢

  • J5的SPI驱动数据接收正常,但用户空间从内核空间读取数据慢

分析了可能出问题的点,那么下一步就开始进行问题复现。本着先怀疑自己的态度,先从下面两个问题点入手,进行问题复现。

3. 问题排查

  • 最开始在实验室进行复现,但一连跑了几天都没有问题,没办法只能去实车进行复现。

  • 实车资源也是有限,只能趁周末测试同学空闲的时间去使用,但好消息是问题终于复现了。

先补充点背景信息
Description

如上图为整个SPI数据获取过程中的线程关系图。

  1. 首先应用程序进行数据读取会有一个read线程,当内核如果没有数据时,read线程就会wait在一个信号量上。

  2. 此外在spidev字符设备层有一个传输线程,用于从SPI驱动读取数据或向SPI驱动发送数据。

  3. SPI框架层于对数据传输的实现又包含两种,同步和异步方式,如果是同步方式侧直接由SPI transfer线程与驱动交互,如果是异步方式则SPI transfer线程会先把任务提交到框架层的队列上,再由框架层的工作线程进行处理;但不管是同步还是异步方式,其都在wait在框架层的complete上。

  4. 为什么要wait在complete上呢,因为要等驱动把数据传输完成,这里由于使用dma进行数据搬运也就是说要等dma完成后,通过dma的callback函数把框架层的工作线程给唤醒。

了解了线程关系,我们接着唠。

  • 首先分析到vechleio中的read线程是一个普通信息,怀疑当它被唤醒时由于调度策略与优先级太低,导致唤醒不及时,因此出现数据接收超时的情况,后来提高read线程的优先级并把调度方式改为FIFO,依然可以复现问题。

  • 继续分析,read线程的调度延时解决了,但是spi transfer线程还是普通(cfs)的调度策略,因此可能是该线程唤醒不及时从而拖慢了整个数据接收过程,又对spi transfer线程的调度策略及优先级进行修改,发现也不能解决问题。

  • 没办法那继续分析,难道是SPI框架层的aync线程优先级不够?修改后验证依然没有效果(后面分析和async线程没有关系,因此在J5上使用的是同步方式,这个异步线程压根就没有用到)。

  • 那最后只能怀疑是dma callback函数调度不及时了,因为dma callback是在tasklet中执行的,dma完成传输后中产生一个中断,在中断下半部(tasklet)中会调用callback函数来唤醒框架层线程,但是tasklet的调度策略和优先级是不好修改的,没办法了上ftrace。

  • 通过ftrace可以获取系统中进程的调度信息,进而分析出是什么原因导致read线程被block太长时间的,但是又遇到了新的问题,升级编译好的版本,在实车上并不能复现问题,这是什么情况????

  • 开始以为是某笔提交把这个问题意外修复了,但回退版本后,依然不能复现问题。难道是镜像问题?不得以请相关同学制作了一个完整版本,果然问题复现了,赶紧抓trace。最终的trace大概长成下面的样子。

Description
  • 通过分析trace信息,发现read线程在执行期间被切换出去执行另一个进程,但是该进程在执行期间,所处的核没有任何调度动作(正常情况下该核调度很频繁),好像"死"掉一样。当该进程执行完毕后,该核恢复正常。破案了,原来是"你小子"再搞鬼,占着cpu不释放,但是为什么会占用这么长时间呢?并且为什么此期间没有任何进程调度呢?

  • 继续分析发现该进程是一个与加密相关的进程,为了保证信息安全,加密的过程是在EL3中实现的,由于kernel是在EL1中运行的,当在EL3中执行加密操作,kernel的代码不会再执行,这也就解释了为什么长时间没有进程调度的现象;另外由于加密操作在EL3中执行时间过长,导致EL1(kernel)中原有的任务得不到执行,这也是为什么read线程执行超时的根因。

  • 问题根因找到了,解法就比较简单,优化加密操作在EL3中的执行时间,最终将该问题解决。

4. 问题总结

  • 首先要找到能够稳定复现问题的方法或者能够加速问题复现的路径,一般情况要收集出问题的版本,出问题时的场景,出问题前的操作等信息。

  • 然后按系统,进程,线程,代码位置的方式逐渐缩小问题范围。

  • 当陷入僵局或者没有思路时,可以找一些人进行讨论,头脑风暴一下。

算法工具链
社区征文技术深度解析征程5杂谈
+3
评论0
0/1000