谢宝友:深入理解RCU之六:分级RCU基础

原创 Linux阅码场 2023-12-27 08:02

本文简介

本文介绍Linux 2.6.32-rc7中,分级RCU的基础。

这不是一篇单独的文章,这是《谢宝友:深入理解Linux RCU》系列的第6篇。关注Linuxer公众号看前五篇:

作者简介

谢宝友,别名浪子燕青,在编程一线工作已经有20年时间,其中10年时间工作于Linux操作系统。

同时,他也是《深入理解并行编程》一书的译者。该书作者PaulE.McKeney是IBM Linux中心技术领导者,LinuxRCU Maintainer。《深入理解RCU》系列文章整理了PaulE.McKeney的相关著作,希望能帮助读者更深刻的理解Linux内核中非常难于理解的模块:RCU。

目前,他编写的Hot-Pot操作系统即使发布第一个版本。这个版本基于ARM 64多核系统,包含了调度、中断、定时器、内存管理、LEXT3文件系统、LWIP网络协议栈、一个精简版本的内核态C库、一些基本的Shell命令等等基本的操作系统功能。虽然目前代码还比较丑陋,仅仅算是Linux的一个小学生,但是也可以自豪的声称:Hot-Pot操作系统拥有了一个Good Start。今年,他将编写一本《Hot-Pot操作系统详解--迈向工业级服务器操作系统的实现》来详细阐述这个操作系统,并以GPL协议公布源代码,热切希望所有有兴趣的好汉一起参与。

    联系方式:

    mail:scxby@163.com

    微信:linux-kernel


1、准备工作

本文基于linux 2.6.32-rc7版本的源码, 因此请准备一份linux2.6.32-rc7代码。建议用如下两种方法获取源代码:

1、直接在linux.org上面下载源码包。

2、使用git从linux-next拉取最新代码,然后使用git checkout -b linux-2.6.32-rc7 v2.6.32-rc7检出2.6.32-rc7版本的源码。

2、概述

虽然Linux更早版本中的经典RCU,其读端原语拥有出色的性能和扩展性,但是写端原语则需要判断预先存在的读端临界区在什么时候完成,它仅仅被设计用于数十个CPU的系统。经典RCU的实现,要求在每个优雅周期内,每个CPU必须获取一个全局锁,这使得它们的扩展性受到了限制。虽然在实际生产系统中,经典RCU可以运行在几百个CPU的系统中,甚至能够比较困难的使用到上千个 CPU的系统中,但是大型多核系统仍然需要更好的扩展性。

另外,经典RCU有一个不是最优的dynticks 接口,导致经典RCU在每一个优雅周期都要唤醒每一个CPU,即使这些CPU处于idle状态。我们考虑一个16核的系统,它只有四个CPU比较忙,其他CPU的负载都很轻。理想情况下,余下12个CPU可以一直处于深度睡眠模式以节约能源。然而不幸的是,如果四个忙的CPU频繁的执行RCU更新,这12个空闲CPU会被周期性的唤醒,浪费了重要的能源。因此,对于经典RCU的任何优化,都应当让这些睡眠状态的CPU继续处于睡眠状态。

经典RCU和分级RCU实现都有和经典RCU相同的语义和API。但是,原有的实现被称为“经典RCU”,新实现被称为“分级RCU”。

2.1.  RCU基础回顾

从最基本的方面来说,RCU 是一种等待事务完成的方法。当然,要等待事务完成,还存在很多其他方法,包括引用计数、读写锁、事件等等。RCU的一个大的优势是可以同时等待20,000个不同的事件,而不必具体的跟踪其中每一个事件,并且不用担心性能被降低,以及扩展性被限制,也不用担心复杂的死锁情况和内存泄漏的危险。

在RCU中,被等待的事件被称为“RCU 读端临界区”。RCU读端临界区以rcu_read_lock()原语开始,以相应的rcu_read_unlock() 原语结束。RCU读端临界区可以嵌套,也可以包含相当多的代码,只要这些代码不阻塞或者睡眠(当然,这是针对经典RCU来说的。有一种特殊的名为SRCU的可睡眠RCU,它允许在SRCU读端临界区中进行短期睡眠)。如果您遵从这些约束,您可以使用RCU来等待任何代码片段完成。

RCU通过间接的确定其他事务何时完成来实现这一点。但是,请注意:在特定的优雅周期之后开始的RCU 读端临界区能够、也必然会延长优雅周期的结束点。

2.2.  经典RCU实现概要

经典RCU实现的关键原理是:经典RCU 读端临界区限制其中的内核代码不允许阻塞。这意味着在任意时刻,一个特定的CPU只要看起来处于阻塞状态、IDLE循环、或者离开了内核后,我们就知道所有RCU读端临界区已经完成。这些状态被称为“静止状态”,当每一个CPU已经经历过至少一次静止状态时,RCU优雅周期结束。

经典RCU最重要的数据结构是rcu_ctrlblk,它包含了->cpumask字段,每一个CPU在该字段中包含一位,如上图所示。当每一个优雅周期开始时,每一个 CPU相应的位被设置为1,每一个CPU经过一次静止状态时,必须清除相应的位。由于多个CPU可能希望同时清除它们的位,这将破坏->cpumask 字段,因此使用了一个->lock自旋锁来保护->cpumask。不幸的是,当超过几千个CPU时,这个自旋锁会遇到严重的竞争状态。更糟糕的是,事实上所有CPU必须清除它们的位,意味着在一个优雅周期内,CPU不允许一直睡眠。这削弱了LINUX节能的能力。

2.3.  RCU 迫切要解决的问题

实时RCU迫切要解决的问题列表如下:

1.   延迟销毁。这样,直到所有已经预先存在的RCU读端临界区已经完成,一个RCU优雅周期才能结束。

2.   可靠性,这样RCU支持24x7运行。

3.   可以在IRQ处理函数中调用。

4.   包含内存标记,这样,如果有很多回调过程,这种机制将加快结束优雅周期。

5.   独立的内存块,这样RCU能够基于可信的内存分配器进行工作。

6.   synchronization-free的读端,这样允许通常的非原子指令操作于CPU(或者任务)的本地内存。

7.   无条件的read-to-write提升,在LINUX内核中,有几个地方需要这样使用。

8.   兼容的API。

9.   抢占RCU读端临界区的要求可以被去掉。

10.  极低的RCU内部锁的竞争,从而带来极大的扩展性。RCU必须支持至少1,024个CPU,最好是至少4,096个CPU。

11.  节能:RCU必须能够避免唤醒低电压状态的dynticks-idle CPU,但是仍然能够判断当前的优雅周期何时结束。这已经在实时RCU中实现,但是需要大大的简化。

12.  RCU读端临界区必须允许在NMI处理函数中使用,就如在中断处理函数中一样。

13.  RCU必须很好的管理不停的CPU热插拨操作。

14.  必须能够等待所有事先注册的RCU回调完成,虽然这已经以rcu_barrier()的形式提供。

15.  检测失去响应的CPU是值得的,以帮助诊断RCU和死循环BUG及硬件错误,这能够防止RCU优雅周期不能结束的情况。

16.  加快RCU优雅周期是值得的,这样RCU优雅周期能够强制在数百微秒内完成。但是,这样的操作预期会带来严重的CPU负载。

最急迫的首要需求是:可扩展性。因此需要减少RCU的内部锁。

2.4.  迈向可扩展RCU实现

减少锁竞争的一个有效方法是创建一个分级结构,如上图所示。在此,四个rcu_node 结构中的每一个都有各自的锁,这样只有 CPU 0 和 1 会获取最左边的 rcu_node的锁, CPU 2 和 3 会获取中间的rcu_node的锁,CPU 4和5会获取右边的rcu_node的锁。在任一个优雅周期期间,仅仅某一个CPU节点会访问rcu_node 结构的上一层的rcu_node。也就是说,在上图中,每一对CPU(它们处于同一个CPU节点)中,最后一个记录静止状态的CPU才会访问上一层的rcu_node。

这样做的最终结果,是减少了锁的竞争。在经典RCU中,6个CPU在每一个优雅周期内竞争同一个全局锁,在上图中,仅仅是三个节点竞争最上层的rcu_node锁 (降低了50%)。

rcu_node结构树被嵌入到rcu_state 结构的一个线性数组,树根是结点0,如上图。它是一个8-CPU的、三层分级结构的系统。每一个箭头将一个rcu_node 结构链接到它的父结点,这对应着rcu_node结构的->parent 字段。每一个rcu_node都标示了它所覆盖的CPU范围,这样根结点覆盖了所有CPU,每一个二级结点覆盖了一半的CPU,每一个叶子结点覆盖了两个 CPU。这个数组在编译时基于NR_CPUS的值静态分配。

上图显示了如何检测优雅周期。在第一个图中,没有CPU经过静止状态,并用红块标示。假设所有6个CPU试图同时告诉RCU,它们已经经过一个静止状态。那么,在每一对CPU中,仅仅其中某一个CPU能够获得底层rcu_node结构的锁。第二个图中,假设CPU0、3、5比较幸运的获得了底层rcu_node结构的锁,在图中标识为绿色块。一旦这些幸运的CPU结束了,那么其他CPU将获得锁,如图3所示。这三个CPU中,每一个CPU将会发现它们是组内最后一个CPU,因此所有三个CPU尝试移到上层rcu_node。此时,仅仅其中一个能获得上层rcu_node 锁。我们假设CPU1、2、4依次获得了锁,则第4、5、6图显示了相应的状态。最后,第6图显示了所有CPU已经经过一次静止状态,因此优雅周期结束。

在上面的顺序中,没有超过3个CPU为同一个锁产生竞争,与经典RCU进行对比,我们会高兴的发现,经典RCU中,所有6个CPU都可能冲突。但是,对更多的CPU来说,可以再显著的减少锁之间的冲突。考虑有64个底层结构及64*64=4,096 CPU的分组结构,如图上图。

在此,每一个底层rcu_node 结构的锁被64个CPU申请,将从经典RCU的4096个CPU竞争一个单一的锁降为64个CPU竞争一个锁。在一个特定的优雅周期期间,仅仅一个底层rcu_node 中的某一个CPU会申请上级rcu_node 的锁。这样,与经典RCU相比,减少了64倍的锁竞争。

2.5.  迈向不成熟的RCU实现

正如较早前提示的一样,这些努力的一个重要目的是使一个处于睡眠状态的CPU保持它的睡眠状态,以节约能源。与之相对的是,经典RCU至少会在一个优雅周期内唤醒每一个处于睡眠状态的CPU。当其他大多数CPU都处于空闲状态时,这些个别的CPU进行rcu写操作,会使得这种处理方法不是最优的。这种情形将在周期性的高负载系统中发生,我们需要更好的处理这种情况。

这是通过要求所有CPU操作位于一个每CPU rcu_dynticks 结构中的计数器来实现的。不是那么准确的说,当相应的CPU处于dynticks idle模式时,计数器的值为偶数,否则是奇数。这样,RCU仅仅需要等待rcu_dynticks 计数值为奇数的CPU经过静止状态,而不必唤醒正在睡眠的CPU。如上图,每一个每CPU rcu_dynticks结构被“rcu”和“rcu_bh”实现所共享。

2.6.  状态机

从十分高层的视角来看,Linux内核RCU 实现可以被认为是一个高级状态机,如上图。在一个很繁忙的系统上,通常的路径是最上面的两个循环。在每一个优雅周期(GP)开始时进行初始化,等待静止状态 (QS)。在一个特定的优雅周期中,当每一个CPU都经历过静止状态时,它其实什么都不用做。在这样一个系统中,经历如下事件表明产生一个静止状态:

1、每一次进程切换

2、在CPU进入idle状态

3、或者执行用户态代码时

CPU热插拨事件将使状态机进入“CPU Offline”流程。而“holdout”CPU(那些由于软件或者硬件原因导致迟迟不能经过一次静止状态的CPU)的出现,使得不能快速经历一次静止状态,这将使状态机进入“send reschedIPIs to Holdout CPUs”(发送重新调度IPI给Holdout CPUS)流程。为了避免不必要的唤醒处于dyntick-idle 状态的CPU,RCU 实现将标记这些CPU处于扩展的静止状态,通过“Y”分支离开“CPUs in dyntick-idle Mode?”(但是请注意,这些处于dyntick-idle模式的CPU将不会被发送重新调度IPI)。最后,如果CONFIG_RCU_CPU_STALL_DETECTOR打开了,过迟的到达静止状态将使状态机进入“Complain About Holdout CPUs”流程。

上面的状态图中,事件会与不同的数据结构交互。但是,状态图不会被任何RCU实现直接翻译为C代码。相反的,这些实现在内核中被编码为事件驱动的系统。我们通过一些用例来表示这些事件。 

2.7. 用例

这些事件驱动的用例包括:

1. 开始一个新的优雅周期 

2. 经历一个静止状态

3. 向RCU通告一个静止状态 

4. 进入、退出Dynticks Idle 模式 

5. 从Dynticks Idle 模式进入中断 

6. 从 Dynticks Idle 模式进入NMI 

7. 标记一个CPU处于Dynticks Idle 模式 

8. CPU离线 

9. CPU上线 

10. 检测一个太长的优雅周期

2.7.1. 开始一个新的优雅周期

rcu_start_gp()函数开始一个新的优雅周期。当一个CPU存在回调,而该回调需要等待优雅周期时,就需要调用此函数。

rcu_start_gp()函数更新rcu_state和rcu_data结构中的状态,以标识开始一个新的优雅周期,获取->onoff 锁 (并关中断) 以防止任何并发的CPU热插拨操作,在所有的rcu_node结构中设置位,以标识所有CPU (包括当前CPU) 必须经历一次静止状态,最后释放->onoff 锁。

设置位操作分两个阶段进行。首先,在没有持有任何锁的情况下,非叶子节点rcu_node 的位被设置,然后,在持有->lock的情况下,每一个叶子节点的rcu_node 结构的位被设置。

2.7.2. 经历一次静止状态

rcu和rcu_bh有各自的静止状态集合。

RCU的静止状态是进程切换、IDLE (不管是dynticks 还是IDLE循环)、以及执行用户态程序。

RCU-bh的静止状态是在开中断状态下,退出软中断。

需要注意的是,rcu的静止状态也是rcu_bh的静止状态。rcu的静止状态通过调用rcu_qsctr_inc()来记录。而rcu_bh的静止状态通过调用rcu_bh_qsctr_inc()来记录。这两个函数将它们的状态记录到当前CPU的rcu_data 结构中。请注意:在2.6.32版本中,rcu_qsctr_inc和rcu_bh_qsctr_inc函数已经被更名。如何通过git查找它们被更名为什么名称,这个任务就留给作者当做练习了。

这些函数在调度器、__do_softirq()和rcu_check_callbacks()中被调用。后面这个函数在调度时钟中断中调用,并分析状态以确定中断是否发生在一个静止状态中,以确定是调用rcu_qsctr_inc()或者 rcu_bh_qsctr_inc()。它也触发RCU_SOFTIRQ软中断,并导致当前CPU在随后的软中断上下文中调用rcu_process_callbacks(),rcu_process_callbacks函数处理RCU在每个CPU上的回调函数以释放资源。

2.7.3. 向RCU宣告一次静止状态

前述的rcu_process_callbacks() 函数要完成几个事情:

1. 确定何时结束一个太长的优雅周期(通过force_quiescent_state())。

2. 当CPU检测到优雅周期结束时,采用适当的动作。(通过 rcu_process_gp_end())。“适当的动作”包括加快本CPU的回调,以及记录新的优雅周期。同一个函数也更新状态以响应其他CPU。 

3. 向RCU核心机制报告当前CPU的静止状态。(通过 rcu_check_quiescent_state(),它会调用 cpu_quiet())。当然,这个过程也会标记当前的优雅周期结束。

4. 如果没有处理优雅周期,并且这个CPU有RCU回调等待优雅周期,则开始一个新的优雅周期(通过 cpu_needs_another_gp()和rcu_start_gp())。

5. 当优雅周期结束时,调用这个CPU的回调 (通过 rcu_do_batch())。

这些接口都经过精心实现,以避免BUG,如:错误的从上一个优雅周期向当前优雅周期报告静止状态这样的BUG。

2.7.4. 进入和退出 Dynticks Idle模式

调度器调用rcu_enter_nohz()进入dynticks-idle 模式,并调用 rcu_exit_nohz()离开此模式。rcu_enter_nohz() 函数递增每CPU dynticks_nesting变量,也递增每CPU dynticks计数器,然后,后者必然拥有一个偶数值。rcu_exit_nohz() 函数递减每CPU dynticks_nesting 变量,并且再一次递增每CPU dynticks计数器,后者将拥有一个奇数值。

dynticks 计数器可以被其他 CPU采样。如果其值是偶数,那么该CPU处于扩展静止状态。类似的,如果计数器在一个特定的优雅周期内发生了改变,那么CPU必然在优雅周期期间的某个时间点上处于扩展静止状态。但是,还需要采样另外一个dynticks_nmi每CPU变量,随后我们将讨论这个变量。

2.7.5. 从Dynticks Idle 模式进入中断

从dynticks idle 模式进入中断由rcu_irq_enter() 和 rcu_irq_exit()处理。rcu_irq_enter() 函数递增每CPU dynticks_nesting 变量,如果此变量为0,也递增dynticks每CPU变量 (它将拥有一个奇数值)。

rcu_irq_exit()函数递减每CPU dynticks_nesting变量。并且,如果新值是0,也递增dynticks每CPU变量 (它将拥有一个偶数值)。

注意:进入中断会处理退出dynticks idle模式,反之也一样。进入、退出之间不一致可能导致一些混乱,不用警告你也应该想得到这一点。

2.7.6. 从Dynticks Idle 模式进入NMI

从dynticks idle模式进入NMI由rcu_nmi_enter()和rcu_nmi_exit()处理。这些函数同时递增dynticks_nmi计数器,但仅仅是在前述dynticks 计数是偶数时才进行递增。换句话说,如果NMI发生时,处于非dynticks-idle模式或者处于中断状态,那么 NMI将不操作dynticks_nmi计数器。

这两个函数之间唯一的差异在于错误检查,rcu_nmi_enter()必然使dynticks_nmi计数器为奇数值,rcu_nmi_exit()必然使这个计数器为偶数值。

2.7.7. 标记CPU处于Dynticks Idle模式

force_quiescent_state()函数实现一个三阶段的状态机。第一个阶段 (RCU_INITIALIZING)等待rcu_start_gp()完成对优雅周期的初始化。这个状态不是从force_quiescent_state()退出,就是从rcu_start_gp()退出。

在第二阶段(RCU_SAVE_DYNTICK),dyntick_save_progress_counter()函数扫描还没有报告静止状态的CPU,记录它们的每CPU dynticks 和dynticks_nmi 计数器。如果这些计数器都是偶数值,那么相应的CPU处于dynticks-idle 状态,因此标记它们为扩展静止状态(通过cpu_quiet_msk()报告)。

在第三阶段(RCU_FORCE_QS),rcu_implicit_dynticks_qs()函数再一次扫描仍然没有报告静止状态的CPU (既没有明确标示,也没有在RCU_SAVE_DYNTICK阶段隐含的标示),再一次检查每CPU dynticks 和 dynticks_nmi计数器。如果每一个值都变化,或者目前为偶数,那么相应的相应的CPU已经经过一次静止状态或者目前处于dynticks idle模式,也就是前述扩展静止状态。

如果rcu_implicit_dynticks_qs()发现特定CPU既没有处于dynticks idle模式,也没有报告一个静止状态,它调用rcu_implicit_offline_qs(),这个函数检查CPU是否处于离线状态,如果是,那么也报告一个扩展静止状态。如果CPU在线,那么rcu_implicit_offline_qs()发送一个重新调度IPI,尝试提醒该CPU应当向RCU报告一个静止状态。

请注意:force_quiescent_state() 既不直接调用dyntick_save_progress_counter(),也不直接调用rcu_implicit_dynticks_qs(),而是将它们传递给rcu_process_dyntick() 函数。这个函数抽象出扫描CPU、报告扩展静止状态的通用代码。

2.7.8. CPU离线

CPU离线事件导致rcu_cpu_notify()调用rcu_offline_cpu(),在rcu和rcu_bh上依次调用__rcu_offline_cpu()。这个函数清除离线CPU的位,这样,后面的优雅周期将不再期望这个CPU宣告静止状态,随后调用cpu_quiet(),以宣告离线扩展静止状态。这是在持有全局->onofflock锁的情况下执行的,这是为了防止与优雅周期初始化相冲突。

2.7.9. CPU上线

CPU上线事件导致rcu_cpu_notify()调用rcu_online_cpu(),用于初始化CPU的dynticks状态,然后调用rcu_init_percpu_data()初始化CPU的rcu_data 数据结构,也设置这个 CPU的位(同样通过全局->onofflock进行保护),这样后面的优雅周期将等待这个CPU的静止状态。最后,rcu_online_cpu()设置这个CPU的RCU 软中断向量。

2.7.10. 检测太长的优雅周期

当配置了CONFIG_RCU_CPU_STALL_DETECTOR内核参数时,record_gp_stall_check_time() 函数记录当前时间,以及3秒以后的时间戳。如果当前优雅周期到期后仍然没有结束,那么check_cpu_stall函数将检测罪魁祸首。并且如果当前CPU是造成延迟的CPU,则调用print_cpu_stall(),如果不是,则调用print_other_cpu_stall()。两个jiffies的时间差有助于确保其他CPU在可能的情况下报告它的状态,利用这个时间差,CPU能够做一些事情,例如跟踪它自己的堆栈。

2.8. 测试

RCU是基本的同步代码,因此RCU的错误导致的后果是随机的、难于调试的内存错误。因此,高可靠的RCU是非常重要的。这些可靠性来自于小心的设计,但是最终还是需要依赖于高强度的压力测试。

幸运的是,虽然有一些关于覆盖性方面的争论,但是仍然可以对软件进行一些压力测试。实际上,进行这些测试是被强烈建议的,因为不对你的软件进行折磨性测试的话,它就会反过来折磨你,这种折磨来自于:它在不合时宜的时候崩溃掉。

因此,我们使用rcutorture模块来对RCU进行压力测试。

但是,根据通常情况下的RCU用法来对RCU进行测试,显得还不是很充分。也有必要针对不常用的情况进行压力测试。例如,CPU并发的上线或者离线,CPU并发的进入及退出dynticks idle模式。Paul使用了一个脚本CodeSamples,并向模块rcutorture使用test_no_idle_hz 模块参数对dynticks idle模式进行压力测试。有时作者也比较疑神疑鬼,因此尽量在测试时运行一个kernbench负载测试程序。在128路的机器上运行10个小时的压力测试,看起来是足够测试出几乎所有BUG了。

实际上这还不算完。Alexey Dobriyan和Nick Piggin早在2008年就证明过,以所有相关内核参数组合对RCU进行压力测试是必要的。相关的内核参数可以使用另外一个脚本CodeSamples进行标识。

1. CONFIG_CLASSIC_RCU:经典 RCU。

2. CONFIG_PREEMPT_RCU:可抢占 (实时) RCU。

3. CONFIG_TREE_RCU:用于大型SMP系统的经典 RCU。

4. CONFIG_RCU_FANOUT:每一个rcu_node 的子结点数量。

5. CONFIG_RCU_FANOUT_EXACT:平衡rcu_node 树。

6. CONFIG_HOTPLUG_CPU:允许 CPU上线、离线。

7. CONFIG_NO_HZ:打开dyntick-idle 模式。

8. CONFIG_SMP:打开 multi-CPU选项。

9. CONFIG_RCU_CPU_STALL_DETECTOR:当CPU进入扩展静止状态时进行RCU检测。

10. CONFIG_RCU_TRACE:在debugfs中生成 RCU跟踪文件。

我们忽略CONFIG_DEBUG_LOCK_ALLOC 配置变量,因为我们假设分级RCU不能打断 lockdep。仍然有10个配置变量,如果它们是独立的布尔值,则导致1024种组合。幸运的是,首先,其中前三个是互斥的,这样可以将组合数量减少到384个,但是CONFIG_RCU_FANOUT可以取值2-64,将组合数量增加到12,096。这么大量的组合是不可能都实施的。

关键的一点是:如果CONFIG_CLASSIC_RCU或者CONFIG_PREEMPT_RCU有效时,预期仅仅CONFIG_NO_HZ 和 CONFIG_PREEMPT 可能会改变其行为。这几乎减少了三分之二的组合。

而且,并不是这些所有可能的CONFIG_RCU_FANOUT值都会产生显著有效的结果,实际上仅仅一部分情况需要分别测试:

1. 单结点“tree”。

2. 两级平衡树。

3. 三级平衡树。

4. 自动平衡树,当 CONFIG_RCU_FANOUT 指定一个不平衡树,但是没有配置CONFIG_RCU_FANOUT_EXACT 时,进行自动平衡。 

5. 非平衡树。

更进一步说,CONFIG_HOTPLUG_CPU仅仅在指定CONFIG_SMP 时才有用,CONFIG_RCU_CPU_STALL_DETECTOR是独立的,因此仅仅需要测试一次(然而有些人比我还多疑,他们可能决定在有CONFIG_SMP和没有CONFIG_SMP 时,都测试它)。类似的,CONFIG_RCU_TRACE也仅仅需要测试一次。但是象我一样多疑的人,会选择在有CONFIG_NO_HZ 和没有CONFIG_NO_HZ 时,都测试一下它。

这允许我们在15种测试情形下,得到一个覆盖率较好的RCU测试。所有这些测试情形都指定如下配置参数以运行rcutorture,这样CONFIG_HOTPLUG_CPU=n会产生实际的效果:

CONFIG_RCU_TORTURE_TEST=m

CONFIG_MODULE_UNLOAD=y

CONFIG_SUSPEND=n

CONFIG_HIBERNATION=n

15个测试用例如下:

1. 强制单节点“树”,用于小型系统:

CONFIG_NR_CPUS=8

CONFIG_RCU_FANOUT=8

CONFIG_RCU_FANOUT_EXACT=n

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

2. 强制两级节点树用于大型系统:

CONFIG_NR_CPUS=8

CONFIG_RCU_FANOUT=4

CONFIG_RCU_FANOUT_EXACT=n

CONFIG_RCU_TRACE=n

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

3. 强制三级节点树,用于非常大型的系统: 

CONFIG_NR_CPUS=8

CONFIG_RCU_FANOUT=2

CONFIG_RCU_FANOUT_EXACT=n

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

4. 测试自动平衡:

CONFIG_NR_CPUS=8

CONFIG_RCU_FANOUT=6

CONFIG_RCU_FANOUT_EXACT=n

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

5. 测试不平衡树:

CONFIG_NR_CPUS=8

CONFIG_RCU_FANOUT=6

CONFIG_RCU_FANOUT_EXACT=y

CONFIG_RCU_CPU_STALL_DETECTOR=y

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

6. 禁止CPU延迟检测:

CONFIG_SMP=y

CONFIG_NO_HZ=y

CONFIG_RCU_CPU_STALL_DETECTOR=n

CONFIG_HOTPLUG_CPU=y

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

7. 禁止 CPU延迟检测及dyntick idle 模式:

CONFIG_SMP=y

CONFIG_NO_HZ=n

CONFIG_RCU_CPU_STALL_DETECTOR=n

CONFIG_HOTPLUG_CPU=y

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

8. 禁止 CPU延迟检测及CPU热插拨:

CONFIG_SMP=y

CONFIG_NO_HZ=y

CONFIG_RCU_CPU_STALL_DETECTOR=n

CONFIG_HOTPLUG_CPU=n

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

9. 禁止 CPU延迟检测,dyntick idle 模式,及CPU热插拨:

CONFIG_SMP=y

CONFIG_NO_HZ=n

CONFIG_RCU_CPU_STALL_DETECTOR=n

CONFIG_HOTPLUG_CPU=n

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

10. 禁止SMP、CPU延迟检测、dyntick idle 模式、及CPU热插拨:

CONFIG_SMP=n

CONFIG_NO_HZ=n

CONFIG_RCU_CPU_STALL_DETECTOR=n

CONFIG_HOTPLUG_CPU=n

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

这个组合有一些编译警告。

11. 禁止SMP、禁止CPU热插拨:

CONFIG_SMP=n

CONFIG_NO_HZ=y

CONFIG_RCU_CPU_STALL_DETECTOR=y

CONFIG_HOTPLUG_CPU=n

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=y

12. 有dynticks idle 但是没有抢占的情况下,测试经典RCU:

CONFIG_NO_HZ=y

CONFIG_PREEMPT=n

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=y

CONFIG_TREE_RCU=n

13. 有抢占但是没有dynticks idle时,测试经典RCU:

CONFIG_NO_HZ=n

CONFIG_PREEMPT=y

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=n

CONFIG_CLASSIC_RCU=y

CONFIG_TREE_RCU=n

14. 在dynticks idle情况下,测试可抢占RCU:

CONFIG_NO_HZ=y

CONFIG_PREEMPT=y

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=y

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=n

15. 在没有 dynticks idle时,测试可抢占RCU:

CONFIG_NO_HZ=n

CONFIG_PREEMPT=y

CONFIG_RCU_TRACE=y

CONFIG_PREEMPT_RCU=y

CONFIG_CLASSIC_RCU=n

CONFIG_TREE_RCU=n

对于每一次大的影响RCU核心代码的变化,都应当以上面的组合运行rcutorture,并且在CONFIG_HOTPLUG_CPU时,并发的进行CPU热插拨。对小的变化,在每一种情况下运行kernbench就行了。当然,如果变化仅仅限于配置参数的部分子集,就可以减少测试用例的数量。

作者强烈推荐压力测试软件:Geneva Convention!

2.9. 结论

这个分级RCU实现减少了锁竞争,避免了不必要的唤醒dyntick-idle睡眠状态的CPU,因此有助于调试Linux CPU热插拨代码。这个实现被设计用于处理数千个CPU的大型系统,并且在64位系统上,CPU数量限制是250,000,在今后一段时间内,这个限制是没有问题的。

这个RCU实现当然也有一些局限:

1. force_quiescent_state()可能在关中断下扫描整个CPU集。这在实时RCU实现中,是一个重大缺陷。因此,如果需要在可抢占RCU中加入分级,则需要其他方法。在4096个CPU的系统中,它可能会产生一些问题,但是需要在实际的系统中进行测试以证明真的有问题。

在繁忙的系统中,不能指望force_quiescent_state()扫描会发生,CPU将在开始一个静止状态后,在三个jiffies内经历一次静止状态。在半繁忙的系统中,仅仅处于dynticks-idle模式的CPU需要扫描。其他情况下,例如,在一个dynticks-idle CPU扫描过程中,处理一个中断时,后继的扫描是需要的。但是,这样的扫描是分别在相应的CPU上执行的,因此相应的调度延迟仅仅影响该扫描过程所在的CPU负载。

如果扫描被证明确实有问题,一个好的方法是进行递增扫描。这将稍微增加一点代码复杂性,也增加一点结束优雅周期的时间,但是这也确实算是一个好的方案。

2. rcu_node分级在编译时创建,因此其长度是最大的CPU数量NR_CPUS。但是,即使在4,096 CPU的系统中,在64位系统上,rcu_node 分级也仅仅消耗65个缓存行。(即使在32位系统上包含4,096 CPUs也是这样!)。当然,在一个16 CPU的系统中,配置NR_CPUS=4096将使用一个二级树,实际上在这种情况下,单节点树也会运行得很好。虽然这个配置会增加锁的负载,但是实际上不会影响经常执行的读端代码,因此事实上不会有太大的问题。

3. 这个补丁会稍微增加内核代码及数据尺寸:在NR_CPUS=4的系统中,从经典RCU的1,757字节内核代码、456字节数据,共2213字节的内核尺寸,而分级RCU则增加到4,006字节的内核代码、624字节的内核数据,共计4,630字节尺寸。即使对大多数嵌入式系统来说,这也不是一个问题。这些系统通常有上百兆主内存。但是对特别小的系统来说,这可能就是一个问题了,需要提供两种类型的RCU实现以满足这样的嵌入式系统。不过有一个有趣的问题,在这样的系统中,也许仅仅包含一个CPU,这样的系统完全可以用一个特别简单的RCU实现。

即使有这些问题,相对于经典RCU来说,在数百个CPU的系统中,这个分级RCU实现仍然是一个巨大的进步。最后需要说明一下,经典RCU设计用于16-32个CPU的系统。

在某些地方,在可抢占RCU实现中使用分级是有必要的。

后续章节将继续分析分级RCU的代码,以及Linux中其他一些RCU的实现。也许还会讨论实现RCU这类复杂并行软件的开发方法及其形式化验证。


近期精彩文章:

EMC潘国林: 大话存储系列之磁盘娶亲(RAID)

ARM刘永康: 浅谈Android数字版权管理之视频保护

宋宝华:是谁关闭了Linux抢占,而抢占又关闭了谁?

...

Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论 (0)
  • 伴随无线技术的迅速发展,无线路由器市场商机日益庞大。现代消费者在选购无线路由器(Wi-Fi AP)时,通常依赖的是该产品在无干扰的实验室环境中,量测得到的数据报告。然而,这些数据往往是在受控的RF隔离环境中进行测试,无法完全反映真实使用场景。这种情况导致许多消费者抱怨,他们购买的产品效能与宣称的数据不符。在实际应用中,消费者常因Wi-Fi讯号不稳定、传输速度不如预期或设备过热而产生客诉。产品仰赖实验室的数据够吗?无线路由器(Wi-Fi AP)ODM供货商遇到什么挑战?一家台湾知名的无线路由器(W
    百佳泰测试实验室 2025-04-05 00:12 55浏览
  • 【拆解】+南孚测电器拆解 之前在天猫上买了一盒南孚电池,他给我送了一个小东西—测电器。今天我们就来拆解一下这个小东西,看看它是怎么设计和工作的。 三颗指示灯显示电池剩余电量。当点亮3颗LED时,则表示点亮充足。当点亮2颗LED时,则表示还能用。当点亮1颗LED时,表示点亮地建议更换,当无法点亮LED时,则表示没电了。外壳上还印有正负极,以免用户将电池放反。 这个小东西拆解也很方便,一个螺丝刀稍微撬几下。外壳就下来了,它是通过卡扣连接。 开盖后,测电线路板清晰呈现在眼前。 让我们看看小小的线路板有
    zhusx123 2025-04-05 15:41 53浏览
  • 在追求环境质量升级与产业效能突破的当下,温湿度控制正成为横跨多个行业领域的核心命题。作为环境参数中的关键指标,温湿度的精准调控不仅承载着人们对舒适人居环境的期待,更深度关联着工业生产、科研实验及仓储物流等场景的运营效率与安全标准。从应用场景上看,智能家居领域要求温湿度系统实现与人体节律的协同调节,半导体洁净车间要求控制温湿度范围及其波动以保障良品率,而现代化仓储物流体系则依赖温湿度的实时监测预防各种产品的腐损与锈化。温湿度传感器作为实现温湿度监测的关键元器件,其重要性正在各行各业中凸显而出。温湿
    华普微HOPERF 2025-04-07 10:05 77浏览
  • 医疗影像设备(如CT、MRI、超声诊断仪等)对PCB的精度、可靠性和信号完整性要求极高。这类设备需要处理微伏级信号、高频数据传输,同时需通过严格的EMC/EMI测试。制造此类PCB需从材料选择、层叠设计、工艺控制等多维度优化。以下是关键技术与经验分享。 1. 材料选择:高频与生物兼容性优先医疗影像设备PCB常采用 Rogers RO4000系列 或 Isola FR4高速材料,以降低介电损耗并保证信号稳定性。例如,捷多邦在客户案例中曾为某超声探头厂商推荐 Rogers RO4350B
    捷多邦 2025-04-07 10:22 79浏览
  • 及时生产 JIT(Just In Time)的起源JIT 起源于 20 世纪 70 年代爆发的全球石油危机和由此引发的自然资源短缺,这对仰赖进口原物料发展经济的日本冲击最大。当时日本的生产企业为了增强竞争力、提高产品利润,在原物料成本难以降低的情况下,只能从生产和流通过程中寻找利润源,降低库存、库存和运输等方面的生产性费用。根据这种思想,日本丰田汽车公司创立的一种具有特色的现代化生产方式,即 JIT,并由此取得了意想不到的成果。由于它不断地用于汽车生产,随后被越来越多的许多行业和企业所采用,为日
    优思学院 2025-04-07 11:56 88浏览
  • 引言:POPO声的成因与影响在语音芯片应用中,WT588F08A作为一款支持DAC+功放输出的高集成方案,常因电路设计或信号处理不当,在音频播放结束后出现POPO声(瞬态噪声)。这种噪声不仅影响用户体验,还可能暴露电路设计缺陷。本文将基于实际案例,解析POPO声的成因并提供系统化的解决方案。一、POPO声的根源分析1. 功放电路状态切换的瞬态冲击当DAC输出的音频信号突然停止时,功放芯片的输入端若处于高阻态或无信号状态,其内部放大电路会因电源电压突变产生瞬态电流,通过喇叭表现为POPO声。关键因
    广州唯创电子 2025-04-07 09:01 91浏览
  • 在科技浪潮奔涌的当下,云计算领域的竞争可谓是如火如荼。百度智能云作为其中的重要参与者,近年来成绩斐然。2024年,百度智能云在第四季度营收同比增长26%,这样的增速在行业内十分惹眼。回顾全年,智能云业务的强劲增长势头也十分明显,2024年第一季度,其收入达到47亿元,同比增长12%;第二季度营收51亿元,同比增长14%。从数据来看,百度智能云在营收方面一路高歌猛进,展现出强大的发展潜力。然而,市场对百度智能云的表现似乎并不完全买账。2024年,尽管百度智能云数据亮眼,但百度股价却在震荡中下行。在
    用户1742991715177 2025-04-06 20:25 63浏览
  • 在影像软的发展历程中,美图曾凭借着美图秀秀等一系列产品,在“颜值经济”的赛道上占据了领先地位,成为了人们日常生活中不可或缺的一部分,也曾在资本市场上风光无限,2016 年上市时,市值一度超过46亿美元,备受瞩目。 然而,随着市场的不断发展和竞争的日益激烈,美图逐渐陷入了困境。商业模式单一,过度依赖在线广告收入,使得其在市场波动面前显得脆弱不堪;多元化尝试,涉足手机、电商、短视频、医美等多个领域,但大多以失败告终,不仅未能带来新的增长点,反而消耗了大量的资源。更为严峻的是,用户流失问题日
    用户1742991715177 2025-04-05 22:24 65浏览
  • OT(Operational Technology,运营技术)指的是用于监控和控制物理设备、流程和基础设施的技术,广泛应用于工业控制系统(ICS)、制造业、能源、电力、交通、水利等领域。OT网络主要包括SCADA(数据采集与监控系统)、DCS(分布式控制系统)、PLC(可编程逻辑控制器)等设备和协议,如Modbus、PROFINET、EtherCAT等。随着 IT/OT 融合、工业物联网(IIoT)、NDR、零信任架构等技术的落地,OT 网络正在向更开放、智能和安全的方向发展。然而,针对 OT
    艾体宝IT 2025-04-03 16:39 35浏览
  • 【拆解】+沈月同款CCD相机SONY DSC-P8拆解 这个清明假期,闲来无事,给大伙带来一个老古董物品的拆解--索尼SONY DSC-P8 CCD相机。这个产品是老婆好几年前在海鲜市场淘来的,由于显示屏老化,无法正常显示界面了,只有显示背光。但是这也无法阻止爱人的拍照。一顿盲操作依旧可以拍出CCD古董相机的质感。如下实拍: 由于这个相机目前都在吃灰。我就拿过来拆解,看看里面都是怎样个设计,满足下电子爱好者的探索。 首先给大伙展示下这台老相机的全貌。正视图  后视图 
    zhusx123 2025-04-06 17:38 86浏览
  •   安全生产预警系统作为现代工业与安全管理的重要组成部分,正以前所未有的技术引领力,创新性地塑造着未来的安全管理模式。这一系统通过集成多种先进技术,如物联网、大数据、人工智能、云计算等,实现了对生产环境中潜在危险因素的实时监测、智能分析与及时预警,为企业的安全生产提供了坚实的技术保障。   技术引领:   物联网技术:物联网技术使得各类安全监测设备能够互联互通,形成一张覆盖全生产区域的安全感知网络。传感器、摄像头等终端设备实时采集温度、压力、气体浓度、人员位置等关键数据,为预警系统提供丰富的
    北京华盛恒辉软件开发 2025-04-05 22:18 76浏览
  • 一、为什么流量可见性如此重要?在网络管理中,及时掌握流量状况至关重要,这不仅有助于快速排查故障、优化性能,还能提升安全防护能力。为了实现这一目标,企业通常依赖 SPAN 端口(交换机端口镜像)或 网络 TAP(测试接入点)来捕获和分析流量。然而,这两种方法在数据完整性、性能影响和监控能力上存在显著差异。如何选择合适的方案,以确保网络监控的精准性和高效性?本文将深入解析 SPAN 端口与网络 TAP 的核心区别,帮助你做出明智决策。二、SPAN 端口:简单易用,但有局限SPAN 端口也称为镜像端口
    艾体宝IT 2025-04-03 16:41 39浏览
  • 探针台是一种用于半导体、光电、集成电路等领域的精密测试设备,主要用于对芯片、晶圆、微电子器件等进行电气性能测试和分析。以下是探针台的主要用途和功能。1. ‌半导体行业‌‌晶圆测试‌:在晶圆制造过程中,探针台用于检测晶圆上各个芯片的电气性能,及时发现缺陷芯片,避免后续封装成本的浪费。‌失效分析与可靠性验证‌:通过高精度测试,探针台能够识别芯片设计中的微小缺陷,确保产品的质量和可靠性。2. ‌光电行业‌‌光电元件测试‌:探针台用于测试LED、光电探测器和激光器等光电元件的性能,支
    锦正茂科技 2025-04-03 16:25 35浏览
  • 文/杜杰编辑/cc孙聪颖‍2025年的3月,成功挺过造车至暗时刻的小米创始人雷军,接连迎来人生的高光。(详情见:雷军熬过黑夜,寄望小米SU7成为及时雨)在颜值即正义的舆论导向之下,全国两会期间,雷军凭借得体的衣着、挺拔的身姿赢得赞誉。面对雷军的压人表现,连行事一向沉稳、不愿跟风的海尔,都推出“leadership”组合拳,试图助力自家boss,不落下风。(详情见:两会声音|本届全国两会,周云杰为海尔省了多少广告费?)喜事接连不断,紧接着的3月18日,雷军重磅宣布小米 “史上最强年报”。雷军的公关
    华尔街科技眼 2025-04-03 20:30 42浏览
  • 引言:小型化趋势下的语音芯片需求随着消费电子、物联网及便携式设备的快速发展,产品设计对芯片的小型化、高集成度和低功耗提出了更高要求。厂家凭借其创新的QFN封装技术,推出WTV系列(如WTV380)及WT2003H系列语音芯片,以超小体积、高性能和成本优势,为紧凑型设备提供理想解决方案。产品核心亮点1. QFN封装技术赋能超小体积极致尺寸:WTV380采用QFN32封装,尺寸仅4×4毫米,WT2003H系列同样基于QFN工艺,可满足智能穿戴、微型传感器等对空间严苛的场景需求。高密度集成:QFN封装
    广州唯创电子 2025-04-07 08:47 68浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦