RCU(Read-Copy Update,讀取-複製-更新)是 Linux 核心中一種高效的同步機制,其核心思想在於「讀者無鎖、寫者複製」。對於受 RCU 保護的共享資料結構,讀者無需獲取任何鎖即可訪問;寫者則需先複製一份副本進行修改,最後透過回調機制在合適時機將指針指向新資料。這個「合適時機」即為 Grace Period(寬限期),由核心根據所有 CPU 是否歷經一次 Quiescent State(靜默狀態,即上下文切換)來判定。本文基於 Linux 2.6.21 核心原始碼,深入剖析 RCU 的實現細節。
RCU 可視為改進版的讀寫鎖:讀者幾乎零同步開銷,無需鎖、原子指令,亦不需記憶體屏障(Alpha 架構除外),避免了鎖競爭、記憶體延遲與流水線停滯。寫者雖需承擔較大同步成本(延遲釋放、複製資料、同步並發寫者),但讀寫並發性能極佳。需注意:RCU 不適用於寫操作頻繁的場景,否則寫者開銷將抵消讀者收益。
RCU 運作的關鍵前提:讀者在臨界區內不可被阻塞(即 CPU 不發生上下文切換)。寫者修改流程為:複製副本 → 修改副本 → 註冊回調函數 → 等待 Grace Period 結束 → 執行實際更新/釋放。Grace Period 定義為所有 CPU 皆歷經一次 Quiescent State 所需的等待時間。
rcu_read_lock() / rcu_read_unlock():標記讀者臨界區,禁止搶佔rcu_dereference():讀者獲取受保護指針rcu_assign_pointer():寫者發布新指針值(含寫記憶體屏障)synchronize_rcu():阻塞寫者,等待所有既有讀者退出call_rcu():非阻塞式註冊回調,Grace Period 後執行#define __rcu_read_lock() \
do { \
preempt_disable(); \
__acquire(RCU); \
rcu_read_acquire(); \
} while (0)
#define __rcu_read_unlock() \
do { \
rcu_read_release(); \
__release(RCU); \
preempt_enable(); \
} while (0)
本質上僅透過 preempt_disable()/enable() 禁止/啟用核心搶佔,確保臨界區內無上下文切換。
#define rcu_dereference(p) ({ \
typeof(p) _________p1 = ACCESS_ONCE(p); \
smp_read_barrier_depends(); \
(_________p1); \
})
#define rcu_assign_pointer(p, v) ({ \
if (!__builtin_constant_p(v) || ((v) != NULL)) \
smp_wmb(); \
(p) = (v); \
})
利用 smp_read_barrier_depends() 與 smp_wmb() 保證指針讀寫的可見性與順序性。
void synchronize_rcu(void) {
struct rcu_synchronize rcu;
init_completion(&rcu.completion);
call_rcu(&rcu.head, wakeme_after_rcu);
wait_for_completion(&rcu.completion);
}
static void wakeme_after_rcu(struct rcu_head *head) {
struct rcu_synchronize *rcu = container_of(head, struct rcu_synchronize, head);
complete(&rcu->completion);
}
synchronize_rcu() 同步等待;call_rcu() 將回調掛入當前 CPU 的 rcu_data.nxtlist,啟動異步處理流程。
核心維護全域 rcu_ctrlblk(記錄 cur、completed、cpumask)與 Per-CPU rcu_data(含 nxtlist、curlist、donelist 三條回調鏈表、passed_quiesc 標誌、batch/quiescbatch 批次號)。
時鐘中斷 → update_process_times() → rcu_pending() 檢查四種條件 → 觸發 RCU_SOFTIRQ → 執行 __rcu_process_callbacks():
curlist 為空,將 nxtlist 移入,遞增全域 cur,重置 cpumask(全 1 表示所有 CPU 待檢測)quiescbatch 與 rcp->cur,不等則將 passed_quiesc=0、qs_pending=1,更新 quiescbatchpassed_quiesc=1,從 cpumask 清除該 CPU;若 cpumask 歸零,Grace Period 結束,completed=curcurlist 移入 donelist,rcu_do_batch() 逐個執行回調(含 wakeme_after_rcu 喚醒寫者)schedule() 中調用 rcu_qsctr_inc() 置 passed_quiesc=1__do_softirq() 中調用 rcu_bh_qsctr_inc(),以軟中斷執行作為靜默標誌核心提供 list_add_rcu()、list_del_rcu()、list_replace_rcu()、list_for_each_entry_rcu() 等,核心差異在於:
rcu_dereference()rcu_assign_pointer()next 為毒值,防止遍歷中訪問失效讀端:read_lock → rcu_read_lock,list_for_each_entry → list_for_each_entry_rcu
寫端:write_lock → spin_lock(或無鎖),list_del → list_del_rcu + call_rcu,list_add → list_add_rcu
採用 Copy-on-Write:kmalloc 複製節點 → 修改副本 → list_replace_rcu() 原子替換 → call_rcu() 延遲釋放舊節點
節點增設 deleted 旗標 + 自旋鎖保護;讀端檢查旗標;刪除端先置旗標再 list_del_rcu + call_rcu
RCU 以「讀者無鎖、寫者複製、Grace Period 延遲回收」為核心,極大提升讀多寫少場景的並發性能。代價為記憶體延遲釋放與寫者複雜度上升。理解其 Per-CPU 狀態機、軟中斷驅動的 Grace Period 推進、以及 Quiescent State 檢測機制,是掌握 RCU 精髓的關鍵。