<\![CDATA[

引言

RCU(Read-Copy-Update)是Linux内核中最精妙的同步机制之一,其核心思想可以用一句话概括:读者永远不需要等待写者,写者通过复制-替换-延迟释放来实现更新。这种在读多写少场景下几乎零开销的读端性能,使得RCU成为内核中最广泛使用的同步原语之一——从进程调度、文件系统中断处理到网络路由表维护,处处可见RCU的身影。

本文将从RCU的基本语义出发,深入剖析宽限期(Grace Period)的实现机制、发布-订阅内存模型、读端与写端的原句API,并通过完整的实战代码示例展示RCU在内核中的经典应用场景。

RCU核心思想

传统锁的困境

在内核并发场景中,读多写少是最常见的数据访问模式。然而传统的同步原语在这类场景下表现不佳:

  • 读写锁(rwlock):读者之间仍需通过原子操作竞争读锁,在大量读者场景下造成缓存行弹跳(Cache Line Bouncing)
  • 自旋锁(spinlock):读者与写者互斥,并发性差
  • 顺序锁(seqlock):读者需重试,无法保护有指针的数据结构

RCU突破了这些限制:真正的读者零开销——rcu_read_lock() 和 rcu_read_unlock() 在大多数架构上几乎不产生任何指令开销。

读-拷贝-更新三阶段

RCU的更新操作分为三个阶段:

  1. 读(Read):读者持有读锁期间可以自由访问受RCU保护的数据,无需原子操作或内存屏障
  2. 拷贝(Copy):写者复制一份原始数据副本,在副本上执行修改
  3. 更新(Update):写者通过单一原子写操作将新数据指针发布到全局可见位置(发布语义),然后等待所有已存在的读者退出读端临界区(宽限期过后),最后安全回收旧数据

读端原句API

基本读端API

// 进入读端临界区(仅仅是编译器屏障,无运行时开销)
rcu_read_lock();

// 读取受RCU保护的数据
struct my_data *p = rcu_dereference(global_ptr);

// 使用数据...
// 此时p即使被写者替换,仍然指向一个有效的旧版本

// 退出读端临界区
rcu_read_unlock();

关键点:rcu_read_lock() 和 rcu_read_unlock() 在UP(单处理器)系统和抢占禁用的内核中退化为编译器屏障(barrier()),不产生任何原子指令。即使在SMP抢占内核中,也仅增加抢占计数。

rcu_dereference() — 发布-订阅语义

rcu_dereference() 是一个内存屏障宏,保证:

  • 后续的指针解引用发生在指针本身被读取之后(控制依赖屏障)
  • 在弱内存序架构(ARM、PowerPC)上,防止CPU对指针读取和解引用进行重排
static inline typeof(p) rcu_dereference(p)
{
    typeof(*p) *_________p1 = READ_ONCE((p));
    smp_read_depends_barrier();  // 依赖屏障
    return _________p1;
}

嵌套与可重入

RCU读端临界区支持任意深度的嵌套,同一个线程可以多次调用 rcu_read_lock(),必须配对相同次数的 rcu_read_unlock() 才真正退出。

rcu_read_lock();   // 计数: 1
rcu_read_lock();   // 计c: 2(嵌套)
p = rcu_dereference(head);
rcu_read_unlock(); // 计数: 1(仍在读端)
q = rcu_dereference(p->next);
rcu_read_unlock(); // 计数: 0(真正退出)

写端原句API

同步等待:synchronize_rcu()

synchronize_rcu() 是RCU最核心的写端API,它阻塞调用者直到所有已存在的读者退出读端临界区:

// 写端更新示例:替换全局指针
void update_data(struct my_data *new_data)
{
    struct my_data *old_data;
    
    spin_lock(                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部