对应路线图: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); // :496open 即 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 这段,避免崩溃时把设备寄存器状态拷出来。安全不是"禁止访问",而是"把访问钳制在设备该有的语义里"。
🐧 内核对照:正常内核驱动 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 问
- 为什么 VFIO 的 mmap 要设置
pgprot_noncached? → BAR 是设备寄存器语义,必须绕过 CPU cache(vfio_pci_core.c:1748);配合VM_IO | VM_PFNMAP(:1756)让内核以物理页帧方式管理这段 vma。 - 用户态程序拿到设备后搞挂了怎么办? → open 时先
pci_try_reset_function(:491),下次打开自动复位;应用退出时走 close 路径收回资源(vfio_pci_ops.close_device = vfio_pci_core_close_device,vfio_pci.c:135),release 在 :133。 - 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 供日常使用。
评论 (0)