3. IOMMU数据结构:I/O页表、设备上下文与域
好,咱们今天聊聊IOMMU里最核心的三个数据结构。说实话,我刚接触IOMMU那会儿,最头疼的就是搞不清这几个东西之间的关系。I/O页表、设备上下文、域——这三个概念就像一家三口,各有各的职责,但又紧密配合。
3.1 I/O页表(IOPT)——地址翻译的“字典”
I/O页表,说白了就是一张地址映射表。它告诉IOMMU:“设备A想访问虚拟地址0x1000,你帮我翻译成物理地址0x8000。” 这和CPU的页表原理类似,但有个关键区别——CPU页表是给进程用的,而I/O页表是给设备用的。
我习惯把I/O页表理解成一本“字典”。设备发起DMA时,它手里拿的是虚拟地址(也叫I/O虚拟地址,IOVA),IOMMU就翻这本字典,找到对应的物理地址。
举个例子,一个简单的二级页表结构长这样:
// 伪代码:I/O页表查找过程
uint64_t iova_to_phys(struct iopt *iopt, uint64_t iova) {
uint64_t l1_index = (iova >> 21) & 0x1FF; // 一级索引
uint64_t l2_index = (iova >> 12) & 0x1FF; // 二级索引
uint64_t *l1_table = iopt->l1_base;
uint64_t l1_entry = l1_table[l1_index];
uint64_t *l2_table = phys_to_virt(l1_entry & PAGE_MASK);
uint64_t l2_entry = l2_table[l2_index];
return (l2_entry & PAGE_MASK) + (iova & 0xFFF);
}
嗯,这里要注意:页表项里不光有物理地址,还有权限位。比如可读、可写、是否允许非缓存访问等。我在项目中遇到过一个问题——设备DMA写操作一直报错,查了半天发现是页表项里没设“可写”位。这种坑,你想想看,排查起来多费劲。
3.2 设备上下文(Device Context)——设备的“身份证”
设备上下文,我管它叫设备的“身份证”。每个PCIe设备在IOMMU里都有一个对应的上下文条目。这个条目记录了设备的基本信息,比如:
- 设备所属的域(Domain)
- 设备使用的I/O页表指针
- 设备的能力标志(是否支持PASID、是否支持ATS等)
设备上下文通常存放在内存中的一个表格里,叫设备上下文表(Device Context Table)。IOMMU硬件通过设备的BDF号(Bus:Device.Function)来索引这个表。
设备上下文的结构大致如下:
struct device_context {
uint64_t domain_id; // 所属域ID
uint64_t iopt_ptr; // I/O页表基地址
uint32_t flags; // 各种标志位
uint32_t pasid_enabled; // 是否启用PASID
// ... 其他字段
};
3.3 域(Domain)——隔离的“沙盒”
域这个概念,我觉得是IOMMU里最优雅的设计。一个域就是一个独立的地址空间。你可以把多个设备放到同一个域里,它们共享同一套I/O页表。也可以让每个设备独享一个域,实现完全的地址隔离。
为什么会需要域?举个例子:虚拟化场景下,每个虚拟机(VM)通常对应一个域。VM里的所有设备(比如虚拟网卡、虚拟磁盘)都共享这个域的地址空间。这样,IOMMU就能保证VM A的设备无法访问VM B的内存。
域、设备上下文、I/O页表三者的关系,我用一张图来展示:
从这张图能看出来:一个域可以包含多个设备上下文,每个设备上下文又指向一个I/O页表。实际应用中,同一个域里的设备上下文通常指向同一个I/O页表,这样它们就能共享地址空间。
3.4 三者如何协同工作
咱们捋一下整个流程:
- 设备发起DMA:设备发出一个内存访问请求,携带I/O虚拟地址(IOVA)。
- IOMMU查设备上下文:IOMMU根据设备的BDF号,找到对应的设备上下文条目。
- 获取I/O页表:从设备上下文里读出I/O页表的基地址。
- 地址翻译:IOMMU遍历I/O页表,把IOVA翻译成物理地址。
- 权限检查:检查页表项里的权限位,确认设备是否有权访问。
- 执行访问:通过后,IOMMU把物理地址发到内存控制器,完成DMA。
3.5 实际项目中的注意事项
最后,分享几个我在项目中踩过的坑:
- 页表分配要连续:I/O页表的多级结构要求每级页表在物理内存中连续。用vmalloc分配的内存可能不连续,得用__get_free_pages或alloc_pages。
- 设备上下文表要对齐:硬件要求设备上下文表的基地址按4KB对齐。我见过有人用kmalloc分配,结果地址没对齐,IOMMU直接报错。
- 域的生命周期管理:域里的所有设备都移除后,才能释放域。否则,设备还在用,域却没了,那DMA就会访问到野指针。
嗯,这三个数据结构搞明白了,IOMMU的基本框架也就掌握了。下次咱们可以深入聊聊页表的具体格式和硬件加速的细节。