Linux内核spin_lock与spin_lock_irq分析

本文详细解析了Linux内核中的spin_lock与spin_lock_irq两种互斥锁的区别及应用场景,阐述了它们之间的调用关系,并通过实例说明了使用spin_lock可能引发的死锁问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

在Linux内核中何时使用spin_lock,何时使用spin_lock_irqsave很容易混淆。首先看一下代码是如何实现的。

spin_lock的调用关系

     spin_lock

            |

           + ----->  raw_spin_lock

|

+------>  _raw_spin_lock

                         |

                        +--------> __raw_spin_lock

  1. static inline void __raw_spin_lock(raw_spinlock_t *lock)  
  2. {  
  3.         preempt_disable();  
  4.         spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);  
  5.         LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);  
  6. }  

spin_lock_irq的调用关系

    spin_lock_irq

                |

               +-------> raw_spin_lock_irq

                                           |

                                          +---------> _raw_spin_lock_irq

                                                                      |

                                                                      +------------> __raw_spin_lock_irq

  1. static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)  
  2. {  
  3.         local_irq_disable();  
  4.         preempt_disable();  
  5.         spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);  
  6.         LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);  
  7. }  

可以看出来他们两者只有一个差别:是否调用local_irq_disable()函数, 即是否禁止本地中断。

在任何情况下使用spin_lock_irq都是安全的。因为它既禁止本地中断,又禁止内核抢占。

spin_lock比spin_lock_irq速度快,但是它并不是任何情况下都是安全的。

举个例子:进程A中调用了spin_lock(&lock)然后进入临界区,此时来了一个中断(interrupt),

该中断也运行在和进程A相同的CPU上,并且在该中断处理程序中恰巧也会spin_lock(&lock)

试图获取同一个锁。由于是在同一个CPU上被中断,进程A会被设置为TASK_INTERRUPT状态,

中断处理程序无法获得锁,会不停的忙等,由于进程A被设置为中断状态,schedule()进程调度就

无法再调度进程A运行,这样就导致了死锁!

但是如果该中断处理程序运行在不同的CPU上就不会触发死锁。 因为在不同的CPU上出现中断不会导致

进程A的状态被设为TASK_INTERRUPT,只是换出。当中断处理程序忙等被换出后,进程A还是有机会

获得CPU,执行并退出临界区。

所以在使用spin_lock时要明确知道该锁不会在中断处理程序中使用。

### 关于 `spin_lock` 的实现用法解析 #### 1. **`spin_lock` 的基本概念** `spin_lock` 是一种轻量级的锁定机制,通常用于保护共享资源免受并发访问的影响。它的主要特点是简单高效,但在高负载场景下可能导致 CPU 饱和,因为线程会持续轮询直到锁可用[^1]。 ```c static inline void __raw_spin_lock(raw_spinlock_t *lock) { preempt_disable(); spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); } ``` 此代码片段展示了 `__raw_spin_lock` 的核心逻辑,其中包含了抢占禁用 (`preempt_disable`) 和获取依赖映射的功能调用[^2]。 --- #### 2. **`spin_lock_irq` 的增强特性** 为了进一步提升安全性并适应更复杂的环境需求,Linux 提供了带有额外功能扩展版本的自旋锁 —— 即 `spin_lock_irq` 。该变体除了提供基础加锁能力外还自动关闭本地中断以阻止潜在竞争条件的发生。 ```c static inline void __raw_spin_lock_irq(raw_spinlock_t *lock) { local_irq_disable(); // 禁用局部中断 preempt_disable(); // 禁用内核抢占 spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); } ``` 这里可以看到相比普通的 `spin_lock` 多出了两步重要操作:一是通过 `local_irq_disable()` 来屏蔽硬件级别的干扰信号;二是借助 `preempt_disable()` 实现软件层面的任务调度控制权冻结[^3]。 --- #### 3. **典型应用场景举例** ##### 场景一:设备驱动程序中的数据结构保护 假设在一个网络适配器驱动里维护着一张描述符表用来追踪发送接收包的状态信息,则可以在其读写过程中加入适当类型的锁来确保一致性。 ```c struct net_device_stats stats; DEFINE_RAW_SPINLOCK(stats_lock); void update_statistics(struct sk_buff *skb) { unsigned long flags; raw_spin_lock_irqsave(&stats_lock, flags); stats.tx_bytes += skb->len; stats.tx_packets++; raw_spin_unlock_irqrestore(&stats_lock, flags); } ``` 上述例子中利用了保存恢复标志位的方式灵活切换不同状态间的转换过程[^4]。 --- ##### 场景二:软中断处理期间的安全区域定义 当编写 softirq 或 tasklet 类型的工作队列处理器时也需要特别注意同步问题以免造成混乱局面出现。 ```c static DEFINE_PER_CPU(raw_spinlock_t, my_softirq_lock); void handle_my_event(void) { int cpu = smp_processor_id(); raw_spin_lock(&per_cpu(my_softirq_lock, cpu)); /* Process event safely here */ raw_spin_unlock(&per_cpu(my_softirq_lock, cpu)); } ``` 此处采用 per-CPU 数据结构配合相应锁机构共同构建起独立运作空间从而减少跨节点通信开销提高整体效率水平[^5]。 --- ### 总结思考方向提示 通过对以上内容的学习理解可以帮助开发者更好地掌握如何合理运用这些工具解决实际开发当中遇到的各种挑战难题。同时也要意识到每种解决方案都有各自适用范围以及局限所在因此需谨慎挑选最适合当下情境的那个选项才行。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值