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

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

相关推荐
東雪蓮☆3 小时前
Linux 网络流量监控 Shell 脚本详解(支持邮件告警)
linux·运维·服务器
吐个泡泡v3 小时前
网络编程基础:一文搞懂 Socket、HTTP、HTTPS、TCP/IP、SSL 的关系
网络·网络协议·http·https·socket·ssl·tcp
数模加油站4 小时前
25高教社杯数模国赛【C题国一学长思路+问题分析】第二弹
算法·数学建模·数模国赛·高教社杯全国大学生数学建模竞赛
小跌—4 小时前
Linux:进程信号理解
linux·c++·算法
Tadas-Gao4 小时前
阿里云通义MoE全局均衡技术:突破专家负载失衡的革新之道
人工智能·架构·大模型·llm·云计算
liulilittle4 小时前
HTTP简易客户端实现
开发语言·网络·c++·网络协议·http·编程语言
東雪蓮☆4 小时前
使用 Shell 脚本监控服务器 IOWait 并发送邮件告警
linux·运维·服务器
Lin_Aries_04215 小时前
在 CentOS 9 上安装 Docker 的完整指南
linux·docker·centos
DreamLife☼5 小时前
工业领域 ACP 协议全解析:从入门到实战案例
网络·安全·ai·工业·行为·acp·管控