邮箱与信号量相关问题

时间:2021-03-02 20:04:58

嵌入式操作系统也会提供邮箱和信号量,用作进程同步、临界区保护、简单的消息传递等。在使用过程中如果设计不当会发生许多问题,本文列举了我在调试过程中遇到的一些相关案例。

1.增减操作不匹配

C6455的SRIO接收到数据时在中断里抛出一个邮件,然后在读函数中等待邮件,当收到邮件时读取一包数据。运行一段时间后发现当系统负载变大时,会有一定概率出现淤包,即接收方很长时间后才能收到数据,有时候停止收发后发现接收方接收包计数小于发送方发送包计数,即有若干包数据未收到。

首先想知道线路上是否有丢包现象。将发送数据包头加入序号,在接收中断中判断数据包头是否连续。结果未发现中间有丢包,并且发送方最后一包在接收中断中也能找到。因此丢包发生在从中断向上层提交的过程中。
继续在读函数中加入对包序号的判断,发现也是连续的,但最后缺少了几包。这进一步证实了上面的猜测。
然后怀疑最后几包是否被覆盖或丢失,通过仿真器跟踪内存中的包,最后发现数据还在内存中,只是未被读走。
再分析接收逻辑,最后发现是pend与post的个数不匹配导致的。

实际上SRIO线上数据会有突发,此时接收模块会收到连续多包数据,为了避免频繁中断CPU导致的性能下降,该模块被设计为间隔一定时间才中断CPU一次。但驱动中对多个数据包只抛送了一个邮件,而接收函数此时会认为只有一个数据包,而下一包数据只有在下一次中断到来时才会处理,累积下来最后就会有最后的若干包数据得不到处理。

这实际上属于对邮件的含义定位不清。中断认为一个邮件代表有数据到来,而接收函数认为一个邮件代表一个数据包到来。修改方法就是对邮件定义明确,要么在中断中抛出多个邮件,要么在接收函数中一次读多个包。最后的修改方案选择了前者,再测试该问题不再出现。
后来在C6455的HPI接口驱动中也发现了类似的问题,也用同样的方法做了修复。

2.邮箱溢出

在创建邮箱时要指定其中能容纳的最大邮件个数,若运行中累积的邮件个数超出指定大小,则根据抛邮件方式,调用进程可能阻塞,也可能发生邮件丢失。
继续上一个案例,修改后SRIO的一包对应一个邮件,但事实上多个接口共享同一个邮箱,因为设计时未考虑邮箱个数问题,在后来一次线上有突发数据时邮箱溢出,邮件丢失,同样导致了消息包淤积。
最终把邮箱个数设计为每个接口能接收的最大包数之和,避免了溢出事件的发生。

3.关中断导致漏抛邮件

在IPC的使用中,接收方每收到一个中断抛出一个邮件。但是IPC只能每个中断对应一个邮件,这样如果数据过于频繁,则在中断处理过程中的多个数据包只能引发一次中断。虽然事实上在一次IPC中断处理过程中很难有多包数据同时到来,但若在长时间关中断时有可能遗漏中断。目前该问题尚未找到一个好的解决方案,只能通过尽量少关中断避免。

4.二值与计数信号量

信号量分为二值与计数两种。二值信号量只表示有无资源,计数信号量还可以表示资源个数。
某次使用计数信号量,由于一个设计问题导致pend函数得不到执行,而另一个进程在不断的post,一段时间后系统崩溃。最后跟踪发现原因是在SYS/BIOS中信号量计数最大值默认是65535,当信号量只增不减时超出最大值会导致溢出。而多次抛出二值信号量则不会出现溢出问题。因此在使用时需要仔细选择信号量类型。

5.死锁

当使用邮箱或信号量来实现临界区保护时,很容易出现死锁现象。这个不需要再举例了,避免死锁的方法就是破坏死锁产生的四个必要条件之一即可。

6.非法上下文阻塞

某次系统在运行过程中有一定机率崩溃,尤其当系统负载较高时很容易发生。从现象上看呼吸灯不闪,网络也ping不通,业务也运行不正常。幸运的是这个问题可复现。
可复现的问题通常都较容易解决,但一开始怀疑是死锁,但经过仔细分析排除了问题,后来架上仿真器运行时发现系统有崩溃打印信息,提示”ti.sysbios.knl.Semaphore: line 204: assertion failure: A_badContext: bad calling context.”
顺着这个信息就很容易定位了,事实上这是一个在软中断中调用了Semaphore_pend()操作引起的问题。

在硬中断、软中断等上下文中,是禁止调用阻塞函数的,否则会导致崩溃或挂死。显式的错误很容易发现,这里的调用是隐式的。

某通道的发送缓冲区是公共资源,被多个进程使用,为此使用计数信号量做资源保护,每个信号量代表一个发送缓冲包。每次发送函数里先要申请信号量,如果缓冲区有剩余,则该操作立即返回,这时可以放心取得缓冲区并放到发送队列中,待发送完成后再释放信号量。此设计中发送函数是阻塞函数。

该发送函数再次经过了一层包装以实现与其他通道的接口统一,在经过包装后很容易遗忘这个函数的阻塞属性,并且在系统负载不高时它也不会阻塞。某次在加入新功能时,在一个定时器的超时回调函数内对某种异常做的处理是给主控模块发送一条消息,用到了这个阻塞的发送函数。由于定时器是软中断,因此其回调函数仍是在软中断上下文中。如果进入了该异常分支且发送资源不足,就会引发该问题。