You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何irqs_disabled()是禁用抢占的不安全方式?

为什么禁用IRQs是一种不安全的抢占禁用方式?

Great question—this is a super common pitfall when working with preemptible Linux kernels, and it boils down to a key misunderstanding: disabling IRQs does not equal disabling preemption. Let's break this down step by step:


1. 抢占的核心控制是抢占计数,而非IRQ状态

在可抢占内核中,决定是否允许抢占的核心开关是抢占计数(一个CPU本地的整数)。当计数大于0时,抢占被阻止;计数归0时,抢占被允许。

调用local_irq_disable()只是关闭当前CPU的硬件中断(包括定时器中断),但它完全不会修改抢占计数。所以即使IRQs被禁用,只要抢占计数是0,内核依然认为抢占是允许的。

2. spin_unlock()即使在IRQs禁用时也可能触发调度

假设你用spin_lock_irq()保护临界区,这个函数会做两件事:禁用IRQs,同时增加抢占计数(阻止抢占)。当你后续调用spin_unlock_irqrestore()时,它会恢复原本的IRQ状态,同时减少抢占计数。

如果这次递减让抢占计数归0,内核会立刻检查是否有更高优先级的任务在等待运行。如果有,就会触发preempt_schedule()——哪怕此时IRQs仍然是禁用的。这种调度不是由定时器中断触发的,而是内核主动检查并切换任务,完全绕开了IRQ禁用的限制。

3. 像printk()这类“无害”函数也可能触发抢占

还有一个容易踩的坑:printk()看起来没什么风险,但它内部可能会调用console_unlock()来刷新日志。在某些场景下(比如等待控制台缓冲区空闲),console_unlock()会显式调用schedule()让出CPU。

如果你只禁用了IRQs(但抢占计数还是0),这个schedule()调用会正常执行——你的任务会被抢占,哪怕你以为IRQs禁用就能避免这种情况。这会直接破坏你原本以为安全的临界区,导致竞态条件和数据结构损坏。


总结

禁用IRQs只能阻止由硬件中断触发的抢占(比如定时器 tick 或者设备中断),但无法阻止内核主动发起的调度(比如显式的schedule()调用,或者spin_unlock()的副作用)。

要在可抢占内核中安全禁用抢占,应该使用能修改抢占计数的机制:

  • 对基于自旋锁的临界区,使用spin_lock()(它会增加抢占计数)
  • 直接调用preempt_disable()来阻止抢占,完成后用preempt_enable()恢复

内容的提问来源于stack exchange,提问作者sherlock

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:09:58