IOMMU SVA 共享虚拟地址:设备直通与进程地址空间 ID 深度工程实战
现代计算架构中,GPU、FPGA、NVMe、网卡等高性能外设对直接内存访问的需求与日俱增。IOMMU SVA(Shared Virtual Addressing)作为 Linux 内核中最前沿的设备直通技术,彻底重构了 CPU 与设备共享内存的编程模型。本文从 PCIe ATS/PASID 协议根基出发,深入剖析 SVA 在内核中的体系结构设计、mmu_notifier 协同机制、PASID 生命周期管理,并给出生产环境部署的完整工程实践。
一、问题域:为什么需要 SVA
1.1 传统 DMA 编程模型的困境
在没有 SVA 的设备直通场景中,虚拟机或容器中的驱动程序必须通过虚拟机监控器(Hypervisor)或宿主机内核的中转服务来完成地址映射。经典流程如下:
应用程序分配内存 (GVA)
↓
Guest OS 页表 (GVA → GPA)
↓
Hypervisor 页表 (GPA → HPA)
↓
IOMMU 使用 HPA 做 DMA
这条路径存在三个根本性问题:
- 双重地址转换开销:Hypervisor EPT/NPT 表与 IOMMU 页表形成两层穿越,TLB Miss 惩罚叠加
- 内存气球锁定:Guest 分配给设备的内存必须在气球锁定(balloon pinning)后才能建立映射,延迟不可控
- 流式 DMA 不连续:Guest 视角连续的内存在 Host 物理上可能碎片化,IOMMU 页表必须逐个映射
1.2 SVA 的核心思想
SVA 的关键洞察是:让 CPU 和 PCIe 设备使用同一张页表。当进程申请 DMA 缓冲区时,内核直接将进程虚拟地址(VA)映射到 IOMMU,设备发起的 DMA 使用与 CPU 相同的虚拟地址,无需中间转换层。
┌─────────────────────────────────────────────┐
│ 进程虚拟地址空间 │
│ 0x7F00_0000 ~ 0x7FFF_FFFF │
│ ┌──────────────────────┐ │
│ │ DMA 共享缓冲区 │ │
│ └──────────────────────┘ │
└────────────────┬────────────────────────────┘
│ 同一份页表
┌─────────┴─────────┐
│ │
CPU 执行 DMA 传输
(MMU 翻译) (IOMMU 翻译)
│ │
└─────→ 物理页 ←────┘
这意味着:
- 设备可以理解 0x7F00_4000 这样的CPU虚拟地址
- CPU 和设备看到的内存布局完全一致
- 零拷贝环形缓冲区无需多次映射
二、协议基础:PCIe ATS 与 PASID
2.1 Address Translation Services (ATS)
ATS 是 PCIe 4.0 引入的协议扩展(已成为 PCIe 6.0 的强制要求),核心思想是让每个 PCIe 功能(Function)具备自己的地址翻译缓存(ATC, Address Translation Cache)。
Endpoint设备 Root Complex / IOMMU
│ │
│─── Address Translation ─────→│ (请求翻译)
│ Request (with VA) │
│←── Address Translation ──────│
│ Completion (with HPA) │
│ │
│ 后续直接使用缓存的翻译结果 │ (ATC 命中,跳过 IOMMU)
│─────────── Memory ──────────→│
│ Read/Write │
ATS 工作流程分为两个阶段:
- 翻译阶段:设备发起标签化的 Memory Read 请求(带翻译请求 TLP),IOMMU 翻译后返回 HPA 给设备缓存
- 执行阶段:设备使用缓存的 HPA 直接发起 DMA,无需 IOMMU 介入
ATC 一致性由 IOMMU 通过 INVALIDATE_REQUEST TLP 来维护:当内核修改页表时(进程退出、内存迁移),主动通知设备废弃缓存条目。
2.2 Process Address Space ID (PASID)
ATS 使用设备级地址空间(BDF 标识),但现代虚拟化场景需要同一设备内多进程独立的地址空间。PASID TLP 扩展(ECR 0x002F)在每个 TLP Header 中插入 20 位 PASID,允许同一 Function 承载最多 104 万个独立地址空间。
┌─────────────────────────────────────────────────┐
│ TLP Header (64-bit addressing) │
├─────────────────────────────────────────────────┤
│ Fmt│Type│TC│A│... │PASID │Requester ID│Tag│...│
│ │ │ │ │ │(20b) │ (16b) │(8b)│
└─────────────────────────────────────────────────┘
↑
标识目标进程的地址空间
PASID 的关键语义是:IOMMU 页表查找 = PASID 选择地址空间 + 虚拟地址翻译,实现了类似 ASID(Address Space ID)的设备侧效果。
2.3 PRI (Page Request Interface)
SVA 使用 PRI(Page Request Interface)支持按需分页的 DMA,使设备可以在 ATC 未命中时触发 page fault 等待软件服务,而非直接报错:
设备 ATC miss (VA不存在)
↓
产生 PRI Request TLP
↓
IOMMU 提取 (PASID, VA) 通知内核
↓
内核分配物理页 → 更新 IOMMU 页表
↓
Device 重试 TLP → ATC miss → 翻译命中 → 完成 DMA
这是 GPU 按需内存分配的协议基础,CUDA Unified Memory 和 Intel OpenCL SVM 都依赖 PRI。
三、Linux 内核 SVA 架构
3.1 IOMMU Subsystem 重构
Linux 内核 5.8+ 引入了完整的 SVA 子系统,架构如下:
┌─────────────────────────────────────────────────────────┐
│ 用户空间驱动程序 │
│ iommu_sva_bind_device() / unbind() │
└──────────────────────┬──────────────────────────────────┘
│ syscall / VFIO uAPI
┌──────────────────────▼──────────────────────────────────┐
│ iommu/core - SVA Layer │
│ ┌─────────────────────────────────────────────────┐ │
│ │ iommu_sva_bind_device() │ │
│ │ → iommu_get_pasid() │ │
│ │ → mm_notifier_alloc() │ │
│ │ → domain->ops->sva_bind() │ │
│ │ → intel_iommu_sva_bind() │ │
│ │ → arm_smmu_sva_bind() │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────┬──────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────┐
│ IOMMU Driver (硬件后端) │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ Intel VT-d │ │ ARM SMMU v3 │ │ AMD-Vi │ │
│ │ (vtd.c) │ │ (arm-smmu- │ │ (amd/iommu.c) │ │
│ │ │ │ v3.c) │ │ │ │
│ └─────────────┘ └──────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────┘
3.2 核心数据结构
// include/linux/iommu.h
struct iommu_sva {
struct device *dev; // 绑定的 PCIe 设备
struct mm_struct *mm; // 绑定的进程地址空间
struct iommu_domain *domain; // 共享的 IOMMU 域
ioasid_t pasid; // 分配的 PASID 值
refcount_t users;
struct list_head list;
};
struct iommu_domain {
const struct iommu_ops *ops;
unsigned long pgsize_bitmap; // 支持的页大小 (4K/2M/1G/512G)
enum iommu_domain_type type; // UNMANAGED / DMA / IDENTITY / SVA
void *handler;
...
};
关键洞察是 iommu_sva 将进程 mm_struct、PCIe 设备、IOMMU 域绑定为单一实体,实现了三者生命周期的一致性管理。
3.3 PASID 分配器
内核使用 XArray (pasid_xa) 管理全局 PASID 分配:
// drivers/iommu/iommu.c
static DEFINE_XARRAY_ALLOC(pasid_xa);
static DEFINE_MUTEX(pasid_mutex);
int iommu_pasid_alloc(struct device *dev, ioasid_t start, ioasid_t end)
{
if (start > end || end >= pasid_max)
return -EINVAL;
mutex_lock(&pasid_mutex);
ret = xa_alloc_cyclic(&pasid_xa, &pasid, entry,
XA_LIMIT(start, end), GFP_KERNEL);
mutex_unlock(&pasid_mutex);
return ret ? ret : pasid;
}
PASID 范围由 IOMMU 硬件决定,PCIe PRI 的 20 位宽度支持最多 524,288 个有效值(取决于 PASID_WIDTH capability)。
四、mmu_notifier 协同机制
SVA 最关键的技术挑战是内存一致性:当内核修改进程页表时(exit、exec、mremap、page migration),IOMMU 的 ATC 缓存必须同步失效。
4.1 双向通知链
// 内核通过 mmu_notifier 订阅进程地址空间事件
sva->mn.notifier_call = iommu_mmu_notifier;
int iommu_mmu_notifier(struct mmu_notifier *mn,
struct mm_struct *mm,
unsigned long start,
unsigned long end)
{
// 清理 IOMMU 页表中该范围映射
iommu_unmap(sva->domain, start, end - start);
// 发送 ATC invalidation 给设备
iommu_device_invalidate(sva->domain, start, end);
}
内核通过 mmu_notifier_register() 订阅事件,关键回调包括:
invalidate_range:页表映射修改(mremap、page migration)release:进程退出,必须立即回收 PASID 并清理映射clear_flush_young:页面访问位清除回收
4.2 IOMMU ATC Invalidation 流程
// drivers/iommu/intel/iommu.c - Intel VT-d 实现
static void intel_iommu_device_invalidate(struct iommu_domain *domain,
unsigned long start,
unsigned long end)
{
struct dmar_domain *dmar_domain = to_dmar_domain(domain);
struct intel_iommu *iommu;
// 1. 遍历该 domain 绑定的所有 IOMMU 单元
for_each_active_iommu(iommu, dmar_domain) {
// 2. 构建 PASID-based Invalidation Descriptor
qi_desc desc = {
.hi = DMA_INV_DESC_PASID(dmar_domain->pasid),
.lo = DMA_INV_DESC_ADDR(start) |
DMA_INV_DESC_SIZE(end - start)
};
// 3. 通过 Queued Invalidation 提交
qi_submit(iommu, &desc, 1);
}
// 4. 等待 QI 完成
qi_sync(iommu);
}
性能关键点:Intel VT-d 使用 Queued Invalidation (QI) 机制将 invalidation 操作异步化,通过内存环形队列提交,避免每次 spin lock 等待。
五、生产环境工程实践
5.1 设备驱动中的 SVA 绑定流程
以下是一个典型的 DSA(Data Streaming Accelerator)驱动使用 SVA 的代码示例:
/* drivers/dsa/engine.c */
#include <linux/iommu.h>
struct dsa_device {
struct device *dev;
struct iommu_sva *sva;
int pasid;
};
static int dsa_sva_init(struct dsa_device *dsa)
{
struct iommu_sva *sva;
struct device *dev = dsa->dev;
int ret;
/* 1. 检查 IOMMU 子系统是否支持 SVA */
if (!iommu_dev_feature_enabled(dev, IOMMU_DEV_FEAT_SVA))
return -ENODEV;
/* 2. 分配 PASID (范围: 1 ~ pasid_max) */
dsa->pasid = iommu_aux_get_pasid(dev, NULL);
if (dsa->pasid < 0)
return dsa->pasid;
/* 3. SVA 设备绑定 */
sva = iommu_sva_bind_device(dev, current->mm);
if (IS_ERR(sva)) {
ret = PTR_ERR(sva);
goto put_pasid;
}
dsa->sva = sva;
dsa->pasid = sva->pasid;
/* 4. 启用 PCIe PASID + ATS (需硬件支持) */
pci_enable_pasid(dev, pci_pasid_features(dev) &
PCI_PASID_ENABLE);
/* 5. 分配 DMA 共享缓冲区 */
dsa->desc_ring = dma_alloc_coherent(dev, DESC_RING_SIZE,
&dma_handle, GFP_KERNEL);
return 0;
put_pasid:
iommu_aux_free_pasid(dev, dsa->pasid);
return ret;
}
5.2 中断隔离与 Posted Interrupts
SVA 场景下中断也需要通过 PASID 寻址。Intel VT-d 的 Posted Interrupt (PI) 机制将中断向量直接写入设备的 Posted Interrupt Request 描述符的虚拟地址:
// 设置 Posted Interrupt 描述符
struct pi_desc {
u32 pir[8]; // 每 bit 对应一个中断向量
union {
struct { u32 ...; }; // 非虚拟posted模式
struct {
u32 ndst; // 目标 CPU
u16 nv; // 通知向量
u16 apic_id;
u32 pid_pasid; // PASID 字段(SVA场景)
} virt;
};
};
设备触发中断时使用 ITS (Interrupt Translation Service) 将 MSI 翻译为 PASID-tagged 的 posted中断,实现隔离到进程级别的 MSI 投递。
5.3 vfio-mdev 与 vGPU 场景的 SVA
SVA 是 vGPU 和弹性设备共享(MDEV)的技术基石:
┌─────────────────────────────────────┐
│ 用户空间 QEMU/KVM │
│ │
│ VFIO_DEVICE_BIND_IOMMU_SVA │
│ ↓ │
│ ioctl(vfio_dev, SVA_BIND, &args) │
│ ↓ │
│ 分配 PASID → 建立进程页表 → 启用 │
└────────────────┬────────────────────┘
│
┌────────────▼────────────┐
│ vGPU/MDev 虚拟设备 │
│ 使用 Guest 页表翻译 GVA │
│ (GVA → GPA → HPA 直通) │
└─────────────────────────┘
vGPU 场景重点在于将 Guest 内存管理直接暴露给设备,Guest 页表变更通过 vfio-mdev 的 pasid 管理接口同步更新 IOMMU。
六、性能分析与调优
6.1 ATC 命中率优化
ATC 是小型缓存(通常 64~128 条目),关键优化方向是减少工作集热点区的缓存抖动:
# 查看 IOMMU ATC 统计 (内核 >= 5.12, Intel VT-d)
cat /sys/bus/pci/devices/0000:00:02.0/sva_stats
# PASID alloc: 482
# ATC hit: 982341
# ATC miss: 1765
# ATC hit rate: 99.82%
# Invalidation: 4128
无效化(Invalidation)是 ATC 的主要开销来源。内核提供两种 invalidation 模式:
| 模式 | 特点 | 适用场景 |
|---|---|---|
| 全局 invalidation | 简单、快速但清空全部 | 单进程独占设备 |
| PASID invalidation | 精确到地址范围 | 多进程共享设备 |
生产环境推荐使用 PASID invalidation,配合大页减少 invalidation 次数。
6.2 页表大页对齐
SVA 支持与 CPU 页表相同的大页(2MB/1G),但要求 IOMMU 硬件支持 PML4/PDPE 的大页映射:
// 检查 IOMMU 支持的页大小
iommu_domain_get_attr(domain, DOMAIN_ATTR_PGSIZES, &pgsize_bitmap);
// 分配大页 DMA 缓冲区
buf = (void *)mmap(NULL, 2 * 1024 * 1024,
PROT_READ | PROT_WRITE,
MAP_HUGETLB | MAP_HUGE_2MB |
MAP_SHARED | MAP_POPULATE,
fd, 0);
使用 2MB 大页时,相同容量的 IOMMU 映射条目数减少 512 倍,ATC 命中率可提升 15%~30%。
6.3 PRI 页错误处理优化
高 PRI 页错误率(>1000/sec)会导致严重的系统抖动。优化策略:
- 页预分配:进程启动时通过
madvise(MADV_HUGEPAGE | MADV_SEQUENTIAL)预分配 DMA 缓冲区 - 异步 invalidation:内核线程批量处理 ATC invalidation,减少同步等待延迟
- PASID 优先回收:长时间空闲的 PASID 优先分配给新进程,配额用尽时触发 LRU 回收
七、可观测性与调试
7.1 Ftrace 动态追踪
# 追踪 IOMMU SVA 相关事件
echo 1 > /sys/kernel/debug/tracing/events/iommu/enable
echo 1 > /sys/kernel/debug/tracing/events/sva/enable
# 追踪 PASID 分配/释放
cat /sys/kernel/debug/tracing/trace_pipe | grep -E "sva_bind|sva_unbind|pasid_alloc"
7.2 IOMMU DebugFS
# 查看已绑定的 SVA 设备列表
cat /sys/kernel/debug/iommu/sva_devices
# pasid 4789 dev=0000:b1:00.1 pid=2843 cmdline=./gpu_compute_app
# 查看 PASID 地址空间范围
cat /sys/kernel/debug/iommu/pasids/4789/ranges
# [0x7f0000000000-0x7f0000800000) rn=1 pgsize=2M
# [0x7f0000800000-0x7f0001000000) rn=3 pgsize=4K
八、故障排查清单
| 现象 | 可能原因 | 排查命令 |
|---|---|---|
sva_bind 返回 -ENODEV |
设备不支持 PASID | lspci -vvv \| grep -i pasid |
sva_bind 返回 -ENOMEM |
PASID 资源耗尽 | cat /sys/kernel/debug/iommu/pasid_stats |
| DMA 超时/ATC 错误 | invalidation 未送达 | dmesg \| grep -i "DMAR.*PASID" |
| 页表共享后系统崩溃 | mm 引用计数错误 | crash> files -p <task_struct> |
| 性能下降远超基线 | ATC thrashing | perf stat -e iommu/ATC_MISS/ |
九、总结与展望
IOMMU SVA 的本质是将设备纳入进程内存管理的一等公民,其技术深度远超简单的地址映射。从协议层 ATS/PASID/PRI 到内核 mmu_notifier 协同,再到生产环境中的大页优化和 cold/hot path 调优,每一个环节都涉及软硬件协同的精细设计。
当前发展方向包括:
- SPDM(Security Protocol and Data Model)认证:SVA 绑定的设备需要 SPDM 握手防止 PASID spoofing 攻击
- CXL.cache 场景下的 SVA:通过 CXL.mem 共享内存时 SVA 仍保证一致性
- RISC-V IOMMU SBA 扩展:RISC-V 的 I标准正在定义 SVA 类扩展,2024 年 Q1 Ratified
工程经验法则:SVA 不是万能的,它最适合高性能计算、GPU 计算、NVMe 原生 I/O 等需要设备理解进程地址空间的场景。对于传统的中低速网卡驱动,VFIO 标准映射模式仍然更简单可靠。评估时衡量标准是:ATS 命中率是否 >99%、invalidation 频率是否 <1k/sec、PRI 页错误率是否 <10/sec 这三个数字。
文章涵盖 IOMMU SVA 协议基础、Linux 内核实现架构、mmu_notifier 协同机制、PASID 管理、生产性能调优和故障排查,约 2800 字

发表评论 取消回复