本文主要针对PCIe NVMe SSD。
SATA/AHCI SSD 也可能使用 MSI/MSI-X,但其命令完成模型是 AHCI/NCQ,不完全适用 NVMe 的 SQ/CQ、Completion Queue Interrupt Vector 等概念。
目录
1. 先给结论
2. INTx、MSI、MSI-X 的区别
2.1 Legacy INTx
2.2 MSI
MSI 的关键特点
2.3 MSI-X
MSI-X 的优势
2.4 对比表
3. MSI/MSI-X 中的几个“编号”不要混淆
3.1 NVMe Completion Queue ID
3.2 MSI-X Vector Index
3.3 Linux IRQ Number
3.4 CPU Interrupt Vector
4. NVMe 中的队列和中断关系
4.1 Submission Queue 和 Completion Queue
4.2 Completion Queue 的 Interrupt Vector
5. NVMe MSI-X 的完整工作流程
5.1 PCIe 枚举阶段
5.2 MSI-X Table 初始化
5.3 NVMe 控制器初始化
5.4 主机提交 I/O
5.5 SSD 写入 Completion Queue
5.6 CPU 处理中断
6. 为什么有时“完成了但没有中断”
6.1 处于轮询模式
6.2 中断被 Mask
6.3 中断聚合
7. 常见应用场景
7.1 一队列一个 MSI-X 向量
7.2 多个 CQ 共享一个 MSI-X 向量
7.3 降级到单 MSI
7.4 轮询模式
7.5 虚拟化和 SR-IOV
8. Linux 下的检查方法
8.1 查看 MSI/MSI-X 是否启用
8.2 查看实际中断计数
8.3 查看 PCI 设备的 MSI IRQ 列表
8.4 查看 IRQ CPU 亲和性
8.5 查看内核日志
8.6 查看 PCIe 链路和 AER 状态
9. 常见故障现象和排查思路
9.1 现象一:I/O 超时,/proc/interrupts 不增长
优先检查
常见原因
9.2 现象二:IRQ 计数增长,但 I/O 仍然超时
9.3 现象三:中断风暴
9.4 现象四:只有一个 CPU 有中断,其他 CPU 没有
9.5 现象五:性能低、CPU 中断占用高
9.6 现象六:系统重置或热复位后中断失效
10. MSI-X 硬件或 FPGA SSD 的调试重点
10.1 MSI-X Capability 描述错误
10.2 没有使用主机写入的地址和数据
10.3 CQE 和 MSI-X 消息的顺序错误
10.4 Vector Index 和 CQ 配置不一致
10.5 Mask 和 Pending 处理错误
11. 建议的系统化排查流程
第一步:确认是否真的需要中断
第二步:确认 PCIe 中断模式
第三步:确认实际 IRQ
第四步:确认控制器和队列状态
第五步:确认所有 Mask 层
第六步:确认 PCIe、IOMMU 和中断路由
第七步:确认 CPU 和 NUMA
12. 需要特别记住的几个结论
1. 先给结论
NVMe SSD 的一次典型 I/O 完成过程可以概括为:
主机提交 SQ Entry │ ▼ SSD 控制器 DMA 读取 SQ │ ▼ SSD 执行命令 │ ▼ SSD DMA 写入 CQE │ ▼ SSD 发送 MSI/MSI-X 消息 │ ▼ Root Complex / IOMMU / APIC 或 GIC │ ▼ CPU 进入 NVMe 中断处理函数 │ ▼ 驱动读取 CQE、完成请求、更新 CQ Head Doorbell最重要的一点:
MSI/MSI-X 只是“通知主机有完成事件”,中断消息本身不携带 NVMe Completion Entry。
真正的完成信息已经由 SSD 通过 DMA 写入主机内存中的 Completion Queue。
2. INTx、MSI、MSI-X 的区别
2.1 Legacy INTx
传统 PCI 中断通常使用 INTx 引脚:
设备拉低中断线 │ ▼ 主机响应中断 │ ▼ 驱动读取设备状态并清除中断特点:
- 物理或逻辑中断线;
- 多个 PCI 设备可能共享同一条中断线;
- 属于电平触发方式;
- 需要设备和驱动显式清除中断状态;
- 扩展性差,容易产生共享中断和中断抖动。
在现代 NVMe SSD 中,INTx 通常只是兼容性兜底方式。
2.2 MSI
MSI,即 Message Signaled Interrupt。
它不是通过独立的中断引脚,而是设备发起一个 PCIe Memory Write TLP,写入由操作系统配置好的中断消息地址和数据:
SSD │ │ PCIe Memory Write TLP ▼ MSI 地址 / 数据 │ ▼ Root Complex │ ▼ APIC / GIC / Interrupt Remapping │ ▼ CPU IRQMSI 的关键特点
- 不使用共享中断线;
- 中断通过 PCIe 总线事务传递;
- 一般支持 1、2、4、8、16、32 个消息;
- 多 MSI 数量通常要求是 2 的幂;
- 所有 MSI 消息通常使用相同的 Message Address,仅 Message Data 有区别;
- 每个 MSI 向量的独立性不如 MSI-X;
- 支持能力受设备、操作系统和平台限制。
PCI MSI Capability 中通常包括:
- MSI Enable;
- Multiple Message Capable;
- Multiple Message Enable;
- 32 位或 64 位 Message Address;
- Message Data;
- 可选的 Per-Vector Mask 和 Pending Bits。
2.3 MSI-X
MSI-X 是为大量独立中断向量设计的扩展机制。
MSI-X 一般最多支持 2048 个向量,而且每个向量都有独立的:
Message Address Message Data Vector Mask典型结构如下:
MSI-X Table ┌────────────────────────────┐ │ Entry 0: Addr/Data/Mask │ │ Entry 1: Addr/Data/Mask │ │ Entry 2: Addr/Data/Mask │ │ ... │ │ Entry N: Addr/Data/Mask │ └────────────────────────────┘ PBA: Pending Bit ArrayMSI-X Table 和 Pending Bit Array 通常位于设备 BAR 指定的位置。
每个 MSI-X Table Entry 通常包含:
Message Address Low Message Address High Message Data Vector Control其中 Vector Control 中通常有 Mask 位。
MSI-X 的优势
- 每个队列可以使用独立向量;
- 每个向量可以绑定到不同 CPU;
- 适合 NVMe 多队列、高 IOPS 场景;
- 支持任意数量的向量,不要求 2 的幂;
- 向量间隔离性更好;
- 可以将不同队列分散到不同 NUMA 节点和 CPU。
2.4 对比表
| 特性 | INTx | MSI | MSI-X |
|---|---|---|---|
| 传递方式 | 中断线 | PCIe Memory Write | PCIe Memory Write |
| 是否共享 | 经常共享 | 通常不共享 | 通常不共享 |
| 向量数量 | 少 | 通常最多 32 | 最多 2048 |
| 向量数量要求 | 无 | 通常为 2 的幂 | 可任意分配 |
| 每向量独立地址 | 无 | 有限 | 有 |
| 每向量独立 Mask | 无 | 可选 | 支持 |
| 适合 NVMe 多队列 | 较差 | 一般 | 最适合 |
| 典型使用 | 兼容性回退 | 低队列或降级场景 | 高性能默认方案 |
3. MSI/MSI-X 中的几个“编号”不要混淆
NVMe 调试中经常把以下几个概念混为一谈。
3.1 NVMe Completion Queue ID
例如:
CQ1、CQ2、CQ3这是 NVMe 协议中的队列编号。
3.2 MSI-X Vector Index
例如:
MSI-X vector 0、vector 1、vector 2这是 MSI-X Table 的索引,也是 NVMe Create I/O Completion Queue 命令中 IV 字段所引用的向量编号。
3.3 Linux IRQ Number
例如:
IRQ 123 IRQ 124这是 Linux 内核分配给设备中断的 IRQ 号。
3.4 CPU Interrupt Vector
这是 CPU/APIC 层面的硬件中断向量,例如某个内部向量号。
它们之间并不一定相等:
NVMe IV = 3 │ ▼ MSI-X Table Entry 3 │ ▼ Linux IRQ 157 │ ▼ CPU APIC Vector 0xE1因此:
NVMe 的 Interrupt Vector、MSI-X Table Index、Linux IRQ 号,不是同一个概念。
4. NVMe 中的队列和中断关系
4.1 Submission Queue 和 Completion Queue
NVMe 使用成对或多对队列:
Submission Queue,SQ:主机提交命令 Completion Queue,CQ:SSD 返回完成结果典型关系:
SQ1 ───────┐ ├──> CQ1 ──> MSI-X Vector 1 SQ2 ───────┘ SQ3 ───────┐ ├──> CQ2 ──> MSI-X Vector 2 SQ4 ───────┘多个 Submission Queue 可以关联到同一个 Completion Queue。
真正决定中断向量的是Completion Queue,而不是 Submission Queue。
4.2 Completion Queue 的 Interrupt Vector
创建 I/O Completion Queue 时,主机通常会指定:
- Completion Queue ID;
- 队列深度;
- 队列物理地址;
- Interrupt Enable,通常称为
IEN; - Interrupt Vector,通常称为
IV。
概念上相当于:
Create CQ1: IEN = 1 IV = 1 Create CQ2: IEN = 1 IV = 2于是:
CQ1 的完成事件 -> MSI-X Vector 1 CQ2 的完成事件 -> MSI-X Vector 2Admin Completion Queue 在规范和常见驱动实现中通常使用 vector 0,但具体仍以驱动和控制器实现为准。
5. NVMe MSI-X 的完整工作流程
5.1 PCIe 枚举阶段
系统启动时,PCIe Root Complex 枚举 SSD:
- 读取 PCI Configuration Space;
- 检查 MSI Capability;
- 检查 MSI-X Capability;
- 读取 MSI-X Table Size;
- 读取 MSI-X Table 的 BAR、偏移;
- 读取 PBA 的 BAR、偏移;
- 分配 PCI 资源;
- 建立中断路由。
Linux 中驱动通常会通过类似以下机制申请中断向量:
pci_alloc_irq_vectors(...) pci_irq_vector(...) request_irq(...)实际函数和参数会随内核版本变化。
一般选择顺序是:
MSI-X ↓ 失败 MSI ↓ 失败 Legacy INTx不过具体行为依赖驱动和操作系统。
5.2 MSI-X Table 初始化
操作系统或 PCI 子系统会为每个 MSI-X Table Entry 写入:
Message Address Message Data Vector Mask例如:
MSI-X Entry 0 -> Linux IRQ 100 MSI-X Entry 1 -> Linux IRQ 101 MSI-X Entry 2 -> Linux IRQ 102注意:
- Table 中的 Message Address 不是固定值;
- 不能把 x86 上常见的
0xFEE...地址硬编码到设备中; - 在 IOMMU、Interrupt Remapping、虚拟化或 ARM GIC ITS 环境下,地址和数据可能完全不同;
- 设备必须使用主机写入的地址和数据。
5.3 NVMe 控制器初始化
驱动随后初始化 NVMe 控制器:
- 创建 Admin Submission Queue;
- 创建 Admin Completion Queue;
- 使能控制器;
- 查询或设置 I/O Queue 数量;
- 创建 I/O Completion Queues;
- 为每个 CQ 指定
IV; - 创建与 CQ 关联的 Submission Queue;
- 开启相关中断;
- 绑定 IRQ affinity。
一个常见布局可能是:
MSI-X Vector 0 -> Admin CQ MSI-X Vector 1 -> I/O CQ 1 MSI-X Vector 2 -> I/O CQ 2 MSI-X Vector 3 -> I/O CQ 3 ...但如果向量不够,也可能是:
MSI-X Vector 1 -> CQ1、CQ5 MSI-X Vector 2 -> CQ2、CQ6 MSI-X Vector 3 -> CQ3、CQ75.4 主机提交 I/O
主机驱动完成以下动作:
- 在 SQ 中填写 NVMe Command;
- 更新本地 SQ Tail;
- 通过 MMIO 写 SQ Tail Doorbell;
- SSD 看到 Doorbell 变化;
- SSD DMA 读取 SQ Entry。
5.5 SSD 写入 Completion Queue
命令完成后,SSD:
- 执行存储操作;
- 生成 Completion Entry;
- 通过 DMA 将 CQE 写入主机内存;
- 更新 CQ Entry 中的 Phase Bit;
- 根据中断聚合和 Mask 状态决定是否发送中断;
- 找到对应的 Interrupt Vector;
- 读取 MSI-X Table Entry;
- 生成 MSI-X PCIe Memory Write。
CQE 中通常包含:
- Command Identifier,CID;
- Submission Queue Head;
- Submission Queue Identifier;
- Status;
- Phase Bit;
- 其他状态信息。
5.6 CPU 处理中断
CPU 收到 MSI-X 后进入类似以下逻辑:
nvme_irq(vector) ├── 找到该 vector 关联的 CQ ├── 检查 CQ 中是否有新的 CQE ├── 检查 Phase Bit ├── 读取 CID 和 Status ├── 完成对应 Block Layer Request ├── 推进本地 CQ Head └── 写 CQ Head Doorbell注意:
在 MSI-X 中,设备没有像 INTx 那样的“拉线保持”和明确的硬件 ACK 概念。
主机真正解除设备侧待处理状态的关键动作,是消费 CQE 并更新 CQ Head Doorbell。
CPU 的 APIC EOI 仍由操作系统中断子系统处理。
6. 为什么有时“完成了但没有中断”
以下情况都可能导致 CQE 已经写入,但暂时没有看到中断:
6.1 处于轮询模式
某些 NVMe 驱动、SPDK、DPDK 或高性能应用会使用 polling:
主机主动轮询 CQ 而不是等待 MSI-X这种情况下:
- I/O 可以正常完成;
/proc/interrupts中断计数可能不增长;- 没有中断并不代表异常。
Linux 也可能配置部分 polling queues。
6.2 中断被 Mask
可能存在多层 Mask:
- MSI-X Table Entry 的 Vector Mask;
- MSI-X Function Mask;
- NVMe
INTMS寄存器; - CQ 创建时
IEN = 0; - Linux IRQ 被禁用;
- 设备处于复位或电源管理状态。
这些 Mask 并不等价,需要逐层确认。
6.3 中断聚合
NVMe 支持 Interrupt Coalescing。
控制器可能不会在每个 CQE 写入后都立刻中断,而是满足以下条件之一后再中断:
- 累计完成数达到阈值;
- 等待时间达到定时器值;
- 控制器内部触发强制刷新。
好处:
- 减少中断次数;
- 降低 CPU 消耗;
- 提高高 IOPS 场景的吞吐。
代价:
- 增加单个 I/O 的完成延迟;
- 参数不合理时表现为“中断很少”或“延迟突然升高”。
NVMe 中通常有:
- Interrupt Coalescing Feature;
- Interrupt Vector Configuration Feature。
工具支持时可以查看类似:
nvme get-feature /dev/nvme0 -f 0x08不同工具版本和控制器支持情况可能不同。
7. 常见应用场景
7.1 一队列一个 MSI-X 向量
CQ1 -> Vector 1 -> CPU 1 CQ2 -> Vector 2 -> CPU 2 CQ3 -> Vector 3 -> CPU 3适合:
- 高并发随机 I/O;
- 多核系统;
- 高端 PCIe SSD;
- 每 CPU 或每 NUMA 节点绑定一个队列。
优点:
- 低共享;
- 中断处理并行;
- 便于 CPU affinity;
- 适合高 IOPS。
缺点:
- 占用更多向量;
- 每个队列都可能产生中断;
- 中断频率过高时 CPU 消耗增加。
7.2 多个 CQ 共享一个 MSI-X 向量
CQ1 ─┐ CQ5 ─┴──> Vector 1 CQ2 ─┐ CQ6 ─┴──> Vector 2适合:
- MSI-X 向量数量不足;
- 虚拟机或 SR-IOV VF;
- 低端控制器;
- 系统限制了最大 IRQ 数量。
中断处理函数通常需要扫描该 vector 关联的多个 CQ。
缺点:
- 共享 vector 的队列相互影响;
- 扫描队列增加处理开销;
- 可能导致某些队列延迟增加。
7.3 降级到单 MSI
所有 CQ -> MSI Vector 0常见原因:
- MSI-X 分配失败;
- 平台不支持 MSI-X;
- 虚拟化环境限制;
- 驱动参数或内核策略;
- 控制器 MSI-X Capability 描述错误。
这种模式下 I/O 仍然可能正常,但:
- 所有队列共享一个 IRQ;
- 中断处理集中到一个 CPU;
- 高 IOPS 场景吞吐和延迟可能明显变差。
7.4 轮询模式
应用或驱动主动读取 CQ适合:
- 极低延迟场景;
- 用户态 NVMe;
- SPDK;
- 高速存储测试;
- CPU 资源充足的环境。
优点:
- 避免中断切换;
- 延迟更稳定;
- 高 IOPS 下性能好。
缺点:
- 消耗 CPU;
- 空闲时效率低;
- 通用块设备场景不一定适合。
7.5 虚拟化和 SR-IOV
在虚拟化环境中,MSI-X 可能经过:
Guest MSI-X ↓ 虚拟机监控器 ↓ 物理 MSI-X / Posted Interrupt / vIOMMU ↓ 物理 CPU可能遇到:
- VF 的 MSI-X 向量数较少;
- Guest 中看到的向量数与 PF 不同;
- 中断经过虚拟化转发;
- 虚拟机迁移或 reset 后向量重新分配;
- IOMMU/Interrupt Remapping 配置导致消息地址不同。
8. Linux 下的检查方法
8.1 查看 MSI/MSI-X 是否启用
lspci -vv -s 0000:xx:yy.z重点观察类似信息:
MSI: Enable+ Count=1/1 MSI-X: Enable+ Count=64 Masked-含义:
Enable+:该模式当前启用;Count=64:设备最多提供或报告 64 个 MSI-X 表项;- 不代表操作系统实际创建了 64 个 I/O 队列;
Masked-:通常表示没有被整体 Mask,但仍不能替代对 NVMe 和驱动状态的检查。
注意:
MSI-X Capability 中的 Count 通常是最大能力,不一定等于实际分配的 IRQ 数量。
8.2 查看实际中断计数
grep -i nvme /proc/interrupts可能看到:
120: 10023 0 0 0 IR-PCI-MSI nvme0q0 121: 0 9821 0 0 IR-PCI-MSI nvme0q1 122: 0 0 9934 0 IR-PCI-MSI nvme0q2可以观察:
- 是否存在多个
nvme0qX; - 每个 IRQ 是否都在增长;
- 是否只有一个 IRQ 增长;
- 是否只有 CPU0 处理;
- IRQ 是否极度集中。
注意:
- 没有 I/O 时计数不增长是正常的;
- polling 模式下计数不增长也可能正常;
- 中断计数增长不代表 CQE 一定被正确处理。
8.3 查看 PCI 设备的 MSI IRQ 列表
BDF=0000:xx:yy.z ls /sys/bus/pci/devices/$BDF/msi_irqs/ cat /sys/bus/pci/devices/$BDF/irq不同内核版本中 sysfs 内容可能略有不同。
8.4 查看 IRQ CPU 亲和性
IRQ=120 cat /proc/irq/$IRQ/smp_affinity_list cat /proc/irq/$IRQ/effective_affinity_list还可以查看设备 NUMA 节点:
cat /sys/bus/pci/devices/$BDF/numa_node重点确认:
- IRQ 是否运行在正确 CPU;
- CPU 是否和 SSD 位于同一 NUMA 节点;
- 是否被 irqbalance 自动迁移;
- 是否所有 IRQ 集中在一个 CPU;
- 是否某些 IRQ 被绑定到了不合适的 NUMA 节点。
8.5 查看内核日志
dmesg -T | grep -iE \ 'nvme|msi|msix|aer|iommu|dmar|amd-vi|timeout|reset'重点关注:
I/O timeout;reset controller;failed to allocate IRQ;MSI-X分配失败;AER错误;- IOMMU DMA fault;
- PCIe link down;
- Completion Queue overflow;
- controller fatal status。
8.6 查看 PCIe 链路和 AER 状态
lspci -vv -s 0000:xx:yy.z关注:
LnkSta;- Link Speed;
- Link Width;
- Correctable Error;
- Non-Fatal Error;
- Fatal Error;
- Unsupported Request;
- Completion Timeout;
- Receiver Error。
MSI-X 消息本身也是 PCIe 事务。如果 PCIe 链路、Root Port、IOMMU 或 Interrupt Remapping 有问题,设备可能已经完成命令,但中断消息没有正常抵达 CPU。
9. 常见故障现象和排查思路
9.1 现象一:I/O 超时,/proc/interrupts不增长
优先检查
- 是否真的产生了 I/O;
- 是否启用了 polling;
lspci -vv中 MSI-X 是否为Enable+;- 实际是否降级到了 MSI 或 INTx;
- NVMe 控制器是否处于
CSTS.RDY=1; - NVMe
INTMS是否屏蔽; - MSI-X Table Entry 是否被 Mask;
- CQ 是否创建时
IEN=1; - CQ 的 IV 是否指向正确向量;
- 设备是否真的写入了 CQE;
- 是否存在 IOMMU、AER、PCIe 链路错误。
常见原因
- MSI-X 没有启用;
- MSI-X Table 被错误配置;
- CQ 使用了错误的 Interrupt Vector;
- 控制器处于复位状态;
- IRQ 被禁用;
- IOMMU 拒绝了 MSI 或 DMA;
- SSD 没有产生 CQE;
- 设备写入 CQE 的 DMA 地址错误;
- 使用 polling,误以为应该有中断。
9.2 现象二:IRQ 计数增长,但 I/O 仍然超时
这种情况通常说明中断消息已经到达 CPU,但 NVMe 完成处理链路有问题。
重点检查:
- 中断处理函数是否找到正确的 CQ;
- CQE 是否真的写入;
- CQE Phase Bit 是否正确;
- CID 是否能匹配到原始请求;
- CQ Head 是否正确推进;
- CQ Doorbell 是否正确写回;
- DMA 内存是否可见;
- CPU Cache 是否同步;
- 内核驱动是否出现锁竞争或异常退出。
常见原因:
- CQE DMA 写入错误地址;
- Phase Bit 处理错误;
- CQ Head 没有更新;
- CQE 中 CID 错误;
- 设备生成了错误的 MSI-X vector;
- ISR 进入了错误的队列处理路径;
- 共享 vector 扫描逻辑存在问题。
9.3 现象三:中断风暴
表现为:
/proc/interrupts 中某个 nvme IRQ 快速增长 CPU 使用率升高 I/O 性能下降常见原因:
- ISR 没有消费 CQE;
- CQ Head Doorbell 没有更新;
- Phase Bit 判断错误;
- 设备不断认为 CQ 中存在未处理 CQE;
- MSI-X Pending Bit 没有正确清除;
- 共享 vector 处理函数没有扫描到真正产生事件的 CQ;
- 中断聚合参数不合理;
- AER 或设备错误导致重复中断;
- 控制器持续报告异步事件。
排查重点:
是否存在未消费 CQE? CQ Head 是否推进? CQ Tail/Phase 是否一致? ISR 退出前是否还有 pending CQE? 是否是共享 vector?9.4 现象四:只有一个 CPU 有中断,其他 CPU 没有
可能原因:
- 只分配了一个 MSI/MSI-X 向量;
- 所有 CQ 共享一个 vector;
- irqbalance 将 IRQ 集中到了一个 CPU;
- IRQ affinity 没有正确配置;
- NVMe 驱动根据 CPU 数量只创建了少量队列;
- 虚拟化环境限制了向量数量;
- 控制器 NUMA 拓扑和 CPU 绑定不合理。
可检查:
grep -i nvme /proc/interrupts cat /proc/irq/<IRQ>/effective_affinity_list性能优化方向:
- 增加 I/O Queue 数量;
- 使用 MSI-X;
- 让队列数与 CPU 或工作线程数匹配;
- 设置合理 IRQ affinity;
- 让 IRQ 与内存、SSD 位于同一 NUMA 节点;
- 避免所有队列共享同一个 vector。
9.5 现象五:性能低、CPU 中断占用高
常见原因:
- 降级到了单 MSI;
- 多个 CQ 共享一个向量;
- I/O 粒度很小;
- 没有启用中断聚合;
- 中断亲和性配置不合理;
- 中断集中在一个 CPU;
- 设备队列数少;
- 使用了不合适的 NUMA 绑定;
- 中断与软中断、块层处理产生竞争。
可以从以下角度优化:
- 确认 MSI-X 是否启用;
- 确认实际分配的 vector 数量;
- 检查 CQ 与 vector 的映射;
- 调整 CPU affinity;
- 检查 NUMA;
- 调整 NVMe Interrupt Coalescing;
- 对极低延迟业务评估 polling;
- 对高 IOPS 业务增加队列并避免过度共享。
9.6 现象六:系统重置或热复位后中断失效
常见原因:
- FLR 后 MSI-X Table 内容被设备清除;
- 驱动没有重新写入 MSI-X Table;
- IRQ 已经重新分配,但设备仍使用旧的地址/数据;
- CQ 和 SQ 已经被重新创建,但 IV 映射未更新;
- 控制器尚未重新解除 Mask;
- reset 流程中存在旧请求和新队列并发;
- PCIe Link Recovery 后设备状态与驱动状态不一致。
正确的 reset 流程通常需要:
停止新 I/O 等待或取消旧请求 Mask 中断 停止控制器 重新初始化 MSI/MSI-X 重新创建 Admin Queue 重新创建 I/O CQ/SQ 重新配置 vector 映射 解除 Mask 恢复 I/O10. MSI-X 硬件或 FPGA SSD 的调试重点
如果是自研 SSD 控制器、FPGA NVMe Endpoint 或定制 PCIe 设备,MSI-X 问题通常集中在以下位置。
10.1 MSI-X Capability 描述错误
检查:
- Capability 链表是否正确;
- Table Size 是否正确;
- Table BIR 是否指向正确 BAR;
- Table Offset 是否正确;
- PBA BIR 是否正确;
- PBA Offset 是否正确;
- Table 是否按规范对齐;
- Table Entry 是否为正确大小;
- BAR 空间是否足够。
MSI-X Table Entry 通常为 16 字节。
10.2 没有使用主机写入的地址和数据
错误做法:
设备内部硬编码一个 MSI 地址 设备内部硬编码一个中断数据正确做法:
主机通过 MSI-X Table 写入地址和数据 设备读取或使用对应 Table Entry 设备按照该 Entry 生成 MSI-X Message特别是在以下环境中,硬编码几乎一定会失败:
- IOMMU 开启;
- Interrupt Remapping 开启;
- ARM GIC ITS;
- 虚拟机;
- SR-IOV;
- 不同 BIOS 或不同内核;
- 多 Socket 系统。
10.3 CQE 和 MSI-X 消息的顺序错误
设备必须保证:
先让 CQE 对主机可见 再发送 MSI-X Message错误顺序可能是:
先发送 MSI-X 后完成 CQE DMA 写入此时 CPU 进入 ISR 后可能读不到有效 CQE,表现为:
- 中断计数增长;
- 驱动找不到完成项;
- I/O 长时间 pending;
- 后续可能产生中断风暴。
需要关注:
- PCIe TLP 顺序;
- 设备内部 DMA 写入完成标志;
- 内存屏障;
- DMA 一致性;
- 非一致性 ARM 平台上的 Cache Sync;
- CQE 写入和 MSI 写入之间的排序保证。
10.4 Vector Index 和 CQ 配置不一致
例如:
主机创建 CQ3,指定 IV=3 设备实际向 Table Entry 2 发送中断可能导致:
- 中断进入错误的 ISR;
- ISR 找不到待处理 CQ;
- 某个 CQ 一直不处理;
- 另一个 CQ 产生大量“伪中断”。
测试时建议:
- 先只启用一个 CQ;
- 只使用 vector 0 或 vector 1;
- 确认单队列路径;
- 再逐步增加 CQ 和 MSI-X vector;
- 最后测试共享 vector。
10.5 Mask 和 Pending 处理错误
需要同时检查:
- MSI-X Function Mask;
- MSI-X Vector Control Mask;
- PBA Pending Bit;
- NVMe INTMS;
- CQ 的 IEN;
- 控制器内部中断 pending 状态;
- Reset 后是否清理旧状态。
11. 建议的系统化排查流程
可以按以下顺序排查。
第一步:确认是否真的需要中断
是否使用 polling? 是否有实际 I/O? 是否只是空闲状态?第二步:确认 PCIe 中断模式
lspci -vv -s <BDF>确认:
MSI-X: Enable+如果是:
MSI-X: Enable- MSI: Enable+说明实际使用的是 MSI,而不是 MSI-X。
第三步:确认实际 IRQ
grep -i nvme /proc/interrupts ls /sys/bus/pci/devices/<BDF>/msi_irqs/判断:
- 是否只有一个 IRQ;
- 是否有多个 nvme 队列 IRQ;
- 是否有 IRQ 计数;
- 是否 IRQ 分布在多个 CPU。
第四步:确认控制器和队列状态
关注:
- Controller 是否 Ready;
- Admin Queue 是否正常;
- I/O CQ 是否创建成功;
- CQ 的 IV 是否正确;
- CQ 的 IEN 是否打开;
- CQ 是否有有效 CQE;
- Phase Bit 是否变化;
- CQ Head Doorbell 是否推进。
第五步:确认所有 Mask 层
PCI MSI/MSI-X Enable MSI-X Function Mask MSI-X Vector Mask NVMe INTMS CQ IEN Linux IRQ enable 状态第六步:确认 PCIe、IOMMU 和中断路由
检查:
dmesg -T | grep -iE 'aer|iommu|dmar|amd-vi|nvme|pci'排除:
- PCIe 链路异常;
- Completion Timeout;
- IOMMU DMA Fault;
- Interrupt Remapping 异常;
- Root Port 错误;
- 虚拟化中断注入异常。
第七步:确认 CPU 和 NUMA
检查:
cat /proc/irq/<IRQ>/effective_affinity_list cat /sys/bus/pci/devices/<BDF>/numa_node避免:
- 所有中断集中在 CPU0;
- IRQ 位于远端 NUMA 节点;
- irqbalance 与手工 affinity 冲突;
- I/O 线程、内存和中断位于不同 NUMA 节点。
12. 需要特别记住的几个结论
- MSI/MSI-X 是 PCIe 消息,不是传统中断线。
- MSI/MSI-X 只传递通知,不携带 NVMe CQE 内容。
- CQE 由 SSD DMA 写入主机内存。
- NVMe 的中断向量绑定在 Completion Queue 上。
- Submission Queue 不直接决定中断向量。
- MSI-X Count 是设备最大能力,不等于实际使用的 IRQ 数量。
- NVMe IV、MSI-X Table Index、Linux IRQ、CPU Vector 不是同一个编号。
- MSI-X Enable+ 不代表一定已经有中断产生。
- 中断不增长可能是 polling,也可能是 Mask、路由、设备或 DMA 故障。
- 中断增长但 I/O 不完成,问题可能在 CQE、Phase、CID、Doorbell 或 DMA,而不一定在 MSI-X。
- 高性能 NVMe 通常优先使用 MSI-X,多队列和 CPU affinity 是性能关键。
- 自研硬件不能硬编码 MSI 地址和数据,必须使用主机配置到 MSI-X Table 的内容。
整体上,可以把 NVMe MSI-X 理解为:
Completion Queue │ │ 通过 IV 指定 ▼ MSI-X Vector │ │ 通过 Table Entry 指定地址和数据 ▼ PCIe MSI-X Message │ ▼ Linux IRQ / CPU │ ▼ 驱动消费 CQE而排查问题时,最有效的思路是把链路拆成四段:
SSD 是否写入 CQE? ↓ SSD 是否生成 MSI/MSI-X? ↓ PCIe/平台是否把 MSI 送到 CPU? ↓ 驱动是否正确消费 CQE?这四段分别对应设备数据通路、设备中断生成、PCIe/平台中断路由、操作系统驱动处理。只要逐段确认,绝大多数“NVMe 中断不工作、IRQ 风暴、I/O 超时或 MSI-X 性能异常”都可以定位。