0%

Linux内核soft-lockup检测机制

基本逻辑

Linux内核调度各个不同的用户以及内核线程交替在CPU上运行,内核中的一段执行流长时间
的占用CPU时,会触发内核的soft lockup报错。

这里所谓的一段执行流是指用户线程的内核部分或者是内核线程。最常见的触发情况是,对
于不支持内核抢占的内核,内核中的一段执行流持续执行,没有主动让出CPU的情况。对于
非抢占内核,没有主动让出CPU,内核中的执行流总是持续执行。

注意,对于非抢占内核,中断打断一段内核执行流,中断执行后,将继续返回被打断的内核
执行流程,如果中断打断的是一段用户态执行流,中断执行完后,返回用户态被打断点前内
核会执行下调度,结果完全有可能是调度一个新的用户线程进来执行。

同样的道理,对于支持抢占的内核,在禁止抢占的内核执行流上,依然有上面的逻辑成立。

Linux提供用户态接口配置多长时间触发soft lockup报错,以及soft lockup后是否panic内
核。具体的接口是,/proc/sys/kernel/watchdog_thresh的值的2倍是触发soft lockup的秒
数,/proc/sys/kernel/softlockup_panic配置soft lockup后是否panic。

我们简单构造一个触发soft lockup的场景出来。首先,编译一个非抢占的内核出来,确认
CONFIG_PREEMPT_NONE=Y,确认打开soft lock的内核配置:CONFIG_SOFTLOCKUP_DETECTOR=y,
再写一个内核模块,在init里做死循环。使用qemu启动这个系统,配置/proc/sys/kernel/watchdog_thresh
为2,然后insmod如上的内核模块,我们会得到如下的日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[ ... ]
# echo 2 > /proc/sys/kernel/watchdog_thresh
# insmod busyloop.ko
[ 81.186060] busyloop: loading out-of-tree module taints kernel.
[ 87.420704] watchdog: BUG: soft lockup - CPU#1 stuck for 6s! [insmod:137]
[ 87.421300] Modules linked in: busyloop(O+)
[ 87.422007] CPU: 1 PID: 137 Comm: insmod Tainted: G O 6.8.0-rc5-00035-gdb6db2d782b8 #38
[ 87.422240] Hardware name: linux,dummy-virt (DT)
[ 87.424730] pstate: 40000005 (nZcv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 87.424917] pc : busyloop_init+0x0/0x1000 [busyloop]
[ 87.425420] lr : do_one_initcall+0x70/0x1b8
[ 87.425721] sp : ffff80008050ba60
[ 87.425794] x29: ffff80008050ba60 x28: ffff80008050bcd8 x27: ffffcd4b689ca040
[ 87.425991] x26: 0000000000000000 x25: ffffcd4b689ca058 x24: ffffcd4be3ff5480
[ 87.426148] x23: 0000000000000000 x22: ffff25fbc0bbc300 x21: ffff25fbc0bbc300
[ 87.426294] x20: ffffcd4b689ce000 x19: ffffcd4be3fc9000 x18: 0000000000000002
[ 87.426446] x17: ffff800080505000 x16: ffff25fbc015eef9 x15: 0000ae0f476185e6
[ 87.426596] x14: 000000000000037d x13: 000000000000037d x12: 0000000000000000
[ 87.426730] x11: 0000000000000000 x10: 0000000000000000 x9 : 00000000000007e0
[ 87.426886] x8 : ffff25fbc1a3b200 x7 : ffff25fbc0bbc738 x6 : 0000000000000742
[ 87.427079] x5 : ffff25fbc1822c80 x4 : fffffc97ef0608a0 x3 : 0000000080800080
[ 87.427275] x2 : 0000000000000000 x1 : 0000000000000000 x0 : 0000000000000000
[ 87.427524] Call trace:
[ 87.427707] busyloop_init+0x0/0x1000 [busyloop]
[ 87.427889] do_init_module+0x58/0x1e4
[ 87.427989] load_module+0x19bc/0x1a8c
[ 87.428073] init_module_from_file+0x88/0xc8
[ 87.428164] __arm64_sys_finit_module+0x1ec/0x320
[ 87.428262] invoke_syscall+0x44/0x104
[ 87.428342] el0_svc_common.constprop.0+0x40/0xe0
[ 87.428436] do_el0_svc+0x1c/0x28
[ 87.428497] el0_svc+0x34/0xb8
[ 87.428562] el0t_64_sync_handler+0xc0/0xc4
[ 87.428628] el0t_64_sync+0x190/0x194
[ ... ]

代码分析

Linux内核在每个core上启动一个高精度定时器(hrtimer)支持soft lockup,hrtimer定时到
期的处理函数的主要工作有:1. 更新hrtimer;2. 触发migration线程更新soft lockup的
时间戳;3. 通过时间戳检查是否出现soft lockup。

如果一个core长时间被一段执行流占据,虽然hrtimer总可以触发migration线程,叫它更新
soft lockup的时间戳,但是migration得不到运行,总是无法完成具体的更新动作,超过一
定的时间,hrtimer的处理函数就可以检测到一段流程长时间独占CPU。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* init/main.c */
kernel_init_freeable
/* kernel/watchdog.c */
+-> lockup_detector_init
/* 注意有CONFIG_SOFTLOCKUP_DETECTOR开启和关闭的版本 */
+-> lockup_detector_setup
+-> __lockup_detector_reconfigure
+-> softlockup_start_all
/*
* 在每个core上调用softlockup_start_fn,这里delay到了对应的work
* queue里执行。
*/
+-> for_each_cpu(cpu, &watchdog_allowed_mask)
smp_call_on_cpu(cpu, softlockup_start_fn, ...)

/* 在每个cores上初始化和启动hrtimer */
softlockup_start_fn
+-> watchdog_enable
/* 回调函数是watchdog_timer_fn */
+-> hrtimer_init/hrtimer_start

/*
* 每个core对应两个时间戳变量,hrtimer每次都触发migration线程更新watchdog_touch_ts
* 和watchdog_report_ts。hrtimer处理函数读出当前的时间戳和watchdog_report_ts比较,
* 如果超过watchdog_thresh的2倍则触发soft lockup。这里为什么要两个时间戳变量?
*/
watchdog_timer_fn
/* 触发migration线程更新时间戳 */
+-> stop_one_cpu_nowait(smp_processor_id(), softlockup_fn, NULL, this_cpu_ptr(&softlockup_stop_work))

migration线程在kernel/stop_machine.c里创建,每个core上创建一个migration内核线程。

1
2
cpu_stop_init
+-> smpboot_register_percpu_thread(&cpu_stop_threads)

当前内核里只有soft lockup和线程迁移用到这个stop_machine的特性,stop_machine想要
支持的语意还不清楚?