在嵌入式开发中,出现freeze的问题很让人头疼,就是那种不发生kernel panic,什么log不输出,串口终端死掉,无法输入输出。这时候,肯定是哪里出现了死锁或者死循环,长时间占用CPU资源,导致其他进程无法运行。
比如说,在底层的USB Host控制器驱动中,设置完某个控制寄存器,然后用while来检查状态寄存器的某一位是否满足条件。如果该控制器本身出现了问题,那么,将会一直卡在while中无法退出,此时就会出现freeze。
还有,我们有时候会看到watchdog/0
进程的CPU占用率非常高,此时也有可能是哪个循环语句占用太长的时间导致的。
以下的内容参照:http://blog.csdn.net/luckyapple1028/article/details/51932414
这篇文章帮助很大,在此表示感谢。
lockup watchdogs detector简介
关于lockup watchdogs detector
,可以参照:Documentation/lockup-watchdogs.txt
文章。Linux Kernel中有个看门狗,用于检测死锁问题,包括softlockup
和hardlockup
。softlockup
的介绍如下:
A
softlockup
is defined as a bug that causes the kernel to loop in kernel mode for more than 20 seconds (seeImplementation
below fordetails), without giving other tasks a chance to run.
The current stack trace is displayed upon detection and, by default, the system will stay locked up. Alternatively, the kernel can be configured to panic;
a sysctl,kernel.softlockup_panic
, a kernel parameter,softlockup_panic
(seeDocumentation/kernel-parameters.txt
for details), and a compile option,BOOTPARAM_HARDLOCKUP_PANIC
, are provided for this.
hardlockup
介绍如下:
A
hardlockup
is defined as a bug that causes the CPU to loop in kernel mode for more than 10 seconds (seeImplementation
below fordetails), without letting other interrupts have a chance to run.
Similarly to the softlockup case, the current stack trace is displayedupon detection and the system will stay locked up unless the default behavior is changed, which can be done through a compile time knob,BOOTPARAM_HARDLOCKUP_PANIC
, and a kernel parameter,nmi_watchdog
(seeDocumentation/kernel-parameters.txt
for details).
softlockup
是进程上下文出现的死锁,hardlockup
是中断上下文出现的死锁。
R状态死锁
指的是某一任务一直处于TASK_RUNNING态且一直占用着CPU
,从而导致其他进程得不到调度而饿死的情况。一般情况下,R状态死锁较可能是由于程序出现死循环导致的,可以出现在内核态的进程上下文中(内核配置为非抢占式,soft lockup),也可以出现在中断上下文中的中断处理程序中(hard lockup)。
异常的程序一直运行,CPU无法调度到其他的任务运行,对于单CPU的设备,则直接的表现就是“死机”。这种死锁现象较难定位,内核也同样提供了一种检测手段来检测这种死锁并向用户发出告警–LOCKUP_DETECTOR
,它可支持监测进程上下文和中断上下文中的R状态死锁(SOFTLOCKUP_DETECTOR
和HARDLOCKUP_DETECTOR
),由于HARDLOCKUP_DETECTOR
需要nmi中断
的支持且目前的arm32环境并不支持,本文仅分析其中SOFTLOCKUP_DETECTOR
中的原理及实现方式,并给出一个示例。
lockup watchdogs detectors实现机制
该机制是基于hrtimer
和perf
子系统,介绍如下:
A periodic hrtimer runs to generate interrupts and kick the watchdog task. An NMI perf event is generated every
watchdog_thresh
(compile-time initialized to 10 and configurable through sysctl of the same name) seconds to check for hardlockups. If any CPU in the system does not receive any hrtimer interrupt during that time thehardlockup detector
(the handler for the NMI perf event) will generate a kernel warning or call panic, depending on theconfiguration.The watchdog task is a high priority kernel thread that updates a timestamp every time it is scheduled. If that timestamp is not updatedfor
2*watchdog_thresh
seconds (the softlockup threshold) thesoftlockup detector
(coded inside the hrtimer callback function)will dump useful debug information to the system log, after which it will call panic if it was instructed to do so or resume execution of other kernel code.
lockup detector机制
在内核代码的kernel/watchdog.c
中实现,本文以Linux 4.1.15版本源码为例进行分析。首先了解其背后的设计原理:
关于这部分的流程分析,我也不是很懂。我只想知道,多久喂一次狗?多久会超时?超时之后会做怎样的动作?
更具体的流程,请参照:
http://blog.csdn.net/luckyapple1028/article/details/51932414
lockup watchdogs detectors常见参数
主要参数在<Kernel_Dir>/Kernel/watchdog.c
文件中设定。
watchdog_thresh
设置看门狗超时时间,hard lockup的时间默认是10s,soft lockup的时间是20s,该值可通过/proc/sys/kernel/watchdog_thresh
去配置。
sample_period
该值定义喂狗时间,默认是watchdog_thresh
的五分一。
1 | static void set_sample_period(void) |
watchdog_timer_fn()
看门狗超时执行的函数,在这个函数里,会打印堆栈信息,用来判断是谁lockup了。
1 |
|
注意,在做上述测试softlock watchdog
的时候,要确保rcu stall detector
机制是关闭的或者设置rcu stall detector
的超时更长,否则会使rcu stall detector
先检测出来死锁。
设置时间要大于softlock watchdog_thresh
的值,方法如下:
1 |
或者将rcu_sched
的stall warning
输出关闭,方法如下:
1 |
这里的堆栈信息,可以使用反汇编技术,确定是在哪一条语句出现问题的。
我这里使用的测试程序如下:
1 |
|
如果有打开softlockup_panic
参数,那么还会调用panic("softlockup: hung tasks");
以此输出panic信息供调试。
softlockup_panic
该参数用来选择当看门狗超时的时候,是否触发panic。有以下两种方式来设置该值。非0,表示输出panic信息。该值默认为0,
1 | unsigned int __read_mostly softlockup_panic = |
第1种方法通过在defconfig
中设置CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=y
即可,因为BOOTPARAM_SOFTLOCKUP_PANIC_VALUE
的值依赖于CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE
.
1 | config BOOTPARAM_SOFTLOCKUP_PANIC_VALUE |
第2种方法是在启动命令中加入softlockup_panic=
的参数进行选择。
1 | static int __init softlockup_panic_setup(char *str) |
watchdog_user_enabled
是否打开lockup watchdogs
功能,默认为1,可通过/proc/sys/kernel/watchdog
去设置,对应的接口函数为:proc_dowatchdog()
。
watchdog/0 进程CPU占用率高调试方法
有时候会发现watchdog/0
(这里的0表示CPU0,下面以CPU0为例)或者其他CPU核上的watchdog
进程的CPU占用率很高,有可能是内核中某个驱动长时间占用CPU,但是没有被锁住触发到lockup watchdog detector
,所以就表现为watchdog/0
的CPU占用率非常高。
比如说在上面的watchdog_test.c
的驱动程序中,将while(1)
语句改成mdelay(6000)
,延时6s,加载该驱动后,打印出来的watchdog/0
CPU占用率非常高。
1 | xxx_project:/data |
上面只能看到watchdog
的CPU占用率高的情况,没有看到更多的信息,不好确定驱动中的哪里有长时间占用CPU的情况。
如果要调试这种情况,我能想到将看门狗的触发时间watchdog_thresh
设置短一点,刚好让其触发超时。比如说我这里将watchdog_thresh
设置为2,也就是softlock
的看门狗时间为4s,小于测试代码中的6s,看其是否能输出调试信息。
测试下来,这种方法是可行的。当然,仅限于死锁时间大于4s的情况,除非自己修改驱动代码。
rcu stall detector简介
在上面验证lockup watchdogs detector
机制,加载watchdog_test.ko
驱动之前,要先将rcu_cpu_stall_timeout
的时间设置比2*watchdog_thresh
长,否则加载watchdog_test.ko
之后,出现的log是rcu cpu stall
相关的log,log如下:
1 | [ 208.383733] in watchdog_test_init, line = 16 starttttt |
关于RCU stall detector
的详细内容,可以参照:<Kernel_Dir>/Documentation/RCU/stallwarn.txt
文件,里面分别讲了什么情况下会有RCU CPU Stall Warnings
,如何修改RCU CPU Stall Detector
的参数,以及如何去解析该warnings。
下内容转自下面几篇文章,关于RCU的内容不是很熟悉,以后要专门学习一下。
https://www.kernel.org/doc/Documentation/RCU/stallwarn.txt
http://blog.csdn.net/wdsfup/article/details/76087670
https://www.ibm.com/developerworks/cn/linux/l-rcu/
https://lwn.net/Articles/301910/
什么情况下触发rcu cpu stall warning
RCU是基于其原理命名的,Read-Copy Update
:
- [Read]指的是对于被RCU保护的共享数据,reader可以直接访问,不需要获得任何锁;
- [Copy Update]指的是writer修改数据前首先拷贝一个副本,然后在副本上进行修改,修改完毕后向reclaimer(垃圾回收器)注册一个回调函数(callback),在适当的时机完成真正的修改操作:把原数据的指针重新指向新的被修改的数据,这里所说的适当的时机就是当既有的reader全都退出临界区的时候,而等待恰当时机的过程被称为
grace period
。 - 在RCU机制中,writer不需要和reader竞争任何锁,只在有多个writer的情况下它们之间需要某种锁进行同步作,如果写操作频繁的话RCU的性能会严重下降,所以RCU只适用于读多写少的情况。RCU CPU Stall Detector它有助于检测导致 grace period 过度延迟的因素,因为grace period的长短是RCU性能的重要因素。
rcu stall detector相关参数
在 sys 下,提供了两个接口来配置RCU的参数。
/sys/module/rcupdate/parameters/rcu_cpu_stall_timeout
来配置timerout,默认值为21。/sys/module/rcupdate/parameters/rcu_cpu_stall_suppress
来配置是否输出warning,默认值为0,表示输出。
具体的参数详见:<Kernel_Dir>/lib/Kconfig.debug
文件
1 | config RCU_CPU_STALL_TIMEOUT |
解析rcu cpu stall warning
常见的一个warning如下:
1 | [ 229.389303] INFO: rcu_sched detected stalls on CPUs/tasks: {} (detected by 0, t=5252 jiffies, g=51235, c=51234, q=13) |
这里的打印在<Kernel_Dir>/kernel/rcu/tree.c
中的check_cpu_stall()
-> print_other_cpu_stall()
函数中打印:
The
detected by
line indicates which CPU detected the stall (in thiscase, CPU 0), how many jiffies have elapsed since the start of thegrace period (in this case 5252), the number of the last grace periodto start and to complete (51235 and 51234, respectively), and an estimateof the total number of RCU callbacks queued across all CPUs (13 inthis case).
jiffies与HZ和tick的关系
系统运行时间(以秒为单位):system_time = (jiffies)/HZ
HZ
:Linux核心每隔固定周期会发出timer interrupt (IRQ 0)
,HZ
是用来定义每一秒有几次timer interrupts;使用CONFIG_HZ
定义;tick
:是HZ
的倒数,意即timer interrupt每发生一次中断的时间。如HZ
为250时,tick
为4毫秒(millisecond)。jiffies
:为Linux核心变数(32位元变数,unsigned long),它被用来纪录系统自开机以来,已经过多少的tick。每发生一次timer interrupt,Jiffies变数会被加一。