4. DMA重映射核心概念:重映射表、I/O页表、域(Domain)的概念
DMA重映射(DMA Remapping)是系统级I/O虚拟化和隔离的关键技术。其核心思想是在DMA引擎与物理内存之间插入一个硬件转换层,由IOMMU(I/O Memory Management Unit)负责将设备发起的DMA请求中的设备虚拟地址(或称为I/O总线地址)转换为物理地址。这一过程依赖三个核心概念:重映射表、I/O页表和域(Domain)。
💡 核心理解: 可以把IOMMU看作是为DMA设备准备的“MMU”。重映射表是转换规则库,I/O页表是具体的地址映射数据结构,而域则是这些映射规则的“作用范围”或“安全容器”。
4.1 重映射表(Remapping Table)
重映射表是IOMMU硬件用于查找DMA地址转换规则的根数据结构。它通常位于系统内存中,由软件(内核驱动)创建并维护,IOMMU硬件通过寄存器中的基地址指针来访问它。
- 作用: 作为整个地址转换的入口点。当设备发起DMA请求时,IOMMU根据请求中的设备标识(如PCIe的Bus/Device/Function号)和请求地址,索引重映射表,找到对应的转换条目。
- 结构: 重映射表通常是一个多级结构(类似于CPU的页表),包含上下文条目(Context Entry)。每个上下文条目指向一个设备或一组设备所使用的I/O页表。
- 关键字段: 每个上下文条目包含域标识符、I/O页表指针、地址空间大小、权限控制位(读/写/执行)以及故障处理信息。
📌 硬件视角: 在Intel VT-d中,重映射表被称为Root Entry Table和Context Entry Table;在ARM SMMU中,则对应Stream Table和Context Descriptor。虽然名称不同,但逻辑功能一致。
下表对比了不同架构中重映射表的层级结构:
| 架构 | 顶层结构 | 中间层结构 | 底层结构 |
|---|---|---|---|
| Intel VT-d | Root Entry Table | Context Entry Table | I/O页表(SL Page Tables) |
| ARM SMMUv3 | Stream Table | Context Descriptor | Stage-1 / Stage-2 页表 |
| AMD IOMMU | Device Table | — | I/O页表(Page Tables) |
4.2 I/O页表(I/O Page Table)
I/O页表是描述设备虚拟地址到物理地址映射关系的多级页表结构。它与CPU页表类似,但服务于DMA设备。I/O页表由重映射表中的上下文条目引用,支持不同粒度的映射(如4KB、2MB、1GB等)。
- 映射粒度: 支持多种页面大小,大页可以减少页表层级和TLB压力。
- 权限控制: 每个页表条目(PTE)包含读(R)、写(W)、执行(X)权限位,实现细粒度的访问控制。
- 嵌套映射: 在虚拟化场景中,I/O页表支持两级映射(Stage-1和Stage-2),分别用于GVA→GPA和GPA→HPA的转换。
⚠️ 关键差异: I/O页表与CPU页表虽然结构相似,但它们是独立的硬件单元。CPU页表由MMU管理,I/O页表由IOMMU管理。两者在TLB、缓存一致性、故障处理等方面存在差异,驱动开发时必须区分对待。
I/O页表条目(PTE)的关键字段:
| 字段 | 位宽 | 描述 |
|---|---|---|
| 物理地址 | 可变(如40位) | 映射到的物理页帧号(PFN) |
| 读权限(R) | 1 bit | 允许DMA读取该页 |
| 写权限(W) | 1 bit | 允许DMA写入该页 |
| 执行权限(X) | 1 bit | 允许DMA执行该页(通常用于GPU) |
| 脏位(D) | 1 bit | 页面是否被写入过 |
| 访问位(A) | 1 bit | 页面是否被访问过 |
4.3 域(Domain)的概念
域(Domain)是IOMMU中用于隔离和保护的一组逻辑资源。一个域定义了一个独立的地址空间,包含一个或多个I/O页表,以及一组被授权访问该地址空间的设备。域是实现设备间DMA隔离的核心抽象。
- 隔离边界: 不同域之间的设备无法通过DMA互相访问对方的内存,除非显式配置共享映射。
- 设备分组: 一个域可以包含多个设备(如同一PCIe总线上的多个功能),这些设备共享同一个地址空间。
- 生命周期: 域由内核驱动创建和销毁。设备在初始化时被分配到某个域,在热插拔或驱动卸载时从域中移除。
🔒 安全意义: 在虚拟化场景中,每个虚拟机(VM)通常拥有一个独立的域。VM中的设备DMA只能访问该VM的内存,无法窥探或破坏宿主机或其他VM的数据。这正是DMA重映射实现“I/O隔离”的核心机制。
域与设备、页表的关系:
| 组件 | 与域的关系 | 说明 |
|---|---|---|
| 设备 | 属于某个域 | 设备在加入域后,其DMA请求受该域地址空间约束 |
| I/O页表 | 域持有页表 | 域通过页表定义其地址空间映射 |
| 重映射表 | 引用域 | 上下文条目中的域ID指向对应的域 |
| 物理内存 | 被域映射 | 物理内存页可以被映射到多个域(共享内存) |
4.4 三者协同工作流程
当设备发起DMA请求时,重映射表、I/O页表和域三者协同完成地址转换和访问控制:
- 设备发起DMA请求: 设备发送包含设备虚拟地址(I/O总线地址)的读写请求。
- IOMMU拦截请求: IOMMU根据请求来源的设备标识(如PCIe Requester ID)查找重映射表,获取对应的上下文条目。
- 定位域和页表: 上下文条目中包含域ID和I/O页表基地址指针。IOMMU由此确定请求所属的域和地址转换规则。
- 地址转换: IOMMU遍历I/O页表,将设备虚拟地址转换为物理地址。同时检查页表条目中的权限位(R/W/X)。
- 访问物理内存: 转换成功后,IOMMU使用物理地址访问内存。如果转换失败(如地址未映射或权限不足),IOMMU触发DMA重映射故障(DMA Remapping Fault)。
⚙️ 驱动开发要点: 在Linux内核中,
iommu_domain_alloc() 用于创建域,iommu_attach_device() 将设备加入域,iommu_map() 用于建立I/O页表映射。理解这三个核心概念是编写IOMMU感知驱动的基石。
以下是一个简化的内核代码示例,展示域和页表的创建过程:
/* 分配一个IOMMU域 */
struct iommu_domain *domain;
domain = iommu_domain_alloc(&platform_bus_type);
if (IS_ERR(domain)) {
pr_err("Failed to allocate IOMMU domain\n");
return PTR_ERR(domain);
}
/* 将设备附加到域 */
int ret = iommu_attach_device(domain, dev);
if (ret) {
pr_err("Failed to attach device to domain\n");
iommu_domain_free(domain);
return ret;
}
/* 建立I/O页表映射:设备虚拟地址 0x1000 → 物理地址 0x80000,大小4KB,读写权限 */
ret = iommu_map(domain, 0x1000, 0x80000, 4096, IOMMU_READ | IOMMU_WRITE);
if (ret) {
pr_err("Failed to create I/O page table mapping\n");
iommu_detach_device(domain, dev);
iommu_domain_free(domain);
return ret;
}
通过上述机制,DMA重映射实现了设备地址空间的虚拟化、隔离和安全控制,为虚拟化、容器安全和系统可靠性提供了关键支撑。