文章目录
上篇文章:ARM Linux 系统稳定性分析入门及渐进 1 – Crash 工具简介
下篇文章:ARM Linux 系统稳定性分析入门及渐进 3 – 栈溢出
1.1 SoftLockup
1.1.1 soft lockup 简介
Softlockup(watchdog) 用于检测系统调度是否正常,即软锁的情况,当发生 Softlockup
时,内核不能调度,但还能响应中断,使系统呈现 “软 hung 死的状态”(CPU 上还是有程序在跑着,就是没法让别人来抢占它了而已,所以用户看起来系统不能切换任务了,像 hung 死了一样。
1.1.2 进程分类
Linux 内核中实现了Scheduler Classes,来实现多个调度类(Scheduler class)的协同工作,每个不同的调度类对应不同的类型的线程,而且每个调度类都有自身的优先级,Linux调度管理基础代码会遍历在内核中注册了的调度类,选择高优先级的调度类,然后让此调度类按照自己的调度算法选择下一个执行的线程。Linux系统中常用的几种调度类为:
- SCHED_OTHER 分时调度策略;
- SCHED_FIFO 实时调度策略,先到先服务。一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃;
- SCHED_RR 实时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平;
- SCHED_NORMAL 是用于普通线程的调度类,而 SCHED_FIFO 和 SCHED_RR 是用于实时线程的调度类,优先级高于 SCHED_NORMAL。
SCHED_OTHER
是不支持优先级使用的,而 SCHED_FIFO
和 SCHED_RR
支持优先级的使用,他们分别为 1
和 99
,数值越大优先级越高。
Linux 内核会将大量工作放置在内核线程中,这些线程是在内核地址空间中运行的特殊进程。大多数内核线程运行在 SCHED_NORMAL
类中,必须与普通用户空间进程争夺CPU时间。但是有一些内核线程它的开发者们认为它们非常特殊,应该比用户空间进程要有更高优先级。因此也会把这些内核线程放到 SCHED_FIFO
中去。
无论优先级高低,实时进程都会优先于 SCHED_NORMAL
中的所有进程先执行,因为后者里面都是普通的非实时进程。
1.1.3 Soft lockup 基本原理
内核为每个 CPU 启动一个内核实时线程线程(watchdog/x
),优先级最高的实时线程,它会被周期地调度. 这个线程的工作就是写一个变量 watchdog_touch_ts
,其实就是把当前 CPU 上维护的一个时间戳写进去。假如该 CPU 上的调度没有问题,那么这个变量的值是会非常新的.假如不是,比如调度器有好久没在这个 CPU 上切换任务了,那么这个时间戳值就会比较旧了。
有一个高精度 hrtimer,它也会周期地被唤醒,时钟处理函数,会去检测这个变量值,假如它发现 watchdog_touch_ts
这个变量值,距离当前最新时间的间隔已经超过了 softlockup
允许的最大时间值(默认是20s,可以设置),它就会报警,如果设置了 /proc/sys/kernel/softlockup_panic
为1
,那么它就让系统 panic。
hrtimer 依赖中断机制,而中断机制不依赖调度器,所以可以用它来监控调度器工作与否。
watchdog(unsigned int cpu)
-->__touch_watchdog();
/* 获取当前时间并赋值给 watchdog_touch_ts,
/* 目的是周期性的检测是否是 soft_lockup*/
-->__this_cpu_write(watchdog_touch_ts, get_timestamp());
(watchdog/x) 线程为优先级最高的实时线程,内核同时会启动周期为4秒的 hrtimer 定时器(喂看门狗),这个高精度定时器 hrtimer
和 CPU 绑定,使用的变量都是 percpu 的。确保每个CPU之间不相互干扰。
watchdog_enable(unsigned int cpu)
-->hrtimer->function = watchdog_timer_fn; //HRTIMER_RESTART,会周期性的执行
-->watchdog_check_hardlockup_other_cpu();
-->wake_up_process(__this_cpu_read(softlockup_watchdog));
因为 hrtimer
超时函数在软中断中调用,在中断产生后会比线程优先得到执行。hrtimer 的回调函数中会判断 watchdog_touch_ts
和当前时间差,如果超过给定值,那就证明内核调度失败,则说明这段时间内都没有发生调度(因为此线程优先级最高),则打印相应告警或根据配置可以进入 panic
流程。
1.1.4 Soft lockup 测试例子
在某个线程里添加如下语句:
preempt_disable();
while(1);
然后过会就会打印如下log:
[ 28.548865] BUG: soft lockup - CPU#1 stuck for 22s! [us kthread:1106]
[ 28.555378] Modules linked in:
1.2 hard lockup 简介
hard lockup 主要是发现某个 cpu 因为长期 disable interrupt 的情况。由于某种原因导致系统处于内核态超过 10s 导致中断无法运行(hard lockup)。由于调度,时钟等重要内核事件都是要时间中断来触发的,现在中断给屏蔽了,就导致这一切都无法触发,不说调度,连各种重要的时钟事件都无法处理了。系统基本啥也干不了了,所以就呈现 hung 死状态,什么也做不了。
原理:
在每一个 cpu 上有一个变量 hrtimer_interrupts, 这个变量会在 watchdog timer 中断的时候加 1,如果发现某个 cpu 上这个变量的值长时间没有增加,就说明发生了hardlock。
1.1.1 检查 hrtimer_interrupts 变量2种方法
- 每个 cpu 会有一个 NMI 中断(
Non-maskable Interrupt
),因为是 NMI 中断,所以即使 cpu 上禁止了中断,这个 NMI 中断也会发生。在中断处理函数中会检查自己这个 cpu 上hrtimer_interrupts
的值是否增加。这种方法在 arm 中没有采用。 - 每个cpu 会在 watchdog timer 中断中检查下一个 cpu 的 hrtimer_interrupts 的值(因为下一个 cpu 已经禁止中断,它自己的
watchdog timer callback
无法被调用,所以只能靠别的 cpu 去检查。例如 cpu8 检查 cpu9, cpu9 检查 cpu0。如果下一个 cpu不在线,会检查再下一个 cpu。
1.1.2 hard lockup 测试例子
在下面例子中,中断被关闭,普通中断无法被相应(包括时钟中断),线程无法被调度,在这种情况下,不仅仅[watchdog/x]线程也无法工作,hrtimer 也无法被相应。
static DEFINE_SPINLOCK(test_lock);
static int hard_lockup_thread(void *data)
{
unsigned long flags;
spin_lock_irqsave(&test_lock, flags);
while (1);
/* unreached */
return 0;
}
然后过会就会打印如下log:
...
[60.624324] NMI watchdog: Watchdog detected hard LOCKUP on cpu 0
...
上篇文章:ARM Linux 系统稳定性分析入门及渐进 1 – Crash 工具简介
下篇文章:ARM Linux 系统稳定性分析入门及渐进 3 – 栈溢出
推荐阅读:
https://editor.csdn.net/md/?articleId=128133753
https://editor.csdn.net/md/?articleId=128133753