Linux RCU (Read-Copy-Update) 机制深度分析

Linux RCU (Read-Copy-Update) 机制深度分析

1. 快速概览

RCU(Read-Copy-Update)是 Linux 内核中针对"读多写少"场景设计的高性能并发原语。核心思想很直接:

  • 读者可以在无锁的情况下并发读取共享数据;
  • 更新通过发布新版本并延迟回收旧版本来保证安全;
  • 回收旧对象必须等待一个宽限期 (Grace Period),确保所有可能正在访问旧对象的读者完成。

这种设计带来极低的读延迟和良好的可扩展性,适合网络协议、缓存层、驱动等读密集型内核子系统。


2. 核心原理

2.1 三个基本要素

  1. 发布/订阅(Publish/Subscribe)

    更新者发布新版本,读者在自己的临界区内直接读取当前版本指针,不阻塞。

  2. 宽限期(Grace Period)

    系统需要确认所有可能的读者都已离开旧版本的临界区,才可以回收旧数据。

  3. 延迟回调(Deferred Callback)

    回收工作通过回调(call_rcu())登记,等到宽限期结束再执行实际释放。

这些要点合起来,就是 RCU 能做到读无锁、写延迟回收的根本原因。
rcu_read_lock rcu_read_lock rcu_read_lock rcu_read_unlock Update Data Yes No Reader Thread 1 RCU Critical Section Reader Thread 2 Reader Thread 3 Exit Critical Section Writer Thread call_rcu Register Callback Wait Grace Period Execute Callback Free Old Data Grace Period Detection All CPUs passed QS? GP Complete Wait for QS

2.2 常见的 RCU 变体(选型要点)

  • TREE_RCU(CONFIG_TREE_RCU):面向多核、大规模系统,使用分层节点做可扩展聚合和跟踪。
  • TINY_RCU(CONFIG_TINY_RCU):面向单核或资源受限设备,实现精简、占用少。
  • PREEMPT_RCU(CONFIG_PREEMPT_RCU):支持内核抢占,适用于实时/低延迟需求的系统。

选型原则:系统规模、是否需要抢占、内存/CPU 受限程度 ------ 根据这些条件选合适的 RCU 实现。


3. 关键数据结构

下面列出内核中常见的 RCU 数据结构,阅读时把重点放在"它们在 GP(宽限期)与回调管理中承担的角色"。

3.1 struct rcu_state(全局状态)

该结构管理 RCU 的树状节点、宽限期序列和用于调度 GP 的内核线程等。关键字段包括 CPU 数量、GP 序列号、GP 内核线程、以及屏障和快速 GP 的相关控制字段。

c 复制代码
struct rcu_state {
    struct rcu_node node[NUM_RCU_NODES];    /* 分层树状结构 */
    struct rcu_node *level[RCU_NUM_LVLS + 1]; /* 层级指针数组 */
    int ncpus;                               /* CPU 数量 */
    int n_online_cpus;                       /* 在线 CPU 数量 */
    
    /* Grace Period 管理字段 */
    unsigned long gp_seq;                    /* Grace Period 序列号 */
    unsigned long gp_max;                    /* 最大 GP 持续时间 */
    struct task_struct *gp_kthread;          /* GP 内核线程 */
    struct swait_queue_head gp_wq;           /* GP 等待队列 */
    short gp_flags;                          /* GP 命令标志 */
    short gp_state;                          /* GP 状态 */
    
    /* 屏障同步字段 */
    struct mutex barrier_mutex;              /* 屏障互斥锁 */
    atomic_t barrier_cpu_count;              /* 等待的 CPU 数量 */
    struct completion barrier_completion;    /* 屏障完成信号 */
    
    /* 快速 Grace Period 字段 */
    struct mutex exp_mutex;                  /* 快速 GP 互斥锁 */
    unsigned long expedited_sequence;        /* 快速 GP 序列号 */
    atomic_t expedited_need_qs;              /* 需要 QS 的 CPU 数量 */
};

3.2 struct rcu_node(树节点)

rcu_node 用于把 CPU 分组为层级树,便于在大系统中聚合各组的 QS(quiescent state)信息。它也保存与回调、阻塞任务及优先级提升相关的管理结构。

c 复制代码
struct rcu_node {
    raw_spinlock_t lock;                     /* 节点锁 */
    unsigned long gp_seq;                    /* Grace Period 序列号 */
    unsigned long gp_seq_needed;             /* 需要的 GP 序列号 */
    unsigned long completedqs;               /* 完成的 QS 掩码 */
    unsigned long qsmask;                    /* QS 掩码 */
    unsigned long rcu_gp_init_mask;          /* GP 初始化掩码 */
    
    /* 树状结构字段 */
    u8 level;                                /* 树中的层级 */
    u8 grplo;                                /* 组内最低 CPU */
    u8 grphi;                                /* 组内最高 CPU */
    u8 grpnum;                               /* 组号 */
    struct rcu_node *parent;                 /* 父节点 */
    
    /* 阻塞任务管理 */
    struct list_head blkd_tasks;             /* 阻塞任务列表 */
    struct list_head *gp_tasks;              /* GP 任务指针 */
    struct list_head *exp_tasks;             /* 快速 GP 任务指针 */
    
    /* 优先级提升 */
    struct task_struct *boost_kthread_task;  /* 提升内核线程 */
    unsigned long boost_time;                /* 提升时间 */
    struct mutex boost_mutex;                /* 提升互斥锁 */
};

3.3 struct rcu_data(每 CPU 的 RCU 数据)

每个 CPU 保留一份 rcu_data,用于跟踪该 CPU 的 QS、回调队列(segmented callback list)、以及与无回调(no-CBs)相关的锁和统计信息。

c 复制代码
struct rcu_data {
    /* Grace Period 相关 */
    unsigned long gp_seq;                    /* GP 序列号 */
    unsigned long gp_seq_needed;             /* 需要的 GP 序列号 */
    union rcu_noqs rcu_qs_ctr_snap;          /* QS 计数器快照 */
    
    /* 回调管理 */
    struct rcu_segcblist cblist;             /* 分段回调列表 */
    long qlen_last_fqs_check;                /* 上次 FQS 检查时的队列长度 */
    
    /* CPU 状态 */
    bool rcu_urgent_qs;                      /* 紧急 QS 标志 */
    bool rcu_forced_tick;                    /* 强制时钟中断标志 */
    bool rcu_forced_tick_exp;                /* 快速 GP 强制时钟标志 */
    
    /* 节点关联 */
    struct rcu_node *mynode;                 /* 关联的 rcu_node */
    unsigned long grpmask;                   /* 组内掩码 */
    
    /* 无回调 CPU 支持 */
    raw_spinlock_t nocb_lock;                /* 无回调锁 */
    atomic_t nocb_lock_contended;            /* 锁竞争计数 */
    
    /* 统计信息 */
    unsigned long n_cbs_invoked;             /* 调用的回调数量 */
    unsigned long n_force_qs_snap;           /* 强制 QS 快照 */
    long blimit;                             /* 批处理限制 */
};

4. 分段回调列表(segmented callback list)

RCU 将回调按段保存(NEXT、WAIT、DONE 等),便于在不同的宽限期阶段进行移动和处理,从而避免回调在单一链表中长期堆积,利于批量执行与延迟释放。

c 复制代码
struct rcu_segcblist {
    struct rcu_head *head;                   /* 回调列表头 */
    struct rcu_head **tails[RCU_CBLIST_NSEGS]; /* 分段尾指针数组 */
    unsigned long gp_seq[RCU_CBLIST_NSEGS];  /* 各段的 GP 序列号 */
    long len;                                /* 总长度 */
    long seglen[RCU_CBLIST_NSEGS];           /* 各段长度 */
    u8 flags;                                /* 标志位 */
};

5. RCU 层次与回调流(图示保留原样)

5.1 树状层次(CPU → rcu_node → root)

Grace Period Flow Initialize Tree GP Start Wait for QS from all CPUs GP Complete Execute Callbacks Root rcu_node Level 0 rcu_node Level 1-1 rcu_node Level 1-2 rcu_node Level 1-3 CPU 0-7 rcu_data CPU 8-15 rcu_data CPU 16-23 rcu_data CPU 24-31 rcu_data CPU 32-39 rcu_data CPU 40-47 rcu_data

5.2 回调分段管理(阶段迁移)

New Callbacks NEXT Segment NEXT_TAIL Segment WAIT Segment WAIT_TAIL Segment DONE Segment Execute & Free Grace Period N Grace Period N+1 Move WAIT to DONE

(图说明:分段移动实现了"等待至少两个 GP 以后才真正释放"的策略,从而避免竞争窗口内的回收风险。)


6. API 快速参考(读者 / 更新者 / 快速 GP)

6.1 读者 API(必须尽量短的临界区)

  • rcu_read_lock() ------ 进入 RCU 读临界区
  • rcu_read_unlock() ------ 退出读临界区
  • rcu_dereference() ------ 安全读取 RCU 保护的指针(带内存屏障/读取语义)

注意:读临界区不可睡眠(除非使用相应的可睡眠 RCU API),要尽量短以加快 GP 完成。

6.2 更新者 API(发布与回收)

  • rcu_assign_pointer() ------ 原子发布新指针(写者使用)
  • call_rcu() ------ 异步登记回调,在 GP 完成后执行释放
  • synchronize_rcu() ------ 阻塞直到当前 GP 完成(同步等待,代价较大)

6.3 快速(Expedited)GP

  • call_rcu_expedited() / synchronize_rcu_expedited() ------ 用于紧急情形,强制快速推进 GP,但开销更高,应该谨慎使用。

7. 与其他同步机制比较(实用角度)

同步机制 读性能 写性能 内存开销 典型适用场景
RWLock 中等 中等 低 读写均衡
Spinlock 高(短临界区) 高 低 短临界区、高一致性需求
Mutex 低 中等 低 可能睡眠的长期操作
RCU 极高(读) 中等(写) 中等 读多写少、低延迟读

Synchronization Methods Reader Performance Writer Performance Memory Overhead RCU: O(1) RWLock: O(n) Spinlock: O(1) RCU: O(n) RWLock: O(1) Spinlock: O(1) RCU: Medium RWLock: Low Spinlock: Low

实践提示:若读操作占主导且对延迟敏感,优先考虑 RCU;否则按传统锁方案选择。


8. 最佳实践

8.1 读者端准则

  • 一律用 rcu_read_lock() / rcu_read_unlock() 包围读操作;尽量把临界区缩短到只包含必须的读取/解引用。
  • 读取指针时用 rcu_dereference(),以确保正确的内存序语义。
  • 切忌在 RCU 读临界区做长时间阻塞或可能睡眠的操作。

8.2 写者端准则

  • 用 rcu_assign_pointer() 发布新数据;旧对象的释放放到 call_rcu() 的回调里。
  • 避免在 RCU 回调里做复杂或阻塞的工作;回调只做释放或极轻量的清理。
  • 只有确实需要同步等待时才用 synchronize_rcu(),否则尽量用异步回调以保留写者并发。

8.3 数据结构与性能优化

  • 有回收需求的结构内嵌 struct rcu_head,便于统一回调管理。
  • 若回收对象数量大,优先批量回收而不是一个对象一个回调;可显著降低回调开销。
  • 选择合适的 RCU 变体:TREE_RCU(大系统)、TINY_RCU(嵌入式)、PREEMPT_RCU(实时/抢占)。

示例:批量释放的回调模式(示意)

c 复制代码
// 批量释放而不是单个释放
void batch_free_callback(struct rcu_head *head) {
    struct batch_data *batch = container_of(head, struct batch_data, rcu);
    // 批量处理多个对象
}

9. 总结

RCU 的价值在于把"低延迟读"与"安全延迟回收"巧妙结合:读者几乎零开销,写者通过宽限期+回调来完成不破坏并发安全的回收工作。

典型适用场景:网络协议栈、页缓存/文件系统缓存、设备驱动、内核中需要高并发读访问的数据结构。

相关推荐
Apipi*6 小时前
30天速通Linux 第六章信号及信号处理
linux·运维·信号处理
不像程序员的程序媛7 小时前
检测网络的命令mtr详解
网络
jonyleek7 小时前
IoT自动化别写死规则:用JVS-IOT可视化规则引擎实现可运维、可追溯、可复用的工业规则治理
边缘计算·可观测性·工业自动化·可视化编排·jvs-iot·iot规则引擎·规则治理
天启HTTP7 小时前
爬虫防封核心原理:IP轮换机制与风控规避逻辑
linux·服务器·网络·爬虫·tcp/ip
运维行者_8 小时前
网络性能监控怎么做?从自动发现到根因分析的4个环节
运维·服务器·网络·人工智能·支持向量机
信奥卷王8 小时前
[GESP202609 六级] 分树规划
算法
夏幻灵8 小时前
LeetCode 21:合并两个有序链表——双指针与哑节点详解
算法·leetcode·链表
徐小黑ACG9 小时前
Golang 基础05 结构体struct
开发语言·算法·golang
hz5678910 小时前
涉密视频会议设备配置指南:终端、音视频采集与配套设施选型
服务器·网络·数据库·安全·实时音视频·信息与通信·智能硬件