VFIO的使用及原理

发布时间:2026/5/23 16:37:44

VFIO的使用及原理 vfio设备透传主要用于将设备直通给虚拟机以提高性能本篇以一张网卡为例讲述VFIO设备的配置使用及底层原理。其中涉及的技术背景主要有qemukvmvfio。一、VFIO网卡的配置使用1.host配置iommu首先是宿主机host必须支持硬件虚拟化技术如x86架构的VT-d其中有关IO的有iommu的支持x86默认是不开启的可以通过追加内核参数“intel_iommuon”来打开。vim /etc/default/grub添加完后重启宿主机并dmesg | grep -i iommu查看是否开启成功powerpc架构下的iommu是默认开启的所以可以直接跳过此步。2.选择设备本次以一张网卡为例常见的intel网卡如I350等都是支持VFIO透传的先看一下系统总体网卡情况然后在dmesg看到网卡的iommu支持。ethtool -i ethxxx //查看某个网卡信息lshw -C network //查看系统总体网络信息记住一定要选择一个宿主机不用的网卡如stateDOWN的或ifconfig查看 rx和tx0 bytes的网卡来配置透传毕竟vfio属于二选一透传给VM则host不能再用了。本次选择enP1p27s0fx这张网卡3.设备解绑与重新绑定vfio-pci驱动#查看group里面的设备可以发现这个group中共有4个设备[rootlocalhost jcf]# ls /sys/bus/pci/devices/0001\:1b\:00.0/iommu_group/devices/0001:1b:00.0 0001:1b:00.1 0001:1b:00.2 0001:1b:00.3#将设备与驱动程序解绑注意这里要把同一group下的4张网卡全部解绑[rootlocalhost jcf]# echo 0001:1b:00.0 /sys/bus/pci/devices/0001\:1b\:00.0/driver/unbind[rootlocalhost jcf]# echo 0001:1b:00.1 /sys/bus/pci/devices/0001\:1b\:00.1/driver/unbind[rootlocalhost jcf]# echo 0001:1b:00.2 /sys/bus/pci/devices/0001\:1b\:00.2/driver/unbind[rootlocalhost jcf]# echo 0001:1b:00.3 /sys/bus/pci/devices/0001\:1b\:00.3/driver/unbind#找到设备的生产商设备ID[rootlocalhost jcf]# lspci -n -s 0001:1b:00.00001:1b:00.0 0200: 8086:1521 (rev 01)#将设备绑定到vfio-pci驱动这会导致一个新的设备节点“/dev/vfio/5”被创建[rootlocalhost jcf]# echo 8086 1521 /sys/bus/pci/drivers/vfio-pci/new_id#查看刚生成的设备节点[rootlocalhost jcf]# ls /dev/vfio/若/sys/bus/pci/drivers/目录下没有vfio-pci目录则记得insmod vfio_pci模块此时host中已看不到之前的enP1p27s0fx网卡4.创建虚机添加参数 -device vfio-pci,hostxxxx,idnet0/home/jcf/qemu-6.2.0/build/qemu-system-ppc64 -m 32G -smp 8 -boot c -hda /home/jcf/pseries.img -machine pseries --enable-kvm -device vfio-pci,host0001:1b:00.0,idnet0 -vnc :25通过qemu monitor可以看到该网卡已经透传到了虚机内。4.1 若是通过SR-IOV配置的网卡就是一开始多一步创建VF其余步骤相同。4.2 若是通过libvirt创建虚机则需添加如下xml透传配置其中DBDF要自行改变devices hostdev modesubsystem typepci managedyes driver namevfio/ source address domain0x0001 bus0x1b slot0x00 function0x0/ /source /hostdev devices/此时上一步骤的解绑绑定都不需要了libvirt自动设置好。4.3 若是直通NVMe磁盘则可以通过如下找到DBDFARM叫stream id5.登录并配置网络qemu启动虚机后可以通过vnc登录进虚机查看如图发现有一张VFIO网卡。ip addr add 10.100.11.111/24 dev enp0s2# 设置enp0s2 网口IP地址为10.100.11.111此处VM的IP需要与Host保持在同一网段。如果此时发现ping不通请先确认下Host上的这张网卡是否是NO-CARRIER(没插网线)笔者曾经被它困扰了好久 -_-!route -n //查看路由ip route add default via 10.100.11.254 # 设置系统默认路由ip route add default via 10.100.11.254 dev enp0s2 # 设置enp0s2默认网关为10.100.11.254# 或ip route add 10.100.11.0/24 via 10.100.11.254 dev enp0s2 # 设置10.100.11.0网段的网关为10.100.11.254数据走enp0s2接口此时也可通过ssh直接链接到VM将上述配置写入到/etc/sysconfig/network-scripts/ifcfg-xxx配置好其中的IPADDRGATEWAYONBOOTyes就可以自动联网了。抑或将以上两项写成sh脚本放入/etc/rc.d/rc.local或单独成一个sh文件这样可以方便手动调试自动联网。6.恢复现场测试完毕记得回归原状.二、VFIO原理0.config配置空间1.DMA重映射VFIO的目的是透传设备让Guest直接控制以达类似Host的高速读写。但这样会引入安全问题比如Guest1利用设备访问Host中Guest2或其它内存地址所以便出现了DMA重映射。和MMU类似在内存与CPU之间建立映射IOMMU在内存与设备之间增加DMA重映射单元。当外设访问内存时内存地址首先到达DMA重映射硬件DMA重映射硬件根据这个外设的BDF确定其对应的页表查表得出物理内存地址然后将地址送上总线。qemu中在vfio初始化阶段会将Guest的各个MR通过ioctl(VFIO_IOMMU_MAP_DMA)系统调用请求Host内核在页表中建立好GPA-HPA的映射关系。其中最主要的就是vm.ram以arm64为例DMA的映射在虚机启动时就已经静态建立好了映射要涵盖整个GPA地址范围同时虚机GPA对应的物理页都不会交换出去。对于设备的MMIO空间如virtio_net_pci.rom、vga.ram/rom、VFIO.BAR会在加载相应的设备驱动时动态映射至Host中建立GPA-HPA的关联。对于vfio iommu type1x86和arm驱动来说Host内核中处理ioctl(VFIO_IOMMU_MAP_DMA)的核心函数是vfio_dma_do_map()-iommu_map()继而调用arch相关的回调函数iommu_domain-iommu_ops-map()其中arm架构的是arm_smmu_map()x86架构是intel_iommu_map()。在真正map之前中会先通过HVA获取pfn然后在arch回调中根据iova(Guest不开启vsmmu时就是GPA)从页表中找到具体的表项然后将HPA写入表项中完成IOMMU页表的建立。2.中断重映射同样基于安全等因素的考虑vfio设备中断和cpu之间也需要拦截即在外设和CPU之间加了一个硬件中断重映射单元。当接收到来自外设的中断时硬件中断重映射单元会对中断请求的来源进行有效性验证然后以中断号为索引查询中断重映射表代替外设向目标发送中断。图7-61宏观在初始化过程中QEMU会在VM的fd上调用ioctl(KVM_IRQFD)设置一个eventfd与VFIO设备的虚拟中断号virq联系起来当eventfd上有信号时则通过vfio驱动及kvm向虚拟机注入中断且中断号为之前Guest中的virq。同时QEMU还会在VFIO设备的fd上调用ioctl(VFIO_DEVICE_SET_IRQS)通过vfio驱动在Host上申请真正的透传设备中断号当vfio-pci驱动接收到直通设备的中断时就会通过ISR中断handler向前述eventfd发送信号。这样即完成了物理设备触发中断、虚拟机接收中断的流程。以MSI中断为例当虚机内部为透传设备配置中断时会触发vm_exit这时会调用qemu中vfio_pci_write_config-vfio_msi_enable().Qemu-6.2.0/hw/vfio/pci.c static void vfio_msi_enable(VFIOPCIDevice *vdev) { …… for (i 0; i vdev-nr_vectors; i) { …… /* Attempt to enable route through KVM irqchip, default to userspace handling if unavailable. */ vfio_add_kvm_msi_virq(vdev, vector, i, false); kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, vector-kvm_interrupt, NULL, virq) kvm_irqchip_assign_irqfd(s, n, rn, virq, true); kvm_vm_ioctl(s, KVM_IRQFD, irqfd); //向KVM中注入监听事件在kvm中建立virq和eventfd的关联 } vfio_enable_vectors(vdev, false){ fds (int32_t *)irq_set-data; for (i 0; i vdev-nr_vectors; i) { if (vdev-msi_vectors[i].use) { if (vdev-msi_vectors[i].virq 0 || (msix msix_is_masked(vdev-pdev, i))) { fd event_notifier_get_fd(vdev-msi_vectors[i].interrupt); } else { fd event_notifier_get_fd(vdev-msi_vectors[i].kvm_interrupt); } } fds[i] fd; } ioctl(vdev-vbasedev.fd, VFIO_DEVICE_SET_IRQS, irq_set); //利用vfio提供的ioctl命令为透传设备申请真正的中断 } …… }当透传设备产生中断时vfio_msihandler ISR执行(MSI中断)该函数不做实际的服务程序处理仅仅通过eventfd_signal激活irqfd_inject然后最终调用arch相关回调向虚机注入中断中断号即为虚机配置透传设备时的中断号。图7-61微观

相关新闻