news 2026/10/16 3:08:04

第 6 篇 IOMMU 的基本职能:地址翻译与设备隔离

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第 6 篇 IOMMU 的基本职能:地址翻译与设备隔离

本文属于第二册“IOMMU:地址翻译与隔离的硬件机制及内核管理”,回答第 0 篇提出的问题 6:IOMMU 如何同时解决物理连续性、寻址范围与设备隔离三个问题?其内部的地址翻译过程是怎样的?

第 5 篇第 6 节指出,物理连续性与地址位宽两类约束,以及设备访问范围不受限制的问题,都源于设备地址直接对应物理地址这一前提,并提出了在设备与内存之间加入一层地址翻译的思路。本文首先说明 IOMMU 在系统中的位置,然后以一次 DMA 写入为例说明其翻译过程,接着结合内核 DMA 映射层的实现说明三个问题如何得到统一解决,最后讨论隔离的粒度,即 domain 与 group 两个概念。

1. 问题回顾

第 5 篇第 6 节将现有手段的局限归结为一点:设备发出的地址就是物理地址,内核只能让物理内存的布局迁就设备。IOMMU 的思路是令设备发出的地址成为 I/O 虚拟地址(IOVA),由 IOMMU 依据内核建立的 I/O 页表翻译为物理地址。

这一思路要在硬件上实现,需要回答以下几个具体问题:

  • IOMMU 应位于何处,才能检查设备发出的每一个请求;
  • 不同设备应使用不同的 I/O 页表,IOMMU 如何判断请求来自哪一个设备;
  • 翻译失败时如何处理;
  • 内核如何利用这一机制满足第 5 篇所述的各项约束;
  • 隔离能否精确到单个设备。

以下各节依次讨论。

2. IOMMU 在系统中的位置

2.1 位于设备访问内存的必经路径上

IOMMU 只有位于设备访问内存的必经路径上,才能对每一个请求进行翻译与检查。在 PCIe 系统中,设备发往内存的请求经由交换机、根端口向上传递,进入 Root Complex 后再转发至内存控制器(第 2 篇第 2 节)。因此 IOMMU 通常集成在 Root Complex 或主桥中,对进入的上游请求进行处理。在 SoC 中,IOMMU 一般位于设备(或一组设备)与片上互连之间。图 1 对比了这两种结构:无论哪一种,设备访问内存的请求都必须先经过 IOMMU,而 CPU 访问内存的路径不经过 IOMMU。

图 1: IOMMU硬件单元的位置

一个系统中通常存在多个 IOMMU 实例,每个实例负责一部分设备。设备与 IOMMU 实例的对应关系由固件描述,内核在启动时据此建立关联。主要的实现如下:

实现硬件单元固件描述内核驱动
Intel VT-dDMA Remapping 硬件单元ACPI DMAR 表drivers/iommu/intel/
AMD-ViAMD IOMMUACPI IVRS 表drivers/iommu/amd/
ARM SMMUSMMUv2、SMMUv3ACPI IORT 表,或设备树的iommus、iommu-map属性drivers/iommu/arm/
RISC-V IOMMURISC-V IOMMU设备树等drivers/iommu/riscv/

各实现的术语与数据结构不同,但基本职能一致。以下以 Intel VT-d 为主线,并在必要处对照 AMD 与 ARM 的实现。

2.2 与 CPU MMU 的比较

IOMMU 与 CPU 的 MMU 都以多级页表完成地址翻译,但二者所处的位置不同,由此产生以下差异:

方面CPU MMUIOMMU
输入地址进程的虚拟地址设备发出的 IOVA
页表的选择由进程切换时写入的页表基址寄存器决定,如 x86 的 CR3按每个请求的来源设备查表选择,无须切换
翻译缓存TLBIOTLB(第 7 篇)
翻译失败触发缺页异常,内核处理后重新执行访存指令通常直接拦截请求并记录故障;可恢复的设备缺页需要设备与 IOMMU 的额外支持(第 21 篇)

其中最关键的差异在于页表的选择。CPU 在同一时刻只执行一个上下文,页表基址随上下文切换而更新;IOMMU 则同时接收来自多个设备的请求,必须依据每个请求本身携带的信息确定应使用的页表。

3. 地址翻译过程

3.1 请求的来源标识

第 2 篇第 2.2 节介绍了 PCIe 事务层报文(TLP)的字段,其中 Requester ID 由总线号、设备号与功能号组成(即 BDF),共 16 位,标识发出请求的设备。第 2 篇第 2.3 节提到,Root Complex 依据 Requester ID 选择 I/O 页表。IOMMU 正是以 Requester ID 作为查表的索引。

ARM SMMU 使用 StreamID 标识请求来源。对于 PCIe 设备,StreamID 由 Requester ID 按固件描述的规则换算得到;对于 SoC 内的设备,StreamID 由互连在请求上附加。此外,PCIe 还允许请求携带 PASID,用于在同一设备内区分不同的地址空间,这一机制将在第 20 篇讨论。

3.2 设备表查找

IOMMU 首先以来源标识查找设备表,取得该设备的翻译配置。各实现的设备表组织如下:

实现设备表结构表项内容
Intel VT-d两级:根表以总线号索引,每项指向一个上下文表;上下文表以设备号与功能号索引翻译类型、页表根地址、地址宽度、domain ID
AMD-Vi单级设备表,以 16 位设备 ID 索引,每项 256 位有效位、页表级数、页表根地址、读写权限、domain ID
ARM SMMUv3流表,以 StreamID 索引,表项称为 STE配置类型(中止、旁路、第一阶段翻译、第二阶段翻译)、页表或上下文描述符地址、VMID

Intel VT-d 上下文表项的格式(drivers/iommu/intel/iommu.h):

/* * low 64 bits: * 0: present * 1: fault processing disable * 2-3: translation type * 12-63: address space root * high 64 bits: * 0-2: address width * 3-6: aval * 8-23: domain id */structcontext_entry{u64 lo;u64 hi;};

其中,翻译类型决定请求是经过多级页表翻译(CONTEXT_TT_MULTI_LEVEL),还是直接以输入地址作为物理地址(CONTEXT_TT_PASS_THROUGH);address space root 给出 I/O 页表的根地址;domain id 为 16 位,用于标识该设备所属的地址空间,其含义将在第 5.1 节讨论。

内核查找某一设备的上下文表项时,按照硬件的两级结构进行(drivers/iommu/intel/iommu.c,节选):

structcontext_entry*iommu_context_addr(structintel_iommu*iommu,u8 bus,u8 devfn,intalloc){structroot_entry*root=&iommu->root_entry[bus];structcontext_entry*context;u64*entry;...entry=&root->lo;...if(*entry&1)context=phys_to_virt(*entry&VTD_PAGE_MASK);else{...}return&context[devfn];}

根表以总线号bus为下标,表项的最低位表示该总线的上下文表是否存在;上下文表再以devfn为下标取得设备的表项。省略部分处理表项不存在时的分配,以及可扩展模式(scalable mode)下的不同布局。

ARM SMMUv3 的 STE 以配置字段区分处理方式(drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h):

#defineSTRTAB_STE_0_CFGGENMASK_ULL(3,1)#defineSTRTAB_STE_0_CFG_ABORT0#defineSTRTAB_STE_0_CFG_BYPASS4#defineSTRTAB_STE_0_CFG_S1_TRANS5#defineSTRTAB_STE_0_CFG_S2_TRANS6

ABORT表示拒绝该设备的全部请求,BYPASS表示不经翻译直接访问,其余两项表示经由第一阶段或第二阶段页表翻译,其中第二阶段翻译主要用于虚拟化场景。

3.3 I/O 页表遍历

取得页表根地址后,IOMMU 以 IOVA 为输入遍历多级页表。以 4 KB 页、48 位 IOVA 为例,IOVA 的低 12 位为页内偏移,其余 36 位按每级 9 位拆分为 4 级索引,每级页表包含 512 个表项。VT-d 与 AMD-Vi 还支持更多级数,以覆盖更宽的 IOVA。

VT-d 页表项的格式(drivers/iommu/intel/iommu.h):

/* * 0: readable * 1: writable * 2-6: reserved * 7: super page * 8-10: available * 11: snoop behavior * 12-63: Host physical address */structdma_pte{u64 val;};

表项中的读、写两位决定设备对该页的访问权限;super page 位表示该表项直接映射一个大页(如 2 MB),遍历在此提前结束;snoop behavior 位与第 4 篇第 3.3 节讨论的 No Snoop 属性有关,硬件支持时,该位可使经由此映射的请求强制侦听 CPU cache。

ARM SMMU 使用与 CPU 相同的 LPAE 页表格式,内核在drivers/iommu/io-pgtable-arm.c中实现,CPU 与 SMMU 的页表格式因此可以共用。

图 2 以网卡的一次 DMA 写入为例,展示上述过程。

图 2: 在IOMMU的系统中,网卡的一次 DMA 写入

每次翻译都需要多次读取内存中的页表,IOMMU 因此以 IOTLB 缓存翻译结果。IOTLB 的结构以及页表更新时的失效操作,将在第 7 篇讨论。

3.4 翻译失败的处理

若设备表项无效、页表中不存在对应映射,或请求的读写类型与表项的权限不符,翻译即告失败。此时 IOMMU 不会将请求转发至内存:写请求被丢弃,读请求则返回表示失败的完成报文。同时,IOMMU 将故障信息记录在故障寄存器或事件队列中,并以中断通知内核。

Intel VT-d 驱动的故障日志格式(drivers/iommu/intel/dmar.c,节选):

pr_err("[%s NO_PASID] Request device [%02x:%02x.%d] fault addr 0x%llx [fault reason 0x%02x] %s\n",type?"DMA Read":"DMA Write",source_id>>8,PCI_SLOT(source_id&0xFF),PCI_FUNC(source_id&0xFF),addr,fault_reason,reason);...dmar_fault_dump_ptes(iommu,source_id,addr,pasid);

日志给出了请求类型、来源设备的 BDF、出错的 IOVA 与故障原因,随后转储该地址对应的页表项。AMD-Vi 的对应日志为IO_PAGE_FAULT事件,ARM SMMUv3 则通过事件队列报告翻译故障(EVT_ID_TRANSLATION_FAULT)与权限故障(EVT_ID_PERMISSION_FAULT)。

与 CPU 缺页不同,上述故障一般不可恢复:被拦截的请求不会被重新发出,设备通常也无法得知需要等待内核补充映射。这类故障在多数情况下意味着驱动或设备存在缺陷,第 6.3 节将进一步讨论。

4. 三个问题的统一解决

IOMMU 提供了地址翻译的硬件能力,但翻译的内容,即哪些 IOVA 映射到哪些物理页,由内核决定。内核在 DMA 映射层完成这一工作,驱动使用的接口与未启用 IOMMU 时完全相同。dma_map_single()、dma_map_page()等流式映射接口最终进入dma_map_phys(),后者根据设备的配置选择实现路径(kernel/dma/mapping.c,节选):

if(dma_map_direct(dev,ops)||(!is_mmio&&!is_cc_shared&&arch_dma_map_phys_direct(dev,phys+size)))addr=dma_direct_map_phys(dev,phys,size,dir,attrs,true);elseif(is_cc_shared)returnDMA_MAPPING_ERROR;elseif(use_dma_iommu(dev))addr=iommu_dma_map_phys(dev,phys,size,dir,attrs);

未启用 IOMMU 时,映射走dma_direct_map_phys(),即第 5 篇讨论的直接映射路径,必要时经由 SWIOTLB 反弹;设备挂接到由 DMA 映射层管理的 IOMMU 地址空间时,use_dma_iommu()为真,映射交由drivers/iommu/dma-iommu.c中的iommu_dma_map_phys()处理。DMA 映射层的分层结构将在第 12 篇讨论,以下仅关注 IOMMU 路径如何满足三项约束。

图 3 概括了 IOMMU 路径下 IOVA 与物理内存的映射关系,以下三小节分别说明其中的寻址范围、物理连续性与设备隔离。

图 3: IOMMU 路径下 IOVA 与物理内存的映射关系

4.1 寻址范围

iommu_dma_map_phys()的核心步骤由__iommu_dma_map()完成(drivers/iommu/dma-iommu.c,节选):

size=iova_align(iovad,size+iova_off);iova=iommu_dma_alloc_iova(domain,size,dma_mask,dev);if(!iova)returnDMA_MAPPING_ERROR;if(iommu_map(domain,iova,phys-iova_off,size,prot,GFP_ATOMIC)){iommu_dma_free_iova(domain,iova,size,NULL);returnDMA_MAPPING_ERROR;}returniova+iova_off;

映射分为两步:先在设备的寻址范围dma_mask之内分配一段 IOVA,再将其映射到缓冲区所在的物理页。返回给驱动的 DMA 地址是 IOVA,而不是物理地址。

IOVA 的分配范围由设备的寻址能力决定,与物理页的实际位置无关。即使缓冲区位于 4 GB 以上,只要分配到的 IOVA 在 32 位以内,32 位设备即可访问,无须经由 SWIOTLB 中转。IOVA 的分配过程考虑了设备掩码、总线限制与 IOMMU 自身的地址宽度(drivers/iommu/dma-iommu.c,节选):

dma_limit=min_not_zero(dma_limit,dev->bus_dma_limit);if(domain->geometry.force_aperture)dma_limit=min(dma_limit,(u64)domain->geometry.aperture_end);/* * Try to use all the 32-bit PCI addresses first. The original SAC vs. * DAC reasoning loses relevance with PCIe, but enough hardware and * firmware bugs are still lurking out there that it's safest not to * venture into the 64-bit space until necessary. * * If your device goes wrong after seeing the notice then likely either * its driver is not setting DMA masks accurately, the hardware has * some inherent bug in handling >32-bit addresses, or not all the * expected address bits are wired up between the device and the IOMMU. */if(dma_limit>DMA_BIT_MASK(32)&&dev->iommu->pci_32bit_workaround){iova=alloc_iova_fast(iovad,iova_len,DMA_BIT_MASK(32)>>shift,false);if(iova)gotodone;dev->iommu->pci_32bit_workaround=false;dev_notice(dev,"Using %d-bit DMA addresses\n",bits_per(dma_limit));}

bus_dma_limit即第 5 篇第 3.2 节所述的总线限制,aperture_end为 IOMMU 硬件支持的最大 IOVA。对于 PCI 设备,即使其掩码超过 32 位,内核也优先在 32 位以内分配 IOVA,只有在该范围耗尽后才使用更高的地址。注释说明了这一做法的原因:部分硬件与固件在处理 32 位以上的地址时仍存在缺陷。

4.2 物理连续性

物理连续性在 IOMMU 路径下转化为 IOVA 的连续性。对于 SG 映射,iommu_dma_map_sg()先计算整个列表所需的 IOVA 长度,一次分配一段连续的 IOVA,再将各段物理内存依次映射到其中。函数开头的注释说明了该函数的作用(drivers/iommu/dma-iommu.c):

/* * The DMA API client is passing in a scatterlist which could describe * any old buffer layout, but the IOMMU API requires everything to be * aligned to IOMMU pages. Hence the need for this complicated bit of * impedance-matching, to be able to hand off a suitably-aligned list, * but still preserve the original offsets and sizes for the caller. */

映射完成后,物理上分散的多个段在设备看来可以合并为一段或少数几段连续的 IOVA。即使设备不支持 SG,或支持的段数有限,也能处理物理分散的缓冲区。合并的规则以及段长度、段边界约束的处理,将在第 10 篇讨论。

一致性分配同样受益。dma_alloc_coherent()在 IOMMU 路径下,对于允许睡眠的调用,由iommu_dma_alloc_remap()逐页或以较小的阶数分配物理页,再将其映射到一段连续的 IOVA,并为 CPU 建立一段连续的内核虚拟地址。分配物理页的函数中有如下注释(drivers/iommu/dma-iommu.c,节选):

/* IOMMU can map any pages, so himem can also be used here */gfp|=__GFP_NOWARN|__GFP_HIGHMEM;.../* * Higher-order allocations are a convenience rather * than a necessity, hence using __GFP_NORETRY until * falling back to minimum-order allocations. */

第一条注释说明物理页不必位于设备可直接访问的区域,第二条说明高阶分配只是为了提高效率,并非必要条件。第 5 篇第 2.4 节所述的大块连续内存分配问题,在此不再依赖 CMA 或高阶伙伴分配解决。

4.3 设备隔离

在 IOMMU 路径下,设备只能访问其 I/O 页表中已建立映射的 IOVA。驱动调用映射接口时建立映射,调用解除映射接口时撤销映射,设备在任一时刻能够访问的内存,仅限于驱动当前交给它的缓冲区。访问其他地址的请求将按第 3.4 节所述被拦截。

映射的权限由 DMA 方向决定(drivers/iommu/dma-iommu.c,节选):

switch(dir){caseDMA_BIDIRECTIONAL:returnprot|IOMMU_READ|IOMMU_WRITE;caseDMA_TO_DEVICE:returnprot|IOMMU_READ;caseDMA_FROM_DEVICE:returnprot|IOMMU_WRITE;

以DMA_TO_DEVICE映射的发送缓冲区只允许设备读取,设备对其写入将引发权限故障。DMA 方向的含义将在第 9 篇讨论。

这一隔离以页为粒度。若驱动映射的缓冲区不足一页,例如kmalloc()分配的一个小对象,设备实际可访问的是该对象所在的整页,其中可能包含其他内核数据。对于一般设备,这一残余风险可以接受;对于可能发起 DMA 攻击的设备,内核另行处理。PCI 子系统将此类设备标记为不可信(include/linux/pci.h):

/* * Devices marked being untrusted are the ones that can potentially * execute DMA attacks and similar. They are typically connected * through external ports such as Thunderbolt but not limited to * that. When an IOMMU is enabled they should be getting full * mappings to make sure they cannot access arbitrary memory. */unsignedintuntrusted:1;

对于不可信设备,若缓冲区的起始地址或长度未按 IOMMU 页对齐,iommu_dma_map_phys()先将数据复制到 SWIOTLB 的中转缓冲区,再映射中转缓冲区所在的页(drivers/iommu/dma-iommu.c,节选):

staticbooldev_use_swiotlb(structdevice*dev,size_tsize,enumdma_data_directiondir){returnIS_ENABLED(CONFIG_SWIOTLB)&&(dev_is_untrusted(dev)||dma_kmalloc_needs_bounce(dev,size,dir));}...if(dev_use_swiotlb(dev,size,dir)&&iova_unaligned(iovad,phys,size)){...phys=iommu_dma_map_swiotlb(dev,phys,size,dir,attrs);

与第 5 篇第 4.3 节不同,此处使用 SWIOTLB 的目的不是解决寻址范围问题,而是使设备可访问的页中不含其他数据。

5. 隔离的粒度:domain 与 group

第 4 节的讨论隐含了一个前提:每个设备拥有独立的 I/O 页表,且设备的所有请求都经过 IOMMU 检查。实际系统中,这两点并非总能满足。内核以 domain 与 group 两个概念描述隔离的单位。

5.1 domain:I/O 地址空间

domain 表示一个 I/O 地址空间,即一套 I/O 页表及其映射。设备挂接(attach)到某个 domain 后,其请求按该 domain 的页表翻译。多个设备可以挂接到同一个 domain,从而共享同一个 IOVA 空间。在硬件上,domain 对应设备表项中的 domain ID(VT-d 上下文表项的第 8–23 位、AMD-Vi 设备表项中的 domain ID),IOTLB 中的缓存项也以 domain ID 标记,以便按地址空间失效。

内核定义了以下几类 domain(include/linux/iommu.h):

/* * This are the possible domain-types * * IOMMU_DOMAIN_BLOCKED - All DMA is blocked, can be used to isolate * devices * IOMMU_DOMAIN_IDENTITY - DMA addresses are system physical addresses * IOMMU_DOMAIN_UNMANAGED - DMA mappings managed by IOMMU-API user, used * for VMs * IOMMU_DOMAIN_DMA - Internally used for DMA-API implementations. * This flag allows IOMMU drivers to implement * certain optimizations for these domains * IOMMU_DOMAIN_DMA_FQ - As above, but definitely using batched TLB * invalidation. * IOMMU_DOMAIN_SVA - DMA addresses are shared process addresses * represented by mm_struct's. * IOMMU_DOMAIN_PLATFORM - Legacy domain for drivers that do their own * dma_api stuff. Do not use in new drivers. */

各类型的用途如下:

  • BLOCKED:拒绝设备的全部 DMA 请求,用于在设备尚未就绪或切换地址空间的过程中保持隔离。
  • IDENTITY:IOVA 即物理地址,相当于旁路翻译。设备挂接到此类 domain 时,use_dma_iommu()为假,DMA 映射走第 4 节所述的直接映射路径,不再获得 IOMMU 提供的隔离。
  • DMA 与 DMA_FQ:由 DMA 映射层管理的地址空间,即第 4 节所述的路径。二者的区别在于 IOTLB 失效的方式,将在第 7 篇讨论。
  • UNMANAGED:映射由 DMA 映射层以外的使用者管理,典型的使用者是 VFIO,将在第 14 篇讨论。
  • SVA:设备直接使用进程的虚拟地址,与进程共享页表,将在第 20 篇讨论。

内核启动时为每个设备(确切地说,为第 5.2 节所述的每个 group)选择一个默认 domain,其类型为 DMA、DMA_FQ 或 IDENTITY 之一,可通过内核参数iommu.passthrough与iommu.strict调整,也可在运行时通过 sysfs 查看。对于第 4.3 节所述的不可信设备,内核强制使用 DMA 类型的默认 domain,若设备驱动要求使用其他类型,将拒绝加载该驱动。

5.2 group:可隔离的最小单位

IOMMU 依据请求的来源标识区分设备,并且只能检查经过它的请求。若两个设备的请求无法被 IOMMU 区分,或二者之间的访问不经过 IOMMU,IOMMU 就无法隔离这两个设备。内核将这样一组设备称为一个 IOMMU group,即可隔离的最小单位。以下三种情形会使多个设备落入同一个 group。

其一,Requester ID 别名。传统 PCI 总线的事务不携带 Requester ID,PCIe 至 PCI 桥在转发下游设备的请求时,以自身的标识代替原设备的标识,桥下所有设备的请求在 IOMMU 看来来自同一来源。内核在pci_for_each_dma_alias()中枚举设备可能使用的全部来源标识(drivers/pci/search.c,节选):

/* * PCIe-to-PCI/X bridges alias transactions from downstream * devices using the subordinate bus number (PCI Express to * PCI/PCI-X Bridge Spec, rev 1.0, sec 2.3). For all cases * where the upstream bus is PCI/X we alias to the bridge * (there are various conditions in the previous reference * where the bridge may take ownership of transactions, even * when the secondary interface is PCI-X). */

此外,部分设备存在硬件缺陷,以其他功能号的 Requester ID 发出 DMA 请求。例如drivers/pci/quirks.c记载,Marvell 88SE9123 以功能 1 作为 DMA 的 Requester ID。内核以 quirk 将这类别名记录在dma_alias_mask中。

其二,绕过 IOMMU 的点对点访问。第 2 篇第 2.3 节提到,同一交换机下的两个设备之间的请求可由交换机直接转发,不经过 Root Complex,自然也不经过 IOMMU。PCIe 的访问控制服务(ACS)用于约束这一行为,其控制位定义如下(include/uapi/linux/pci_regs.h):

#definePCI_ACS_SV0x0001/* Source Validation */#definePCI_ACS_TB0x0002/* Translation Blocking */#definePCI_ACS_RR0x0004/* P2P Request Redirect */#definePCI_ACS_CR0x0008/* P2P Completion Redirect */#definePCI_ACS_UF0x0010/* Upstream Forwarding */#definePCI_ACS_EC0x0020/* P2P Egress Control */#definePCI_ACS_DT0x0040/* Direct Translated P2P */

内核判断一个端口能否隔离其下游设备时,要求以下四项均已启用(drivers/iommu/iommu.c):

#defineREQ_ACS_FLAGS(PCI_ACS_SV|PCI_ACS_RR|PCI_ACS_CR|PCI_ACS_UF)

其中,源验证(SV)检查请求中的 Requester ID 是否属于该端口下游的总线范围,防止设备冒用其他设备的标识;请求重定向(RR)与完成报文重定向(CR)将点对点的请求与完成报文转发至上游,使其经过 Root Complex 与 IOMMU;上游转发(UF)使经重定向的报文能够继续向上传递。若交换机的下游端口未实现或未启用这些功能,其下的设备之间可以绕过 IOMMU 互相访问,必须归入同一个 group。

其三,多功能设备。同一设备的多个功能之间可能在设备内部直接访问,不产生经过 IOMMU 的总线事务。除非设备通过 ACS 声明各功能之间相互隔离,内核将同一插槽上的多个功能归入同一个 group。

pci_device_group()综合上述三种情形,为 PCI 设备确定所属的 group(drivers/iommu/iommu.c,节选):

/* * Find the upstream DMA alias for the device. A device must not * be aliased due to topology in order to have its own IOMMU group. * If we find an alias along the way that already belongs to a * group, use it. */if(pci_for_each_dma_alias(pdev,get_pci_alias_or_group,&data))returndata.group;pdev=data.pdev;/* * Continue upstream from the point of minimum IOMMU granularity * due to aliases to the point where devices are protected from * peer-to-peer DMA by PCI ACS. Again, if we find an existing * group, use it. */for(bus=pdev->bus;!pci_is_root_bus(bus);bus=bus->parent){if(!bus->self)continue;if(pci_acs_path_enabled(bus->self,NULL,REQ_ACS_FLAGS))break;pdev=bus->self;group=iommu_group_get(&pdev->dev);if(group)returngroup;}...group=get_pci_function_alias_group(pdev,(unsignedlong*)devfns);if(group)returngroup;/* No shared group found, allocate new */returniommu_group_alloc();

函数首先沿别名关系找到设备在 IOMMU 看来的来源,然后从该位置向上遍历,直至遇到启用了所需 ACS 功能的端口为止,途经的桥若已属于某个 group,则设备加入该 group;最后检查同一插槽上的其他功能。只有在以上检查均未发现共享关系时,设备才获得独立的 group。图 4 展示了三种典型拓扑下的 group 划分。

图 4: 三种典型拓扑下的 group 划分

group 的划分直接约束了 domain 的使用:同一 group 内的设备必须挂接到同一个 domain,因为 IOMMU 无法为它们分别施加不同的翻译与权限。系统中的 group 划分可在/sys/kernel/iommu_groups/下查看,每个 group 目录下的devices列出其中的设备,type给出当前默认 domain 的类型。group 的概念在将设备交给用户态或虚拟机时尤为重要:VFIO 以 group 为单位分配设备,这一点将在第 14 篇讨论。

6. 常见误区

6.1 认为启用 IOMMU 后无须正确设置 DMA 掩码

IOMMU 消除了物理内存位置对设备的影响,但 IOVA 仍须位于设备的寻址范围之内。第 4.1 节所示的代码中,dma_mask是 IOVA 分配的上限;若驱动声明的掩码超过设备的实际能力,分配到的 IOVA 可能超出设备能够发出的地址。此外,若设备挂接到 IDENTITY 类型的 domain,映射走直接映射路径,掩码的作用与未启用 IOMMU 时相同。

这类错误往往不会立即暴露。如第 4.1 节所述,内核对 PCI 设备优先分配 32 位以内的 IOVA,掩码设置错误的设备在负载较低时可以正常工作,直到 32 位以内的 IOVA 耗尽、内核打印Using %d-bit DMA addresses之后才出现故障。该处注释已指出,此时出现问题的常见原因是驱动未准确设置 DMA 掩码。DMA 掩码的协商将在第 11 篇讨论。

6.2 认为启用 IOMMU 后设备的所有访问都受到检查

以下情形中,设备的访问不受 IOMMU 的完整约束:

  • 设备挂接到 IDENTITY 类型的 domain 时,IOMMU 不进行翻译与权限检查;
  • 同一 group 内的设备之间可能存在不经过 IOMMU 的点对点访问;
  • 隔离以页为粒度,映射一个小缓冲区会使设备获得整页的访问权限;
  • 解除映射后,IOTLB 中的缓存项在失效之前仍可能被设备使用,失效的时机将在第 7 篇讨论。

因此,判断一个系统中的 DMA 隔离程度,需要同时检查默认 domain 的类型、group 的划分以及设备是否被标记为不可信。

6.3 认为 IOMMU 故障可以像 CPU 缺页一样恢复

CPU 缺页可能是正常的执行流程,例如按需分配与换入;而在 DMA 映射层管理的地址空间中,IOMMU 故障一般表明设备访问了驱动未交给它的地址。常见原因包括:解除映射后设备仍在访问缓冲区、驱动将错误的地址写入描述符、设备复位后沿用了旧的地址,以及对只读映射执行写入等。遇到此类故障时,应从故障日志中的设备标识与 IOVA 入手排查驱动,而不应试图通过放宽隔离来规避。排查方法将在第 24 篇讨论。设备缺页的可恢复处理需要专门的硬件支持,属于另一类机制,将在第 21 篇讨论。

7. 小结

本文回答了问题 6,要点如下:

  • IOMMU 位于设备访问内存的必经路径上。在 PCIe 系统中通常集成于 Root Complex,每个实例负责一部分设备,对应关系由 ACPI DMAR、IVRS、IORT 表或设备树描述。
  • 翻译过程分为设备表查找与页表遍历两步。IOMMU 以请求中的 Requester ID(或 StreamID)查找设备表,取得翻译类型、页表根地址与 domain ID,再以 IOVA 遍历多级 I/O 页表得到物理地址,同时检查读写权限。翻译失败时请求被拦截,故障被记录并上报,一般不可恢复。
  • 三个问题统一转化为 IOVA 的分配与映射。内核在设备的寻址范围内分配 IOVA,解决寻址范围问题;将分散的物理页映射到连续的 IOVA,解决物理连续性问题;设备只能访问已映射的 IOVA,且权限由 DMA 方向决定,实现设备隔离。驱动使用的 DMA 映射接口保持不变。
  • 隔离以 domain 与 group 为单位。domain 是一个 I/O 地址空间,多个设备可以共享;group 是 IOMMU 能够区分并隔离的最小设备集合,由 Requester ID 别名、ACS 配置与多功能设备的隔离能力共同决定,同一 group 内的设备必须使用同一个 domain。

本文的讨论集中于 IOMMU 能够做什么。每次映射都需要分配 IOVA、更新 I/O 页表,每次解除映射都需要使 IOTLB 中的缓存项失效,设备的每次访问也可能因 IOTLB 未命中而遍历页表。这些操作的开销如何产生,频繁的映射与解除映射为何会成为性能瓶颈,即为问题 7。


系列导航:

  • 上一篇《第 5 篇 DMA 与内存布局:物理连续性、寻址位宽与反弹缓冲》
  • 下一篇《第 7 篇 IOMMU 的内部机制:I/O 页表、IOVA 分配与 IOTLB》将回答问题 7:IOMMU 地址翻译存在哪些隐性开销。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 3:07:23

Halcon 数组操作(Tuple)详解与算子说明

Halcon 数组操作(Tuple)详解与算子说明 适用版本:Halcon 17.12(算子名、签名、默认值、取值范围均严格对照本地官方文档 D:/Soft/WorkSoft/Halcon17/doc/html/reference/operators/) 本文档覆盖 Halcon 17.12 Tuple / 数组操作全部 121 个官方算子,共 13 个官方分类。 一…

作者头像 李华
网站建设 2026/10/16 3:06:01

Git本地开发操作指南

目录 一,预备 1.1 Git 介绍 1.2 Git 下载安装 二,Git基本操作 2.1 创建并配置本地仓库 2.2 工作区、暂存区、版本库 2.3 添加文件 2.4 修改文件 2.5 版本回退 2.6 撤销修改 2.7 删除文件 三,分支管理 3.1 关于分支 3.2 分支操作…

作者头像 李华
网站建设 2026/10/16 3:05:15

LangChain Agent 入门:ReAct 循环到底在跑什么

个人主页&#xff1a;> for_ever_love__ <&#xff08;欢迎各位大佬莅临&#x1f60a;&#xff09; 其他栏目: > 大模型开发从0到1 < 其他栏目: > iOS项目总结大全 < 其他栏目: > 我想学python了 < 其他栏目: > iOS UI < 文章目录LangChain Agen…

作者头像 李华
网站建设 2026/10/16 3:04:46

品牌名称语义挖掘的Prompt工程:四维拆解、四类资产与排他性双重检验

一、问题定义 LLM为白酒品类生成slogan时&#xff0c;默认检索的是品类功能词表——水源、窖龄、工艺、大师。这套词表已经被数十万个白酒SKU穷尽&#xff0c;输出必然同质化。 名称语义是差异化剩余区&#xff1a;名字里的字义、词源、谐音、典故&#xff0c;天然私有&#xf…

作者头像 李华
网站建设 2026/10/16 3:04:32

动漫推荐系统毕设实战:Hadoop+Spark+Kafka与知识图谱全链路解析

1. 为什么我推荐用这套技术栈来做动漫推荐系统先说结论&#xff1a;如果你的毕业设计既要体现大数据全链路处理能力&#xff0c;又希望推荐算法不是"调个包交差"&#xff0c;还要有让答辩老师眼前一亮的可视化与知识图谱模块&#xff0c;那HadoopSparkKafkaHive这套组…

作者头像 李华