[RCU stall] RCU stall 分析,RCU stall 内核文档翻译

使用RCU的CPU失速检测器

 

本文档首先讨论RCU的CPU停顿检测器可以定位哪些问题,然后讨论可用于微调检测器操作的内核参数和Kconfig选项。最后,本文解释了失速检测器的“splat”格式。

 

是什么导致RCU CPU停顿警告?

 

是因为您的内核会打印RCU CPU停止警告。下一个问题是“是什么原因引起的?” 以下问题可能导致RCU CPU停顿:

 

警告:

 

o             RCU read-side 关键部分中出现CPU循环。

o             禁用中断时出现CPU循环。

o             禁用抢占时出现CPU循环。这种情况可能会导致RCU调度的停顿,并且,如果使用kso??ftirqd,则会导致RCU-bh停顿。

o             下半部禁用时出现CPU循环。这种情况可能导致RCU调度和RCU-bh停顿。

o             对于!CONFIG_PREEMPT内核,CPU可以在内核中的任何位置循环,而无需调用schedule()。如果确实确实希望在内核中循环,这是您期望的行为,则可能需要向cond_resched()添加一些调用。

 

o             使用太慢的控制台连接来引导Linux,以致无法跟上引导时控制台消息的速率。例如,115Kbaud串行控制台的速度可能太慢而无法跟上启动时消息的速率,经常会导致RCU CPU停顿警告消息。特别是如果您添加了printk()调试代码。

 

o             阻止RCU宽限期kthread运行的任何措施。这可能会导致“All QSes seen”控制台日志消息。此消息将包含有关kthread上次运行的时间以及应该运行的频率的信息。它还可能导致控制台日志消息“ rcu _.*kthread starved for”,其中将包含其他调试信息。

 

O             CONFIG_PREEMPT内核中的CPU绑定的实时任务,这可能恰好抢占了RCU读取侧关键部分中间的低优先级任务。如果不允许该低优先级任务在任何其他CPU上运行,则这尤其有害,在这种情况下,下一个RCU宽限期将永远无法完成,最终将导致系统内存不足并挂起。当系统正在运行其自身的内存不足时,您可能会看到停顿警告消息。

 

o             CONFIG_PREEMPT_RT内核中的CPU绑定实时任务,其运行优先级高于RCU softirq线程。这将防止调用RCU回调,并且在CONFIG_PREEMPT_RCU内核中,将进一步防止RCU宽限期完成。无论哪种方式,系统最终都将耗尽内存并挂起。在CONFIG_PREEMPT_RCU情况下,您可能会看到stall-warning消息。

 

o             周期性中断,其处理程序花费的时间比连续两次中断之间的时间间隔长。这可以阻止RCU的kthread和softirq处理程序运行。请注意,某些高开销的调试选项(例如function_graph跟踪程序)可能导致中断处理程序花费的时间比正常时间长得多,进而可能导致RCU CPU停顿警告。

 

o             在快速的系统上测试工作负载,将停顿警告超时降低到几乎不能避免RCU CPU停顿警告,然后在慢速系统上以相同的停顿警告超时运行相同的工作负载。请注意,温度控制器和按需调速器可能导致单个系统有时快而有时慢!

 

o             硬件或软件问题关闭了不在dyntick-idle模式下的CPU上的调度程序时钟中断。此问题确实发生过,并且似乎最有可能导致CONFIG_NO_HZ_COMMON = n内核出现RCU CPU停顿警告。

 

o             RCU实现中的错误。

 

o             硬件故障。虽然不太可能的,但在现实生活中发生过。一个正在运行的系统中的CPU发生故障,变得无响应,但没有立即导致崩溃。这导致了一系列RCU CPU停顿警告,最终导致人们认识到CPU发生了故障。

 

RCU/RCU-sched/RCU-bh和RCU-tasks实现具有CPU停止警告。注意SRCU确实没有CPU停止警告。请注意,只有在宽限期内,RCU才会检测到CPU停顿。没有宽限期,没有CPU停顿警告。

 

要诊断失速的原因,请检查堆栈踪迹。需要关注的函数通常位于堆栈顶部附近。如果您从单个扩展的停顿中获得了一系列停顿警告,则比较堆栈跟踪通常可以帮助确定发生停顿的位置,通常该函数位于最靠近栈顶部分(与跟踪保持不变)顶部的函数中追踪。如果您可以可靠地触发停顿,则ftrace可能会很有帮助。

 

通常可以借助CONFIG_RCU_TRACE和RCU的事件跟踪来调试RCU错误。有关RCU事件跟踪的信息,请参见 include/trace/events/rcu.h。

 

微调RCU CPU失速检测器

 

rcu update.rcu_cpu_stall_suppress模块参数禁用RCU的CPU停顿检测器,该检测器检测不适当地延迟RCU宽限期的条件。 默认情况下,此模块参数启用CPU停顿检测,但可以通过启动时参数或在运行时通过sysfs进行覆盖。停顿检测器对“过度延迟”的含义由一组内核配置变量和cpp宏控制:

 

CONFIG_RCU_CPU_STALL_TIMEOUT

 

此内核配置参数定义RCU从宽限期开始到发出RCU CPU停止警告的等待时间,该时间段通常为21秒。

 

可以在运行时通过 /sys/module/rcupdate/parameters/ rcu_cpu_stall_timeout更改此配置参数,但是仅在循环开始时才检查此参数。因此,如果您进入40秒停顿的时间为10秒,则将此sysfs参数设置为(例如)5将缩短-next-停顿的超时时间,或者缩短当前停顿的以下警告时间(假设停顿持续足够长的时间)。它不会影响当前停顿的下一个警告的时间。

 

可以通过 /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress 完全启用和禁用失速警告消息。

 

RCU_STALL_DELAY_DELTA

 

尽管lockdep工具非常有用,但确实会增加一些开销。因此,在CONFIG_PROVE_RCU下,RCU_STALL_DELAY_DELTA宏在给出RCU CPU停顿警告消息之前允许额外的五秒钟。(这是一个cpp宏,而不是内核配置参数。)

 

RCU_STALL_RAT_DELAY

 

CPU停顿检测器试图使有问题的CPU打印自己的警告,因为这通常会提供质量更好的堆栈跟踪。但是,如果有问题的CPU在RCU_STALL_RAT_DELAY指定的抖动数量中未检测到自己的停顿,则其他CPU会检查。此延迟通常设置为2个jiffies。(这是一个cpp宏,而不是内核配置参数。)

 

rcupdate.rcu_task_stall_timeout

 

此boot/sysfs参数控制RCU任务停止警告间隔。零或更少的值将抑制RCU任务停止警告。正值将设置stall-warning警告间隔(单位为jiffies)。RCU-tasks停顿警告从以下行开始:

 

INFO: rcu_tasks detected stalls on tasks:

 

并继续执行每个任务的sched_show_task()输出,以停止当前的RCU-tasks宽限期。

 

解释RCU的CPU停顿检测器 "Splats"

 

对于RCU的非RCU-tasks类型,当CPU检测到它正在停止时,它将打印类似于以下内容的消息:

INFO: rcu_sched detected stalls on CPUs/tasks:

2-...: (3 GPs behind) idle=06c/0/0 softirq=1453/1455 fqs=0

16-...: (0 ticks this GP) idle=81c/0/0 softirq=764/764 fqs=0

(detected by 32, t=2603 jiffies, g=7075, q=625)

 

此消息表明CPU 32检测到CPU 2和16都引起了停顿,并且该停顿正在影响RCU调度的时间。在此消息后通常会出现每个CPU的堆栈转储。请注意,PREEMPT_RCU builds可以由任务以及CPU停止,并且任务将由PID指示,例如“ P3421”。甚至有可能由两个CPU和任务引起rcu_preempt_state停顿,在这种情况下,将在列表中调出所有有问题的CPU和任务。

 

CPU 2's "(3 GPs behind)" 表示该CPU在过去的三个宽限期内未与RCU内核进行交互。相反,CPU 16's "(0 ticks this GP)" 表示该CPU在当前停滞的宽限期内没有采取任何调度时钟中断。

 

log中的"idle="部分显示dyntick-idle状态。第一个"/"之前的十六进制数是dynticks计数器的低12位,如果CPU处于dyntick-idle模式,则其将具有偶数值,否则,将具有奇数值。两个"/"之间的十六进制数是嵌套的值,如果在空闲循环中,则将是一个小的非负数(如上所示),否则将是一个非常大的正数。

 

log中的"softirq="部分跟踪停滞的CPU已执行的RCU softirq处理程序的数量。"/"之前的数字是自该CPU启动以来到上次记录宽限期开始时执行的数字,宽限期可能是当前的(停顿的)宽限期,也可能是更早的宽限期(例如,如果CPU可能长时间处于dyntick-idle模式,而"/"后的数字是自启动以来直到当前时间为止执行的数字。警告消息,则可能是RCU的softirq处理程序不再能够在此CPU上执行。如果停滞的CPU在中断时自旋,或者在-rt内核中,可能会发生这种情况。

 

"fps="表示自该CPU上次注意到宽限期开始以来,宽限期kthread在该CPU上进行的强制静态状态空闲/脱机检测通过的次数。

 

"detected by"行指示哪个CPU检测到停顿(在这种情况下为CPU 32),自宽限期开始(在这种情况下为2603)以来已经经过了多少次jiffies,宽限期序号(7075),并估算所有CPU中排队的RCU回调总数(在这种情况下为625)。

 

在具有CONFIG_RCU_FAST_NO_HZ的内核中,将为每个CPU打印更多信息:

 

0: (64628 ticks this GP) idle=dd5/3fffffffffffffff/0 softirq=82/543 last_accelerate: a345/d342 nonlazy_posted: 25 .D

 

当该CPU上次从rcu_needs_cpu()调用rcu_try_advance_all_cbs()或从rcu_prepare_for_idle()上一次调用rcu_accelerate_cbs()时,"last_accelerate:"打印jiffies计数器的低16位(十六进制)。"nonlazy_posted:"显示自上次调用rcu_needs_cpu()以来发布的非延迟回调的数量。

最后,"L"表示当前没有非延迟回调(如上所示,否则打印"."),而"D"表示启用dyntick-idle处理(否则打印".",例如,如果通过"nohz="内核引导参数禁用了)。

 

如果在停止警告开始打印时宽限期结束,则会出现虚假的停止警告消息,其中包括以下内容:

 

INFO: Stall ended before state dump start

 

这种情况很少见,但在现实生活中确实时有发生。在这种情况下,也可以标记zero-jiffy的失速,具体取决于失速警告和宽限期初始化如何发生相互作用。请注意,如果不诉诸stop_machine()之类的事情,就不可能完全消除这种误报,这对于此类问题来说是过大的。

 

如果所有CPU和任务都经过了quiescent状态,但是宽限期仍未结束,则停顿警告脚本将包括以下内容:

 

All QSes seen, last rcu_preempt kthread activity 23807 (4297905177-4297881370), jiffies_till_next_fqs=3, root ->qsmask 0x0

 

"23807"表示自宽限期kthread运行以来,已超过2.3万次jiffies。"jiffies_till_next_fqs"表示该kthread应该运行的频率,给出两次强制静态扫描之间的间隔,在这种情况下为3,比23807小得多。最后,打印出 root rcu_node structure的-> qsmask字段,通常为零。

 

如果相关的宽限期kthread在停顿警告之前无法运行(如上面"All QSes seen"行中的情况),则会打印以下附加行:

 

kthread starved for 23807 jiffies! g7075 f0x0 RCU_GP_WAIT_FQS(3) ->state=0x1 ->cpu=5

 

即使所有CPU和任务均已通过所需的静态状态,使CPU时间的宽限期kthreads Starving, 当然也会导致RCU CPU停顿警告。"g"数字显示当前的宽限期序列号,"f"在宽限期kthread的-> gp_flags命令之前,"RCU_GP_WAIT_FQS"指示kthread正在等待短暂的超时,"state"在task_struct-> state字段的值之前,"cpu"表示宽限期kthread最后在CPU 5上运行。

 

一个stall时的多个警告

 

如果停顿持续足够长的时间,则会为它打印多个停顿警告消息。第二条消息和后续消息的打印时间间隔较长,因此(例如)第一条消息和第二条消息之间的时间大约是停顿开始时间和第一条消息之间的间隔的三倍。

 

Expedited宽限期的失速警告

 

如果expedited的宽限期检测到停顿,它将在dmesg中放置如下消息:

 

INFO: rcu_sched detected expedited stalls on CPUs/tasks: { 7-... } 21119 jiffies s: 73 root: 0x2/.

 

这表明CPU 7无法响应重新调度IPI。CPU编号后面的三个句点("...")表示CPU处于在线状态(否则,第一个周期为"O"),CPU在加宽宽限期开始时处于在线状态(否则,第二个周期而是从启动开始,CPU至少已联机一次(否则,第三个时间段将改为"N")。"jiffies"之前的数字表示加速的宽限期已经进行了21119个jiffies。"s:"后面的数字表示expedited宽限期序列计数器为73。该最后一个值为奇数的事实表示加倍宽限期正在运行。"root:"之后的数字 是一个位掩码,指示根rcu_node结构的哪些子代对应于阻止当前加急宽限期的CPU and/or tasks。如果树具有多个级别,则将为树中其他rcu_node结构的状态打印其他十六进制数字。

 

与正常宽限期一样,PREEMPT_RCU构建可以由任务以及CPU暂停,并且任务将由PID指示,例如"P3421"。

 

完全有可能在同一轮运行中同时从正常和加速宽限期看到失速警告。

posted @ 2021-03-08 15:45  smilingsusu  阅读(4964)  评论(0编辑  收藏  举报