一. 背景介绍:
softlock_up:
内核开关, 打开之后可以检测软锁。软锁发生之后, 当cpu在设定的时间间隔中没有发生时间中断的话, 该机制就会发送一个nmi中断, 让系统重启。该机制用看门狗机制实现。
nohz_full:
内核开关, 用于消除某个cpu上的时钟中断, 可以在uboot的cmdline里指定想要关闭时钟中断的cpu核心序号。该功能主要应用于一些对时间性能很敏感的系统, 因为任务切换会伴随着时钟中断, 其中的上下文切换会对任务的性能和实时性产生影响, 若应用需要强性能和强实时性, 最好的情况是一个任务独占一个cpu, 不做任何时间片切换。
一般该内核开关会伴随着核隔离使用, 下面是一个例子:
setenv bootargs '... isolcpus=5-7 nohz_full=5-7 rw'
arch_timer:
系统时钟
二. 问题描述
在一个需求中, dpdk需要在nohz_full和核隔离的情况使用。但是发现在4.19内核上打开了softlock_up和nohz_full隔离5~7号cpu。然而发现当两个内核开关打开的时候, 5 ~ 7号cpu依旧有来自于arch_timer的系统时钟。
使用cat /proc/interrupts | grep arch_timer
命令可以发现该中断在cpu 5 ~ 7 上增加。
但是根据内核文档[3], 发现该现象是和该模块设计思路相悖的, 于是开始调查。
默认情况下所有在线cpu上都会运行一个watchdog线程。不过在内核配置了”NO_HZ_FULL“的
情况下watchdog线程默认只会运行在管家(housekeeping)cpu上,而”nohz_full“启动参数指
定的cpu上则不会有watchdog线程运行。试想,如果我们允许watchdog线程在”nohz_full“指
定的cpu上运行,这些cpu上必须得运行时钟定时器来激发watchdog线程调度;这样一来就会
使”nohz_full“保护用户程序免受内核干扰的功能失效。当然,副作用就是”nohz_full“指定
的cpu即使在内核产生了lockup问题我们也无法检测到。不过,至少我们可以允许watchdog
线程在管家(non-tickless)核上继续运行以便我们能继续正常的监测这些cpus上的lockups
事件。
三. 调查
首先调查softlock_up相关代码, 发现具体实现在 kernel/watchdog.c
里, 其中开启softlock_up的watchdog入口函数为static void watchdog_enable(unsigned int cpu)
, 在同一个c文件里搜索其调用, 发现两处调用,
以下是整理的两条调用链,
①:
kernel_init
kernel_init_freeable
lockup_detector_init watchdog.c
lockup_detector_setup
lockup_detector_reconfigure
softlockup_start_all
softlockup_start_fn
watchdog_enable
②:
lockup_detector_online_cpu
watchdog_enable
查看watchdog_enable
的①②上一级调用函数softlockup_start_all
和 lockup_detector_online_cpu
:
static void softlockup_start_all(void)
{
int cpu;
cpumask_copy(&watchdog_allowed_mask, &watchdog_cpumask);
for_each_cpu(cpu, &watchdog_allowed_mask)
smp_call_on_cpu(cpu, softlockup_start_fn, NULL, false);
}
int lockup_detector_online_cpu(unsigned int cpu)
{
watchdog_enable(cpu);
return 0;
}
发现①是用了cpu mask的, ②是没有使用cpu mask的。
在两处添加打印, 发现softlockup_start_all
在0~4号cpu上放置watchdog, 而lockup_detector_online_cpu
在0 ~ 7上都放置了watchdog。所以第二个应该是和该模块设计思路不符合的, 应该是内的一个纰漏。
查看5.15内核版本, 发现该问题已经被修复, 5.15版本lockup_detector_online_cpu
函数为:
int lockup_detector_online_cpu(unsigned int cpu)
{
if (cpumask_test_cpu(cpu, &watchdog_allowed_mask))
watchdog_enable(cpu);
return 0;
}
已经使用了cpu mask。
将该函数放在4.19内核上, 问题修复。
再来分析一下为什么watchdog会影响NOHZ_FULL机制
static void tick_sched_handle(struct tick_sched *ts, struct pt_regs *regs)
{
#ifdef CONFIG_NO_HZ_COMMON
/*
* When we are idle and the tick is stopped, we have to touch
* the watchdog as we might not schedule for a really long
* time. This happens on complete idle SMP systems while
* waiting on the login prompt. We also increment the "start of
* idle" jiffy stamp so the idle accounting adjustment we do
* when we go busy again does not account too much ticks.
*/
if (ts->tick_stopped) {
touch_softlockup_watchdog_sched();
...
}
从代码中可以看到, 当打开了NO_HZ_COMMON开关(NOHZ_FULL相关), 当发现该cpu是idle状态下, 且watchdog打开了的话, 必须调用touch_softlockup_watchdog_sched();
函数告诉softlockup_watchdog
我没有问题, 不需要对系统进行修正。
下面是touch_softlockup_watchdog_sched()
的代码, 从注释中也可以看出该代码的用处:
/**
* touch_softlockup_watchdog_sched - touch watchdog on scheduler stalls
*
* Call when the scheduler may have stalled for legitimate reasons
* preventing the watchdog task from executing - e.g. the scheduler
* entering idle state. This should only be used for scheduler events.
* Use touch_softlockup_watchdog() for everything else.
*/
notrace void touch_softlockup_watchdog_sched(void)
{
/*
* Preemption can be enabled. It doesn't matter which CPU's watchdog
* report period gets restarted here, so use the raw_ operation.
*/
raw_cpu_write(watchdog_report_ts, SOFTLOCKUP_DELAY_REPORT);
}
因此, 开了watchdog的cpu得接受arch_timer发过来的中断, 因为得定期通知softlock_up_watchdog.
四. 参考:
内核文档:
[1] Linux NO_HZ_FULL NO_HZ 框架实现分析
[2] (Nearly) full tickless operation in 3.10
[3]内核代码文档:Documentation/translations/zh_CN/admin-guide/lockup-watchdogs.rst
[4] linux 内核Lockup机制浅析