当前位置: 首页 > web >正文

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 的相关控制字段。

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)信息。它也保存与回调、阻塞任务及优先级提升相关的管理结构。

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)相关的锁和统计信息。

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 等),便于在不同的宽限期阶段进行移动和处理,从而避免回调在单一链表中长期堆积,利于批量执行与延迟释放。

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(实时/抢占)。

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

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

9. 总结

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

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

http://www.xdnf.cn/news/20533.html

相关文章:

  • leetcode 912 排序数组(快速排序)
  • 【CV】Opencv图像处理——①几何变换 (1)
  • 神马 M66S+ 282T矿机参数详解:SHA-256算法与Hydro冷却技术
  • 贪心算法应用:食品生产线排序问题详解
  • 继承详解(c++)
  • langchain源码概览
  • Java全栈开发面试实录:从基础到实战的深度解析
  • 【牛客刷题-剑指Offer】BM18 二维数组中的查找:一题四解,从暴力到最优
  • Python元组:不可变但灵活的数据容器
  • LwIP入门实战 — 3 以太网外设 (ETH)
  • 什么是JQ
  • solidity函数篇2
  • Netty从0到1系列之EventLoop
  • 魅族 Note 16 解锁 BL 及 Root 官方刷机包下载Flyme 12.0.1.5A 型号 M521Q
  • 基于SVN搭建企业内部知识库系统实践
  • 试用电子实验记录本ELN的经验之谈
  • 【算法】92.翻转链表Ⅱ--通俗讲解
  • Vue 3项目中引用ECharts并设计多种图表组件的实现方案
  • 行政区划编码树形题解
  • 09_多态
  • `IntersectionObserver`延迟加载不在首屏的自动播放视频/图片/埋点/
  • Boost电路:稳态和小信号分析
  • Linux匿名管道和命名管道以及共享内存
  • C++并发编程指南 递归锁 介绍
  • Kimi K2-0905 256K 上下文 API 状态管理优化教程
  • 2.虚拟内存:分页、分段、页面置换算法
  • 分享一个基于Python+大数据的房地产一手房成交数据关联分析与可视化系统,基于机器学习的深圳房产价格走势分析与预测系统
  • Embedding上限在哪里?- On the Theoretical Limitations of Embedding-Based Retrieval
  • AI产品经理面试宝典第86天:提示词设计核心原则与面试应答策略
  • 《sklearn机器学习——聚类性能指标》Calinski-Harabaz 指数