VFIO 设备直通的 DMA 地址转换与隔离
一、让设备访问客户机内存并限制范围
设备直通把物理设备的功能交给客户机驱动使用。驱动向设备提交缓冲区地址后,设备可以通过 DMA 读写客户机内存。宿主机需要把这些地址转换成实际的物理页,同时限制设备能够访问的范围。VFIO 管理设备访问与 DMA 资源,宿主 IOMMU 执行地址转换和权限检查。
本文分析 x86 / KVM 下普通 PCIe 设备访问客户机 RAM 的过程:客户机不配置虚拟 IOMMU,设备使用 GPA 作为 DMA 地址,宿主启用 IOMMU 隔离;内存采用常规可固定的 RAM。本例不涉及 PCIe 对等访问、ATS、PASID 和可恢复页故障。设备寄存器与中断仅作为请求提交和完成的上下文。
源码版本:QEMU 10.2.0、Linux 6.18。
二、CPU 与 DMA 的地址转换路径
同一块客户机内存,客户机 CPU、QEMU 进程和物理设备使用的地址不同。建映射时,QEMU 提供设备侧地址与用户态内存位置;运行时,IOMMU 根据设备所属的转换域处理 DMA。
| 地址 | 含义 | 本例中的用途 |
|---|---|---|
| GPA | 客户机物理地址 | 客户机 RAM 中缓冲区的位置 |
| IOVA | 设备发出的 I/O 虚拟地址 | 交给宿主 IOMMU 转换;本例数值等于 GPA |
| HVA | QEMU 进程的宿主虚拟地址 | 向内核描述 RAM 的后备内存 |
| HPA | 宿主物理地址 | DMA 最终访问的宿主物理页 |
例如,客户机缓冲区位于 GPA 0x00400080,设备发出同值的 IOVA;映射覆盖 0x00400000–0x00400fff,对应宿主页 HPA 0x21000000。IOMMU 保留页内偏移 0x80,得到 HPA 0x21000080。QEMU 映射这块页时可以使用完全不同的 HVA。连续的 IOVA 区间也可以对应多个不连续的宿主物理页。
Mermaid 源码
flowchart TB
accTitle: CPU 与设备 DMA 访问同一客户机后备页
accDescr: 客户机 CPU 经客户机页表与 EPT 或 NPT 完成地址转换,物理设备经宿主 IOMMU 完成 DMA 地址转换。QEMU 的 HVA 用于配置后备内存,内核固定页面并建立 IOVA 到 HPA 的映射,HVA 不随设备 DMA 请求传输。
subgraph CPU["CPU 访存路径"]
GVA["客户机虚拟地址 GVA"]
GPA["客户机物理地址 GPA<br/>0x00400080"]
EPT["EPT / NPT<br/>CPU 第二阶段转换"]
GVA -->|客户机页表| GPA
GPA --> EPT
end
subgraph DMA["设备 DMA 路径"]
DEV["PCIe 设备标识 + IOVA<br/>0x00400080"]
IOMMU["宿主 IOMMU<br/>转换上下文、I/O 页表与权限"]
DEV -->|设备读或设备写| IOMMU
end
subgraph MAP["映射配置路径"]
Q["QEMU 提交 MAP<br/>IOVA = 0x00400000<br/>HVA = 0x70001000<br/>长度 = 4 KiB · 权限 = READ / WRITE"]
K["内核验证并固定后备页<br/>页基址 HPA = 0x21000000"]
Q --> K
end
P["同一客户机 RAM 后备页<br/>访问 HPA 0x21000080"]
EPT --> P
IOMMU -->|保留页内偏移 0x80| P
K -. 建立 IOVA → HPA 映射 .-> IOMMU
classDef address fill:#e4f1ec,stroke:#438575,color:#173e34
classDef translation fill:#eaf0f6,stroke:#8198ac,color:#263c4c
classDef control fill:#f5f4f1,stroke:#a6aaa7,color:#3c4542
class GVA,GPA,DEV,P address
class EPT,IOMMU translation
class Q,K control
EPT / NPT 负责客户机 CPU 的第二阶段地址转换,具体建图过程见KVM 内存映射与 EPT 缺页处理。PCIe 设备发起的普通 DMA 使用宿主 IOMMU 的转换上下文,QEMU 的 HVA 不随 DMA 请求发送。两条路径需要指向相同的客户机后备页,但页表管理、权限和缓存失效是各自的机制。
客户机配置虚拟 IOMMU 后,IOVA 与 GPA 可以不同,宿主还需跟踪客户机的 DMA 映射变化或使用硬件嵌套转换。QEMU 的内存监听代码为 IOMMU 区域和普通 RAM 分别处理;本文沿普通 RAM 分支继续。IOMMU 与 RAM 的映射分支
三、建立 IOMMU 映射,再提交 DMA
先建立设备与地址空间的关联,再映射客户机 RAM。之后客户机提交请求,设备发出 DMA,IOMMU 检查目标地址与权限。下面可以切换设备读、设备写以及三种映射状态,观察目标内存是否被访问。
DMA 地址与访问权限
设备访问一个 4 KiB 页内的 64 B 缓冲区。先建立映射,再逐步提交和处理请求。
IOMMU_IOAS_MAP · 映射输入
- ioas_id
- 10
- iova
- 0x00400000
- user_va
- 0x70001000 · QEMU HVA
- length
- 4096 B
- flags
- FIXED_IOVA | READABLE | WRITEABLE
设备与页表关联
设备尚未绑定 IOMMUFD
- IOAS 10
- 逻辑 IOVA 映射
- HWPT 20
- 本例设备使用的硬件页表
- HPA 页
- 等待内核建立映射
物理 PCIe 设备
IOVA 0x00400080等待请求
- 长度
- 64 B
- 方向
- 设备读 / 写
宿主 IOMMU
IOVA 页 → HPA 页
- IOVA
- 0x00400000
- HPA
- 0x21000000
- 权限
- 由所选映射决定
检查映射及读写权限
宿主物理内存
HPA 0x21000080客户机 RAM 的后备页
配置路径设置转换关系;正常数据访问由设备与 IOMMU 硬件处理。示例地址用于展示页号和偏移,不对应实际机器。故障仅表示本次数据访问被阻止,不模拟特定设备的错误恢复。
- QEMU 向内核提供 IOVA、后备内存与访问权限,建立硬件映射。
- 客户机驱动提交缓冲区地址,设备发出 DMA。
- IOMMU 检查映射和权限,通过后访问 HPA 对应的 RAM。
- 正常请求完成后,驱动消费或回收缓冲区;缺失映射和越权写入不能产生正常数据完成。
启用 JavaScript 可切换权限和方向,逐步查看状态变化。
1. 配置阶段
- 建立 DMA 所有权。VFIO 检查设备所在 IOMMU group 的所有权,并把设备接入受控的 I/O 地址空间。传统接口通过 group 与 container 建立关系,IOMMUFD 路径通过设备文件描述符绑定并附着 IOAS 或硬件页表。
- 接收内存布局。QEMU 的 MemoryListener 获得可访问的 RAM 区间,计算设备侧 IOVA、进程侧 HVA、长度和只读属性。
- 建立映射。内核验证范围、权限与页粒度,固定相应后备页,在设备关联的 IOMMU domain 中建立 IOVA 到 HPA 的映射。硬件随后可缓存转换结果。
无客户机 IOMMU 时,常规配置通常映射可 DMA 访问的客户机 RAM,映射生命周期长于单个 I/O 请求。客户机驱动每次填写一个缓冲区地址,并不会使 QEMU 重新执行一遍 DMA map ioctl。
2. 数据阶段
- 客户机驱动准备缓冲区和设备所需的描述符,按照设备协议与内存顺序要求提交请求。寄存器访问可通过受支持的 BAR 映射或 VFIO 访问路径到达设备。
- 物理设备发出携带 DMA 地址的读写事务,宿主根据设备标识选择对应 IOMMU 转换上下文。
- IOMMU 检查映射及读写权限。转换成功时访问目标宿主页;本例缺失映射或写只读页时产生故障,目标数据访问被阻止。
- 正常完成的数据请求由设备按其协议报告完成,驱动随后回收或消费缓冲区。DMA 故障后的错误报告、超时或恢复过程依赖设备及平台,不能当作成功完成。
读写权限以设备视角命名:设备读取 RAM 使用 READ,设备把接收数据写入 RAM 使用 WRITE。驱动的“发送”通常包含设备读,“接收”通常包含设备写。内核的权限解释
四、隔离与映射生命周期
设备访问时的上下文选择、IOTLB 命中和撤销同步,在IOMMU:DMA 地址转换、缓存与失效中沿同一个接收缓冲区展开。
1. IOMMU group 的隔离边界
I/O 页表限制设备到内存的访问,但实际隔离还取决于 PCIe 拓扑。某些多功能设备、桥或缺少合适 ACS 能力的路径,可能允许事务在到达 IOMMU 前发生重定向。内核据此把无法独立隔离的设备归入同一 IOMMU group,作为 DMA 所有权的边界。
IOMMUFD 的设备 cdev 接口按设备提供文件描述符,同时检查整个 group 的所有权。同组设备共享硬件隔离边界,由同一 DMA 所有者管理。映射接口接收用户态提供的后备内存,由内核验证并固定页面,再建立设备可访问的范围。VFIO group 与设备 cdev
2. 撤销映射与在途 DMA 的协调
释放或复用 DMA 缓冲区前,需要确保设备已经停止使用它。删除 I/O 页表项还要与硬件转换缓存失效配合,避免缓存中的旧转换继续指向被回收的页。设备队列停止、在途事务处理与映射撤销共同决定安全的回收顺序;单纯删除 QEMU 的用户态指针并不完成这些工作。
QEMU 收到 RAM 区域删除通知后会调用后端的 DMA unmap。内核负责撤销其维护的 IOMMU 映射并释放固定页的引用。大范围映射可减少频繁的 map / unmap,但长期固定内存会约束内存回收、气球回收和热插拔;频繁更新小范围映射则会增加页表管理与 IOTLB 失效开销。区域删除、RAM discard 与固定页的关系
设备 DMA 写入的脏页还需要独立跟踪。vCPU 的 KVM 脏页日志无法覆盖物理设备绕过 CPU 发出的全部内存写入。因此,直通设备热迁移同时依赖设备状态保存、DMA 停止与设备或 IOMMU 的脏页跟踪能力,不能仅凭 CPU 内存迁移成功判断设备可迁移。
五、源码:RAM 区域到 DMA 映射
1. QEMU 从 RAM 布局生成 DMA 映射
vfio_listener_region_add()
vfio_container_region_add()
vfio_get_section_iova_range() # 设备地址区间及页对齐
memory_region_get_ram_ptr() # RAM 在 QEMU 中的地址
vfio_container_dma_map()
legacy: vfio_legacy_dma_map()
VFIO_IOMMU_MAP_DMA
iommufd: iommufd_cdev_map()
iommufd_backend_map_dma()
IOMMU_IOAS_MAP
vfio_container_region_add()利用 MemoryRegionSection 的地址空间偏移计算 IOVA,同时组合 RAM 基址、区域内偏移和对齐偏移得到 HVA;再把 section->readonly传入映射后端。传统后端构造 vfio_iommu_type1_dma_map,IOMMUFD 后端构造 iommu_ioas_map。两者都区分设备读写权限。地址计算、传统映射请求、IOMMUFD 映射请求
这条调用链采用用户虚拟地址提供后备内存。QEMU 还支持另一条分支:当 RAM 有文件后备且后端提供 dma_map_file时,使用内存文件描述符和偏移,走 IOMMU_IOAS_MAP_FILE。文件映射改变后备页的描述方式,设备侧仍使用 IOVA。映射分派、文件后备映射
2. Linux 固定后备页并设置 I/O 页表
传统 Type1:vfio_dma_do_map()
验证范围、对齐、权限与已有映射
vfio_pin_map_dma()
vfio_pin_pages_remote() # 获取并固定后备页
vfio_iommu_map()
iommu_map(domain, iova, paddr, length, prot, ...)
IOMMUFD:iommufd_ioas_map()
获取 IOAS,验证用户参数
iopt_map_user_pages() # 管理页及 IOAS 映射
将映射同步到该 IOAS 关联的硬件页表
Type1 的 vfio_pin_map_dma()分批固定页面,将物理页号交给 vfio_iommu_map()。后者调用 IOMMU 子系统的映射接口,由平台驱动建立硬件所需的转换。IOMMUFD 把地址空间及硬件页表作为独立对象管理。Type1 页固定与映射、IOAS 映射入口
这些函数负责建立或更新转换关系。正常 DMA 由设备和 IOMMU 硬件处理:IOTLB 命中时复用缓存的转换,未命中时可遍历硬件页表;缺失映射或权限不符才形成访问故障。QEMU 参与映射配置,硬件执行随后各次 DMA 的地址转换。
六、直通减少的开销与保留的成本
回到 GPA 0x00400080 的例子,映射建立后,设备通过 IOMMU 访问对应宿主页,无需 QEMU 为这次数据搬运解析一条 virtqueue 或复制报文。省下的是软件设备数据路径的一部分工作;IOMMU 转换、硬件队列、设备中断以及客户机驱动仍要运行。BAR 是否能够直接映射、设备是否需要其他模拟,也会影响实际退出次数。
对本例可以先核对地址和方向:IOVA 0x00400080 应命中映射页,读写权限应与设备操作相符,页内偏移仍为 0x80。映射缺失或越权出现的是 IOMMU 访问故障;性能低但没有故障时,则继续检查队列、中断、DMA 带宽与 NUMA,不能把两种现象都归为地址转换错误。
评估应分开一次性的接管与映射成本、正常运行的吞吐和尾延迟,以及停止、复位和迁移的管理成本。大内存虚拟机还要观察页固定规模与映射耗时;高频小 I/O 则更容易暴露中断和调度开销。若增加虚拟 IOMMU 来细化客户机 DMA 权限,还需评估动态映射和 IOTLB 更新,不能沿用本例长期映射全部可访问 RAM 的开销结论。
七、从 VFIO Container 到 IOMMUFD
本节比较接口分工。设备绑定、IOAS 与 HWPT 的创建、附着及释放过程,见IOMMUFD:设备绑定与 I/O 地址空间管理。
1. 以 group 和 container 组织 DMA
传统 VFIO 将设备访问、硬件隔离组和 IOMMU 管理组织成 device、group、container 三层。应用先把 group 加入 container,再配置 IOMMU 后端,最后通过 group 获取设备文件描述符。多个可兼容的 group 可以共享 container 中的映射,减少重复的页表维护。
这种结构支撑了长期使用的设备直通路径,但 I/O 地址空间管理与 VFIO container 接口紧密绑定。随着嵌套转换、不同硬件页表类型和更细粒度设备绑定的需求出现,用户态需要更直接地表达地址空间、硬件页表及它们与设备的关联。
2. 把地址空间管理交给 IOMMUFD
IOMMUFD 通过 /dev/iommu提供通用 I/O 地址空间接口。IOAS 保存用户态建立的地址映射;HWPT 表示关联的硬件页表。设备经 VFIO cdev 绑定 IOMMUFD,再附着到 IOAS 或 HWPT。向 IOAS 附着时,内核可以复用兼容的 HWPT,也可以新建 HWPT;同一个 IOAS 的映射可以同步到多个硬件页表。VFIO 继续提供设备区域、配置空间和中断访问,DMA 地址空间管理则使用 IOMMUFD 的对象与操作。
QEMU 在 2023 年合入 IOMMUFD 后端实现。公共 VFIO 内存监听逻辑保留,后端分别完成传统 container 或 IOMMUFD 的绑定与 map / unmap。现在的 iommufd_cdev_connect_and_bind()与 iommufd_cdev_attach_ioas_hwpt()对应绑定和附着两个步骤。绑定实现、附着实现
接口拆分为后续页表能力提供了扩展位置,同时引入对象生命周期、能力协商和多种页表关系的管理。切换后端不会改变设备的 DMA 协议,也不会自动补齐设备迁移、复位或硬件隔离能力。比较两条路径时,应分别检查地址空间管理能力与具体设备支持的功能。
3. 与其他设备虚拟化方式的关系
SR-IOV 让一个物理功能提供多个 VF,VF 可以再通过 VFIO 分配;mdev 由宿主设备驱动提供受管理的设备实例;vDPA 则面向 virtio 数据面加速。它们分别解决功能划分、设备实例管理或 virtio 加速的问题。此处讨论的物理 PCIe 设备 DMA 路径,不能不加区分地套用到所有 mdev 或 vDPA 实现。
八、参考资料
- vIOMMU:客户机 DMA 映射与失效同步:客户机启用虚拟 IOMMU 后,IOVA 映射的建立、撤销与缓存一致性。
- VFIO 设备热迁移:设备状态与 DMA 交接:设备迁移能力、DMA 脏页跟踪与停机交接。
- QEMU:VFIO MemoryListener、公共映射分派、传统 container。
- QEMU:VFIO IOMMUFD 后端、IOMMUFD ioctl 封装、后端设计说明。
- Linux:Type1 映射实现、IOAS 管理、VFIO 设备与隔离组。
- Linux IOMMUFD 文档:IOAS、硬件页表、设备绑定与接口语义。