首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

深入理解 RCU 實現機制:從 Linux 2.6.21 核心原始碼解析同步原理

     編輯:Java 綜合教學

前言

RCU(Read-Copy Update,讀取-複製-更新)是 Linux 核心中一種高效的同步機制,其核心思想在於「讀者無鎖、寫者複製」。對於受 RCU 保護的共享資料結構,讀者無需獲取任何鎖即可訪問;寫者則需先複製一份副本進行修改,最後透過回調機制在合適時機將指針指向新資料。這個「合適時機」即為 Grace Period(寬限期),由核心根據所有 CPU 是否歷經一次 Quiescent State(靜默狀態,即上下文切換)來判定。本文基於 Linux 2.6.21 核心原始碼,深入剖析 RCU 的實現細節。

RCU 核心原理

RCU 可視為改進版的讀寫鎖:讀者幾乎零同步開銷,無需鎖、原子指令,亦不需記憶體屏障(Alpha 架構除外),避免了鎖競爭、記憶體延遲與流水線停滯。寫者雖需承擔較大同步成本(延遲釋放、複製資料、同步並發寫者),但讀寫並發性能極佳。需注意:RCU 不適用於寫操作頻繁的場景,否則寫者開銷將抵消讀者收益。

RCU 運作的關鍵前提:讀者在臨界區內不可被阻塞(即 CPU 不發生上下文切換)。寫者修改流程為:複製副本 → 修改副本 → 註冊回調函數 → 等待 Grace Period 結束 → 執行實際更新/釋放。Grace Period 定義為所有 CPU 皆歷經一次 Quiescent State 所需的等待時間。

核心 API 速覽

  • 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() 保證指針讀寫的可見性與順序性。

Grace Period 核心機制:synchronize_rcu() 與 call_rcu()

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,啟動異步處理流程。

Per-CPU 資料結構與狀態機

核心維護全域 rcu_ctrlblk(記錄 cur、completed、cpumask)與 Per-CPU rcu_data(含 nxtlist、curlist、donelist 三條回調鏈表、passed_quiesc 標誌、batch/quiescbatch 批次號)。

軟中斷處理流程:rcu_process_callbacks()

時鐘中斷 → update_process_times() → rcu_pending() 檢查四種條件 → 觸發 RCU_SOFTIRQ → 執行 __rcu_process_callbacks():

  1. 新請求入隊:若 curlist 為空,將 nxtlist 移入,遞增全域 cur,重置 cpumask(全 1 表示所有 CPU 待檢測)
  2. 啟動 Quiescent State 檢測:比較 quiescbatch 與 rcp->cur,不等則將 passed_quiesc=0、qs_pending=1,更新 quiescbatch
  3. 收集 CPU 靜默狀態:後續軟中斷發現 passed_quiesc=1,從 cpumask 清除該 CPU;若 cpumask 歸零,Grace Period 結束,completed=cur
  4. 回調遷移與執行:curlist 移入 donelist,rcu_do_batch() 逐個執行回調(含 wakeme_after_rcu 喚醒寫者)

Quiescent State 觸發點

  • 普通 RCU:schedule() 中調用 rcu_qsctr_inc() 置 passed_quiesc=1
  • BH RCU:__do_softirq() 中調用 rcu_bh_qsctr_inc(),以軟中斷執行作為靜默標誌

RCU 鏈表操作 API

核心提供 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 標誌)

節點增設 deleted 旗標 + 自旋鎖保護;讀端檢查旗標;刪除端先置旗標再 list_del_rcu + call_rcu

總結

RCU 以「讀者無鎖、寫者複製、Grace Period 延遲回收」為核心,極大提升讀多寫少場景的並發性能。代價為記憶體延遲釋放與寫者複雜度上升。理解其 Per-CPU 狀態機、軟中斷驅動的 Grace Period 推進、以及 Quiescent State 檢測機制,是掌握 RCU 精髓的關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved