Linux percpu_rw_semaphoreper-CPU读写信号量与rcu_sync

发布时间:2026/6/14 12:25:08

Linux percpu_rw_semaphoreper-CPU读写信号量与rcu_sync Linux percpu_rw_semaphore per-CPU读写信号量与rcu_syncpercpu_rw_semaphore是Linux内核中一种高性能的读写信号量,其读路径使用per-CPU计数器避免原子操作和缓存行bouncing,写路径借助rcu_sync机制实现高效的读者静默等待.一、使用场景与设计目标percpu_rw_semaphore专为读操作极为频繁而写操作极少的场景设计(如全局系统配置的读取和修改).读者几乎不产生任何原子操作开销,写者需要等待所有读者释放读锁.cstruct percpu_rw_semaphore {struct rcu_sync rss; /* RCU同步机制 */unsigned int __percpu *read_count; /* per-CPU读者计数 */struct rcuwait writer; /* 写者等待队列 */wait_queue_head_t wait_writer; /* 写者等待队列头 */atomic_t block; /* 写者阻塞标志 *//* 锁依赖跟踪 */raw_spinlock_t lock; /* 保护refcount */atomic_t readers_block;};二、读者快路径读者获取锁时,仅做per-CPU变量的原子递增:cstatic inline void percpu_down_read(struct percpu_rw_semaphore *sem){/** 检查是否有写者在等待(block标志)* 无写者: 直接递增per-CPU计数器, 零开销* 有写者: 走慢路径*/if (likely(!atomic_read(sem-block))) {this_cpu_inc(*sem-read_count);/** 读内存屏障: 确保读锁获取前的所有读操作不会重排到获取之后* 同时对写入者可见*/smp_mb();} else {/* 有写者竞争, 走慢路径 */__percpu_down_read(sem);}}static inline void percpu_up_read(struct percpu_rw_semaphore *sem){/** 如果block标志没有被设置, 直接递减per-CPU计数器* 如果block标志被设置, 可能需要唤醒写者*/if (likely(!atomic_read(sem-block))) {smp_mb(); /* 确保临界区的所有读操作在递减前完成 */this_cpu_dec(*sem-read_count);} else {__percpu_up_read(sem);}}三、读者慢路径: 有写者竞争时当检测到有写者等待时,读者走慢路径,确保写者不会被永久饥饿:cstatic void __percpu_down_read(struct percpu_rw_semaphore *sem){/* 增加per-CPU计数器 */this_cpu_inc(*sem-read_count);smp_mb(); /* 确保inc在block检查之前全局可见 *//** 再次检查block标志.* 如果block已清除(写者已完成), 读者可以继续.* 如果block仍置位, 读者需要等待.*/if (likely(!atomic_read(sem-block)))return;/** 写者仍在等待, 先递减计数器(不持有读锁)* 等待写者完成后再重新获取*/this_cpu_dec(*sem-read_count);/* 等待rcu_sync同步 */rcu_sync_drain(sem-rss);/* 等待写者完成 */wait_event(sem-wait_writer, !atomic_read(sem-block));/* 写者完成后, 重新获取读锁 */this_cpu_inc(*sem-read_count);smp_mb();}四、写者路径写者需要等待所有读者释放锁.核心机制是先用rcu_sync等待一个GP(Grace Period),然后设置block标志阻止新读者进入,最后等待per-CPU计数器归零.cstatic inline void percpu_down_write(struct percpu_rw_semaphore *sem){/* 1. 等待一个RCU GP, 确保所有正在运行的读者完成 */rcu_sync_enter(sem-rss);/* 2. 设置block标志: 新读者走慢路径 */atomic_set(sem-block, 1);/* 3. 内存屏障: 确保block设置在读者counter检查之前可见 */smp_mb();/** 4. 等待所有读者释放读锁.* 遍历所有CPU, 求和read_count.* 当所有CPU的read_count均为0时, 写者获得锁.*/wait_event(sem-wait_writer,atomic_read(sem-readers_block) 0);/* 锁依赖跟踪 */rwsem_acquire(sem-dep_map, 0, 0, _RET_IP_);}static inline void percpu_up_write(struct percpu_rw_semaphore *sem){rwsem_release(sem-dep_map, _RET_IP_);/* 1. 清除block标志, 允许读者快路径 */atomic_set(sem-block, 0);/* 2. 唤醒所有等待的读者(包括在__percpu_down_read中等待的) */wake_up_all(sem-wait_writer);/* 3. 退出RCU同步 */rcu_sync_exit(sem-rss);}五、rcu_sync的详细机制rcu_sync是percpu_rw_semaphore实现的核心,用于高效地等待所有读者退出临界区.cstruct rcu_sync {int gp_state; /* GP状态 */int gp_count; /* GP计数 */wait_queue_head_t gp_wait; /* 等待队列 */struct rcu_head cb_head; /* RCU回调 */int sync_state;struct rcu_sync_type *type;};rcu_sync_enter的执行过程:cvoid rcu_sync_enter(struct rcu_sync *rsp){int gp_state;/* 增加gp_count, 标记需要同步 */gp_state atomic_inc_return(rsp-gp_state);if (gp_state 1) {/* 第一个进入者, 需要调度GP */WRITE_ONCE(rsp-gp_count, 1);/** 调用synchronize_rcu()或call_rcu()* 等待一个GP后, 所有在GP前的读者确保已退出*/rcu_sync_call(rsp);}/* 等待GP完成 */wait_event(rsp-gp_wait, READ_ONCE(rsp-gp_state) gp_state);}rcu_sync中使用的synchronize_rcu()确保:当函数返回时,所有在调用前进入RCU读端临界区的读者都已经退出.这是percpu_rw_semaphore能保证写者与读者互斥的关键.六、percpu_rw_semaphore的工作原理动画化时间轴:写者: rcu_sync_enter() - 等待GP - set block1 - 等待read_count0 - 持有写锁读者1: [RCU_READ] 释放 [新读操作快路径]读者2: [尝试读检测到block,走慢路径]关键点:- GP(宽限期)确保在rcu_sync_enter之前的读者完成后,block标志才被设置- 设置block标志后,所有试图进入的读者都走__percpu_down_read- 此时写者等待read_count清零即可七、与普通rw_semaphore的性能对比c/* 普通信号量的读者路径: 原子操作 可能的自旋 */void __sched down_read(struct rw_semaphore *sem){/* 原子递减, 缓存行bouncing */if (unlikely(atomic_long_sub_return_acquire(RWSEM_READER_BIAS,sem-count) 0))rwsem_down_read_failed(sem); /* 慢路径 */}/* percpu_rw_semaphore的读者路径: per-CPU操作, 零原子 */this_cpu_inc(*sem-read_count);smp_mb();/** 性能差异:* 128线程并发读, 普通rw_semaphore: ~2000ns/op (大量缓存行bouncing)* 128线程并发读, percpu_rw_semaphore: ~50ns/op (per-CPU操作)*/八、使用场景限制percpu_rw_semaphore的读者可以睡眠(不禁止抢占),因此不能用在中断上下文或持有spin_lock的代码路径中.同时,由于读者阻塞可能延迟写者,对于写延迟敏感的场景需要权衡.c/* 典型使用者: sysfs属性读取 */static struct percpu_rw_semaphore sysfs_sem;ssize_t sysfs_kf_read(struct kernfs_open_file *of, char *buf,size_t count, loff_t *pos){ssize_t ret;percpu_down_read(sysfs_sem);ret of-kn-attr.ops-read(of, buf, count, pos);percpu_up_read(sysfs_sem);return ret;}ssize_t sysfs_kf_write(struct kernfs_open_file *of, const char *buf,size_t count, loff_t *pos){ssize_t ret;percpu_down_write(sysfs_sem);ret of-kn-attr.ops-write(of, buf, count, pos);percpu_up_write(sysfs_sem);return ret;}percpu_rw_semaphore通过per-CPU计数器和rcu_sync机制,实现了读者零原子操作的高效读写锁.其核心是:读者只操作本地CPU缓存,写者通过RCU GP同步和block标志确保互斥.这一设计在读者远多于写者的场景下,性能远超传统的基于原子操作的读写信号量.

相关新闻