IOMMUFD:设备绑定与 I/O 地址空间管理
一、设备分配中的地址空间管理
把一块网卡 VF 分配给虚拟机,需要同时建立两类访问:客户机通过设备寄存器提交请求,网卡通过 DMA 访问客户机内存。VFIO 提供设备区域、配置空间和中断接口;IOMMUFD 管理 DMA 使用的地址空间与硬件页表。两者通过设备绑定和页表附着连接起来。
本文沿一块 PCIe VF 的接入过程展开:打开设备,建立 DMA 所有权,准备 I/O 地址空间,把客户机 RAM 映射进去,最后停止设备并回收资源。采用 x86 / KVM、普通可固定的 RAM、无客户机虚拟 IOMMU 的配置。设备发出的 IOVA 与 GPA 数值相同,宿主 IOMMU 再把它转换为 HPA。地址转换本身见 IOMMU:DMA 地址转换、缓存与失效,这里重点分析转换关系的建立与管理。
源码版本:QEMU 10.2.0、Linux 6.18。
二、两个文件描述符与三类对象
QEMU 打开 /dev/vfio/devices/vfioX,得到设备文件描述符 devfd;打开 /dev/iommu,得到 IOMMUFD 上下文的文件描述符 iommufd。示例中的 vfioX 由宿主设备注册产生,不是 PCI 地址。
| 入口 | 主要操作 | 保存的关系 |
|---|---|---|
| VFIO 设备 fd | 绑定 IOMMUFD、附着或脱离页表、访问设备区域与中断 | 应用与具体设备的访问关系 |
| IOMMUFD fd | 分配 IOAS / HWPT、映射内存、查询能力与销毁对象 | 一个上下文中的地址空间、页表及对象 ID |
dev_id、ioas_id 和 hwpt_id 都是 IOMMUFD 上下文里的对象编号,不能当作 Linux 文件描述符传给 close()。同一编号在另一个独立打开的 IOMMUFD 上下文中,也不代表同一对象。
| 对象 | 含义 | 本例中的内容 |
|---|---|---|
| DEVICE | 已绑定到 IOMMUFD 的设备 | 网卡 VF 的 dev_id = 1;绑定时建立 DMA 所有权 |
| IOAS | I/O Address Space,逻辑 I/O 地址空间 | ioas_id = 10;保存 IOVA 区间与后备内存的关系 |
| HWPT_PAGING | 由内核管理的硬件 I/O 页表对象 | hwpt_id = 20;关联 IOAS,对应 IOMMU 的 iommu_domain |
IOAS 统一表达映射,HWPT 把这些映射落实到具体 IOMMU 能使用的页表。一个 IOAS 可以关联多个 HWPT:兼容的设备可共享已有 HWPT;无法共用同一硬件页表、但允许共享这组映射的设备,可以使用另一个 HWPT。内核维护这些 HWPT 中一致的 IOVA 映射。共享 IOAS 也意味着共享 DMA 可见的地址范围,不能据此把相互不信任的设备放入同一地址空间。
Mermaid 源码
flowchart TB
accTitle: IOMMUFD 的文件描述符与对象关系
accDescr: QEMU 分别通过 VFIO 设备文件和 IOMMUFD 文件管理设备及页表;DEVICE 附着到 HWPT,HWPT 关联统一的 IOAS 映射。兼容设备可共享硬件页表,其他设备可以通过另一个硬件页表共享 IOAS。
subgraph userspace[QEMU · 用户态入口]
direction LR
DEVFD["VFIO 设备 fd<br/>绑定、附着、设备访问"]
IOMMUFD["/dev/iommu fd<br/>分配对象、MAP / UNMAP"]
end
subgraph objects[IOMMUFD 上下文 · 对象 ID]
direction TB
DEVICE["DEVICE 1<br/>物理 VF 的 DMA 所有权"]
HWPT["HWPT 20<br/>内核管理的硬件页表"]
IOAS["IOAS 10 · 逻辑映射<br/>IOVA → 后备内存"]
DEVICE2["另一块设备<br/>同一虚拟机"]
HWPT2["HWPT 21<br/>需要独立硬件页表时"]
DEVICE -->|附着| HWPT
HWPT -->|关联并同步映射| IOAS
DEVICE2 -.兼容时复用.-> HWPT
DEVICE2 -.否则尝试独立 HWPT.-> HWPT2
HWPT2 -.关联同一映射.-> IOAS
end
RAM["客户机 RAM 后备页<br/>内核管理页引用"]
DEVFD -->|BIND 返回 dev_id| DEVICE
IOMMUFD -->|ALLOC / MAP| IOAS
IOAS --> RAM
DEVICE 到 HWPT 的连接由附着操作建立。仅完成绑定时,设备已有所属的 IOMMUFD 上下文,尚未取得这组 RAM 的 DMA 映射。Linux IOMMUFD 对象说明
三、一块 VF 从打开到可用
下面沿 QEMU 10.2 首次创建 IOMMUFD 地址空间的路径,区分每一步增加的能力。示例取客户机 RAM 中一个 2 MiB 区间:IOVA 起点 0x00400000,QEMU HVA 起点 0x70000000。它表示内存布局中的一段 RAM,并不要求每次 I/O 都重新映射。
1. 打开与绑定
open() 得到设备 fd 后,设备访问仍受限制。QEMU 在该 fd 上调用 VFIO_DEVICE_BIND_IOMMUFD,把 iommufd 作为输入。成功后返回 out_devid,VFIO 才授予设备接口访问权限。内核在此过程中建立设备的 DMA 所有权,并检查所在 IOMMU group 的约束。
设备 cdev 让应用直接按设备打开文件,但物理隔离边界仍由 IOMMU group 决定。同组设备不能同时交给互不相容的 DMA 所有者;PCIe 拓扑造成的隔离限制也不会因接口改名而消失。VFIO 绑定与访问授权
2. 分配地址空间与附着页表
QEMU 在 IOMMUFD fd 上调用 IOMMU_IOAS_ALLOC,得到空的 IOAS。对于本例物理设备,QEMU 随后寻找可复用的 HWPT;没有兼容对象时,通过 IOMMU_HWPT_ALLOC 指定 dev_id 和 ioas_id,分配相应 HWPT。
接着,在设备 fd 上调用 VFIO_DEVICE_ATTACH_IOMMUFD_PT,将 pt_id 设为 HWPT ID。附着成功后,设备与该硬件转换域关联起来。此时 IOAS 仍为空,目标 RAM 的 DMA 访问尚不可用。QEMU 分配与附着 HWPT
API 也允许直接向 IOAS 附着,由内核选择兼容 HWPT 或创建一个。QEMU 这里显式管理物理设备的 HWPT,便于查询能力、复用页表和设置分配标志。两种方式都要经过设备与页表的兼容性检查。
3. 注册 RAM 映射
地址空间关联完成后,QEMU 注册 VFIO MemoryListener。内存监听器枚举现有 RAM 区间,并将设备侧地址、QEMU 内存位置、长度和权限传给 IOMMUFD 后端。
IOMMU_IOAS_MAP
ioas_id = 10
iova = 0x00400000 # 设备侧起点,本例等于 GPA
user_va = 0x70000000 # QEMU 进程中的后备内存起点
length = 0x00200000 # 2 MiB 的映射区间
flags = FIXED_IOVA | READABLE | WRITEABLE
内核验证范围与权限,获取并固定后备页,再把映射同步到关联的 HWPT。2 MiB 的区间长度不保证生成一个 2 MiB 硬件大页;实际页粒度还取决于物理连续性、对齐、IOMMU 能力和映射条件。READABLE、WRITEABLE 从设备视角解释:网卡读取发送缓冲区需要读权限,写入接收缓冲区需要写权限。
QEMU 的本条路径是先附着、后映射。API 允许先向 IOAS 映射、再附着设备,但新设备带来的 IOVA 保留区、地址宽度或页粒度限制,可能使后续附着失败。先后顺序可以选择,地址空间必须满足最终所有设备的约束。QEMU 首次接入顺序、映射参数构造
四、映射建立后的 DMA 与资源回收
当客户机驱动向网卡提交 IOVA 0x00400080 的接收缓冲区时,设备通过已附着的页表访问对应宿主页。IOMMUFD 的 map ioctl 不出现在每个数据包的路径上;它配置的页表和内存引用,服务于后续一系列 DMA。
Mermaid 源码
sequenceDiagram
accTitle: 单个 VF 的 IOMMUFD 生命周期
accDescr: QEMU 先绑定 VFIO 设备,再分配 IOAS 和 HWPT,附着设备,映射 RAM;设备使用映射进行 DMA。停止队列和在途 DMA 后,解除关联、撤销映射并回收对象及文件描述符。
participant Q as QEMU
participant V as VFIO 设备 fd
participant I as IOMMUFD / IOMMU
participant D as 网卡 VF 与 RAM
Q->>V: BIND_IOMMUFD(iommufd)
V->>I: 检查 DMA 所有权并绑定设备
I-->>Q: DEVICE ID
Q->>I: IOAS_ALLOC → IOAS 10
Q->>I: HWPT_ALLOC(dev_id, IOAS 10)
I-->>Q: HWPT 20
Q->>V: ATTACH_IOMMUFD_PT(HWPT 20)
V->>I: 设备附着到硬件页表
Note over I,D: IOAS 尚无目标映射<br/>不能访问该 RAM 区间
Q->>I: IOAS_MAP(IOVA, HVA, 长度, 权限)
I->>D: 固定后备页并建立硬件映射
Note over Q,D: 客户机驱动准备队列并开始提交请求
D->>I: 设备 DMA 地址与权限检查
I-->>D: 转换成功,访问目标 RAM
Note over Q,D: 回收前停止设备队列<br/>并处理在途 DMA
Q->>V: DETACH_IOMMUFD_PT
V->>I: 脱离 HWPT 20
Q->>I: DESTROY(HWPT 20)
Q->>I: IOAS_UNMAP(目标区间)
I->>D: 撤销映射并释放页引用
Q->>I: DESTROY(IOAS 10)
Q->>V: close(devfd) → 解绑设备
Q->>I: close(iommufd)
资源回收从设备使用结束开始:停止提交新请求,按设备协议停止队列并处理在途 DMA。随后脱离 HWPT,销毁不再使用的 HWPT,撤销 RAM 映射,再释放 IOAS 和设备绑定。回收共享对象前,还要确认其他设备不再使用它。
VFIO_DEVICE_DETACH_IOMMUFD_PT 解除设备与页表的关系,不会自动删除 IOAS 中的映射。IOMMU_IOAS_UNMAP 删除映射,内核完成相应的页表更新、转换缓存处理与页引用回收。它也不会销毁 IOAS。两类操作分别处理关联与内容,故障回滚时同样需要分清。
绑定由 VFIO 设备 fd 持有,关闭设备 fd 时执行解绑。IOAS 与显式分配的 HWPT 可以通过 IOMMU_DESTROY 释放;仍被依赖的对象不能任意销毁。设备绑定还会持有 IOMMUFD 上下文引用,因此仅提前关闭 /dev/iommu 的用户态 fd,不能视作所有设备和地址空间已立即回收。QEMU 设备脱离及对象释放、内核解绑路径
设备与地址空间的生命周期
逐步接入一块 VF,观察对象和映射的建立、使用与回收。可切换失败场景,比较回滚需要释放的资源。
DEVICE 1
未绑定
网卡 VF 的 DMA 所有权HWPT 20
未分配
硬件 I/O 页表IOAS 10
未分配
逻辑 I/O 地址空间- IOVA
- 0x00400000
- QEMU HVA
- 0x70000000
- 长度 / 权限
- 2 MiB / 设备读写
演示采用一个设备、一个 IOAS 和显式分配的 HWPT。失败场景停在首次被拒绝的操作,随后只清理本次创建的资源;不会启动 DMA。
- 打开两个 fd,绑定设备,分配 IOAS 和 HWPT。
- 设备附着到 HWPT,再把 RAM 区间映射到 IOAS。
- 启动设备队列,使用现有映射处理 DMA。
- 停止队列并处理在途 DMA,脱离并销毁 HWPT,撤销映射,最后释放 IOAS 与设备绑定并关闭 fd。
启用 JavaScript 后可逐步查看对象状态和失败回滚。
六、源码中的控制路径
前面的三个动作在源码中有清晰分工。BIND 和 ATTACH 发给 VFIO 设备 fd;ALLOC、MAP、UNMAP 和 DESTROY 发给 IOMMUFD fd。
iommufd_cdev_attach()
iommufd_cdev_connect_and_bind()
VFIO_DEVICE_BIND_IOMMUFD → DEVICE
iommufd_backend_alloc_ioas()
IOMMU_IOAS_ALLOC → IOAS
iommufd_cdev_attach_container()
iommufd_cdev_autodomains_get()
复用兼容 HWPT,或 IOMMU_HWPT_ALLOC
VFIO_DEVICE_ATTACH_IOMMUFD_PT
vfio_listener_register()
RAM 区域通知 → iommufd_backend_map_dma()
IOMMU_IOAS_MAP
在 Linux 侧,VFIO 先从 fd 找到 IOMMUFD 上下文,再把设备操作交给具体 VFIO 驱动。IOMMUFD 的 IOAS 映射入口则验证用户参数,进入后备页和 I/O 页表管理。设备访问权限、对象引用以及映射失败回滚,都在这些边界上检查;用户态得到的成功返回值对应已经建立的关系,不能只看对象 ID 是否非零。
| 源码入口 | 重点观察 |
|---|---|
iommufd_cdev_connect_and_bind() |
两个 fd 的连接、设备 ID 及失败回滚 |
iommufd_cdev_autodomains_get() |
HWPT 复用、能力查询与显式分配 |
iommufd_backend_map_dma() |
IOVA、HVA、长度与设备读写权限 |
vfio_df_ioctl_attach_pt() |
VFIO 设备接口到 IOMMUFD 页表关联 |
iommufd_ioas_map() |
映射参数校验与用户后备页管理 |
QEMU 还提供以文件描述符和偏移描述后备内存的 IOMMU_IOAS_MAP_FILE 分支。它改变内存来源的表达方式,IOVA、IOAS 与 HWPT 的关系保持一致。文件后备映射
七、从 VFIO Container 到独立的页表管理
传统 VFIO Type1 路径以 group 和 container 组织 DMA:group 加入 container,应用在 container fd 上配置映射,再获得设备访问。它能满足常规直通,并支持兼容设备共享映射;地址空间接口与 VFIO 的 container 模型紧密关联。
IOMMUFD 把通用 I/O 页表管理独立出来,用对象 ID 明确表达“逻辑地址空间”“具体硬件页表”和“设备附着”。QEMU 在 2023 年引入 IOMMUFD 后端,保留公共 VFIO 内存监听逻辑,再由不同后端完成映射。这使设备访问接口和页表能力能够分别演进。
| 需求 | IOMMUFD 的组织方式 | 仍需付出的成本或满足的条件 |
|---|---|---|
| 多设备共享 RAM | 一个 IOAS 关联兼容 HWPT;必要时同步多个 HWPT | 多个硬件页表仍需维护;所有设备的地址限制要相容 |
| 控制硬件页表属性 | 显式分配 HWPT,并查询设备所处 IOMMU 能力 | 分配与附着可能失败,应用需处理对象依赖和回滚 |
| 嵌套 DMA 转换 | 用嵌套 HWPT 与父页表表达两阶段关系 | 依赖硬件、驱动与 VMM 配套,不能给普通 HWPT 任意添加一层 |
| DMA 脏页跟踪 | 支持的 HWPT 可启停跟踪并读取位图 | 能力按 IOMMU / 驱动判断,设备状态迁移仍需另行支持 |
QEMU 10.2 已在 hw/vfio/iommufd.c 查询 IOMMU_HW_CAP_DIRTY_TRACKING,具备能力时请求 IOMMU_HWPT_ALLOC_DIRTY_TRACKING,并实现跟踪启停及位图读取。它并不代表所有平台都提供该能力,也不代表任何直通设备都能热迁移。设备状态保存、队列停止、DMA 一致性以及目标端兼容性仍需共同满足。能力门控与 HWPT 分配、脏页跟踪操作
对最初的网卡 VF,正常运行的数据路径依旧是设备通过 IOMMU 访问 RAM。IOMMUFD 的主要收益体现在地址空间表达、能力扩展与资源管理上。评估时应分别测量接入与大内存映射耗时、运行期 I/O 性能、动态映射更新和回收成本,不能仅凭更换接口就推导吞吐提升。
八、参考资料
- Linux 6.18:IOMMUFD 用户 API:IOAS、HWPT、设备绑定及对象生命周期。
- Linux:VFIO 设备与隔离组:cdev 接口、DMA 所有权及 group 隔离。
- QEMU:VFIO IOMMUFD 实现与 IOMMUFD ioctl 后端。
- Linux:IOAS 实现与 VFIO 设备 cdev。
- VFIO 设备直通与 DMA 地址转换:结合缓冲区地址与访问权限观察完整 DMA 请求。