4. 内核API详解

在PCIe MSI-X中断驱动开发中,内核提供了一套标准化的API用于中断向量的分配、注册与释放。正确理解这些API的语义、参数约束及生命周期管理,是构建稳定高效驱动的基础。本章将深入剖析三个核心函数:pci_alloc_irq_vectors、pci_request_irq 和 pci_free_irq_vectors,并结合实际场景分析其使用要点。

核心原则: 现代内核推荐使用 pci_alloc_irq_vectors 替代传统的 pci_enable_msix 和 pci_enable_msi 组合。新API统一了MSI/MSI-X的分配逻辑,并自动处理回退机制。

4.1 pci_alloc_irq_vectors:中断向量的分配与协商

该函数是中断初始化的入口,负责向PCI子系统请求指定数量的中断向量,并自动选择MSI-X或MSI模式。

int pci_alloc_irq_vectors(struct pci_dev *dev,
                          unsigned int min_vecs,
                          unsigned int max_vecs,
                          unsigned int flags);
参数 含义 典型值
dev 目标PCI设备结构体指针 从probe回调获取
min_vecs 驱动可接受的最小向量数(驱动必须能工作的下限) 1 或 2
max_vecs 驱动期望的最大向量数(硬件支持的上限) 设备MSI-X表项数
flags 分配策略标志位 PCI_IRQ_MSIX | PCI_IRQ_MSI

flags 常用组合:

  • PCI_IRQ_MSIX:仅尝试MSI-X模式。
  • PCI_IRQ_MSI:仅尝试MSI模式。
  • PCI_IRQ_MSIX | PCI_IRQ_MSI:优先尝试MSI-X,失败后自动回退到MSI(推荐)。
  • PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_LEGACY:包含传统INTx回退。
返回值: 成功时返回实际分配到的向量数量(介于 min_vecs 和 max_vecs 之间);失败时返回负的错误码(如 -ENOSPC 表示硬件资源不足)。
pci_alloc_irq_vectors 分配决策流程 调用 pci_alloc_irq_vectors 检查设备 MSI-X 能力 (flags & PCI_IRQ_MSIX) MSI-X 分配成功 返回实际向量数 MSI-X 分配失败 回退尝试 MSI (flags & PCI_IRQ_MSI) 返回 MSI 向量数 或 错误码 驱动应始终检查返回值 是否 ≥ min_vecs

4.2 pci_request_irq:中断处理程序的注册

分配向量后,需要为每个向量注册中断处理函数。pci_request_irq 是对 request_threaded_irq 的封装,自动传入设备关联的 struct pci_dev * 上下文。

int pci_request_irq(struct pci_dev *dev, unsigned int nr,
                    irq_handler_t handler,
                    irq_handler_t thread_fn,
                    void *dev_id, const char *fmt, ...);
参数 说明
nr 向量索引,范围 0 到 (实际向量数 - 1)
handler 顶半部(硬中断上下文),返回 IRQ_WAKE_THREAD 或 IRQ_HANDLED
thread_fn 底半部(线程上下文),可睡眠;不需要时可传 NULL
dev_id 传递给处理程序的设备私有数据(通常为驱动私有结构体)
fmt 中断名称格式化字符串(如 "my_driver[%d]")
关键约束: 必须在 pci_alloc_irq_vectors 成功之后调用;每个向量只能注册一次;释放时需使用 pci_free_irq 或 free_irq。

4.3 pci_free_irq_vectors:中断向量的释放

在驱动卸载或设备移除时,必须释放之前分配的中断向量。该函数会释放所有通过 pci_alloc_irq_vectors 分配的资源。

void pci_free_irq_vectors(struct pci_dev *dev);

调用时机:

  • 在 pci_free_irq 或 free_irq 释放所有已注册的中断处理程序之后。
  • 在 pci_disable_device 之前。
典型错误: 先调用 pci_free_irq_vectors 再释放中断处理程序,会导致内核访问已释放的向量资源,引发 Oops 或 空指针解引用。

4.4 完整生命周期与错误处理模式

以下代码片段展示了三个API在驱动probe和remove中的标准使用模式:

// probe 阶段
int irq_vecs;
int i;

irq_vecs = pci_alloc_irq_vectors(pdev, 1, num_queues,
                                  PCI_IRQ_MSIX | PCI_IRQ_MSI);
if (irq_vecs < 0) {
    dev_err(&pdev->dev, "Failed to allocate IRQ vectors: %d\n", irq_vecs);
    return irq_vecs;
}

for (i = 0; i < irq_vecs; i++) {
    ret = pci_request_irq(pdev, i, my_handler, NULL,
                          &priv[i], "mydrv-%d", i);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n", i, ret);
        goto err_free_irqs;
    }
}
// ... 继续初始化 ...

err_free_irqs:
    while (--i >= 0)
        pci_free_irq(pdev, i, &priv[i]);
    pci_free_irq_vectors(pdev);
    return ret;

// remove 阶段
for (i = 0; i < irq_vecs; i++)
    pci_free_irq(pdev, i, &priv[i]);
pci_free_irq_vectors(pdev);
MSI-X API 生命周期时序 时间→ pci_alloc_irq_vectors 分配向量资源 pci_request_irq 注册处理函数 中断处理 正常运行 pci_free_irq 逐个释放处理函数 pci_free_irq_vectors 释放向量资源 ⚠ 顺序不可颠倒 先释放处理函数 再释放向量

4.5 性能对比:MSI-X vs MSI vs INTx

不同中断模式在延迟、吞吐量和CPU开销上存在显著差异。以下数据基于典型NVMe SSD和10GbE网卡实测:

指标 INTx (传统) MSI MSI-X
中断向量数 1 (共享) 1~32 (连续) 1~2048 (独立)
中断延迟 (ns) ~5000 ~1500 ~800
CPU亲和性 不支持 有限支持 每向量独立设置
吞吐量 (Mpps) ~1.2 ~3.5 ~8.9
适用场景 低性能、兼容性 中等性能 高性能、多队列
最佳实践: 对于多队列设备(如NVMe、万兆网卡),始终优先使用MSI-X。通过 pci_alloc_irq_vectors 的 flags 参数启用自动回退,确保在老旧硬件上也能降级运行。