1.0 直通对于虚拟io的支持
整体架构图:
### 直通技术(Passthrough)对虚拟化 I/O 的支持原理
在虚拟化场景中,设备直通(Device Passthrough) 是一种将物理设备直接分配给虚拟机(VM)的技术,使得虚拟机能够绕过 Hypervisor 的模拟层,直接控制硬件设备。这种技术显著提升了 I/O 性能(接近原生速度),常用于 GPU、NVMe 存储、高性能网卡等场景。其核心原理可分解为以下几个关键点:
1.1. 硬件支持:IOMMU 与中断隔离
(1) IOMMU(I/O 内存管理单元)
- 作用:IOMMU 提供 DMA 重映射 和 地址隔离,确保设备只能访问虚拟机显式申请的内存。
- 地址翻译:设备发起的 DMA 请求中的 I/O 虚拟地址(IOVA)会被 IOMMU 转换为物理地址(PA)。
- 权限控制:限制设备对内存的读写权限(如只读、不可执行)。
- 硬件实现:
- Intel VT-d:通过 Root Table 和 Context Table 管理设备到域(Domain)的映射。
- AMD-Vi:使用 Device Table 和 IO Page Table 实现类似功能。
(2) 中断隔离
- 中断重映射(Interrupt Remapping):
- 设备的中断请求(MSI/MSI-X)会被 IOMMU 重定向到虚拟机所属的 CPU 核心。
- 防止设备直接向宿主机发送中断,确保中断隔离。
- 虚拟中断注入:Hypervisor(如 KVM)将设备中断转换为虚拟中断(如 KVM 的
IRQFD
机制),直接注入虚拟机。
1.2. 软件框架:VFIO 与用户态驱动
(1) VFIO(Virtual Function I/O)
- 功能:Linux 内核提供的 安全设备直通框架,通过以下机制支持直通:
- 设备绑定:从宿主机解绑设备驱动,绑定到
vfio-pci
驱动。 - DMA 映射:通过
vfio_dma_map
等接口管理 IOVA 到 PA 的映射(见前一回答)。 - 中断转发:将设备中断路由到用户态(如 QEMU),再注入虚拟机。
- 设备绑定:从宿主机解绑设备驱动,绑定到
- 安全性:
- 用户态程序(如 QEMU)需显式申请 DMA 内存区域。
- IOMMU 强制隔离,阻止设备越界访问。
(2) 用户态驱动协作
- QEMU/KVM 的角色:
- 通过
VFIO 容器(Container)
管理设备的 DMA 空间和中断。 - 将虚拟机的内存映射到设备的 IOVA 空间(通过
VFIO_IOMMU_MAP_DMA
ioctl)。
- 通过
- 设备配置空间模拟:
- 对设备的 PCI 配置空间访问由 QEMU 拦截,部分字段(如 BAR)需模拟以适配虚拟机。
1.3. 直通工作流程(以 PCI 设备为例)
-
设备初始化:
- 宿主机加载
vfio-pci
驱动,解绑原生驱动(如igb
)。 - 设备被标记为
vfio
管理模式,DMA 和中断由 VFIO 接管。
- 宿主机加载
-
虚拟机启动:
- QEMU 通过
-device vfio-pci
参数将设备分配给虚拟机。 - QEMU 调用
VFIO_GROUP_GET_DEVICE_FD
获取设备控制句柄。
- QEMU 通过
-
内存映射:
- 虚拟机申请内存(如
mmap
),QEMU 通过vfio_dma_map
将其锁定并映射到设备的 IOVA 空间。 - IOMMU 更新页表,建立 IOVA→PA 的转换关系。
- 虚拟机申请内存(如
-
中断处理:
- 设备触发中断 → IOMMU 重映射中断目标 CPU → Hypervisor 注入虚拟中断到虚拟机。
- 虚拟机的中断处理程序直接响应设备中断。
-
DMA 操作:
- 设备通过 DMA 直接读写虚拟机的内存(无需 Hypervisor 参与)。
- IOMMU 确保 DMA 地址在映射范围内,且权限合法。
** 1.4. 性能优化技术**
(1) 减少 VM-Exit
- 避免配置空间模拟:对性能关键路径(如网卡的数据面),透传设备的 PCI 配置空间,减少退出到 Hypervisor 的开销。
- MSI-X 直通:允许设备中断直接路由到虚拟机,无需 Hypervisor 转发。
(2) 大页与 IOMMU 映射优化
- 大页支持:使用 2MB/1GB 大页减少 IOMMU 页表项数量,降低 TLB Miss 概率。
- 预映射连续内存:虚拟机申请物理连续的内存区域(如 DPDK 大页内存),减少 IOMMU 映射的碎片化。
(3) SR-IOV 硬件虚拟化
- 物理功能(PF)与虚拟功能(VF):
- PF 由宿主机管理,VF 直通给虚拟机。
- 每个 VF 有独立配置空间、队列和中断,实现硬件级隔离。
- 优势:绕过软件虚拟化层,性能接近物理设备。
** 1.5. 安全与隔离机制**
- IOMMU 域隔离:每个虚拟机拥有独立的 IOMMU 域,设备只能访问所属域的内存。
- DMA 攻击防护:恶意设备无法绕过 IOMMU 访问其他虚拟机或宿主机的内存。
- 中断过滤:仅允许设备发送已注册的中断类型和向量。
** 1.6. 典型应用场景**
- GPU 直通:虚拟机直接使用物理 GPU 进行图形渲染或计算(如 CUDA)。
- 低延迟网络:SR-IOV 网卡直通(如 Mellanox ConnectX),用于 HFT 或 NFV。
- 高性能存储:NVMe SSD 直通,避免虚拟磁盘层的性能损耗。
** 1.7与传统 PCI Passthrough 的对比**
特性 | VFIO-PCI | 传统 PCI Passthrough(如 pci-stub) |
---|---|---|
安全性 | 依赖 IOMMU,隔离 DMA | 无 IOMMU 保护,DMA 不安全 |
性能 | 接近原生 | 原生 |
多设备支持 | 支持设备组(Device Group) | 单设备直通 |
中断处理 | 支持直接注入虚拟机 | 依赖宿主机转发 |
适用场景 | 生产环境 | 测试环境(非安全场景) |
7.0 Root Table 和 Context Table 管理设备到域(Domain)的映射过程
2 通过 Root Table 和 Context Table 管理设备到域的映射(Intel VT-d 实现)
Intel VT-d 技术通过 Root Table 和 Context Table 实现设备到虚拟机域(Domain)的映射,其核心是通过硬件表结构将 PCI 设备与内存隔离域(IOMMU Domain)关联。以下是详细流程和伪代码演示。
** 2.1. 硬件表结构定义**
(1) Root Table
- 作用:每个 PCI Segment(总线组)对应一个 Root Table,用于索引设备所属的 Context Table。
- 结构:
- Root Table 是一个数组,每个条目(Root Entry)对应一个 PCI Bus 编号。
- 每个 Root Entry 包含 Context Table 的基地址(
Context Table Pointer
)和有效位(Present
)。
- 格式(简化):
struct root_entry {
uint64_t context_table_base : 40; // Context Table 物理基地址
uint64_t reserved : 20;
uint64_t present : 1; // 条目是否有效
};
(2) Context Table
- 作用:每个 Context Table 条目(Context Entry)对应一个 PCI 设备(由 Bus/Device/Function 标识),定义设备所属的域(Domain)和地址转换规则。
- 结构:
- 每个 Context Entry 包含域的地址空间根指针(
ASR
,即 IOMMU 页表基地址)、权限控制位等。
- 每个 Context Entry 包含域的地址空间根指针(
- 格式(简化):
struct context_entry {
uint64_t asr : 40; // 地址空间根指针(IOMMU 页表基地址)
uint64_t reserved : 12;
uint64_t translation_type : 2; // 地址转换类型(如 Passthrough、Nested)
uint64_t present : 1; // 条目是否有效
};
** 2.2. 映射流程(以 PCI 设备直通为例)**
步骤 1:初始化 Root Table
- 操作系统为每个 PCI Segment 分配 Root Table 的物理内存。
- 遍历所有 PCI Bus,填充 Root Table 条目:
- 设置
context_table_base
指向对应的 Context Table。 - 设置
present
位为有效。
- 设置
步骤 2:创建 Context Table
- 当设备需要直通给虚拟机时,分配一个 Context Table。
- 根据设备的 BDF(Bus/Device/Function)计算在 Context Table 中的索引:
- 索引公式:
index = (Device Number << 3) | Function Number
- 索引公式:
- 填充 Context Entry:
asr
:设置为虚拟机域的 IOMMU 页表基地址。translation_type
:设置为直通模式(如TT_PASSTHROUGH
)。present
:置 1。
步骤 3:绑定设备到 Domain
- 虚拟机启动时,Hypervisor(如 KVM)为虚拟机分配一个 IOMMU Domain。
- 通过 Intel VT-d 寄存器(如
DMAR_RTADDR_REG
)设置 Root Table 的物理地址。 - 设备发起 DMA 请求时,IOMMU 根据 Root Table → Context Table → Domain 的路径完成地址转换。
** 2.3. 伪代码示例**
以下是概念性伪代码,展示 Root Table 和 Context Table 的初始化与绑定逻辑(实际代码在 Linux 内核的 drivers/iommu/intel/iommu.c
中):
(1) Root Table 初始化
// 分配 Root Table 物理内存
struct root_entry *root_table = alloc_phys_page();
// 遍历所有 PCI Bus,初始化 Root Table
for (int bus = 0; bus < MAX_BUS; bus++) {
struct context_entry *ctx_table = alloc_phys_page(); // 分配 Context Table
root_table[bus].context_table_base = virt_to_phys(ctx_table);
root_table[bus].present = 1;
}
// 将 Root Table 基地址写入 VT-d 寄存器
writeq(virt_to_phys(root_table), DMAR_RTADDR_REG);
(2) 设备绑定到 Domain
// 获取设备的 BDF(Bus 1, Device 2, Function 0)
uint16_t bus = 1, dev = 2, func = 0;
// 计算 Context Table 索引
int ctx_index = (dev << 3) | func;
// 获取对应的 Context Table
struct context_entry *ctx_table = root_table[bus].context_table_base;
// 填充 Context Entry
ctx_table[ctx_index].asr = domain->iommu_pgtbl_base; // 虚拟机域页表基地址
ctx_table[ctx_index].translation_type = TT_PASSTHROUGH;
ctx_table[ctx_index].present = 1;
// 刷新 IOMMU TLB
iommu_flush_cache();
** 2.4. 地址转换流程**
- 设备发起 DMA 请求:
- PCI 设备生成 DMA 请求,携带 I/O 虚拟地址(IOVA)。
- Root Table 查找:
- IOMMU 根据 PCI Bus 号索引 Root Table,获取 Context Table 基地址。
- Context Table 查找:
- 根据设备 BDF 计算索引,找到对应的 Context Entry。
- 地址转换:
- 使用 Context Entry 中的
asr
(IOMMU 页表基地址)和 IOVA,查询多级页表,得到物理地址(PA)。
- 使用 Context Entry 中的
- 权限检查:
- 检查 Context Entry 中的权限位(如读/写),若非法则触发错误。
** 2.5. 实际代码参考(Linux 内核)**
在 Linux 内核中,Intel IOMMU 的实现位于 drivers/iommu/intel/iommu.c
:
- Root Table 初始化:
intel_iommu_init()
→iommu_init_domains()
- Context Entry 填充:
domain_context_mapping()
→context_set_address_root()
- 地址转换:
__iommu_dma_map()
→intel_map_page()
关键代码片段:
// 绑定设备到 Domain(drivers/iommu/intel/iommu.c)
static int domain_context_mapping(struct dmar_domain *domain, struct device *dev)
{
struct context_entry *context;
unsigned long flags;
int ret;
// 获取设备的 BDF
bus = dev->bus->number;
devfn = dev->devfn;
// 获取 Context Table
context = get_context_addr(dmar_domain, bus, devfn);
// 设置 Context Entry
context_set_address_root(context, domain->pgd);
context_set_translation_type(context, PASSTHROUGH);
context_set_present(context);
// 刷新缓存
iommu_flush_context(domain->iommu, domain->id, bus, devfn);
return 0;
}
** 2.6. 总结**
通过 Root Table → Context Table → Domain 的层级映射,Intel VT-d 实现了:
- 设备隔离:每个设备绑定到独立的 Domain,限制其 DMA 范围。
- 地址转换:IOVA 到 PA 的转换由硬件自动完成,性能接近原生。
- 权限控制:通过 Context Entry 中的权限位限制设备的读写操作。
此机制是虚拟机设备直通(如 VFIO)的硬件基础,确保了安全性与高性能的平衡。
3.0 vfio_dma_map分析
3.1. VFIO 框架背景
VFIO(Virtual Function I/O)是 Linux 内核提供的 用户态设备直通框架,允许用户态程序(如 QEMU)直接控制 PCI/PCIe 设备,同时通过 IOMMU(如 Intel VT-d、AMD-Vi)保证安全性。其核心功能包括:
- DMA 重映射:将设备 DMA 的 I/O 虚拟地址(IOVA)转换为物理地址(PA)。
- 中断隔离:防止设备直接向主机发起中断。
- 内存保护:确保设备只能访问用户态显式映射的内存区域。
vfio_dma_map
是 VFIO 实现 DMA 地址映射的核心接口。
3.2. vfio_dma_map
函数逻辑解析
函数原型
int vfio_dma_map(struct vfio_iommu *iommu, dma_addr_t iova,
unsigned long vaddr, size_t size, int prot);
- 参数解析:
iommu
:指向 VFIO IOMMU 实例的指针,管理 DMA 地址空间。iova
:设备视角的 I/O 虚拟地址(起始地址)。vaddr
:用户态内存的虚拟地址(需已通过mmap
映射)。size
:映射的内存区域大小。prot
:保护标志(如IOMMU_READ
、IOMMU_WRITE
)。
核心逻辑流程
-
参数合法性检查:
- 验证
iova
和size
是否对齐到 IOMMU 页大小(例如 4KB)。 - 检查
vaddr
对应的用户内存是否已被锁定(通过mlock
或 VFIO 自动锁定机制),防止换出。
- 验证
-
内存锁定(Pin):
- 调用
vfio_pin_pages
锁定用户内存的物理页,确保 DMA 期间物理地址不变。 - 若内存为匿名映射或文件映射,可能触发缺页异常并分配物理页。
- 调用
-
IOMMU 页表更新:
- 通过 IOMMU 驱动(如
intel_iommu_map
)建立iova
到物理地址的映射。 - 若支持嵌套页表(如 SVM),可能更新 PASID 表。
- 通过 IOMMU 驱动(如
-
反向映射维护:
- 将
iova
映射关系记录到 VFIO 的dma_list
红黑树中,用于快速查找和冲突检测。
- 将
-
TLB 刷新:
- 调用
iommu_flush_iotlb
刷新设备侧的 TLB 缓存,确保新映射立即生效。
- 调用
3.3. 关键设计原理
(1) IOVA 隔离性
- IOVA 空间分区:每个 VFIO 容器(Container)拥有独立的 IOVA 空间,避免不同虚拟机或用户进程间的 DMA 地址冲突。
- 红黑树冲突检测:在
dma_list
中检查新映射的iova
范围是否与已有映射重叠,确保安全性。
(2) 物理内存锁定
- 防止换页:DMA 操作是异步的,若内存被换出会导致设备写入错误地址。
vfio_pin_pages
通过增加页的引用计数锁定内存。 - 大页优化:自动合并连续物理页,尝试使用 2MB/1GB 大页减少 IOMMU 页表项数量。
(3) IOMMU 交互
- 地址转换粒度:IOMMU 页表通常以 4KB 为粒度,但支持大页映射以提高性能。
- 安全扩展:
- 支持
IOMMU_CACHE
控制缓存一致性(如 WC/WT/UC)。 - 启用
IOMMU_PRIV
标志时,仅允许特权 DMA 请求(需硬件支持)。
- 支持
(4) 性能权衡
- 延迟敏感:
vfio_dma_map
的调用频率直接影响虚拟机启动速度和设备性能,需尽量减少小规模映射。 - 批量映射优化:用户态应尽量合并多次
VFIO_IOMMU_MAP_DMA
ioctl 调用,减少上下文切换开销。
3.4. 典型代码路径
以 Intel IOMMU 为例,调用链如下:
vfio_dma_map()
-> vfio_pin_pages() // 锁定物理内存
-> iommu_map() // 调用 IOMMU 驱动
-> intel_iommu_map() // Intel IOMMU 实现
-> domain_pfn_mapping() // 更新页表
-> vfio_link_dma() // 记录到 dma_list
-> iommu_flush_iotlb() // 刷新 TLB
3.5. 安全与隔离
- DMA 攻击防护:通过 IOMMU 强制设备仅能访问显式映射的 IOVA 区域,阻止恶意设备读取任意主机内存。
- 权限控制:
prot
参数限制设备对内存的读写权限,例如只允许 DMA 读不可写。 - 地址空间隔离:不同 VFIO 容器的 IOVA 空间完全隔离,避免跨虚拟机 DMA 干扰。