Linux 内核分析 rcu_sched self-detected stall on CPU_rcu-sched(2)

前言

[  115.958161] rcu: INFO: rcu_sched self-detected stall on CPU
[  115.989538] rcu:     3-....: (14997 ticks this GP) idle=a2e/1/0x4000000000000002 softirq=6190/6192 fqs=7448
[  115.990426]  (t=15000 jiffies g=9409 q=23634)
[  115.990427] Task dump for CPU 3:
[  115.990429] process-name      R  running task        0  1814   1757 0x00000002
[  115.990431] Call trace:
[  115.990435]  dump_backtrace+0x0/0x178
[  115.990436]  show_stack+0x14/0x20
[  115.990438]  sched_show_task.part.0+0xe0/0x108
[  115.990440]  sched_show_task+0x3c/0x40
[  115.990441]  dump_cpu_task+0x40/0x4c
[  115.990443]  rcu_dump_cpu_stacks+0xa0/0xe0
[  115.990444]  rcu_sched_clock_irq+0x530/0x790
[  115.990446]  update_process_times+0x2c/0x88
[  115.990448]  tick_sched_handle.isra.0+0x30/0x50
[  115.990450]  tick_sched_timer+0x48/0x98
[  115.990451]  __hrtimer_run_queues+0xec/0x1e8
[  115.990452]  hrtimer_interrupt+0x110/0x2c0
[  115.990456]  arch_timer_handler_virt+0x30/0x40
[  115.990457]  handle_percpu_devid_irq+0x80/0x140
[  115.990459]  generic_handle_irq+0x24/0x38
[  115.990460]  __handle_domain_irq+0x60/0xb8
[  115.990461]  gic_handle_irq+0x5c/0x148
[  115.990462]  el1_irq+0xb8/0x140
[  115.990464]  __memset+0xf0/0x188
				......

最近内核代码时出现一个偶现的BUG,进程 process-name 一直占用着某一个进程 ,比如CPU3,进程CPU占用率100%,进程 process-name无法停止,且其使用的内核模块也无法也没法正常卸载,处于使用中,导致提示警告:

rcu: INFO: rcu_sched self-detected stall on CPU

RCU(Read-Copy Update)的CPU停滞检测器能够定位和识别与CPU停滞相关的问题。当CPU由于各种原因无法取得进展时,例如长时间运行的任务或锁争用,就会发生停滞。

当存在CPU停滞时,RCU的性能可能会受到影响。CPU停滞指的是CPU由于各种原因而无法取得进展,例如繁重的计算、长时间运行的任务、中断争用或锁争用等。这种停滞可能导致RCU无法及时完成其任务,导致宽限期内该CPU没有发生调度,从而影响系统的响应性能和吞吐量。

为了解决这些问题,Linux内核引入了RCU的CPU停滞检测器。该检测器能够监视CPU的活动并检测停滞情况。当检测到CPU停滞时,检测器会采取一些措施,例如打印警告消息、记录调用堆栈信息或触发调试工具等,以帮助开发人员识别和解决问题。

通常是在驱动代码中的循环部分出现了问题,导致一直占着某个CPU。

一、RCU CPU Stall 警告的原因

RCU(Read-Copy-Update)CPU停滞警告可能由系统中的各种问题引起。以下是可能的原因:

(1)CPU在RCU读侧临界区域中循环:当CPU在持有RCU读侧临界区域时进入循环,阻止其他CPU取得进展,就会发生这种情况。

(2)CPU在中断被禁用时循环:如果CPU禁用了中断并进入循环,就会引起RCU CPU停滞警告,因为中断对于RCU的进展是必需的。

(3)CPU在抢占被禁用时循环:当CPU禁用抢占并进入循环时,会导致RCU停滞,因为抢占对于RCU调度任务是必需的。

(4)CPU在底半部被禁用时循环:底半部是内核用于延迟工作的机制。如果CPU禁用了底半部并进入循环,就会阻止RCU执行必要的任务。

(5)对于非可抢占内核,在内核中的任何位置循环而没有调用schedule():在非可抢占内核中,如果CPU在没有调用schedule()函数的情况下循环,会导致RCU CPU停滞警告。添加调用cond_resched()可以解决这个问题。

(6)引导过程中慢速控制台连接:如果用于Linux引导的控制台连接速度太慢无法跟上消息速率,就会导致RCU CPU停滞警告,尤其是如果添加了额外的调试printk()语句。

(7)任何阻止RCU优雅期k线程运行的因素:如果有任何因素阻止RCU的优雅期k线程运行,会导致RCU CPU停滞警告。这可能包括调度问题或影响RCU任务执行的其他因素。

(8)CPU密集型实时任务:在可抢占内核中,以较高优先级运行的CPU密集型实时任务可能会抢占RCU读侧临界区域中的低优先级任务。这会阻止RCU优雅期完成,导致RCU CPU停滞警告。

(9)执行时间较长的周期性中断处理程序:如果周期性中断的处理程序执行时间超过连续两个中断之间的时间间隔,会阻止RCU的k线程和软中断处理程序运行,导致RCU CPU停滞警告。

(10)在不同系统速度上测试工作负载:如果在速度较快的系统上使用调整过的停滞警告超时时间测试工作负载,然后在速度较慢的系统上使用相同的超时时间运行,由于系统速度的差异,可能会引发RCU CPU停滞警告。

(11)调度器时钟中断问题:在不处于dyntick-idle模式的CPU上关闭调度器时钟中断可能导致RCU CPU停滞警告,尤其适用于CONFIG_NO_HZ_COMMON=n配置的内核。

(12)RCU实现中的错误:在某些情况下,RCU CPU停滞警告可能表明RCU实现本身存在错误。

(13)硬件故障:尽管罕见,但硬件故障(例如,CPU故障)可能会导致RCU CPU停滞警告,如果CPU变得无响应但不会立即导致系统崩溃。

当涉及到调试RCU相关问题时,有几种技术和工具可以提供帮助:

RCU CPU停滞警告:RCU的实现(如RCU、RCU-sched和RCU-tasks)在宽限期(grace period)进行时,如果某个CPU出现延迟,就会生成CPU停滞警告。这些警告表明系统可能存在问题,可以作为进一步调查的起点。

堆栈跟踪:检查堆栈跟踪通常可以揭示导致停滞的有问题函数。通过查看堆栈顶部附近的函数,可以确定造成延迟的代码。

比较堆栈跟踪:如果在一次长时间停滞期间观察到一系列停滞警告,比较不同跟踪之间的堆栈跟踪可以帮助确定造成停滞的具体函数。在所有跟踪中,保持在堆栈顶部附近的函数很可能是问题所在。

Ftrace:Ftrace是Linux内核中强大的跟踪框架,可帮助诊断和调试RCU停滞。通过适当启用和配置Ftrace,可以跟踪执行流程并收集有关停滞期间RCU操作的详细信息。

CONFIG_RCU_TRACE:内核配置选项CONFIG_RCU_TRACE可以启用RCU的附加调试功能。通过启用此选项,可以使用RCU的事件跟踪功能,提供有关RCU活动的详细信息,有助于识别和解决与RCU相关的错误。

二、源码解析

/\*
 \* Called from the timer interrupt handler to charge one tick to the current
 \* process. user\_tick is 1 if the tick is user time, 0 for system.
 \*/
void update\_process\_times(int user_tick)
{
	struct task\_struct \*p = current;

	/\* Note: this timer irq context must be accounted for as well. \*/
	account\_process\_tick(p, user_tick);
	run\_local\_timers();
	rcu\_sched\_clock\_irq(user_tick);
#ifdef CONFIG\_IRQ\_WORK
	if (in\_irq())
		irq\_work\_tick();
#endif
	scheduler\_tick();
	if (IS\_ENABLED(CONFIG_POSIX_TIMERS))
		run\_posix\_cpu\_timers();
}

这段代码是从定时器中断处理程序调用的函数,用于将一个时钟滴答计入当前进程的时间。user_tick参数表示该滴答是否属于用户时间(1表示是),0表示系统时间。

函数首先获取当前进程的task_struct结构体指针,并将其赋值给变量p。然后调用account_process_tick函数,将该滴答的时间计入当前进程的统计信息中,同时根据user_tick参数确定是用户时间还是系统时间。接着调用run_local_timers函数,运行本地定时器。然后调用rcu_sched_clock_irq函数,处理与RCU调度相关的时钟中断,同样根据user_tick参数确定是用户时间还是系统时间。

在定义了CONFIG_IRQ_WORK宏的情况下,如果当前处于中断上下文(in_irq函数返回真),则调用irq_work_tick函数,处理IRQ工作。紧接着调用scheduler_tick函数,执行调度器的时钟滴答处理。最后,如果启用了CONFIG_POSIX_TIMERS宏,在POSIX定时器支持的情况下,调用run_posix_cpu_timers函数,运行POSIX CPU定时器。

总体而言,该函数用于更新当前进程的时间统计信息,并处理与时钟相关的任务,如定时器、RCU调度、IRQ工作和调度器。

在这里我们主要关注 rcu_sched_clock_irq 函数:

rcu\_sched\_clock\_irq()
	-->rcu\_pending()
		-->check\_cpu\_stall()
			-->print\_cpu\_stall()
				-->rcu\_dump\_cpu\_stacks()
					-->dump\_cpu\_task()
						-->sched\_show\_task()
							-->show\_stack()
								-->dump\_backtrace()

最后调用dump_backtrace打印函数调用栈。

其中print_cpu_stall函数表示RCU的CPU停滞检测器开始打印警告信息:

static void print\_cpu\_stall(void)
{
	int cpu;
	unsigned long flags;
	struct rcu\_data \*rdp = this\_cpu\_ptr(&rcu_data);
	struct rcu\_node \*rnp = rcu\_get\_root();
	long totqlen = 0;

	/\* Kick and suppress, if so configured. \*/
	rcu\_stall\_kick\_kthreads();
	if (rcu_cpu_stall_suppress)
		return;

	/\*
 \* OK, time to rat on ourselves...
 \* See Documentation/RCU/stallwarn.txt for info on how to debug
 \* RCU CPU stall warnings.
 \*/
	pr\_err("INFO: %s self-detected stall on CPU\n", rcu_state.name);
	raw\_spin\_lock\_irqsave\_rcu\_node(rdp->mynode, flags);
	print\_cpu\_stall\_info(smp\_processor\_id());
	raw\_spin\_unlock\_irqrestore\_rcu\_node(rdp->mynode, flags);
	for\_each\_possible\_cpu(cpu)
		totqlen += rcu\_get\_n\_cbs\_cpu(cpu);
	pr\_cont("\t(t=%lu jiffies g=%ld q=%lu)\n",
		jiffies - rcu_state.gp_start,
		(long)rcu\_seq\_current(&rcu_state.gp_seq), totqlen);

	rcu\_check\_gp\_kthread\_starvation();

	rcu\_dump\_cpu\_stacks();

	raw\_spin\_lock\_irqsave\_rcu\_node(rnp, flags);
	/\* Rewrite if needed in case of slow consoles. \*/
	if (ULONG\_CMP\_GE(jiffies, READ\_ONCE(rcu_state.jiffies_stall)))
		WRITE\_ONCE(rcu_state.jiffies_stall,
			   jiffies + 3 \* rcu\_jiffies\_till\_stall\_check() + 3);
	raw\_spin\_unlock\_irqrestore\_rcu\_node(rnp, flags);

	panic\_on\_rcu\_stall();

	/\*
 \* Attempt to revive the RCU machinery by forcing a context switch.
 \*
 \* A context switch would normally allow the RCU state machine to make
 \* progress and it could be we're stuck in kernel space without context
 \* switches for an entirely unreasonable amount of time.
 \*/
	set\_tsk\_need\_resched(current);
	set\_preempt\_need\_resched();
}

三、调整RCU CPU停滞检测器的参数

rcuupdate.rcu_cpu_stall_suppress模块参数用于禁用RCU的CPU停滞检测器,该检测器用于检测导致RCU优雅期延迟的条件。此模块参数默认启用CPU停滞检测,但可以通过引导时参数或运行时通过sysfs进行覆盖。停滞检测器对于何种情况被认为是"过度延迟"的判断是由一组内核配置变量和cpp宏控制的:

(1)CONFIG_RCU_CPU_STALL_TIMEOUT

这个内核配置参数定义了RCU从宽限期(grace period)开始等待的时间,直到发出RCU CPU停滞警告。

# cat /boot/config-4.19.90-24.4.v2101.ky10.aarch64 | grep CONFIG\_RCU\_CPU\_STALL\_TIMEOUT
CONFIG_RCU_CPU_STALL_TIMEOUT=60

此配置参数可以通过/sys/module/rcupdate/parameters/rcu_cpu_stall_timeout在运行时进行更改,但是此参数仅在每个周期的开始处进行检查。因此,如果你在一个持续40秒的停滞中已经过了10秒,将此sysfs参数设置为(比如)5将缩短下一个停滞的超时时间或当前停滞的下一个警告的时间(假设停滞的时间足够长)。它不会影响当前停滞的下一个警告的时间。

通过/sys/module/rcupdate/parameters/rcu_cpu_stall_suppress可以完全启用或禁用停滞警告消息。

(2)RCU_STALL_DELAY_DELTA

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

备注:"cpp macro"指的是C/C++语言中的预处理宏(preprocessor macro)。C预处理器(C preprocessor)是一种在编译前对源代码进行预处理的工具。它会根据预定义的宏和指令,对源代码进行文本替换和条件编译等操作。

在给出的描述中,“RCU_STALL_DELAY_DELTA"和"RCU_STALL_RAT_DELAY"被提到是"cpp macro”,意味着它们是在编译前由预处理器处理的宏定义,而不是运行时可配置的内核参数。这些宏定义在代码中使用,在编译时会根据预定义的值进行相应的文本替换。

总之,"cpp macro"指的是C/C++语言中的预处理宏,它们在编译前被预处理器处理,并在编译时进行文本替换。

(3)RCU_STALL_RAT_DELAY
CPU停滞检测器尝试使造成停滞的CPU打印自己的警告消息,因为这样通常可以获得更高质量的堆栈跟踪。但是,如果造成停滞的CPU在RCU_STALL_RAT_DELAY指定的jiffies数内没有检测到自己的停滞,那么其他某个CPU将会进行投诉。这个延迟通常设置为两个jiffies。(这是一个cpp宏,而不是内核配置参数)

(4)rcupdate.rcu_task_stall_timeout

这个引导/sysfs参数控制RCU-tasks停滞警告的间隔时间。值为零或更小的值会抑制RCU-tasks的停滞警告。正值设置停滞警告的间隔时间(以秒为单位)。RCU-tasks停滞警告以以下行开始:

INFO: rcu_tasks detected stalls on tasks:

然后继续输出每个导致当前RCU-tasks优雅期停滞的任务的sched_show_task()输出。

通过调整这些参数,你可以根据你的需求来定制RCU CPU停滞检测器的行为。请注意,对这些参数的更改可能需要在引导时或运行时进行,并且应谨慎评估其对系统性能和调试能力的影响。

四、RCU的CPU停滞检测器"Splats"的解释

(1)
对于非RCU任务的RCU版本,在CPU检测到停滞时,它会打印类似以下的消息:

## 最后

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**

![img](https://img-blog.csdnimg.cn/img_convert/3f24dbef3e5bf75a3094897aebeaacd7.png)

![img](https://img-blog.csdnimg.cn/img_convert/9bd00f1d4d38e251b2e3b127aac9bc9a.png)

![img](https://img-blog.csdnimg.cn/img_convert/6163eb37634f8ed2dec31093ab8f9ff4.png)

![img](https://img-blog.csdnimg.cn/img_convert/6c21949c55ce92a5fb8ed378dbd4d7bb.png)

![img](https://img-blog.csdnimg.cn/img_convert/2ede778eac17713fdcab7ca712b25440.png)

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618653875)

**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

715530238391)]

[外链图片转存中...(img-YBVW2yim-1715530238391)]

[外链图片转存中...(img-Sx3TGWy0-1715530238392)]

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618653875)

**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值