为何__nmi_enter()中要执行__preempt_count_add(NMI_OFFSET+HARDIRQ_OFFSET)?
关于Linux内核__nmi_enter()宏的两个问题
代码背景
#define __nmi_enter() do { lockdep_off(); arch_nmi_enter(); BUG_ON(in_nmi() == NMI_MASK); __preempt_count_add(NMI_OFFSET + HARDIRQ_OFFSET); } while (0)
问题1:为何累加NMI_OFFSET + HARDIRQ_OFFSET而非仅NMI_OFFSET?
要理解这个设计,得从preempt_count的核心作用说起——它是内核跟踪抢占状态、中断嵌套的核心计数器,不同偏移对应不同的上下文标识:
HARDIRQ_OFFSET用于标记当前处于硬中断上下文,内核里大量通用逻辑(比如判断是否允许调度、能否执行进程上下文专属操作)会通过in_hardirq()检查这个位。NMI_OFFSET是专门标记NMI专属上下文的偏移,用来区分NMI和普通硬中断。
同时累加两个偏移的原因主要有两点:
- 复用通用硬中断检查逻辑:NMI本质是特殊的硬中断,需要让
in_hardirq()这类通用函数也能识别出当前处于硬中断类上下文,避免进程上下文的操作(如抢占、调度)在NMI中执行。 - 正确跟踪嵌套关系:NMI可以嵌套在普通硬中断上下文中触发,累加两个偏移后,内核能准确识别NMI的嵌套层数,同时保留硬中断上下文的标识,确保所有中断相关的安全检查逻辑正常工作。
退出NMI时,内核会对应执行__preempt_count_sub(NMI_OFFSET + HARDIRQ_OFFSET),保证计数器状态回退正确。
问题2:NMI属于硬中断吗?
是的,NMI(不可屏蔽中断)属于硬中断的特殊子类:
- 它由硬件触发,和普通硬中断一样是外部硬件信号引发的中断请求。
- 区别在于普通硬中断可通过CPU的中断屏蔽寄存器关闭,而NMI优先级更高,无法被屏蔽,专门用于处理最紧急的硬件故障(如内存校验错误、CPU温度过高)。
- 内核在设计中把NMI归为硬中断范畴,只是它的处理流程更特殊:可以嵌套在普通硬中断上下文里执行,且处理过程中不会被普通中断打断。
内容的提问来源于stack exchange,提问作者Mark Kang
相关产品推荐
相关产品推荐

