目录
- 3. 读写锁机制:rwlock与rw_semaphore
- 5. RCU革命:Read-Copy-Update深度剖析
1. 引言:并发编程的挑战与Linux内核演进
技术背景
在现代多核处理器系统中,并发访问共享资源是内核设计的核心挑战。Linux内核从早期的单处理器时代发展到今天的数百核系统,同步机制经历了革命性演进:
1991-2000: 简单自旋锁 + 关中断2000-2010: Mutex/RWLock标准化2010-2020: RCU普及 + PREEMPT_RT2020-2025: 新型锁机制 + 硬件加速
核心问题:
本文目标
2. 基础同步原语:自旋锁与信号量
自旋锁 (Spinlock)
基本原理
自旋锁是忙等待锁,当线程无法获取锁时,会在CPU上循环自旋:
// 自旋锁定义spinlock_t lock;spin_lock_init(&lock);// 临界区保护spin_lock(&lock);// 访问共享资源shared_data++;spin_unlock(&lock);
工作流程:
类型与变体
// 标准自旋锁(可抢占)spinlock_t lock;// 原始自旋锁(不可抢占)raw_spinlock_t raw_lock;// 底层自旋锁(中断安全)unsignedlong flags;local_irq_save(flags);// 临界区local_irq_restore(flags);
关键区别:
- •
spinlock_t:在PREEMPT_RT下变为互斥锁 - •
raw_spinlock_t:始终忙等待,用于核心代码 - •
local_irq_save:禁用中断,用于中断处理程序
性能特征
优点:
缺点:
适用场景:
信号量 (Semaphore)
基本原理
信号量是计数型锁,支持多个访问者:
// 定义信号量(初始值为1)structsemaphoresem;sema_init(&sem, 1);// 获取信号量(可睡眠)down(&sem);// 临界区shared_data++;up(&sem);
操作类型:
- •
down_interruptible():可被信号中断
关键特性
// 二元信号量(初始值1)sema_init(&sem, 1);// 计数信号量(允许N个并发)sema_init(&sem, N);// 读写信号量(多读单写)structrw_semaphorerwsem;init_rwsem(&rwsem);
PREEMPT_RT行为:
适用场景:
3. 读写锁机制:rwlock与rw_semaphore
读写自旋锁 (rwlock_t)
设计哲学
允许多个读者并发,但写者独占:
// 定义读写锁rwlock_t rw_lock;rwlock_init(&rw_lock);// 读操作(共享)read_lock(&rw_lock);// 读取数据read_unlock(&rw_lock);// 写操作(独占)write_lock(&rw_lock);// 修改数据write_unlock(&rw_lock);
实现原理
// 简化的rwlock状态structrwlock_t {union {atomic_t counter;struct {// 高16位:读者计数// 低16位:写者标志 }; };};
获取读锁:
获取写锁:
PREEMPT_RT转变
在实时内核中,rwlock_t映射到rt_mutex:
// 非PREEMPT_RT:忙等待// PREEMPT_RT:可睡眠,支持优先级继承
影响:
读写信号量 (rw_semaphore)
增强功能
比rwlock更强大,支持更复杂的语义:
structrw_semaphorerwsem;init_rwsem(&rwsem);// 读操作down_read(&rwsem);// 读取数据up_read(&rwsem);// 写操作down_write(&rwsem);// 修改数据up_write(&rwsem);// 降级(写→读)downgrade_write(&rwsem);
关键特性
状态表示:
// 简化的状态机// 正数:读者数量// -1:写者独占// <-1:写者等待者
升级/降级:
- •
downgrade_write():写锁降级为读锁(不释放重获取)
PREEMPT_RT问题
文档指出的缺陷:
"On PREEMPT_RT, a preempted low-priority reader will continue holding its lock, thus starving even high-priority writers"
原因:
解决方案:
- • 使用
percpu_rw_semaphore(每CPU读写信号量)
4. 互斥锁进化:从mutex到rt_mutex
标准互斥锁 (mutex)
基本API
structmutexmutex;mutex_init(&mutex);// 获取锁(可睡眠)mutex_lock(&mutex);// 临界区mutex_unlock(&mutex);// 非阻塞尝试if (mutex_trylock(&mutex)) {// 成功获取 mutex_unlock(&mutex);}
内部实现
structmutex {atomic_t count; // 1: unlocked, 0: lockedstructlist_headwait_list;// 等待队列structtask_struct *owner;// 持有者(用于调试)};
获取流程:
- 3. 若失败(count < 0):加入等待队列,调度出去
关键特性
实时互斥锁 (rt_mutex)
优先级继承 (Priority Inheritance)
问题:优先级反转
高优先级任务T1 → 等待 → 低优先级任务T2T2被中优先级任务T3抢占结果:T1被T3间接阻塞(优先级反转)
解决方案:优先级继承
T2继承T1的高优先级T3无法抢占T2T1快速获得锁
实现
structrt_mutex {raw_spinlock_t wait_lock; // 保护等待队列structrb_root_cachedwaiters;// 优先级队列structtask_struct *owner;// 持有者};// 使用(与mutex相同API)structrt_mutexrt_lock;rt_mutex_init(&rt_lock);rt_mutex_lock(&rt_lock);rt_mutex_unlock(&rt_lock);
动态优先级调整
// 当T1等待T2的锁时voidadjust_priority(struct task_struct *waiter,struct task_struct *holder) {// 1. 计算继承优先级int new_prio = min(waiter->prio, holder->prio);// 2. 更新持有者优先级 holder->prio = new_prio;// 3. 传播到持有者的持有者(链式继承)if (holder->blocked_on) adjust_priority(waiter, holder->blocked_on);}
PREEMPT_RT下的转变
关键变化:
// 非PREEMPT_RTspinlock_t → raw_spinlock_t(忙等待)// PREEMPT_RTspinlock_t → rt_mutex(可睡眠+优先级继承)
影响:
5. RCU革命:Read-Copy-Update深度剖析
RCU核心思想
Read-Copy-Update:读取-复制-更新
// 传统锁:读也互斥read_lock(&lock);data = shared_data;read_unlock(&lock);// RCU:读完全无锁data = rcu_dereference(shared_data);// 更新:复制后原子替换structnew_data *new = kmalloc(...);memcpy(new, old);new->field = value;rcu_assign_pointer(shared_data, new);// 延迟释放旧数据call_rcu(&old->rcu, free_old_data);
三阶段协议
1. 读取阶段 (Read)
// 无锁读取rcu_read_lock(); // 仅禁用抢占(非锁)p = rcu_dereference(pointer); // 内存屏障// 使用prcu_read_unlock(); // 启用抢占
关键点:
- •
rcu_dereference():确保看到完整初始化的数据
2. 更新阶段 (Copy-Update)
// 复制旧数据structentry *new = kmem_cache_alloc(cache);*new = *old; // 浅拷贝// 修改副本new->value = new_value;// 原子替换指针rcu_assign_pointer(global_ptr, new);// 标记旧数据待释放kmem_cache_free(cache, old);
关键点:
- •
rcu_assign_pointer():确保新数据初始化完成后再发布
3. 宽限期 (Grace Period)
// 释放旧数据的回调voidfree_old_data(struct rcu_head *head) {structentry *old = container_of(head, struct entry, rcu); kmem_cache_free(cache, old);}// 注册回调call_rcu(&old->rcu, free_old_data);
宽限期原理:
CPU0: 读者A进入RCU读临界区CPU1: 更新者替换指针CPU2: 读者B使用新指针CPU3: 读者C使用新指针宽限期结束条件:所有CPU都经历过一次上下文切换→ 保证所有旧读者已完成→ 安全释放旧数据
RCU实现机制
经典RCU (Classic RCU)
// 读端rcu_read_lock();p = rcu_dereference(ptr);// 使用prcu_read_unlock();// 写端new = copy_and_update(old);rcu_assign_pointer(ptr, new);call_rcu(&old->rcu, free_func);
宽限期检测:
- • 每个CPU维护quiescent state(静止状态)
- • 当所有CPU都经历quiescent state,宽限期结束
读写RCU (URCU)
// 读端(可睡眠)rcu_read_lock();p = rcu_dereference(ptr);// 使用prcu_read_unlock();// 写端(同步更新)synchronize_rcu(); // 等待宽限期// 安全修改数据
区别:
- •
synchronize_rcu():阻塞直到宽限期结束
任务RCU (Task RCU)
// 用于可睡眠的长读临界区rcu_read_lock_sched();// 可以睡眠的读操作rcu_read_unlock_sched();
适用场景:
RCU性能分析
优势
读性能:
自旋锁:读需要获取锁 → ~100ns互斥锁:读需要睡眠 → ~1μsRCU:读无锁 → ~10ns
扩展性:
劣势
写性能:
内存占用:
适用场景
✅ 强烈推荐:
⚠️ 谨慎使用:
6. PREEMPT_RT实时补丁:锁的范式转变
PREEMPT_RT概述
目标:将Linux变为硬实时操作系统
核心改变:
锁类型映射
非PREEMPT_RT → PREEMPT_RT
| | |
spinlock_t | rt_mutex | |
rwlock_t | rt_mutex | |
local_lock | per-CPU spinlock_t | |
raw_spinlock_t | raw_spinlock_t | 不变 |
代码示例对比
// 非PREEMPT_RTspinlock_t lock;spin_lock(&lock);// 临界区(不可抢占)spin_unlock(&lock);// PREEMPT_RT(相同API,不同语义)spinlock_t lock;spin_lock(&lock);// 临界区(可被高优先级任务抢占)spin_unlock(&lock);
关键区别:
中断处理变化
传统中断
// 顶半部(不可睡眠)irq_handler_t {// 快速处理 handle_irq();// 可能禁用中断 disable_irq();// 唤醒底半部 tasklet_schedule();}
PREEMPT_RT中断线程化
// 中断处理程序irq_handler_t {// 仅做最简处理return IRQ_WAKE_THREAD;}// 线程化处理(可睡眠)threaded_irq_handler_t {// 可以睡眠、获取锁 mutex_lock(&data_lock);// 复杂处理 process_data(); mutex_unlock(&data_lock);}
优势:
代价:
锁嵌套规则
层次结构
1. 睡眠锁(mutex, rt_mutex) ↓2. CPU本地锁(local_lock, spinlock_t) ↓3. 原始自旋锁(raw_spinlock_t, bit spinlocks)
嵌套示例
// ✅ 正确:睡眠锁内嵌原始自旋锁mutex_lock(&mutex);raw_spin_lock(&raw_lock);// 临界区raw_spin_unlock(&raw_lock);mutex_unlock(&mutex);// ❌ 错误:原始自旋锁内嵌睡眠锁raw_spin_lock(&raw_lock);mutex_lock(&mutex); // 死锁风险!raw_spin_unlock(&raw_lock);
原因:
7. 生产环境:最佳实践与性能调优
锁选择指南
决策树
需要锁吗?├─ 否 → 无锁算法(RCU/原子操作)└─ 是 ├─ 读多写少? → RCU ├─ 读写均衡? → rw_semaphore ├─ 短临界区? → spinlock_t ├─ 长临界区? → mutex └─ 实时需求? → rt_mutex
性能对比
死锁预防
常见死锁模式
模式1:锁顺序反转
// 线程Aspin_lock(&lock1);spin_lock(&lock2); // 等待lock2// 线程Bspin_lock(&lock2);spin_lock(&lock1); // 等待lock1 → 死锁
解决方案:全局锁顺序
// 所有线程按相同顺序获取if (lock1 < lock2) { spin_lock(&lock1); spin_lock(&lock2);} else { spin_lock(&lock2); spin_lock(&lock1);}
模式2:中断上下文死锁
// 普通代码spin_lock(&lock);// 被中断抢占// 中断处理程序spin_lock(&lock); // 死锁!
解决方案:
// 普通代码spin_lock_irqsave(&lock, flags);// 临界区spin_unlock_irqrestore(&lock, flags);// 或使用raw_spinlockraw_spin_lock_irqsave(&raw_lock, flags);
模式3:递归死锁
voidfunc() { spin_lock(&lock); func(); // 同一线程重复获取 spin_unlock(&lock);}
解决方案:使用mutex(支持递归检测)或手动记录状态
性能调优
1. 锁粒度优化
粗粒度锁(性能差):
spin_lock(&big_lock);// 大量不相关操作spin_unlock(&big_lock);
细粒度锁(性能好):
spin_lock(&lock1);// 操作1spin_unlock(&lock1);spin_lock(&lock2);// 操作2spin_unlock(&lock2);
2. 读写锁优化
场景:读多写少
// 优化前:互斥锁mutex_lock(&lock);if (read_mode) read_data();else write_data();mutex_unlock(&lock);// 优化后:读写锁if (read_mode) { down_read(&rwsem); read_data(); up_read(&rwsem);} else { down_write(&rwsem); write_data(); up_write(&rwsem);}
3. RCU优化
场景:频繁读,偶尔更新
// 传统锁mutex_lock(&lock);read_data();mutex_unlock(&lock);// RCU优化rcu_read_lock();data = rcu_dereference(ptr);read_data();rcu_read_unlock();
4. 自旋优化
场景:短等待,高竞争
// 优化前:立即自旋spin_lock(&lock);// 优化后:指数退避int retries = 0;while (spin_trylock(&lock)) {if (retries++ > 100) { cpu_relax(); // 让出CPU } udelay(1); // 微秒级延迟}
调试与验证
1. Lockdep(锁依赖验证)
# 启用lockdepecho 1 > /proc/sys/kernel/lockdep# 检测死锁dmesg | grep "possible deadlock"# 查看锁图cat /proc/lockdep
检测能力:
2. 锁统计
# 启用锁统计echo 1 > /proc/sys/kernel/lock_stat# 查看统计cat /proc/lock_stat# 输出示例:# lock_name: spin_lock# acquire: 1000000 times, 50ns avg# hold: 1000000 times, 100ns avg
3. Ftrace锁事件
# 跟踪锁事件echo 1 > /sys/kernel/debug/tracing/events/lock/enable# 查看跟踪cat /sys/kernel/debug/tracing/trace# 分析锁竞争trace-cmd record -e lock:*
4. RCU诊断
# 查看RCU状态cat /sys/kernel/debug/rcu/rcu*# 检测宽限期延迟echo 1 > /sys/kernel/debug/rcu/rcu_normal# 查看回调cat /sys/kernel/debug/rcu/rcu_pending
实际案例分析
案例1:网络驱动性能优化
问题:高并发下网络吞吐量下降
分析:
// 原始代码spin_lock(&rx_lock);process_packet();spin_unlock(&rx_lock);// 性能:100K pps// CPU使用率:80%
优化:
// 每CPU队列per_cpu_struct {structsk_buff_headqueue;spinlock_t lock; // 仅保护本CPU};// 处理spin_lock(&per_cpu->lock);enqueue(per_cpu->queue);spin_unlock(&per_cpu->lock);// 合并处理(RCU读取)rcu_read_lock();for_each_cpu(cpu) { process_queue(per_cpu(cpu)->queue);}rcu_read_unlock();
结果:1M pps,CPU使用率30%
案例2:文件系统元数据保护
问题:目录遍历与修改冲突
分析:
// 传统锁mutex_lock(&dir->lock);list_for_each(entry) { /* 遍历 */ }mutex_unlock(&dir->lock);// 修改时遍历被阻塞
优化:
// RCU保护的链表structentry {structlist_headlist;structrcu_headrcu;};// 读取(无锁)rcu_read_lock();list_for_each_entry_rcu(entry, &dir->list, list) {// 使用entry}rcu_read_unlock();// 修改new_entry = kmalloc(...);list_add_tail_rcu(&new_entry->list, &dir->list);// 延迟释放旧entryif (old_entry) call_rcu(&old_entry->rcu, free_entry);
结果:读操作延迟降低90%
总结
关键要点
- 1. 锁的演进:从简单自旋锁到支持优先级继承的rt_mutex,再到无锁的RCU
- 2. PREEMPT_RT革命:锁语义的根本性转变,实现硬实时能力
- 3. RCU优势:读无锁、高并发、低延迟,适用于读多写少场景
- 4. 锁选择:根据临界区长度、读写比例、实时需求选择合适机制
技术局限
未来展望
内核6.x+趋势:
实时性演进: