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)
DMA重映射表查找流程 DMA重映射表查找流程 DMA设备 DMA请求 IOMMU硬件 索引 重映射表 上下文条目 Context Entry 指向 I/O页表 物理地址 物理内存 重映射表是IOMMU进行地址转换的入口

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 页面是否被访问过
I/O页表多级结构 I/O页表多级结构(以4级为例) L0 页表 L1 页表 L2 页表 L3 页表 每个页表条目(PTE) 指向下一级页表 或直接指向物理页帧 物理页帧

4.3 域(Domain)的概念

域(Domain)是IOMMU中用于隔离和保护的一组逻辑资源。一个域定义了一个独立的地址空间,包含一个或多个I/O页表,以及一组被授权访问该地址空间的设备。域是实现设备间DMA隔离的核心抽象。

  • 隔离边界: 不同域之间的设备无法通过DMA互相访问对方的内存,除非显式配置共享映射。
  • 设备分组: 一个域可以包含多个设备(如同一PCIe总线上的多个功能),这些设备共享同一个地址空间。
  • 生命周期: 域由内核驱动创建和销毁。设备在初始化时被分配到某个域,在热插拔或驱动卸载时从域中移除。
🔒 安全意义: 在虚拟化场景中,每个虚拟机(VM)通常拥有一个独立的域。VM中的设备DMA只能访问该VM的内存,无法窥探或破坏宿主机或其他VM的数据。这正是DMA重映射实现“I/O隔离”的核心机制。

域与设备、页表的关系:

组件 与域的关系 说明
设备 属于某个域 设备在加入域后,其DMA请求受该域地址空间约束
I/O页表 域持有页表 域通过页表定义其地址空间映射
重映射表 引用域 上下文条目中的域ID指向对应的域
物理内存 被域映射 物理内存页可以被映射到多个域(共享内存)
域隔离架构 域(Domain)隔离架构 域 A(VM1) 设备 A1 设备 A2 I/O页表 A(地址空间映射) 物理内存区域 A 域 B(VM2) 设备 B1 设备 B2 I/O页表 B(地址空间映射) 物理内存区域 B 隔离 DMA无法跨域访问 每个域拥有独立的地址空间和设备集合,实现硬件级DMA隔离

4.4 三者协同工作流程

当设备发起DMA请求时,重映射表、I/O页表和域三者协同完成地址转换和访问控制:

  1. 设备发起DMA请求: 设备发送包含设备虚拟地址(I/O总线地址)的读写请求。
  2. IOMMU拦截请求: IOMMU根据请求来源的设备标识(如PCIe Requester ID)查找重映射表,获取对应的上下文条目。
  3. 定位域和页表: 上下文条目中包含域ID和I/O页表基地址指针。IOMMU由此确定请求所属的域和地址转换规则。
  4. 地址转换: IOMMU遍历I/O页表,将设备虚拟地址转换为物理地址。同时检查页表条目中的权限位(R/W/X)。
  5. 访问物理内存: 转换成功后,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重映射实现了设备地址空间的虚拟化、隔离和安全控制,为虚拟化、容器安全和系统可靠性提供了关键支撑。