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))