中断处理的开销:到底多贵、贵在哪、怎么量
这是 interrupts(内核中断处理总纲)的拆分篇之一,专门讲"一次中断要花多少钱"。配套拆分篇:分类体系与设计考量 · 处理流程 · 上半部 / 下半部机制与内部实现。
中断不是免费的。理解它的开销构成,才能理解为什么高吞吐场景要减少中断次数(NAPI)、为什么要把中断从关键核挪走(亲和性)。本文从四部分开销构成、量化数字、观测手段、与系统调用对比、以及工程上怎么降本五个层面展开。
开销的四个组成部分

四部分开销里,①②是"每次中断必付的固定税",③④是"可能很大但很难量化的隐性税"。性能调优时,先看②是否因 handler 拖太长而放大,再看③有没有把关键线程的 cache 打冷。
量化分析
| 开销类型 | 典型耗时 | 可优化? | 优化手段 |
|---|---|---|---|
| 硬件中断响应(检测→查IDT→压栈) | ~50-80 周期(约 15-25 ns @3GHz) | 否,CPU 硬件固化的 | — |
| 上下文保存/恢复(SAVE_ALL/RESTORE_ALL) | ~100-200 周期(约 30-65 ns) | 否,架构必需的 | — |
| 上半部 handler 执行 | 1-5 微秒(设计目标) | 是 | 只做最少的事,其他推下半部 |
| 下半部 softirq 执行 | 不定(取决于负载) | 是 | NAPI 轮询、多队列分散 |
| 返回后 L1 cache miss | 每次 4-5 周期(L2 12 周期,L3 40+ 周期) | 部分 | 中断亲和性让 handler 常驻同一核 |
| 返回后 TLB miss | 每次 10-50 周期(取决于页表层级) | 部分 | 大页、中断栈 per-CPU |
一个关键数字:一次纯中断进出(不含下半部、不含 cache 污染)大约 300-500 个 CPU 周期 ≈ 100-170ns @3GHz。看似小,但乘上频率就是天文数字——见下文"中断频率的典型数值"。
中断 vs 系统调用:都是进内核,为什么中断更贵
系统调用(syscall/sysret)和中断处理都走"进内核 → 干活 → 返回",但有几个本质差异决定了中断通常更贵:
| 维度 | 系统调用 | 中断 |
|---|---|---|
| 触发方 | 进程主动(同步、可预期) | 外设被动(异步、不可预期) |
| 入口指令 | syscall/sysret(专为低延迟设计,硬件做了优化) | 通用 IRQ 路径(查 IDT、压栈多) |
| 页表/地址空间 | 复用当前进程 | 借被打断进程的地址空间(不换页表) |
| 关中断 | 全程开中断(可被中断打断) | 进入时关中断,handler 要主动开 |
| 中断上下文的限制 | 是进程上下文,可睡眠 | 中断上下文,不能睡眠 |
| 缓存影响 | 主要是内核代码路径 | handler + 下半部代码路径更长,且打断进程时机的随机性更大 |
实际测量的经验值:一次 getpid() 空系统调用约 50-100ns,而一次网卡中断从进入 handler 到返回约 2-5µs(含下半部),相差一个数量级。所以**"中断风暴"对系统的伤害远大于"系统调用风暴"**——这也是网络高吞吐场景优先消灭中断的原因。
中断频率的典型数值
空闲系统(只有 timer tick):250 Hz(CONFIG_HZ=250)或 1000 Hz
→ 每秒 250/1000 次中断,开销可忽略
高吞吐网络(10Gbps,小包 64B):
满线速 = 14.88 Mpps → 如果每包一次中断 = 1488 万次/秒
→ 每包约 67 ns 预算,中断进出就耗光了 → 必须 NAPI 批量收包
NVMe 磁盘(百万 IOPS):
100 万 IOPS → 100 万次 IO 完成中断/秒
→ 必须用中断合并(interrupt coalescing)减少中断次数把 100-170ns × 1488 万次/秒算一下:光中断进出就要吃 每秒 0.2 秒的 CPU 时间(在单核上)——也就是说,一台单核机器 10Gbps 小包满速时,20% 的 CPU 纯浪费在中断进出,这还没算下半部和缓存污染。这正是必须 NAPI/中断合并的根本原因。
除了进出开销,还有两个"隐性税":调度延迟与缓存污染
调度延迟:中断下半部唤醒一个等待的进程后,进程不是立刻跑,而是先被标记为 RUNNABLE、塞回运行队列,等调度器挑中它。从"事件到达(包已进内存)"到"进程真正开始处理数据"之间的延迟叫唤醒/调度延迟(wakeup latency),典型 5-100µs 甚至更高(取决于负载和优先级)。对实时应用(如高频交易、音视频)这是比中断进出本身更重要的延迟来源——所以有 SCHED_FIFO/SCHED_RR 实时调度类、sched_setaffinity 绑核等手段。
缓存污染(最难量化的一块):中断打断进程 A 时,handler 和下半部的代码、数据会加载进 L1/L2,把 A 的热数据驱逐出去。A 恢复执行后第一件事就是 cache miss——如果 A 是延迟敏感的业务线程,这次"无辜的 miss"可能比中断本身贵得多。这也是为什么中断亲和性要把设备中断从业务关键核上挪走(见 irq-affinity):不是让中断更少,而是让业务核少被打扰。per-CPU 的中断栈、per-CPU 变量同样是"把中断足迹锁在本核、别污染别的核"的设计。
中断开销的观测
# 看每个 CPU 硬中断/软中断占比
mpstat -P ALL 1 # %irq(硬中断) / %soft(软中断)
# 看中断总数(vmstat 的 'in' 列)
vmstat 1 # in = 每秒中断次数(含 timer tick)
# 看每个 IRQ 在每个 CPU 上的累计次数
cat /proc/interrupts # LOC 行 = 本地 timer tick 中断
# 看每种软中断的每核计数
cat /proc/softirqs # NET_RX/NET_TX 飙升 = 网络繁忙
# 测量实际中断延迟分布(实时性场景)
sudo cyclictest -m -n -p 99 -i 1000 # 每毫秒一次定时,统计延迟抖动
# 测量关中断/中断响应最坏延迟(内核 ftrace)
sudo sh -c 'echo irqsoff > /sys/kernel/debug/tracing/current_tracer'
cat /sys/kernel/debug/tracing/trace_max_latency
# 对具体中断 handler 做火焰图/采样
sudo perf record -g -e irq:irq_handler_entry sleep 5%irq高(硬中断):设备中断频繁,可能中断风暴、驱动问题。%soft高(软中断):多为网络收发繁忙,cat /proc/softirqs看NET_RX/NET_TX增长。ksoftirqd/<n>吃 CPU:软中断多到当场处理不完,被推给内核线程消化——网络风暴的典型信号。- 中断总数异常高:
vmstat的in列远大于HZ * 核数时,说明除 timer 外还有大量设备中断。 cyclictest的 max latency 突增:说明有长关中断窗口(驱动 bug)或中断风暴抢占,实时任务延迟超预算。
工程上怎么"降本":五板斧
| 手段 | 原理 | 适用场景 |
|---|---|---|
| NAPI / 中断合并(coalescing) | 中断只做一次,后续包靠轮询批量收;网卡寄存器可设 coalescing 阈值(如收 4 个包或攒 10µs 才发一次中断) | 高吞吐网络、NVMe IO 完成 |
中断亲和性(smp_affinity) | 把设备中断绑到专用核,业务核不被 cache 污染/打断 | 延迟敏感业务(如交易、游戏服务器) |
| 网卡多队列(RSS)+ RPS/RFS | 中断分散到多个核并行处理,避免单核软中断瓶颈 | 多核高吞吐 |
| 忙轮询(busy polling / SO_BUSYPOLL) | 应用主动轮询收包,完全绕开中断 | 极低延迟、且能持续喂饱 CPU 的场景 |
| 实时调度类 + 绑核 | SCHED_FIFO + sched_setaffinity 保证关键线程不被调度延迟拖累 | 实时性要求高于吞吐的场景 |
一句话总结:中断开销 = 固定税(硬件响应 + 上下文保存恢复,约 100-170ns/次)+ 隐性税(缓存污染、TLB、调度延迟);固定税乘上高频就是天文数字(10Gbps 小包满速光进出就要烧掉单核 20% CPU),所以工程上靠 NAPI/中断合并砍频率、靠亲和性/多队列转移位置、靠忙轮询彻底绕开——理解了"贵在哪、怎么量",才能真正读懂
mpstat的%irq/%soft和vmstat的in。