侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 3 条评论

P5-02 · VFIO 源码深读:把设备安全地交给用户态

PacketForge
2026-08-18 / 0 评论 / 0 阅读 /
对应路线图:Phase 5 (Day 141-168) | DPDK 24.11.6 LTS | 内核对照 v6.6
代码出处:source/linux-stable/drivers/vfio/pci/vfio_pci_core.c 与 vfio_pci.c,引用均标 文件:行号
上一篇:P5-01 DMA/IOVA/IOMMU | 下一篇:P5-03 virtio 与 vhost-user

一句话定位

DPDK 轮询收包的前提是用户态能直接摸到网卡寄存器、收到网卡中断、让网卡 DMA 自己的内存。VFIO 就是管这三件事的内核设施:mmap 交出 BAR,ioctl 交出中断,IOMMU 兜住 DMA——而且每一步都带着安全检查。

真实源码精读

① open 即接管:vfio_pci_open_device(vfio_pci.c:103-128)

static int vfio_pci_open_device(struct vfio_device *core_vdev)
{
    struct vfio_pci_core_device *vdev =
        container_of(core_vdev, struct vfio_pci_core_device, vdev);
    struct pci_dev *pdev = vdev->pdev;
    int ret;

    ret = vfio_pci_core_enable(vdev);
    if (ret)
        return ret;

    if (vfio_pci_is_vga(pdev) && ...) {
        // ...省略 Intel IGD 特殊处理 ...
    }

    vfio_pci_core_finish_enable(vdev);

    return 0;
}

用户态 open("/dev/vfio/<group>") 最终落到这里。核心动作在 vfio_pci_core_enable(vfio_pci_core.c:470 起):先 pci_clear_master(:484,清掉上一个使用者留下的 bus master),pci_enable_device(:486),然后强制复位:

    /* If reset fails because of the device lock, fail this path entirely */
    ret = pci_try_reset_function(pdev);   // vfio_pci_core.c:491
    if (ret == -EAGAIN)
        goto out_disable_device;

    vdev->reset_works = !ret;
    pci_save_state(pdev);                  // :496

open 即 reset:上一个租户(比如虚拟机)把网卡搞到任何脏状态,下一个 open 的人拿到的都是一张干净的卡。这是多租户共享裸机场景的安全底线。

② ioctl 分发表:vfio_pci_core_ioctl(vfio_pci_core.c:1452-1479)

long vfio_pci_core_ioctl(struct vfio_device *core_vdev, unsigned int cmd,
             unsigned long arg)
{
    struct vfio_pci_core_device *vdev =
        container_of(core_vdev, struct vfio_pci_core_device, vdev);
    void __user *uarg = (void __user *)arg;

    switch (cmd) {
    case VFIO_DEVICE_GET_INFO:
        return vfio_pci_ioctl_get_info(vdev, uarg);
    case VFIO_DEVICE_GET_IRQ_INFO:
        return vfio_pci_ioctl_get_irq_info(vdev, uarg);
    // ...省略 HOT_RESET 相关两条 ...
    case VFIO_DEVICE_GET_REGION_INFO:
        return vfio_pci_ioctl_get_region_info(vdev, uarg);
    case VFIO_DEVICE_IOEVENTFD:
        return vfio_pci_ioctl_ioeventfd(vdev, uarg);
    // ...省略 PCI_HOT_RESET / RESET ...
    case VFIO_DEVICE_SET_IRQS:
        return vfio_pci_ioctl_set_irqs(vdev, uarg);
    default:
        return -ENOTTY;
    }
}
EXPORT_SYMBOL_GPL(vfio_pci_core_ioctl);

整个接口面就这 9 个命令。对 DPDK 最关键的两个:GET_REGION_INFO(问清楚每个 BAR 的 offset/大小/可否 mmap),SET_IRQS(把 MSI-X 中断注册成 eventfd——DPDK 的 eal_interrupt 用 epoll 等它)。挂载点在 vfio_pci_ops 虚表里(vfio_pci.c:130-144,.ioctl = vfio_pci_core_ioctl 在 :136)。

③ BAR mmap:物理页映射 + 属性钳制(vfio_pci_core.c:1690-1759,节选)

int vfio_pci_core_mmap(struct vfio_device *core_vdev, struct vm_area_struct *vma)
{
    // ...省略 container_of 和 index 计算 ...
    index = vma->vm_pgoff >> (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT);

    if (index >= VFIO_PCI_NUM_REGIONS + vdev->num_regions)
        return -EINVAL;
    // ...省略合法性检查 ...
    if (!vdev->bar_mmap_supported[index])
        return -EINVAL;
    // ...省略 barmap 申请(pci_request_selected_regions) ...

    vma->vm_private_data = vdev;
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);   // :1748
    vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);   // :1749
    // ...省略注释 ...
    vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);  // :1756
    vma->vm_ops = &vfio_pci_mmap_ops;

    return 0;
}

三个细节:① pgprot_noncached——BAR 是设备寄存器语义,绝不能用 cache 语义(缓存了寄存器读写的值就是灾难);② VM_PFNMAP——这段 vma 背后是物理页帧而不是普通匿名页,缺页路径走 vfio_pci_mmap_ops 的 fault handler 逐页 map;③ VM_DONTDUMP——core dump 时不 dump 这段,避免崩溃时把设备寄存器状态拷出来。安全不是"禁止访问",而是"把访问钳制在设备该有的语义里"。

VFIO 三条通道

🐧 内核对照:正常内核驱动 vs VFIO 用户态直通

维度内核驱动(如 P4-03 的 mini 驱动)VFIO
寄存器访问probe 里 ioremap,内核态读写用户态 mmap BAR 直接读写(:1690)
中断处理注册 irq handler,软中断收包eventfd 通知用户态 epoll(SET_IRQS)
DMA 安全内核统一管,设备可访问全部内存IOMMU 限制在映射范围内(P5-01)
故障面驱动 bug → 内核崩溃应用 bug → 最多杀应用 + 设备复位(:491)
性能中断 + 协议栈开销轮询 + 零拷贝(P1-05 的对比)

VFIO 的哲学是把"驱动"从内核搬到用户态,但只搬运行权,不搬安全权——权限检查、复位、隔离都留在内核。

面试 3 问

  1. 为什么 VFIO 的 mmap 要设置 pgprot_noncached? → BAR 是设备寄存器语义,必须绕过 CPU cache(vfio_pci_core.c:1748);配合 VM_IO | VM_PFNMAP(:1756)让内核以物理页帧方式管理这段 vma。
  2. 用户态程序拿到设备后搞挂了怎么办? → open 时先 pci_try_reset_function(:491),下次打开自动复位;应用退出时走 close 路径收回资源(vfio_pci_ops.close_device = vfio_pci_core_close_device,vfio_pci.c:135),release 在 :133。
  3. VFIO 和 UIO 的核心区别? → UIO 只是简单的中断直通,没有 IOMMU 隔离,DMA 可写全物理内存;VFIO 强绑定 IOMMU group,把 DMA 限制在映射范围内,还支持完整 MSI-X、VF 直通等(group 语义见 drivers/vfio/group.c,本阶段未逐行深读)。

🔧 动手实验

# 1. 绑定网卡到 vfio-pci
modprobe vfio-pci
dpdk-devbind --bind=vfio-pci 03:00.0

# 2. 观察三件事
lspci -v -s 03:00.0 | grep "Kernel driver"     # 期望 vfio-pci
ls /dev/vfio/                                   # 期望出现 group 设备节点
dmesg | tail -20                                # 看 vfio-pci probe 日志

# 3. testpmd 跑通并抓 EAL 日志里的 VFIO 初始化(region/irq 信息)
./testpmd -c 0x3 -n 2 -- -i

# 4. 反向实验:vfio-pci 未加载时跑 testpmd,记录报错
#    再对比 igb_uio/vfio-pci 两种模式下的中断延迟(PPS)

数据落点:perf-data/P5/02-vfio-bind.md(bind 前后 lspci/dmesg 对比、testpmd 启动日志、两种内核驱动绑定方式的 PPS)。J3455 只有一块 82599 的话,注意实验间隙要把网卡绑回 ixgbe 供日常使用。


下一篇:P5-03 · virtio 与 vhost-user:虚拟化的数据面协议

0

评论 (0)

取消