4. VFIO驱动架构详解

VFIO(Virtual Function I/O)是Linux内核提供的一种用户态驱动框架,旨在将设备直接暴露给用户空间,同时通过IOMMU提供硬件级隔离。与UIO的简单内存映射不同,VFIO引入了完整的设备模型、IOMMU组管理、中断重映射和DMA映射机制,是DPDK高性能数据面运行的核心支撑。

核心认知: VFIO不是UIO的简单升级,而是基于IOMMU的完整设备直通方案。它要求硬件支持VT-d/AMD-Vi,但换来了真正的DMA隔离和多进程安全。

4.1 VFIO设备模型

VFIO在内核中抽象了三层对象:设备(Device)、IOMMU组(IOMMU Group)和容器(Container)。用户态通过操作这些对象完成设备控制。

VFIO设备模型三层架构 用户空间 (Userspace) DPDK Primary Process / Secondary Process VFIO 核心层 (vfio-core) 设备文件 /dev/vfio/vfio | IOCTL 接口 | 事件通知 设备驱动层 (vfio-pci / vfio-platform) PCI配置空间 | BAR映射 | MSI/MSI-X中断 | DMA操作 物理硬件 (PCIe Device + IOMMU)

4.1.1 设备对象与文件接口

每个VFIO设备在用户空间表现为一个文件描述符。通过open("/dev/vfio/vfio")获取容器句柄,再通过ioctl绑定IOMMU组和设备。

/* VFIO设备打开与绑定流程 */
int container_fd = open("/dev/vfio/vfio", O_RDWR);
ioctl(container_fd, VFIO_GET_API_VERSION);
ioctl(container_fd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);

int group_fd = open("/dev/vfio/10", O_RDWR);  /* IOMMU组号10 */
ioctl(group_fd, VFIO_GROUP_SET_CONTAINER, &container_fd);

int device_fd = ioctl(group_fd, VFIO_GROUP_GET_DEVICE_FD, "0000:02:00.0");

4.2 IOMMU组与容器

IOMMU组是VFIO隔离的最小单元。一个组内的所有设备共享同一个IOMMU地址空间,组内所有设备必须全部绑定到VFIO驱动后才能使用。

概念 说明 关键属性
IOMMU组 硬件上无法被IOMMU单独隔离的设备集合 组内设备必须全部绑定VFIO;组号由内核分配
容器 用户态创建的虚拟地址空间,管理DMA映射 一个容器可包含多个IOMMU组;DMA映射以容器为单位
IOMMU域 内核中IOMMU硬件页表的抽象 每个容器对应一个IOMMU域;域切换开销较大
IOMMU组与容器关系 容器 (Container) — DMA地址空间 IOMMU组 10 设备: 0000:02:00.0, 0000:02:00.1 IOMMU组 11 设备: 0000:03:00.0 IOMMU域 (Domain) — 硬件页表 IOMMU硬件 (VT-d / AMD-Vi) ⚠ 组内设备必须全部绑定VFIO,否则整个组无法使用
常见陷阱: 某些平台将PCIe桥与下游设备放在同一个IOMMU组中。此时必须将桥设备也绑定到vfio-pci驱动,否则下游设备无法使用。使用 lspci -vvv 查看设备所属IOMMU组。

4.3 VFIO中断与DMA映射

4.3.1 中断机制

VFIO支持三种中断类型:INTx、MSI和MSI-X。用户态通过ioctl注册中断处理,内核通过事件fd(eventfd)将硬件中断转发到用户空间。

/* VFIO中断注册示例 — MSI-X */
struct vfio_irq_set irq_set = {
    .argsz = sizeof(irq_set),
    .flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER,
    .index = VFIO_PCI_MSIX_IRQ_INDEX,
    .start = 0,
    .count = 1
};
int event_fd = eventfd(0, EFD_NONBLOCK);
irq_set.data_fd = event_fd;
ioctl(device_fd, VFIO_DEVICE_SET_IRQS, &irq_set);

/* 在epoll中等待event_fd可读 */
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, event_fd, &ev);
中断类型 特点 DPDK使用场景
INTx 传统中断,共享IRQ线,性能差 仅用于旧设备兼容
MSI 消息信号中断,每个设备独立 少量队列场景
MSI-X 每个队列独立中断,支持中断亲和性 多队列网卡首选

4.3.2 DMA映射

VFIO要求用户态显式注册DMA映射区域。DPDK在初始化时通过VFIO_IOMMU_MAP_DMA将大页内存映射到IOMMU页表,使设备可以直接访问用户态内存。

/* VFIO DMA映射 — 将hugepage内存映射给设备 */
struct vfio_iommu_type1_dma_map dma_map = {
    .argsz = sizeof(dma_map),
    .flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
    .vaddr = (__u64)hugepage_base,   /* 用户态虚拟地址 */
    .iova = 0x100000000,             /* IOMMU虚拟地址 */
    .size = hugepage_size
};
ioctl(container_fd, VFIO_IOMMU_MAP_DMA, &dma_map);
VFIO DMA映射流程 用户态hugepage内存 虚拟地址: 0x7f...000 IOMMU页表映射 IOVA: 0x100000000 → 物理地址 网卡DMA引擎 直接读写IOVA地址 💡 DPDK在rte_vfio_init()中自动完成所有hugepage的DMA映射 无需手动调用ioctl,但理解底层机制对调试至关重要

4.4 与DPDK的交互流程

DPDK在EAL层封装了完整的VFIO初始化流程。从设备探测到数据面启动,共经历以下关键阶段:

  1. VFIO容器创建 — 打开/dev/vfio/vfio,设置IOMMU类型为VFIO_TYPE1_IOMMU。
  2. IOMMU组绑定 — 遍历/sys/kernel/iommu_groups/,找到设备所在组,打开组设备文件并关联到容器。
  3. 设备文件获取 — 通过VFIO_GROUP_GET_DEVICE_FD获取设备句柄,读取PCI配置空间和BAR信息。
  4. 中断配置 — 注册MSI-X中断,创建eventfd并加入epoll监听。
  5. DMA映射 — 遍历所有hugepage,调用VFIO_IOMMU_MAP_DMA建立IOVA到物理地址的映射。
  6. 数据面启动 — 将设备BAR映射到用户空间,初始化收发包队列,开始数据面运行。
DPDK与VFIO交互完整流程 ① 容器创建: open("/dev/vfio/vfio") → VFIO_SET_IOMMU DPDK EAL: rte_vfio_setup() ② IOMMU组绑定: open("/dev/vfio/<group>") → GROUP_SET_CONTAINER DPDK EAL: pci_vfio_get_group_fd() ③ 设备文件获取: GROUP_GET_DEVICE_FD → 读取PCI配置 DPDK EAL: pci_vfio_get_device_fd() ④ 中断配置: SET_IRQS → MSI-X + eventfd DPDK EAL: rte_intr_handle_setup() ⑤ DMA映射: IOMMU_MAP_DMA → 所有hugepage DPDK EAL: rte_vfio_dma_map() ⑥ 数据面启动: BAR映射 → 队列初始化 → 收发包 DPDK PMD: rte_eth_dev_start()
工程要点: DPDK 21.11+ 支持VFIO多进程模式。Secondary进程通过共享容器和DMA映射,实现零拷贝数据交换。但需注意:Secondary进程不能重新注册中断或修改DMA映射,这些操作必须在Primary进程中完成。

4.4.1 关键数据结构

DPDK内部使用struct rte_vfio_container和struct rte_vfio_device管理VFIO资源。每个容器对应一个内核容器,每个设备对应一个设备文件描述符。

/* DPDK VFIO核心数据结构(简化) */
struct rte_vfio_container {
    int fd;                          /* /dev/vfio/vfio 文件描述符 */
    int iommu_type;                  /* VFIO_TYPE1_IOMMU */
    struct rte_vfio_group *groups;   /* 关联的IOMMU组链表 */
    int nb_groups;
};

struct rte_vfio_device {
    int fd;                          /* 设备文件描述符 */
    int group_fd;                    /* 所属组文件描述符 */
    struct rte_pci_addr pci_addr;    /* PCI地址 */
    struct rte_intr_handle intr_handle; /* 中断句柄 */
};

4.5 本章小结

知识点 核心要点 工程影响
VFIO设备模型 设备 → IOMMU组 → 容器 三层结构 组内设备必须全部绑定,否则无法使用
IOMMU组与容器 组是隔离单元,容器是DMA地址空间 多进程共享容器实现零拷贝
中断机制 MSI-X + eventfd 用户态中断 避免内核中断上下文开销
DMA映射 IOMMU页表映射用户态内存 必须映射所有hugepage,否则DMA错误
DPDK交互流程 6步初始化:容器→组→设备→中断→DMA→数据面 EAL封装完整,但调试需理解底层
下节预告: 第5章将深入UIO与VFIO的性能对比,包括中断延迟、DMA带宽、多进程开销等实测数据,帮助你在工程选型中做出量化决策。