RCU-1——内核文档翻译——RCU-tasks

一、The RCU-tasks subsystem:https://lwn.net/Articles/607117/ 翻译

读取-复制-更新(RCU)机制负责保留旧版本的数据结构,直到它知道没有 CPU 可以保存对它们的引用; 一旦发生这种情况,就可以释放这些结构。 不过最近,一位潜在的 RCU 用户提出了一些不同的要求:
是否可以推迟销毁旧数据结构,直到知道没有进程持有对它的引用? 正如 Paul McKenney 最近发布的 RCU-tasks 子系统( https://lwn.net/Articles/606959/ )所证明的那样,答案似乎是“是”。

普通 RCU 处理通过指针访问的数据结构。 当受 RCU 保护的结构必须更改时,维护该结构的代码会从制作副本开始。 对副本进行更改,然后相关指针更改为指向该新副本。 此时,旧版本是不可访问的,但可
能有运行的代码在进行更改之前已经获得了指向它的指针。 所以旧的结构还不能被释放。 相反,RCU 会一直等到系统中的每个 CPU 都经过上下文切换(或处于空闲状态)。 由于 RCU 的规则规定对数据结构
的引用只能保存在原子上下文中,“每个 CPU 都有上下文切换”条件保证不能保存对旧数据结构的引用。

不过,tracing代码使用的蹦床规则似乎有所不同,因为一个进程可以被抢占,同时仍然持有对旧版本的引用(即在其中运行)。 鉴于此,普通的 RCU 将无法管理这些结构,这意味着必须使用其他一些较慢的
锁定机制。 使用类似 RCU 的机制需要对规则进行一些更改。

在正常的 RCU 情况下,只有一个进程可以持有对任何给定 CPU 上受保护结构的引用; 因此,RCU 专注于确定何时没有 CPU 可以保存对给定数据结构的引用。 在这种情况下,每个 CPU 上可能有多个进程引用
受保护的数据结构,因此必须转移焦点。 因此,RCU-tasks 是一种机制,旨在确定何时没有进程(而不是没有处理器)可以持有这样的引用

使用此接口,已替换受保护数据结构的代码将通过调用安排旧版本的处理:

    void call_rcu_tasks(struct head *rhp, void (*func)(struct rcu_head *rhp));

一旦适当的“宽限期”过去,将使用给定的 rhp 调用 func() 以释放结构。 对于 RCU-task 的用户,这几乎就是整个 API。 与普通的 RCU 不同,RCU-tasks 没有等同于 rcu_read_lock() 的方法来访问受保护的数据结构。

多年来,为了最大限度地提高子系统的可扩展性,普通 RCU 已经变得非常复杂。 相反,RCU-tasks 非常简单,至少在其初始实现中是这样。 有一个已传递给 call_rcu_tasks() 但尚未执行的 rcu_head 结构链表。
补丁集添加了一个新的内核线程,负责管理该列表。 每秒一次,它会醒来以查看是否有任何新条目已添加到列表中(后续补丁将轮询替换为等待队列)。如果是这样,整个链表被移动到一个单独的链表,并开始等
待新的宽限期过去。

该等待首先为系统中的每个可运行进程创建一个单独的列表; 根据规则,不可运行的任务不能持有对受 RCU 任务保护的数据结构的引用,因此不需要考虑不可运行的任务。 对于每个可运行的任务,都会在任务结构
中设置一个特殊的“rcu_tasks_holdout”标志。 挂钩已放置在调度程序中,以便在任务自愿放弃 CPU 或返回用户空间时清除该标志。 RCU-tasks 内核线程进入一个单独的循环,每十分之一秒醒来一次,以处理“holdout”
任务列表; 任何已重置标志的都将从列表中删除。 一旦列表为空,就可以调用析构函数回调,循环可以重新开始。

随着补丁系列的继续,代码变得更加复杂。 测试基础设施和stall检测的增加在一定程度上增加了它的足迹。 不过,最大的增加是增加了对在 holdout 链表中退出的任务的处理。 显然,检查可能不再存在的任务结
构中的“holdout”标志是一个坏主意,因此确实需要妥善处理这种情况。 这样做涉及添加一种新型的受锁保护的双向链表和一堆管理代码; 它是整个补丁集中最大的部分。

到目前为止,我们还没有看到让其他代码真正使用这个新功能的补丁。 对这个补丁集的大部分评论来自 Peter Zijlstra,他担心轮询的开销和缺乏对开销的计算。 所以还有几个问题需要回答。 虽然 RCU-tasks 很可能
被证明是对 RCU API 的有用补充,但没有人期望在 3.17 合并窗口中看到它。

 

二、上文中提到的 RCU-tasks implementation:https://lwn.net/Articles/606959/ 翻译

本系列提供了一个 RCU-tasks 实现的原型,它被要求协助移除tramopoline(蹦床)。 这种类型的 RCU 是基于task的而不是基于 CPU 的,############## 并且具有自愿的上下文切换、用户模式执行和空闲循环作为其唯一的静止状态。
这种静止状态的选择确保在宽限期结束时,将不再有任何任务依赖于在该宽限期开始之前删除的trampoline。 这是可行的,因为这样的trampolines 不包含函数调用,不包含自愿的上下文切换,不切换到用户模式,也不切换到空闲状态。

本系列补丁如下:

1. 添加基本的 call_rcu_tasks() 功能。

2. 提供 cond_resched_rcu_qs() 以在长循环中强制进入静止状态,包括 RCU 任务静止状态。注:目前函数名应该改为了 cond_resched_tasks_rcu_qs()

3. 添加同步API:synchronize_rcu_tasks() 和 rcu_barrier_tasks()。

4. 为上述 API 添加 GPL 导出,由 Steven Rostedt 提供。

5. 为 RCU 任务添加 rcutorture 测试。

6. 将 RCU 任务测试用例添加到 rcutorture 脚本中。

7. 为 RCU 任务(stall-warning)添加停顿警告检查。

8. 添加与退出任务的同步,防止 RCU-tasks 等待退出任务。

9. 通过用等待/唤醒代替轮询来提高 RCU 任务的能源效率。


遗留问题包括:

o 尚未为 lockdep 设置列表锁定。

o 尚不清楚从空闲循环调用的函数中的蹦床是否得到正确处理。 或者,是否有人关心从空闲循环调用的函数中的蹦床。

o 当前的实现还不能识别开始执行的任务是用户模式。 相反,它等待下一个调度时钟滴答来记录它们。

o 因此,当前的实现不处理 nohz_full=CPU 执行以用户模式运行的任务。 有几个可能的修复正在考虑中。

o 如果任务在用户模式下执行时被抢占,则 RCU 任务宽限期不会结束,直到该任务恢复。 (是否有一些合理的方法来确定给定的抢占任务是从用户模式执行中抢占的?)

o RCU 任务需要添加到文档/RCU。

o 可能仍然存在错误。


三、相关实验

1. 上文提到的内核线程

# ps -AT | grep rcu
root            10    10     2       0      0 rcu_tasks_kthread   0 S rcu_tasks_kthre //"rcu_tasks_kthread"
root            11    11     2       0      0 rcu_tasks_kthread   0 S rcu_tasks_trace //"rcu_tasks_trace_kthread"

rcu_spawn_tasks_kthread_generic() 函数中注册的这两个内核线程。

 

posted on 2023-02-06 20:49  Hello-World3  阅读(266)  评论(0编辑  收藏  举报

导航