第三篇:/proc/interrupts 与 虚假中断检测 — show_interrupts 与 note_interrupt¶
源码:kernel/irq/proc.c kernel/irq/spurious.c | 头文件:include/linux/interrupt.h
系列目录:IRQ 内核源码深度解析
1. 概述¶
前两篇分析了中断描述符结构和流控处理机制。当 ISR 被调用后,有两个关键的后处理步骤:
1. 统计计数:累加 per-CPU 中断计数,供 /proc/interrupts 展示
2. 虚假中断检测:判断中断是否被正确处理,如果没有设备认领则启动诊断机制
本文将深入 show_interrupts() 的数据来源和 note_interrupt() 的虚假检测算法 — 这是内核最优雅的防御机制之一。
2. per-CPU 中断计数 — kstat_irqs¶
2.1 数据结构¶
// include/linux/kernel_stat.h
struct kernel_stat {
unsigned int irqs_sum; // 所有 IRQ 的计数之和 (快速检查用)
unsigned int softirqs_sum;
};
DECLARE_PER_CPU(struct kernel_stat, kstat);
// irq_desc 中的计数指针
struct irq_desc {
unsigned int __percpu *kstat_irqs; // per-CPU 中断计数数组
// ...
};
每个 IRQ 有独立的 per-CPU 数组,记录了每个 CPU 上该中断的触发次数。
2.2 累积计数¶
// kernel/irq/internal.h (内联)
static inline void kstat_incr_irqs_this_cpu(struct irq_desc *desc)
{
__this_cpu_inc(*desc->kstat_irqs);
__this_cpu_inc(kstat.irqs_sum);
}
每次中断进入 __handle_irq_event_percpu() 时,先累加此计数器 (无论 ISR 是否真的处理了该中断):
// kernel/irq/handle.c:185
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc)
{
// ★ 第一行: 无条件累加计数
__kstat_incr_irqs_this_cpu(desc);
for_each_action_of_desc(desc, action) {
res = action->handler(irq, action->dev_id);
// ...
}
note_interrupt(irq, desc, retval);
return retval;
}
3. /proc/interrupts — show_interrupts (proc.c:453)¶
3.1 输出格式¶
CPU0 CPU1 CPU2 CPU3
1: 123456 10000 200000 150000 IR-IO-APIC 1-edge i8042
7: 2 0 0 0 IR-IO-APIC 1-edge parport0
12: 3 0 0 0 IR-IO-APIC 1-edge i8042
16: 123456 100000 200000 100000 IR-IO-APIC 16-fasteoi ehci_hcd:usb1
23: 432109 200000 100000 300000 IR-IO-APIC 23-fasteoi ehci_hcd:usb2
24: 0 0 0 0 DMAR-MSI 0-edge dmar0
25: 0 0 0 0 DMAR-MSI 1-edge dmar1
26: 0 0 0 0 IR-PCI-MSIX 0000:00:1f.3 0-edge
NMI: 12345 10000 5000 15000 Non-maskable interrupts
LOC: 12345678 10000000 5000000 15000000 Local timer interrupts
SPU: 0 0 0 0 Spurious interrupts
PMI: 12345 10000 5000 15000 Performance monitoring interrupts
IWI: 0 0 0 0 IRQ work interrupts
RTR: 0 0 0 0 APIC ICR read retry
RES: 1234567 500000 600000 450543 Rescheduling interrupts
CAL: 1234567 600000 500000 350543 Function call interrupts
TLB: 1234567 400000 300000 450543 TLB shootdowns
TRM: 12345 10000 5000 15000 Thermal event interrupts
THR: 0 0 0 0 Threshold APIC interrupts
DFR: 0 0 0 0 Deferred Error APIC interrupts
MCE: 0 0 0 0 Machine check exceptions
MCP: 123 456 789 123 Machine check polls
ERR: 0
MIS: 0
PIN: 0 0 0 0 Posted-interrupt notification event
NPI: 0 0 0 0 Nested posted-interrupt event
PIW: 0 0 0 0 Posted-interrupt wakeup event
3.2 源码分析¶
// kernel/irq/proc.c:453
int show_interrupts(struct seq_file *p, void *v)
{
unsigned long flags, any_count = 0;
int i = *(loff_t *) v, j;
struct irq_desc *desc;
struct irqaction *action;
// ───── 第一遍: 输出标题行 ─────
if (i == 0) {
seq_puts(p, " ");
for_each_online_cpu(j)
seq_printf(p, "CPU%-8d", j);
seq_putc(p, '\n');
}
// ───── 第二遍: 遍历所有 IRQ ─────
if (i < nr_irqs) {
desc = irq_to_desc(i);
if (!desc)
return 0;
raw_spin_lock_irqsave(&desc->lock, flags);
// (A) 打印每个 CPU 的计数值
// kstat_irqs 是 per-CPU 的 unsigned int 数组
for_each_online_cpu(j)
any_count |= data_race(*per_cpu_ptr(desc->kstat_irqs, j));
if (any_count || !irq_settings_is_hidden(desc)) {
seq_printf(p, "%3d: ", i);
// 输出每个 CPU 的计数值
for_each_online_cpu(j)
seq_printf(p, "%10u ",
data_race(*per_cpu_ptr(desc->kstat_irqs, j)));
// (B) 打印 chip 名称和触发类型
// desc->irq_data.chip->name → "IR-IO-APIC", "GICv3", ...
// irq_get_trigger_type() → "edge" / "level"
seq_printf(p, " %-14s", desc->irq_data.chip->name
? : "None");
seq_printf(p, " %-10s",
desc->irq_data.domain ? "edge" : desc->irq_data.hwirq);
// (C) 打印所有注册的 ISR 名称
// action->name → "i8042", "ehci_hcd:usb1", ...
action = desc->action;
if (action) {
seq_printf(p, " %s", action->name);
while ((action = action->next) != NULL)
seq_printf(p, ", %s", action->name);
}
seq_putc(p, '\n');
}
raw_spin_unlock_irqrestore(&desc->lock, flags);
}
return 0;
}
3.3 数据流动图¶
硬件中断到达
│
▼
__handle_irq_event_percpu()
│
├── ★ __kstat_incr_irqs_this_cpu(desc)
│ │
│ └── per_cpu_ptr(desc->kstat_irqs)[cpu]++
│ (per-CPU 的无锁自增)
│
└── handle ISR
│
▼
note_interrupt(irq, desc, retval)
│
▼
/proc/interrupts 读取:
│
▼
show_interrupts(seq_file *p, void *v)
│
├── 遍历 nr_irqs
├── 对每个 irq: 读 kstat_irqs per-CPU 数组
├── 打印 chip->name
├── 打印 hwirq / trigger type
└── 遍历 action 链表, 打印 action->name
性能考量:
- kstat_incr_irqs_this_cpu() 使用 __this_cpu_inc() — 单 CPU 上无锁
- show_interrupts() 使用 data_race() 读取 — 不需要精确一致性,允许短暂的不准确
4. /proc/irq/N/ 目录结构 (proc.c:331)¶
// kernel/irq/proc.c:331
void register_irq_proc(unsigned int irq, struct irq_desc *desc)
{
char name [16];
struct proc_dir_entry *entry;
// ... 如果 /proc/irq 不存在则创建 /proc/irq
sprintf(name, "%d", irq);
// 创建 /proc/irq/N/ 目录
desc->dir = proc_mkdir(name, root_irq_dir);
// 创建子文件:
// /proc/irq/N/smp_affinity — 位掩码,设置 CPU affinity
// /proc/irq/N/smp_affinity_list — 列表格式,设置 CPU affinity
// /proc/irq/N/affinity_hint — 建议的 affinity
// /proc/irq/N/spurious — 虚假中断统计
// /proc/irq/N/node — NUMA node
// /proc/irq/N/effective_affinity — 实际生效的 affinity
// /proc/irq/N/effective_affinity_list
}
各文件用途¶
| 文件 | 权限 | 用途 |
|---|---|---|
smp_affinity |
rw | 设置/查看 CPU affinity 位掩码 |
smp_affinity_list |
rw | 设置/查看 CPU affinity 列表 |
affinity_hint |
rw | 驱动程序建议的 best CPU affinity |
spurious |
r | 虚假中断计数和最后未处理时间 |
node |
r | NUMA node ID |
effective_affinity |
r | 实际起效的 affinity (硬件限制后) |
effective_affinity_list |
r | 列表格式的实际 affinity |
5. 虚假中断检测 — note_interrupt (spurious.c:222)¶
note_interrupt() 是内核的多层防御机制,用于检测"没有设备认领"的中断。如果检测到长期未处理的中断,内核会禁用该中断线并启动轮询。
5.1 完整的决策树¶
note_interrupt(irq, desc, action_ret)
│
├── action_ret == IRQ_HANDLED?
│ │
│ ├── YES: 一切正常,直接返回
│ │
│ └── NO: 继续检测
│
├── action_ret == IRQ_WAKE_THREAD?
│ │
│ └── 启动 SPURIOUS_DEFERRED 延迟检测
│ (在线程完成后由 irq_finalize_oneshot 继续检测)
│
├── action_ret == IRQ_NONE
│ │
│ ├── 如果是轮询中断 (poll_spurious_irqs): 不检测
│ │
│ ├── 如果是 IRQS_POLL_INPROGRESS: 不检测
│ │
│ ├── 增加 irqs_unhandled 计数
│ │
│ ├── 检查 100ms 的老化窗口
│ │ └── 如果距上一次 IRQ_NONE 超过 100ms:
│ │ 对 irqs_unhandled 进行衰减
│ │
│ ├── 尝试错误路由 IRQ (irqfixup 内核参数)
│ │
│ ├── 检查 "999/1000 规则"
│ │ └── 如果最近 100,000 次中断中 99,900 次没处理:
│ │ 设置 IRQS_SPURIOUS_DISABLED → 启动轮询
│ │
│ └── 如果轮询成功 (有 handler 响应):
│ 清除 SPURIOUS, 恢复正常处理
│
└── action_ret 无效值 (不是任何 IRQ 返回值)
└── 报告 BAD_IRQ (严重错误, 可能是内核 bug)
5.2 源码分析¶
// kernel/irq/spurious.c:222
void note_interrupt(unsigned int irq, struct irq_desc *desc,
irqreturn_t action_ret)
{
// ───── 情况1: 正常处理 ─────
if (action_ret == IRQ_HANDLED) {
// 一切正常, 清除未处理状态
desc->irqs_unhandled = 0;
return;
}
// ───── 情况2: 线程被唤醒 ─────
if (action_ret == IRQ_WAKE_THREAD) {
// 将在线程完成后再检测 (SPURIOUS_DEFERRED)
desc->irqs_unhandled = 0;
desc->threads_handled |= SPURIOUS_DEFERRED;
return;
}
// ───── 情况3: 轮询中断跳过 ─────
if (irq_settings_is_polled(desc)) {
desc->irqs_unhandled = 0;
return;
}
if (desc->istate & IRQS_POLL_INPROGRESS)
return;
// ───── 情况4: 无效的 action_ret ─────
if (action_ret == IRQ_NONE) {
/*
* 100ms 的老化窗口
*
* 如果两次 IRQ_NONE 之间的时间超过 100ms:
* 将 irqs_unhandled 除以 2 (衰减)
*
* 原因: 短时间的突发 IRQ_NONE 可能是共享线上的正常现象
* 但持续的大量 IRQ_NONE 说明中断线有问题
*/
time_after(desc->last_unhandled + HZ / 10, jiffies)
? desc->irqs_unhandled++ // 100ms 内: 累积
: desc->irqs_unhandled >>= 1; // 超过 100ms: 衰减一半
desc->last_unhandled = jiffies;
/*
* 错误路由中断修复 (irqfixup 内核参数)
*
* 如果检测到中断线可能是错误路由的 (例如 PCI IRQ steering 失败):
* 试一下所有共享中断的其他注册设备
*/
if (unlikely(irqfixup)) {
if (try_one_irq(desc, true))
return;
}
} else {
/*
* action_ret 是无效返回值 (不是 IRQ_HANDLED / IRQ_NONE / IRQ_WAKE_THREAD)
* → 这是一个严重的内核 bug!
*/
__report_bad_irq(desc, action_ret);
}
/*
* 999/1000 规则: 虚假中断检测
*
* 检查多少个中断没有被处理
* 阈值: irqs_unhandled > 99900
* → 意味着在最近约 100,000 次中断中,最多只有 100 次被处理
* → 显然这条中断线有问题
*/
if (unlikely(irqs_unhandled > 99900)) {
/*
* 设置 SPURIOUS_DISABLED 标志
* 启动轮询 (poll_spurious_irqs)
*/
__report_bad_irq(desc, action_ret);
desc->istate |= IRQS_SPURIOUS_DISABLED;
desc->depth++;
irq_disable(desc); // 禁止中断
mod_timer(&poll_spurious_irq_timer,
jiffies + POLL_SPURIOUS_IRQ_INTERVAL); // 1 tick
desc->irqs_unhandled = 0;
}
}
5.3 SPURIOUS_DEFERRED — 延迟检测 (spurious.c:220)¶
当线程化 IRQ 的 PRIMARY handler 返回 IRQ_WAKE_THREAD 时:
1. PRIMARY handler 传递了事件给线程
2. 线程可能会处理事件或报告 IRQ_NONE
3. 延迟到线程完成后才做 spurious 检测
// 在线程完成后的 irq_finalize_oneshot 中:
void irq_finalize_oneshot(struct irq_desc *desc, struct irqaction *action)
{
/*
* 线程完成后:
* 如果 threads_handled 设置了 SPURIOUS_DEFERRED:
* 调用 handle_irq_surplus_thread() 检查
* 确认线程是否真的处理了中断
*/
if (desc->istate & IRQS_ONESHOT) {
if (!(desc->istate & IRQS_ONESHOT_ACTIVE) && thread_handler_done) {
// 延迟检测: 线程是否处理了中断?
handle_irq_surplus_thread(desc, action);
}
}
}
6. "nobody cared" — __report_bad_irq (spurious.c:152)¶
当内核检测到持续的虚假中断时,通过 __report_bad_irq() 输出诊断信息。
// kernel/irq/spurious.c:152
static void __report_bad_irq(struct irq_desc *desc, irqreturn_t action_ret)
{
struct irqaction *action;
// 防止日志洪泛: 仅输出一次
if (desc->istate & IRQS_REPORTED)
return;
desc->istate |= IRQS_REPORTED;
printk(KERN_ERR
"irq %d: nobody cared (try booting with "
"the \"irqpoll\" option)\n", desc->irq_data.irq);
dump_stack();
// 打印所有注册的 ISR 名称
printk(KERN_ERR "handlers:\n");
action = desc->action;
while (action) {
printk(KERN_ERR "[<%p>] %pf%s",
action->handler, action->handler,
(action->thread_fn ? " threaded " : " "));
printk(KERN_CONT "%s", action->name ? : "???");
action = action->next;
}
}
典型输出:
[ERR] irq 16: nobody cared (try booting with the "irqpoll" option)
...
[ERR] handlers:
[ERR] [<ffffffffc0367000>] ehci_hcd:usb1
这意味着中断 16 持续触发,但 ehci_hcd:usb1 的 ISR 返回了 IRQ_NONE (设备不认领)。
7. 轮询机制 — poll_spurious_irqs (spurious.c:106)¶
当中断被标记为 IRQS_SPURIOUS_DISABLED 后,内核启动定时器轮询以检查中断线是否恢复。
// kernel/irq/spurious.c:106
static void poll_spurious_irqs(struct timer_list *unused)
{
struct irq_desc *desc;
int i;
for_each_irq_desc(i, desc) {
// 遍历所有 SPURIOUS_DISABLED 的中断
if (!(desc->istate & IRQS_SPURIOUS_DISABLED))
continue;
// 设置 POLL_INPROGRESS 防止并发
desc->istate |= IRQS_POLL_INPROGRESS;
do {
if (desc->istate & IRQS_PENDING)
irq_startup(desc);
// 尝试发送中断
if (irq_settings_can_autoenable(desc))
irq_startup(desc);
// 为每个 action 创建一个线程来检查
try_one_irq(desc, true);
} while (desc->istate & IRQS_PENDING);
desc->istate &= ~IRQS_POLL_INPROGRESS;
}
// 如果还有 SPURIOUS 中断,重新设置定时器
mod_timer(&poll_spurious_irq_timer,
jiffies + POLL_SPURIOUS_IRQ_INTERVAL);
}
定时器参数:
每 100ms 检查一次是否有中断线恢复。如果设备再次正确响应,内核将重新启用该中断。
7.1 try_one_irq — 尝试触发单次中断 (spurious.c:28)¶
// kernel/irq/spurious.c:28
static int try_one_irq(struct irq_desc *desc, bool force)
{
struct irqaction *action;
int ret = 0;
// ... 如果 IRQ 已被关闭则拒绝 ...
action = desc->action;
if (!action)
goto out;
/*
* 遍历所有 action, 调用它们的 handler
* 如果有任何一个返回 IRQ_HANDLED:
* 说明中断线恢复了 → 清除 SPURIOUS 状态
*/
for (; action; action = action->next) {
irqreturn_t res;
res = action->handler(desc->irq_data.irq, action->dev_id);
if (res == IRQ_HANDLED) {
ret = 1;
break;
}
}
if (ret) {
// 中断线恢复: 重新启用
desc->istate &= ~IRQS_SPURIOUS_DISABLED;
desc->depth--;
irq_startup(desc);
desc->irqs_unhandled = 0;
}
out:
return ret;
}
8. 完整决策流程图¶
┌─────────────────────────────────────────────────────────────────────────────┐
│ note_interrupt 决策树 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ handle_irq_event_percpu() │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ ISR 返回值 action_ret│ │
│ └──────┬──────────────┘ │
│ │ │
│ ┌───┼─────────────┬─────────────────┐ │
│ │ │ │ │ │
│ IRQ_ IRQ_ IRQ_ 无效值 │
│ HAND WAKETHREAD NONE (bug) │
│ LED │ │ │ │
│ │ │ ▼ │
│ │ │ ┌─────────────────┐ │
│ │ │ │ __report_bad_irq │ 严重: ISR 返回损坏值 │
│ │ │ └──────┬──────────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ ┌─────────────────┐ │
│ │ │ │ dump_stack() │ │
│ │ │ └─────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌──────────────────┐ │
│ │ │ 跳过轮询中断? │ │
│ │ │ (polled irq or │──YES──► return │
│ │ │ POLL_INPROGRESS)│ │
│ │ └────┬──┬──────────┘ │
│ │ │ │ │
│ │ │ NO │
│ │ ▼ │
│ │ ┌──────────────────┐ 100ms 老化窗口 │
│ │ │ 距离上一次 IRQ_NONE│ │
│ │ │ 超过 100ms? │ │
│ │ └──┬──────────┬────┘ │
│ │ │ YES │ NO │
│ │ ▼ ▼ │
│ │ ┌──────────┐ ┌──────────┐ │
│ │ │ 衰减一半 │ │ 累积 +1 │ │
│ │ │ >>= 1 │ │ ++ │ │
│ │ └────┬─────┘ └────┬─────┘ │
│ │ └──────┬─────┘ │
│ │ ▼ │
│ │ ┌──────────────────┐ │
│ │ │ try_one_irq() │ (如果 irqfixup 参数启用) │
│ │ │ 尝试错误路由修复 │ │
│ │ └──────┬───────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌──────────────────┐ │
│ │ │ irqs_unhandled │ │
│ │ │ > 99900? │ │
│ │ └──┬───────┬───────┘ │
│ │ │ YES │ NO │
│ │ ▼ ▼ │
│ │ ┌──────────┐ return (continue tracking) │
│ │ │ disabled │ │
│ │ │ + polling │ │
│ │ └──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ clear │ │
│ │ unhandled │ │
│ └──────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
9. 内核启动参数 (spurious.c:17, 382)¶
// kernel/irq/spurious.c:17
static bool irqfixup __read_mostly;
// 通过内核命令行启用错误路由修复
static int __init irqfixup_setup(char *str)
{
irqfixup = 1;
printk(KERN_INFO "Misrouted IRQ fixup support enabled.\n");
return 1;
}
__setup("irqfixup", irqfixup_setup);
以及:
static int __init irqpoll_setup(char *str)
{
// 强制所有中断使用轮询模式
irqfixup = 2; // 完全轮询, 不是只在 IRQ_NONE 时检查
printk(KERN_INFO "Misrouted IRQ fixup and polling support enabled\n");
return 1;
}
__setup("irqpoll", irqpoll_setup);
以及:
static int __init no_irq_debug_setup(char *str)
{
noirqdebug = 1;
return 1;
}
__setup("noirqdebug", no_irq_debug_setup);
参数含义:
| 参数 | 作用 |
|---|---|
irqfixup |
对所有 IRQ_NONE 尝试错误路由修复 (调用全部共享 action) |
irqpoll |
完全轮询模式,忽略硬件中断 |
noirqdebug |
禁用虚假中断检测,不输出 "nobody cared" 信息 |
10. 100ms 老化窗口详解¶
note_interrupt 中的关键机制是 100ms 老化窗口:
/*
* irqs_unhandled 随时间变化
*
* ↑
* │
* 99900├────────────────────────────── 阈值 ──────
* │ ╱
* │ ╱
* │ ╱
* 1000 │ ╱
* │ ╱
* │ ╱
* 10 │ ╱
* └───────────────────────────────────────► 时间
* 每个 IRQ_NONE 增加 +1
* 每 100ms 衰减 (/2)
* 持续高频 IRQ_NONE → 突破阈值 → 禁用
*/
设计精妙之处:
- 偶发 IRQ_NONE (例如共享中断正常情况) 不会触发阈值
- 衰减窗口确保短暂干扰自动恢复
- 只有持续 (超过 100ms 窗口) 大量 (每窗口内 > 99900 次) 的虚假中断才会触发关闭
11. 虚假中断的调试¶
11.1 查看 spurious 统计¶
$ cat /proc/irq/16/spurious
count 0
unhandled 0
last_unhandled 0 ms
$ cat /proc/irq/23/spurious
count 123456 # 总共触发次数
unhandled 42 # 未处理计数
last_unhandled 12345 ms # 距上次未处理已经过 xx ms
11.2 使用 tracepoint¶
# 启用 spurious 检测 tracepoint
echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_exit/enable
# 查看 trace 输出, 找 IRQ_NONE 的 ISR
cat /sys/kernel/debug/tracing/trace | grep IRQ_NONE
11.3 检查 "nobody cared" 的遗留状态¶
12. 完整的中断状态转换¶
正常处理流程
────────────
[正常] ────► IRQ_HANDLED ────► [正常] (计数器递增)
虚假检测
────────
[正常] ────► IRQ_NONE ────► irqs_unhandled++ ────► [检测中]
│ 衰减窗口
└─► 如果 < 阈值: [正常]
│ 超越阈值?
└─► IRQS_SPURIOUS_DISABLED ──► [禁用+轮询]
轮询恢复
────────
[禁用+轮询] ────► poll_spurious_irqs() ──► try_one_irq()
│ 如果 ISR 再次返回 IRQ_HANDLED:
└─► 清除 SPURIOUS_DISABLED ──► [恢复正常]
13. 虚假中断 vs. 共享中断的微妙关系¶
共享中断 (IRQF_SHARED) 是 IRQ_NONE 最常见的原因:
中断线 16 上有两个设备:
Device A: ehci_hcd:usb1, ISR_a()
Device B: i2c_designware, ISR_b()
当中断触发时:
isr_a() → 检查设备寄存器 → 我的设备没中断 → IRQ_NONE
isr_b() → 检查设备寄存器 → 我的设备中断了 → IRQ_HANDLED
note_interrupt 看到的 retval = IRQ_NONE | IRQ_HANDLED = IRQ_HANDLED
→ 正确处理! 虽然 ISR_a 返回 IRQ_NONE 但这在共享中断中是正常的
但如果是以下场景:
中断线 16 上的 Device A 已经被拔出 (USB 热插拔)
但 ISR 仍然注册 → isr_a() 仍然返回 IRQ_NONE
如果中断线 16 保持活跃 (电噪声、浮空引脚):
→ 80% 的中断 isr_b 处理
→ 20% 的中断 无处理 (irqs_unhandled 累积)
→ 不会达到 999/1000 阈值
→ 正常! 内核不会误杀
临界情况:
中断线 23 上的 PCI 设备已拔出:
仍然有新设备占据了此中断线
但此设备的 ISR 还在 → 所有中断都是 IRQ_NONE
→ irqs_unhandled 快速增长 → 超越 99900
内核: "irq 23: nobody cared"
该中断被禁用, 启动轮询
14. 性能影响¶
虚假检测的性能开销:
每个中断 (包括被 IRQ_HANDLED 的):
__handle_irq_event_percpu:
__kstat_incr_irqs_this_cpu // per-CPU inc (快速)
for_each_action_of_desc // 遍历链表 (O(n_actions))
note_interrupt // 如果不是 IRQ_NONE: return 0 (快速)
// 如果是 IRQ_NONE: 累积+老化检查 (快速)
total overhead: ~1-2 微秒
对高频中断 (网络) 的影响极小:
- 处理的返回 IRQ_HANDLED → 直接返回
- per-CPU 变量操作 (无锁)
- 无分配、无事件等待
15. 与后续文章的衔接¶
- 第一篇:irq_desc 结构,kstat_irqs 在描述符中的位置
- 第二篇:三个流控处理器 (level/edge/fasteoi) 的完整路径
- 第三篇 (本文):show_interrupts 数据来源,虚假中断检测算法,轮询恢复机制
- 第四篇:irq_exit → __do_softirq → tasklet — 中断下半部处理
- 第五篇:CPU 热插拔对 IRQ affinity 和 spurious 的影响,MSI 域,电源管理
16. 源码导航¶
kernel/irq/proc.c — show_interrupts, register_irq_proc
kernel/irq/spurious.c — note_interrupt, __report_bad_irq, poll_spurious_irqs
kernel/irq/handle.c — __handle_irq_event_percpu (kstat_incr 调用点)
kernel/irq/manage.c — irq_finalize_oneshot (SPURIOUS_DEFERRED 延迟检测)
include/linux/kernel_stat.h — kstat_irqs 定义
下一篇文章¶
第四篇:软中断与 Tasklet — 从中断上下文到进程上下文