在RHEL 7 和 SELS11 SP2 之后的Linux系统上,有时会看到如下信息:
INFO: rcu_sched_state detected stall on CPU 5 (t=2500 jiffies)
INFO: rcu_bh_state detected stalls on CPUs/tasks: { 3 5 } (detected by 2, 2502 jiffies)
它们来自RCU CPU Stall Detector,要了解RCU CPU Stall Detector是什么,首先要知道RCU是什么。RCU(Read-Copy Update) 是Linux 2.6 内核开始引入的一种新的锁机制,与spinlock、rwlock不同,RCU有其独到之处,它只适用于读多写少的情况。
RCU是基于其原理命名的,Read-Copy Update,[Read]指的是对于被RCU保护的共享数据,reader可以直接访问,不需要获得任何锁;[Copy Update]指的是writer修改数据前首先拷贝一个副本,然后在副本上进行修改,修改完毕后向reclaimer(垃圾回收器)注册一个回调函数(callback),在适当的时机完成真正的修改操作–把原数据的指针重新指向新的被修改的数据,–这里所说的适当的时机就是当既有的reader全都退出临界区的时候,而等待恰当时机的过程被称为grace period 。在RCU机制中,writer不需要和reader竞争任何锁,只在有多个writer的情况下它们之间需要某种锁进行同步作,如果写操作频繁的话RCU的性能会严重下降,所以RCU只适用于读多写少的情况。
RCU API 最核心的函数是:
rcu_read_lock()
rcu_read_unlock()
synchronize_rcu()
call_rcu()
- rcu_read_lock()和rcu_read_unlock()配对使用,由reader调用,用以标记reader进入/退出临界区。夹在这两个函数之间的代码区称为”读端临界区”(read-side critical section),注:读端临界区可以嵌套。reader在临界区内是不能被阻塞的。
- synchronize_rcu()由writer调用,它会阻塞writer(即writer会进入睡眠等待),直到经过grace period后,即所有的reader都退出读端临界区,writer才可以继续下一步操作。如果有多个writer调用该函数,它们将在grace period之后全部被唤醒。
- call_rcu()是synchronize_rcu()的非阻塞版本,它也由 writer调用,但不会阻塞writer(即writer不会进入睡眠,而是继续运行),因而可以在中断上下文或 softirq 中使用,而 synchronize_rcu()只能在进程上下文使用。call_rcu()把参数中指定的callback函数挂接到 RCU回调函数链上,然后立即返回。一旦所有的 CPU 都已经完成端临界区操作(即grace period之后),该callback函数将被调用,用writer修改过的数据副本替换原数据并释放原数据空间。需要指出的是,函数 synchronize_rcu 的实现实际上也使用了函数call_rcu()。
RCU API 有一些特殊用途的分支,比如 RCU BH (防DDoS攻击的API)、 RCU Sched (适用于 scheduler 和 interrupt/NMI-handler 的 API),等。参见 http://lwn.net/Articles/264090/
RCU在Linux 2.6内核中(RHEL6和SLES11 SP1)就已经存在了,那时处理 grace period 利用了软中断(softirq),而到了新的3.x内核之后有所改变,处理grace period利用的是内核线程,因为内核线程可以被抢占,减少了实时任务的响应延迟。所以在3.x内核的系统上,会看到类似如下的内核线程:
[rcu_sched]
[rcu_bh]
[rcuob/0]
[rcuob/1]
[rcuos/0]
[rcuos/1]
还有可能会见到rcu_preempt线程。
注:其中 rcuob/N, rcuos/N 是负责处理 RCU callbacks 的内核线程,参见https://www.kernel.org/doc/Documentation/kernel-per-CPU-kthreads.txt 。
好了,现在终于可以讲 RCU CPU Stall Detector 了,它其实是RCU代码中的一个debugging feature(参见https://lwn.net/Articles/301910/ ),在2.6内核中缺省是关闭的,而到3.x内核中缺省是打开的,它有助于检测导致 grace period 过度延迟的因素,因为grace period的长短是RCU性能的重要因素。发生RCU grace period延迟会在系统日志中记录告警信息,称为 RCU CPU Stall Warnings :
INFO: rcu_sched_state detected stalls on CPUs/tasks: { 15} (detected by 17, t=15002 jiffies)
或
INFO: rcu_bh_state detected stalls on CPUs/tasks: { 3 5 } (detected by 2, 2502 jiffies)
等等。
在上述告警信息之后通常还会看到相关CPU的stack dump,检查stack trace有助于了解当时运行的代码和可能导致延迟的原因。
如果你不想看到以上告警信息,可以通过以下参数关掉它:
/sys/module/rcupdate/parameters/rcu_cpu_stall_suppress
注:缺省值为0,表示显示延迟告警;置为1表示禁止显示延迟告警。
RCU grace period延迟多长时间会触发告警呢?这是以下参数决定的(以秒为单位):
/sys/module/rcupdate/parameters/rcu_cpu_stall_timeout
RCU的其它内核参数,如rcupdate.rcu_task_stall_timeout等参见:
https://www.kernel.org/doc/Documentation/kernel-parameters.txt
有哪些原因会导致 RCU grace period延迟(RCU CPU Stall Warnings)呢?参见https://www.kernel.org/doc/Documentation/RCU/stallwarn.txt 这里仅列举几例:
- CPU 在 RCU read-side 临界区内死循环
- CPU 在屏蔽中断的情况下死循环
- Linux引导过程中输出信息很多,但是所用的 console太慢,跟不上信息输出的速度。
- 任何可能导致 RCU grace-period 内核线程无法运行的因素
- 优先级很高的实时任务占着 CPU 不放,抢占了恰好处于RCU read-side临界区的低优先级任务。
- RCU代码bug。
- 硬件故障。.
- 等等。
具体分析还需从告警信息中输出的stack trace入手。
参考资料:
源代码:kernel/rcupdate.c, kernel/rcutree.c …
https://www.ibm.com/developerworks/cn/linux/l-rcu/
https://www.kernel.org/doc/Documentation/RCU/stallwarn.txt
https://www.kernel.org/doc/Documentation/RCU/whatisRCU.txt
http://lwn.net/Articles/262464/
https://lwn.net/Articles/518953/