RCU 和 lockdep 检查¶
所有 RCU 变体都提供 lockdep 检查,以便 lockdep 知道每个任务何时进入和离开任何 RCU 读端临界区。每种 RCU 变体都被单独跟踪(但请注意,在 2.6.32 及更早版本中并非如此)。这允许 lockdep 的跟踪包含 RCU 状态,这有时可以帮助调试死锁等问题。
此外,RCU 提供了以下检查 lockdep 状态的原语
rcu_read_lock_held() for normal RCU.
rcu_read_lock_bh_held() for RCU-bh.
rcu_read_lock_sched_held() for RCU-sched.
rcu_read_lock_any_held() for any of normal RCU, RCU-bh, and RCU-sched.
srcu_read_lock_held() for SRCU.
rcu_read_lock_trace_held() for RCU Tasks Trace.
这些函数是保守的,因此如果不确定,将返回 1(例如,如果未设置 CONFIG_DEBUG_LOCK_ALLOC)。这可以防止像 WARN_ON(!rcu_read_lock_held()
) 这样的情况在禁用 lockdep 时给出误报。
此外,单独的内核配置参数 CONFIG_PROVE_RCU 允许检查 rcu_dereference()
原语
- rcu_dereference(p)
检查 RCU 读端临界区。
- rcu_dereference_bh(p)
检查 RCU-bh 读端临界区。
- rcu_dereference_sched(p)
检查 RCU-sched 读端临界区。
- srcu_dereference(p, sp)
检查 SRCU 读端临界区。
- rcu_dereference_check(p, c)
使用显式检查表达式 “c” 以及
rcu_read_lock_held()
。这在 RCU 读取器和更新器调用的代码中很有用。- rcu_dereference_bh_check(p, c)
使用显式检查表达式 “c” 以及
rcu_read_lock_bh_held()
。这在 RCU-bh 读取器和更新器调用的代码中很有用。- rcu_dereference_sched_check(p, c)
使用显式检查表达式 “c” 以及 rcu_read_lock_sched_held()。这在 RCU-sched 读取器和更新器调用的代码中很有用。
- srcu_dereference_check(p, c)
使用显式检查表达式 “c” 以及
srcu_read_lock_held()
。这在 SRCU 读取器和更新器调用的代码中很有用。- rcu_dereference_raw(p)
不检查。(尽量少用,如果可能的话,不要用。)
- rcu_dereference_raw_check(p)
根本不做 lockdep。(尽量少用,如果可能的话,不要用。)
- rcu_dereference_protected(p, c)
使用显式检查表达式 “c”,并省略所有屏障和编译器约束。这在数据结构无法更改时很有用,例如,在仅由更新器调用的代码中。
- rcu_access_pointer(p)
返回指针的值并省略所有屏障,但保留防止复制或合并的编译器约束。这在测试指针本身的值时很有用,例如,针对 NULL。
rcu_dereference_check()
检查表达式可以是任何布尔表达式,但通常会包含一个 lockdep 表达式。对于一个稍微复杂的例子,请考虑以下内容
file = rcu_dereference_check(fdt->fd[fd],
lockdep_is_held(&files->file_lock) ||
atomic_read(&files->count) == 1);
此表达式以 RCU 安全的方式获取指针 “fdt->fd[fd]”,并且,如果配置了 CONFIG_PROVE_RCU,则验证此表达式是否在以下情况下使用
RCU 读端临界区(隐式),或
持有 files->file_lock,或
在非共享的 files_struct 上。
在情况 (1) 中,该指针以 RCU 安全的方式获取,用于普通的 RCU 读端临界区,在情况 (2) 中,->file_lock 阻止任何更改发生,最后,在情况 (3) 中,当前任务是唯一访问 file_struct 的任务,同样阻止任何更改发生。如果上述语句仅从更新器代码调用,则可以将其编写为如下
file = rcu_dereference_protected(fdt->fd[fd],
lockdep_is_held(&files->file_lock) ||
atomic_read(&files->count) == 1);
这将验证上述情况 #2 和 #3,并且 lockdep 甚至会抱怨,即使它是在 RCU 读端临界区中使用,除非满足这两个案例之一。因为 rcu_dereference_protected()
省略了所有屏障和编译器约束,所以它生成的代码比其他 rcu_dereference()
变体更好。另一方面,如果 RCU 保护的指针或它指向的 RCU 保护的数据可以并发更改,则使用 rcu_dereference_protected()
是非法的。
像 rcu_dereference()
一样,当启用 lockdep 时,RCU 列表和 hlist 遍历原语会检查是否从 RCU 读端临界区内部调用。但是,可以将 lockdep 表达式作为附加的可选参数传递给它们。使用此 lockdep 表达式,这些遍历原语仅在 lockdep 表达式为 false 且它们是从任何 RCU 读端临界区外部调用时才会抱怨。
例如,workqueue for_each_pwq()
宏旨在用于 RCU 读端临界区内或持有 wq->mutex 的情况下。因此,它实现如下
#define for_each_pwq(pwq, wq)
list_for_each_entry_rcu((pwq), &(wq)->pwqs, pwqs_node,
lock_is_held(&(wq->mutex).dep_map))