1 故障现象
系统比较空闲时(cpu占用率为20%左右),系统死循环检测机制检测到存在任务处于死循环状态。但是,系统明显比较空闲,也就是说系统死循环检测机制发生了误判。为了分析这个故障,首先要了解死循环检测机制的工作原理。
2 死循环检测机制
每隔一分钟,统计一次系统中所有任务的cpu占用率,如果一分钟之内统计到某个任务cpu占有率超过给定的阈值,该条件作为该任务作为死循环发生的一个必要条件。另外,如果比该任务优先级低的任务没有得到调度,并且系统的消息队列没有发生变化,只有这三个条件同时成立时,才认为发生了死循环。而且,需要重复命中两次才可以确认死循环的发生。
这种实现机制存在的缺陷是优先级很低的任务发生死循环的时候,不能够检测到。这是由于发生死循环的任务优先级比较低,总是被高优先级的任务打断,导致发生死循环的任务执行的时间没有达到阈值或者只有其中的一次达到阈值之后,第二次进行重复命中确认的时候,运行时间又没有达到阈值,最终系统认为该任务没有发生死循环。虽然死循环检测机制存在这种缺陷,但是只要确定一个最低优先级死循环能够被检测到,比这个优先级更低的任务即使发生了死循环,也不会影响系统的运行。
根据目前的故障现象,检测到了不存在的死循环,作为死循环的必要条件,任务执行的时间必须达到设定的阈值。也就是说很可能是任务cpu占有率统计出了问题。
3 统计任务的cpu占有率
死循环检测机制统计任务cpu占有率的数据源是/proc/statp文件。而/proc/statp通过读取task_struct的sum_exec_runtime值来计算每个任务的执行时间。由于sum_exec_runtime的时钟精度是纳秒级别的,而循环检测机制的时钟精度提是毫秒级别的(以tick为单位),因此需要sum_exec_runtime的值转换成tick。转换虽然会丢失精度,但是,这个时间相对来说还是要比较准确的。下面看一下statp的实现代码:
static intstatp_show(struct seq_file *m, void *v)
{
struct task_struct *task;
read_lock(&tasklist_lock);
for_each_process(task) {
seq_printf(m, "%d %lu %lu %lu\n",
task->pid ,(unsigned long)nsec_to_clock_t(task->se.sum_exec_runtime),
task->nvcsw + task->nivcsw ,task->rt_priority);
}
read_unlock(&tasklist_lock);
return 0;
}
内核在每个进程发生切换的时候,内核都会更新rq的时钟,同时会计算task->se.sum_exec_runtime的值,代码如下所示:
#ifndef USE_FAST_SCHE_MIPS4KEC
__update_rq_clock(rq);
#else
__update_rq_clock_fast(rq);
#endif
走查代码,运行队列rq的时钟更新,都是通过sched_clock来完成的。通过走查代码,没有发现有异常的地方。在shedule中加打印,将每个任务的执行时间都打印出来,对比在一个时间段里面,每个任务执行时间的走势,发现,在一定的时间间隔内,总是存在某个任务的运行时间发生跳变,该任务的执行时间突然会变的很高,甚至是超过了死循环检测设定的阈值。至此,死循环检测机制误判的原因已经找到,正是由于任务的执行时间发生跳变,导致任务执行的时间超过设定的阈值,死循环检测机制认为该任务处于死循环状态了。但是,为什么任务的运行时间会发生跳变呢?这个问题暂时还不清楚,需要继续分析。
定位故障过程中,发现项目中有一个辅助时钟,每隔5ms就会产生一次中断,用来处理业务相关的流程。如果,将该辅助时钟关闭或者将其产生中断的时间设置超过1个tick的时间,死循环检测机制就能正常工作。因此,可以确定任务运行时间发生跳变与辅助时钟周期有关。
进一步分析内核统计进行运行时间的过程,中断发生,会将中断的时间统计到任务的运行时间中。猜测存在一种概率,这个概率是跟rq队列时钟精度相关的,如果rq队列的时钟精度比辅助时钟的精度要高,在一定的时序中,中断时间更容易被平均到其他任务,命中到同一个任务的概率会减少,反之,如果rq的时钟精度比辅助时钟的精度要低,中断命中到同一个任务的概率会增加。按照这个猜测,当辅助时钟周期设置为5ms时,该时钟周期比rq时钟钟中断周期短,只有0.5 tick,这样在一个固定的时序内,容易发生跳变,增大辅助时钟的周期之后,发生跳变的概率减少,几乎就不发生了。
按照上面的猜测,将中断时间统计下来,在计算任务运行时间时,将中断时间去除,理论上如果是中断时间影响了任务运行时间的统计,那么去除中断时间之后,跳变将会被消除。但是,事与愿违,即使把中断时间扣除,还是会存在跳变,从实验的结果上看,中断时间对任务运行的时间影响非常小。
虽然,任务运行时间跟中断时间没有关系,会不会是辅助时钟中断影响了系统的时序呢?另外,根据辅助时钟的周期,很可能rq的精度只有tick级别,而没有达到纳秒级别。按照这个思路,把内核源码中所有跟sched_clock相关的实现都加上打印,确定当前系统中所使用的sched_clock的实现。根据打印信息,发现,我们的系统中,mips 架构 sched_clock是通过jiffies实现方案时,也就是说精度只有tick。
由于统计精度的问题,会导致运行时间不足1 tick任务不能统计到运行的时间。一旦有多个进程的运行时间不足一个tick,就会导致进程的运行时间统计不准确。因此,需要提高进程的运行统计时钟的精度。
对应arm和ppc来说,sched_clock函数获取一个高精度的统计时间,对于mips来说,由于没有实现sched_clock函数,就采用了默认的实现,而默认的实现方案是采用jiffies来实现的。改进的方案就是通过mips的c0寄存器,将该寄存器的值读取出来,作为高精度定时器,修改之后验证该任务没有发生跳变。