1 事故背景
- 人物:小T(研发中心-操作系统开发工程师);小S(产品开发部-软件工程师)
- 背景:公司正在联合开发基于MIPS架构的产品。研发中心负责操作系统平台开发,产品开发部负责业务逻辑开发。目前操作系统已经进入试用阶段。
2 事故现场
操作系统上线后,一直比较稳定。小T泡上一杯茶,正在浏览着天下大事,心里那个美啊!
“叮铃铃...叮铃铃...”,小T不仅被电话铃声吓了一跳,“谁啊,这么烦”,心里不禁咒骂了一句,不情愿地拿起了桌上的电话,“您好,请问哪位?”。
“小T,我是小S啊!出大事了,咱们的系统网络通信任务消失了,但是系统还在正常跑,其它任务也能工作。是不是咱们的操作系统出问题了啊?”听得出,小S现在是心急如焚。
听到这里,小T额头上的汗珠都下了。操作系统不能正常工作,这可是重大事故啊,弄不好又得被领导批,哎,“别急,把当时的工程代码发给我,我分析看看”小T对小S说。
拿到测试工程,加载运行。若干时间后,网络服务器与客户端的通信自动停止了。
3 确定问题
既然是网络任务不正常,那就先从网络开始分析吧!
首先,分析测试工程中对socket套接字使用;然后是,监控网络协议栈中的数据收发过程;最后是网络驱动程序的数据收发。然而,一无所获。
难道是进程调度算法有问题?于是,小T打印了系统运行中所有任务的调度过程,发现除了网络任务之外都是正常的,网络任务停止的时候,scheduler()调度器中确实没有了相关的进程信息。那说明,调度算法也没啥问题啊。到底是哪里的问题呢?
小T毕竟是一个有着过硬心理素质的人,怎么可能被这点问题吓到呢。他的信条就是,“没有解决不了的bug,如果有,肯定是遗漏了什么重要线索。”
于是,从头梳理。采用最简单粗暴的方式,在网络任务里采用标记法,确认任务停止调度的时候到底停在了哪里。通过打印信息发现,每次都停在了网络数据包(64K)进行CRC32校验的过程中。继续检查CRC32校验算法(本身算法已经用了好多年,不会出问题)。最终发现CRC校验算法中使用了除法/
指令,计算需要计算CRC值的次数,正常的时候这个值是正确的;停止调度的时候,这个值是一个巨大的数值。本身这个值会被作为索引访问数组,导致越界,破坏了任务堆栈。
终于找到了根源,就好办了。小T查阅MIPS架构的除法指令,找到了这么一段内容:
A computed result written to the
HI/LO
pair byDIV
,DIVU
,MULT
, orMULTU
must be read byMFHI
orMFLO
before a new result can be written into eitherHI
orLO
. If anMTLO
instruction is executed following one of these arithmetic instructions, but before anMFLO
orMFHI
instruction, the contents of HI are UNPREDICTABLE.The following example shows this illegal situation:
MUL r2,r4 # start operation that will eventually write to HI,LO ... # code not containing mfhi or mflo MTLO r6 ... # code not containing mfhi MFHI r3 # this mfhi would get an UNPREDICTABLE value
上面翻译成中文就是:
DIV
、DIVU
、MULT
、MULTU
等乘除法指令,会将计算结果写入HI/LO
这一对寄存器中,在下次做乘除法之前,必须使用指令MFHI
和MFLO
将结果取走。如果在乘除算术指令和MFHI
和MFLO
指令之间,调用MTLO
指令写LO
寄存器,那么HI
寄存器的内容是不可预测的。下面是非法情况的一个示例
MUL r2,r4 # r2和r4相乘,结果写入HI、LO ... # 此处没有调用mfhi和mflo MTLO r6 # 将r6的值写入LO ... # 此处没有调用mfhi MFHI r3 # r3的值不可预测
这就是CRC32校验中,计算的次数为什么会出现一个巨大值的原因:多任务系统中,调用乘除法指令的地方发生冲突,如果进程上下文切换中,没有保护HI/LO
寄存器的内容,就会发生不可预料的结果。
小T马上review进程上下文切换的代码,发现确实只保护了通用寄存器,而忽略了这个特殊寄存器。
......(敲击键盘的清脆声,此起彼伏)
修改完代码,下载程序,启动可靠性测试后。小T陷入了深深的反思之中,暗暗懊悔自己的粗心大意,对文档的似是而非。