进程同步:自旋锁与信号量源码对决
·
一、原子操作机制深度对比
1. 自旋锁(spin_lock)
-
实现核心
-
Ticket机制:主流方案通过
next和owner计数器实现公平排队,解决多核竞争下的饥饿问题。ARM64架构基于ldadd指令实现原子递增:void raw_spin_lock(raw_spinlock_t *lock) { while (atomic_fetch_add(1, &lock->slock) != 0) // 原子递增slock [[12]] cpu_relax(); // 忙等待,持续占用CPU }ldadd指令(ARMv8.1 LSE扩展)或ldxr/stxr(ARMv8.0)确保原子性,避免多核同时修改锁状态 。
-
CAS指令:x86通过
cmpxchg实现原子交换,但Ticket机制因公平性更优成为主流 。
-
-
关键特性
- 中断上下文安全:
spin_lock_irqsave()禁用本地中断,防止中断处理程序争用锁导致死锁 。 - 临界区时长限制:仅适用于纳秒级操作(如指针修改)。若持有时间过长,其他核将因忙等待浪费CPU周期 。
- 中断上下文安全:
2. 信号量(down)
-
实现核心
void down(struct semaphore *sem) { raw_spin_lock_irq(&sem->lock); // 自旋锁保护信号量内部状态 [[9]] if (sem->count > 0) sem->count--; // 原子减1 else __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); // 加入等待队列并休眠 [[9, 15]] raw_spin_unlock_irq(&sem->lock); }- 等待队列:休眠进程由
wait_list管理,唤醒时由up()触发调度器切换进程 。 - 主动让出CPU:通过
schedule_timeout()切换进程,减少CPU空转 。
- 等待队列:休眠进程由
-
关键特性
- 支持多值计数:如
count=5允许5个进程并发访问资源 。 - 进程上下文专用:不可用于中断上下文,因睡眠会触发调度器行为 。
- 支持多值计数:如
二、关键差异的底层原理
| 特性 | 自旋锁 | 信号量 | 底层原理 |
|---|---|---|---|
| 等待机制 | 忙等待(CPU自旋) | 进程休眠(调度器切换) | 自旋锁依赖硬件原子指令(如ldadd);信号量依赖调度器+等待队列 。 |
| 中断上下文 | ✅ 可用(需禁用中断) | ❌ 不可用(触发睡眠) | 中断上下文禁止进程切换,信号量休眠违反原子性要求 。 |
| 锁持有时间 | 纳秒级(如修改指针) | 无限制(如文件I/O) | 自旋锁长时间持有导致CPU空转;信号量通过休眠释放CPU资源 。 |
| 实现依赖 | 硬件原子指令 | 进程调度器+等待队列 | 自旋锁需CPU独占指令;信号量需内核调度子系统支持 。 |
| 递归获取 | ❌ 禁止(死锁风险) | ✅ 允许(需设计计数) | 自旋锁递归获取导致同一核死等;信号量通过计数管理重入 。 |
| 内存屏障 | 隐式包含(原子操作自带屏障) | 需显式屏障控制执行顺序 | 原子操作隐含内存序约束;信号量需smp_mb()确保状态可见性 。 |
三、死锁检测:Lockdep机制深度剖析
-
检测原理
- 锁类依赖图:记录锁获取顺序(如A→B),检测到反向路径(B→A)即判定为AB-BA死锁 。
- 递归死锁检测:同一线程重复获取锁时触发警告(如
spin_lock嵌套调用) 。
-
实战案例
[ OK ] Deadlock detected: Thread A: holding hack_spinA → waiting for hack_spinB Thread B: holding hack_spinB → waiting for hack_spinA [[7]]- Tainted标记:
G O表示内核被非GPL模块污染,影响调试可靠性 。 - Backtrace:调用栈定位死锁代码行(如
driver/module.c:120) 。
- Tainted标记:
-
启用配置
CONFIG_PROVE_LOCKING=y # 启用死锁预测算法 CONFIG_DEBUG_LOCK_ALLOC=y # 跟踪锁依赖关系 [[8]]
四、混合使用风险与设计哲学
1. 设计哲学差异
| 机制 | 设计目标 | 代价 | 适用场景 |
|---|---|---|---|
| 自旋锁 | 低延迟(高频轻量操作) | 牺牲CPU效率 | 中断上下文、短临界区 |
| 信号量 | 高CPU利用率(阻塞型长任务) | 牺牲延迟 | 进程上下文、长阻塞操作 |
2. 混合使用陷阱
spin_lock(&lock); // 获取自旋锁
down(&sem); // 尝试获取信号量(触发睡眠!)
- 后果:
- 自旋锁内休眠导致其他核忙等待浪费CPU 。
- 若休眠进程持有其他锁,引发连锁死锁 。
- 替代方案:需睡眠时改用
mutex(互斥锁),其内部规避了自旋锁休眠问题 。
五、同步机制演进趋势
-
RCU(Read-Copy-Update)
- 无锁读:读者无需加锁,直接访问共享数据。
- 延迟回收:写者更新后延迟回收旧副本,适用于读多写少场景(如路由表) 。
-
MCS锁
- 解决缓存行颠簸:将自旋等待分散到各核的本地变量,减少多核争用锁时的总线冲突 。
- O(1)远程引用:每锁获取仅需常数级远程引用,提升可扩展性 。
-
无等待同步(Wait-free)
- 理论保障:任何进程在有限步骤内完成操作,无视其他进程执行速度 。
- 层级化实现:基于共识协议构建无等待对象层次,避免传统锁的阻塞问题 。
六、机制选择决策树

结论:自旋锁与信号量的选择需综合硬件架构(如ARMv8 LSE扩展)、临界区时长、上下文类型(进程/中断)及公平性需求。现代同步机制如RCU和MCS锁通过减少锁争用提升可扩展性,而Lockdep等调试工具为复杂系统提供死锁预防支持。未来无等待同步可能成为高性能计算的基石。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)