
做虚拟化的人绕不开一个话题把物理设备直接塞给虚拟机用。在高性能网络、存储和GPU场景里SMMU IO passthrough一直是关键路径——ARM平台上SMMU负责为设备DMA做隔离和地址翻译KVM/QEMU借助VFIO框架把真实PCI设备交给GuestDMA直接走硬件不经过宿主CPU中转。这篇文章把直通的前因后果、QEMUKVM的实现链路、SMMU两阶段翻译的作用、配置步骤和常见翻车点一次理清楚。不管你是NFV平台运维、嵌入式虚拟化开发还是研究ARM上的DPDK/GPU直通都很值得当一份实战参考来读。1. 先搞清楚IO直通到底解决了什么问题1.1 虚拟机访问外设的三条路软件模拟、半虚拟化、直通虚拟机要访问外部设备基本有三条路径。第一条是纯软件模拟QEMU在用户态把网卡、磁盘控制器模拟出来Guest里的驱动每做一次IO都要触发VM exit回到QEMU去处理。这条路兼容性最好但性能损耗极大高吞吐场景基本没人敢用。第二条是半虚拟化典型代表就是virtio。Guest侧装一个前端驱动后端由QEMU的vhost进程或内核vhost模块承接Shared Memory 通知机制让收发路径比纯软件模拟短了很多。闲时便宜忙时也还行但vCPU还是要参与收包、发包、中断处理小包高PPS场景依然容易成为瓶颈。第三条就是直通也就是本文讨论的核心。把真实的PCIe设备直接分配给某个GuestGuest内的驱动直接操作设备的BAR空间和MSI-X中断DMA由硬件完成宿主CPU基本不参与数据面。打个生活化的比方软件模拟像快递放到驿站再打电话让你去拿多了一道中转virtio像是驿站直接把快递送到你楼下省了一段路直通就是快递员直接送到你手上全程没有代办。这里要建立一个认知直通并不是“把设备共享出去”而是“把设备独占给某个虚拟机”。它牺牲了灵活性和可迁移性换来的是极致的IO性能。SMMU IO passthrough说的就是在ARM平台上如何把这套独占机制做得既快又安全。1.2 为什么ARM平台绕不开SMMUSMMU的全称是System Memory Management Unit你可以把它理解成ARM体系里的IOMMU。它做的事非常纯粹管理所有外设发起的DMA访问让设备不能随便访问物理内存只能访问被授权的那一段地址空间。在非虚拟化场景SMMU主要用来做设备隔离和内核防护。比如某设备驱动有bugDMA写飞了如果没有SMMU它可能直接改写内核关键数据结构导致整个系统崩溃有了SMMU越界访问会被硬件直接拦下。这有点像给设备发了一张“限定区域的通行证”你只能在划定的小区里活动。在QEMUKVM虚拟化场景SMMU的存在更是硬性前提。虚拟机的内存是一段连续的Guest物理地址GPA但真实物理地址HPA很可能是不连续的。设备DMA如果直接按Guest给的地址去写内存根本找不到对应的真实内存。SMMU要在中间做一层翻译把设备侧看到的Guest地址域翻译成宿主机真实的物理地址域。这个翻译动作被设计成两阶段Stage1/Stage2后面我会专门展开讲。1.3 直通与SMMU的关系不是可选是必须很多人会用Intel平台上的VT-d经验类比ARM平台但实际上Intel的IOMMU和ARM的SMMU在概念上同构细节上差异很多。最关键的一点是在ARM平台上做真正的DMA直通SMMU几乎不可缺席。没有SMMUVFIO框架的DMA映射功能根本无法工作设备直通进去后一旦发起DMA就是裸奔。也许有人会说那Linux里不是有vfio-pci的noiommu模式吗确实有这个模式可以在没有IOMMU的情况下强制让VFIO接管设备直通给虚拟机。但代价是什么设备DMA可以访问宿主机的任意物理内存Guest里的驱动一旦被攻破宿主机就是直接沦陷。这东西只能在一个临时环境做功能验证生产环境这么搞等于把安全防火墙拆了。所以SMMU之于直通就像刹车之于汽车。你可以说“我不踩刹车也能跑”但那是下坡路上拿命开玩笑。后面讲的所有配置、映射、排查都建立在SMMU已经正常使能的基础上。2. 从设备到虚拟机QEMUKVM怎么把直通串起来2.1 宿主侧的三层结构设备、IOMMU group、container真正理解QEMUKVM直通的实现必须先理解VFIO框架里的三个概念设备、IOMMU Group和Container。设备好理解就是那个PCIe物理设备。但VFIO不会直接暴露一个设备给用户态而是以IOMMU Group为单位做隔离。IOMMU Group是什么它是DMA隔离的最小单元。一个Group里的所有设备共享同一个DMA地址空间说白一点它们被SMMU划分为同一个“隔离域”。如果某个Group里有三台设备你只把其中一台直通给虚拟机剩下两台还在宿主机上那这个隔离就不成立因为同组设备理论上可以通过共享DMA窗口互相干扰。所以你会看到VFIO检测到Group不干净时会拒绝直通。这就是为什么PCIe ACS特性很重要——它能保证同一上游端口下的多个设备可以被拆分到不同Group从而实现更细粒度的隔离。Container则是一个更大的集合可以理解为一个“IOMMU域”的用户态容器。一个QEMU进程会创建一个Container然后把若干个IOMMU Group都放进去统一管理DMA映射。多个Group里的设备之间可以共享同一套映射关系这是实现多设备直通到同一台虚拟机的基础。三层关系用一句话概括设备挂在Group里Group挂在Container里Container绑定一个IOMMU Domain。QEMU进程就是通过Container和Group的fd向内核申请DMA映射、获取设备操作权限。2.2 QEMU侧的三件套打开VFIO、映射DMA、注册中断QEMU拉起一台带直通设备的虚拟机背后做的事可以拆成三步。第一步打开VFIO并创建Container。QEMU会打开/dev/vfio/vfio然后调用VFIO_SET_IOMMUioctl把Container与TYPE1 IOMMU模式绑定。这个TYPE1是内核里最常用的IOMMU类型对应Linux的vfio_iommu_type1实现它在底层调用通用IOMMU API最终落实到SMMU驱动上。第二步把虚拟机的物理内存映射给SMMU。QEMU会遍历自己的RAM区块对每一块Guest物理内存调用VFIO_IOMMU_MAP_DMA把GPA到HPA的映射关系告诉内核。内核这边调用iommu_map让SMMU在Stage2页表里建立条目。Guest里的设备驱动发起DMA时设备写出的地址是GPASMMU一看Stage2映射表就知道该访问宿主机哪一段真实内存。第三步把设备的中断接到KVM的虚拟中断控制器上。直通设备产生的MSI/MSI-X中断在Host侧就是一个物理中断。QEMU通过irqfd机制把这个物理中断与KVM的irqchip绑定Guest里的vCPU直接收到虚拟中断注入整个过程不需要QEMU进程参与中断处理。中断路径很短这也是直通设备延迟低的重要原因。用伪代码展开一下关键流程int container open(/dev/vfio/vfio, O_RDWR); ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU); int groupfd open(/dev/vfio/21, O_RDWR); ioctl(groupfd, VFIO_GROUP_SET_CONTAINER, container); int devfd ioctl(groupfd, VFIO_GROUP_GET_DEVICE_FD, 0000:01:00.0); struct vfio_iommu_type1_dma_map map { .argsz sizeof(map), .flags VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE, .iova guest_phys_addr, .vaddr host_virt_addr, .size region_size, }; ioctl(container, VFIO_IOMMU_MAP_DMA, map); struct vfio_irq_set irq_set { ... }; ioctl(devfd, VFIO_DEVICE_SET_IRQS, irq_set);这些ioctl看起来平淡无奇但每一步都是安全边界。QEMU进程本身是普通用户态进程如果不是通过VFIO这套机制它根本不可能让一个物理设备只给自己的虚拟机产生DMA。2.3 KVM侧到底管了什么不少人容易有个误解以为是KVM模块在配置SMMU。其实不是。SMMU的驱动在内核IOMMU子系统和arm-smmu-v3驱动里KVM模块做的工作要更轻量。KVM在这里主要干两件事。一件是配合中断注入KVM通过KVM_DEV_TYPE_VFIO注册一个虚拟设备把VFIO Container对应的IOMMU Group与中断重映射状态登记下来。有了这层关系Guest里的中断才能准确路由到目标vCPU也才能配合中断重映射实现安全中断。另一件事是利用KVM自身的加速能力让Guest对设备BAR空间的MMIO访问快速陷入QEMU并转发到硬件减少模拟开销。其实KVM更核心的价值体现在内存虚拟化上。同一台虚拟机的内存映射既涉及CPU侧的页表Stage2页表KVM维护也涉及SMMU侧的Stage2页表IOMMU驱动维护。这两套页表服务于不同的访问者——CPU访问内存和DMA访问内存但它们的GPA到HPA映射必须保持一致。QEMU在创建或修改Guest内存时会同时向KVM和VFIO注册保证两侧页表协同更新。你可以把它们理解为双胞胎一个管CPU的视角一个管设备的视角虽然长得不完全一样但必须同步生长。3. SMMU两阶段翻译直通性能的秘密都在Stage23.1 Stage1/Stage2分别是谁的“地盘”要理解SMMU为什么对直通那么重要必须把两阶段翻译讲透。ARM SMMU的地址翻译和CPU的MMU很像但多了一个维度。CPU负责把虚拟地址VA翻译成物理地址PA中间可能有多级页表SMMU则把设备访问的地址翻译成物理地址也分两级。Stage1通常由Guest操作系统自己控制。Guest内部有自己的页表设备驱动通过Guest内核的IOMMU接口把设备能看到的DMA地址映射到Guest物理地址。在直通场景里Guest可能甚至不需要打开自己的SMMU驱动只要设备用GPA作为DMA地址就行。大多数裸驱动就是这么工作的设备DMA寄存器里填的是Guest物理地址。Stage2则完全由Hypervisor/内核控制。它把Guest物理地址GPA进一步翻译成宿主机物理地址HPA。这一级翻译的核心不是让Guest少干活而是强制隔离。Guest被骗了——它以为自己给设备填的DMA地址就是真实物理地址实际上经过Stage2一翻译可能落到宿主机上完全不同的一块内存。QEMU创建Guest内存时GPA到HPA的映射关系就已经定了而SMMU的Stage2表正是VFIO在每次MAP_DMA时写入的。打个比方Guest以为整栋楼都是它家的SMMU Stage2就是楼里的物业Guest只说“我要去302”物业实际的钥匙只能开到302对应的那个房间而房间的真实编号可能是1008。物理内存被分成了很多“房间”Guest根本不知道真实编号。3.2 为什么直通性能高地址转换在硬件上完成直通性能高的本质是IO数据面绕过了所有软件转发逻辑。但设备DMA不可能完全不做地址转换否则Guest给GPA设备怎么能命中真实内存这个转换由SMMU硬件完成不需要CPU参与每一次DMA。SMMU内部有TLB缓存可以对经常访问的映射条目做缓存。如果QEMU的映射方式合理比如使用2MB甚至1GB大页映射条目数量会很少TLB命中率会很高。直通跑起来后设备DMA命中的是一条非常短的路径设备 - PCIe - SMMU查TLB - L3缓存或内存。对比一下软件模拟Guest驱动触发IO - VM exit - QEMU处理 - 模拟设备寄存器 - 返回Guest中间还要同步设备状态。直通的路径可能只有前者的十分之一。再加上MSI-X中断直接通过irqfd注入中断延迟也大幅下降。直通设备在网络小包转发、NVMe随机读、GPU训练这类场景里能拉开几倍甚至一个数量级的差距道理就在这里。3.3 直通时如何配置Stage2VFIO MAP_DMA的底层动作前面已经提到QEMU调用VFIO_IOMMU_MAP_DMA来建立DMA映射这个动作落到内核里就是vfio_iommu_type1模块的map/unmap逻辑。它调用iommu_map接口让SMMU驱动在Stage2页表里插入条目。这里有几个容易被忽略的细节。一是映射的起始地址必须是GPA而不是Guest虚拟地址。QEMU传给VFIO的iova就是Guest物理地址。如果设备驱动往DMA寄存器里写一个Guest虚拟地址那是驱动写错了SMMU不会帮你做两次翻译。二是整个Guest RAM需要提前映射。QEMU不做动态的“用到再映射”它会在虚拟机初始化阶段把全部RAM通过MAP_DMA一次性映射给SMMU。所以虚拟机内存越大Stage2页表越大这是直通虚拟机启动时会比普通虚拟机慢一点的原因之一。三是内存热插拔场景要注意。一旦给直通虚拟机热插拔内存QEMU需要为新增RAM再次执行MAP_DMA旧RAM被移除时要执行UNMAP_DMA。这个动作如果在某些老版本QEMU上有bug会造成SMMU里留有陈旧映射设备DMA可能访问到已经被收回的内存引发严重的内存破坏。所以直通虚拟机加内存最好在测试环境先验证再把操作流程固化下来。四是内存后端必须连续。直通的DMA映射粒度最好和大页对齐。用2MB大页做后端MAP_DMA的条目数会比4KB页少512倍。条目少了TLB压力小DMA延迟自然更低。这也是为什么几乎所有直通虚拟机都推荐使用HugePage的原因。4. 实操记录在一台ARM服务器上完成网卡直通4.1 环境与前置检查这一步非常重要我在多个项目里反复吃过亏环境没检查透就往下走结果在启动虚拟机时才发现SMMU没使能或者设备Group不干净白白浪费半天。先用以下命令做前置检查# 内核是否编译了SMMU和VFIO zgrep CONFIG_ARM_SMMU /proc/config.gz zgrep CONFIG_VFIO /proc/config.gz zgrep CONFIG_VFIO_PCI /proc/config.gz # SMMU是否正常启动 dmesg | grep -i smmu # 查看IOMMU Group ls /sys/kernel/iommu_groups/重点关注几件事内核有没有编译CONFIG_ARM_SMMU_V3dmesg里SMMU初始化有没有报错/sys/kernel/iommu_groups下有多少个Group。如果设备没有出现在任何Group里说明该设备的DMA没有被IOMMU管住直通直接免谈。然后再用lspci找到要直通的网卡lspci -vnn | grep -A 5 Ethernet假设输出显示网卡BDF是0000:01:00.0。确认它属于哪个Groupls -l /sys/kernel/iommu_groups/*/devices/ | grep 0000:01:00.0比如结果显示它在/sys/kernel/iommu_groups/21/devices/下。这时还要看一眼这个Group里有没有其他设备。如果除了目标网卡还有别的东西先搞清楚关联关系因为Group不干净往往意味着直通会被VFIO拒绝。4.2 把设备切到vfio-pci找到设备BDF后开始切驱动。先看设备当前驱动ls -l /sys/bus/pci/devices/0000:01:00.0/driver如果是厂商自带的网卡驱动先解绑。注意解绑时要保证设备上没有活动流量否则会直接断网或报错echo 0000:01:00.0 /sys/bus/pci/devices/0000:01:00.0/driver/unbind接着加载vfio-pci设置driver_override再绑定modprobe vfio-pci echo vfio-pci /sys/bus/pci/devices/0000:01:00.0/driver_override echo 0000:01:00.0 /sys/bus/pci/drivers/vfio-pci/bind绑定成功后确认/dev/vfio/21这个Group节点存在并且设备已经挂在vfio-pci下面lspci -ks 0000:01:00.0这时QEMU进程就能打开这个设备了。如果系统上还有其他网卡也不影响直通设备只是占用了自己的BDF和中断资源。4.3 QEMU命令行与启动后验证实际启动命令大致长这样qemu-system-aarch64 \ -machine virt,gic-version3,accelkvm \ -cpu host \ -smp 8 -m 8G \ -object memory-backend-file,idmem0,size8G,mem-path/dev/hugepages \ -numa node,memdevmem0 \ -device vfio-pci,host0000:01:00.0 \ -drive file/opt/vm/centos-arm.img,ifnone,iddisk0,formatraw \ -device virtio-blk-pci,drivedisk0 \ -netdev user,idnet0 \ -device virtio-net-pci,netdevnet0 \ -nographic有几个点要专门强调。-mem-path/dev/hugepages一定要配配合系统的nr_hugepages设置。不配的话DMA性能会大打折扣而且当内存压力大时QEMU可能把某些内存页换出去造成Stage2映射失效。-device vfio-pci,host0000:01:00.0是关键。QEMU看到这个参数后会去打开刚才准备好的/dev/vfio/21并在内部完成Container、DMA映射、中断绑定等一系列动作。启动后进入Guest执行lspci如果看到目标网卡且ethtool -i显示的driver不是vfio而是真实的网卡驱动说明直通成功。此时Guest里不需要启用SMMU设备DMA地址直接写GPA由Host侧SMMU Stage2兜底。4.4 性能对比初体验我曾经在ARM平台上用同样一台虚拟机做过对比直通网卡和virtio-net的差距非常直观。用10Gbe网卡跑64字节小包PPSvirtio-net大概能到600K到800K PPS直通模式下直接翻到1.5M以上而且CPU占用率明显低一大截。存储设备更夸张。NVMe SSD直通后用fio做随机读IOPS从virtio-blk的几十万能提升到设备本身的硬件极限延迟也低了差不多一个数量级。当然不同平台、不同设备的具体数字会有差异但趋势是一致且稳定的数据面越重直通优势越明显。测试时记得把Guest里的大页也配上并把vCPU和中断绑到固定的物理核上。CPU亲和性不做好性能测试结果会有比较大的抖动你也很难判断瓶颈到底在设备上还是调度上。5. 直通翻车实录我踩过的典型坑与排查方法5.1 IOMMU Group不干净设备分不出去最常遇到的坑就是设备所在的Group里还有其他设备。常见原因有两种一是PCIe switch不支持ACS同一下游端口的所有设备被划到同一个Group二是多功能设备没有做功能级ACS隔离比如一张双口网卡两个PF在一个Group里。正常情况下VFIO会拒绝直通这个Group里的设备因为隔离不完整。很多人在这个坑上卡住选择了noiommu模式绕过这是很危险的做法。正确姿势是先在BIOS里打开ACS相关选项或者查看设备是否支持ACS。测试环境下内核提供了pcie_acs_override启动参数可以强制拆分但生产环境千万别这么干这会破坏隔离边界等于自己拆了安全墙。部分双口网卡可以通过SR-IOV的VF分配来规避因为VF往往单独成Group比硬拆PF要安全得多。5.2 MAP_DMA失败或Guest启动即崩直通虚拟机启动到一半QEMU报VFIO_IOMMU_MAP_DMA失败或者Guest直接崩溃这个现象我见过不下五次。排查路径基本固定先看Host的dmesg有没有SMMU报错再看QEMU的日志最后用strace跟踪QEMU的ioctl调用。最常见的原因是两个一是大页内存不够导致部分Guest内存落到普通页QEMU在MAP时因为页表属性不匹配被IOMMU拒绝二是设备固件在Host上残留了DMA活动比如网卡还在跑BMC带外流量或做PXE bootGuest启动瞬间设备发起DMA但SMMU里还没有对应映射直接触发translation fault。解决办法也直接提前确认大页数量充足把nr_hugepages调到不小于虚拟机内存大小直通前让设备回到干净状态必要时让设备先reset一次。千万别图省事跳过这里否则后面排查起来非常痛苦。5.3 中断、irqbalance与CPU隔离的坑直通设备跑起来后如果发现性能下降但Host CPU使用率又不高很大概率是中断亲和性问题。MSI-X中断默认可能落在某个忙碌的CPU核上而irqbalance如果还在运行它会频繁移动中断到你没预留的核上导致直通虚拟机vCPU和中断不在同一个NUMA节点跨片访问内存延迟直接拉满。我现在的习惯是直通虚拟机启动前先把Host上的irqbalance停掉手动把设备中断的smp_affinity设置到和vCPU相同的物理核上。在Guest内也一样把驱动的中断队列绑到Guest的vCPU集合上。这步操作能让PPS和延迟明显改善尤其是多队列网卡直通时每个队列绑一个固定核是基本操作。5.4 SMMU特有报错EVTQ和CmdQ问题ARM平台上直通还有一个SMMUv3特有的坑Event QueueEVTQ溢出或者Command QueueCmdQ错误。dmesg里会冒出类似arm-smmu-v3 ... event 0x...的报错。这类报错本质上说明有设备DMA访问了没有映射的地址或者映射权限不匹配。部分网卡在Guest关闭时还会继续发DMA或者VF reset不干净。遇到EVTQ报错先看事件类型如果是translation fault再去查设备在Host侧是不是还开着bus master。可以用setpci或直接重新绑一遍vfio-pci驱动让设备复位。SMMUv3的Event Queue溢出会导致SMMU停止记录新事件这时候再查报错就查不到全貌了所以一旦发现第一时间抓dmesg现场比复现重要。6. 什么时候别直通选型思路与替代方案6.1 直通的隐性成本直通性能虽好但代价也不小。首先是设备独占一台直通设备只能给一台虚拟机资源密度上不去。其次是不好迁移设备DMA状态、SMMU映射、中断路由都和当前机器强绑定live migration基本没戏。如果业务需要弹性和高可用直通会很别扭。我见过一个NFV项目前期为了性能把物理网卡全部直通给一堆虚拟机性能确实满足了但后面遇到宿主机维护所有虚拟机都没办法平滑迁移到别的机器只能停机重搭。运维同学后来把方案改成了SR-IOV VF分配才算解决了部署灵活性的问题。6.2 什么时候该选直通需要低延迟、高吞吐、高确定性的场景直通仍然是首选。典型几类DPDK/vSPP等用户态网络处理需要物理网卡直接给进程高级功能NVMe SSD直通追求存储IOPS和低延迟GPU直通训练和推理场景绕不开实时系统或工业控制需要稳定的IO延迟曲线。这类场景的共同点就是“数据面优先”虚拟机迁移和资源弹性都可以往后放。6.3 兼顾灵活性MDEV、vDPA、SR-IOV如果既想要直通的硬件加速又想要一定的资源切分能力可以考虑三条替代路线。第一条是VFIO-MDEV把一个物理设备切割成多个mediated device每个虚拟设备可以单独分配并不完全占用整卡。最典型的就是GPU和某些加速卡的分区方案。第二条是vDPA/vhost-vDPA它保留了virtio前端驱动后端直接落到硬件设备Guest不用换驱动却能得到接近直通的转发性能。这种方案在云场景里明显更容易落地因为保持virtio生态迁移兼容性好很多。第三条是SR-IOV把物理网卡拆成多个VF每个VF直通给不同虚拟机。PF由宿主机管理VF给Guest用隔离性和性能都能兼顾。这也是当前NFV和电信云里最主流的网卡方案之一。顺带说一句SMMU在SR-IOV场景里同样关键。每个VF需要有自己的DMA隔离域SMMU负责把不同VF的DMA约束在各自Guest的映射空间里。所以说白了无论直通还是SR-IOVSMMU都是ARM平台上设备虚拟化那层看不见却不可或缺的安全底座。最后分享一个我自己的实操习惯每次做直通配置前先写一张“IOMMU检查清单”确认SMMU启动正常、设备位于独立Group、大页预留足够、Host侧irqbalance已停止然后再动手。这个习惯帮我省下了大量反复试错的时间。另外第一次做直通不要直接上生产设备先用一块测试网卡或磁盘把整条链路跑通把SMMU报错的典型日志和QEMU参数都记录下来再切到正式设备你会发现自己对这套机制的理解瞬间扎实很多。