Linux x86-64 IOMMU详解(三)——Intel IOMMU(硬件IOMMU)的功能与基本原理

前言

本系列的第一篇文章已经提到,IOMMU分别有软件和硬件实现方式。软件IOMMU就是SWIOTLB,上一篇文章已经进行过详尽的介绍。而对于硬件IOMMU,有多个厂商都设计了自己的IOMMU。由于我的开发机器为Intel x86平台,因此接触的是Intel IOMMU。
刚开始,我也找过网上很多资料,但是它们要么是基于较旧的Linux内核版本,要么理解不够深入。因此,即使我并没有深入地掌握Intel IOMMU的每一处细节,我还是愿意将自己所了解的知识,以尽可能浅显的形式展现出来。
本文的三张截图,均来自《Intel VT-d SPEC》,它是Intel IOMMU的官方文档。
关于Intel IOMMU,我打算分两篇文章来写。本文介绍Intel IOMMU的功能与基本原理,而下一篇文章(不知道咕咕咕到哪天发)将会介绍Intel IOMMU的配置及一些核心代码。并且,随着学习的深入,这两篇文章发布之后可能还会更新。

预备知识

如果读者了解如下知识(能深入理解当然更好),你将会更容易理解本文:

  1. 了解DMA(直接内存访问)的基本原理。
  2. 了解虚拟内存中的页表机制。
  3. 使用过虚拟机(例如VMWare Workstation)。
  4. 了解SWIOTLB原理(可翻阅本系列第二篇文章)。

术语汇总

本文涉及到诸多术语,在此先进行汇总。

  1. Intel IOMMU:全称为Intel Input/Output Memory Management Unit。根据语境,该术语在本文中,既可表示IOMUU的一种实现方式,也可表示Intel IOMMU硬件。在《Intel VT-d SPEC》中,这两种含义,都有等价表述——前者的等价表述是Intel VT-d(全称为Intel Virtualization Technology for Direct I/O),后者的等价表述是Remapping Hardware。
  2. 虚拟机(Guest):顾名思义,启用虚拟化(运行虚拟机)时的虚拟机器。
  3. 宿主机(Host):启用虚拟化时,虚拟机运行所基于的物理机器,称为宿主机。
  4. HPA:Host Physical Address,在宿主机上的物理地址。不启动虚拟化时,设备看到的就是HPA。
  5. GPA:Guest Physical Address,在虚拟机上的物理地址。启用虚拟化时,设备看到的是GPA。Intel IOMMU负责将GPA映射为HPA。
  6. Domain:一个虚拟机所拥有的所有资源(在本文中主要是涉及内存和CPU),称为这个虚拟机的Domain。
  7. 重映射(Remapping):启用虚拟化时,设备发出的DMA和中断请求,不会直接被物理机器接收,而是会被Intel IOMMU所截获,而后发送到它对应的Guest虚拟机,之后再映射到宿主机的内存或CPU。这个过程称为重映射。

Intel IOMMU与SWIOTLB的比较

Intel IOMMU与SWIOTLB同为IOMMU的实现方式,二者在目的上存在共性——都能够解决“设备无法直接寻址到内核分配给它的DMA buffer”这一问题。
二者的不同点在于:

  1. 功能。Intel IOMMU最大的功能,并不是用来解决“设备寻址能力有限”这一问题——解决上述问题仅仅是Intel IOMMU顺便附带的一个功能。事实上,Intel IOMMU只是我们从功能角度的称谓,它的全称叫做Intel Virtualization Technology for Direct I/O,简称为Intel VT-d。其核心功能是重映射(Remapping),指的是在启用虚拟化技术(Virtualization Technology,可以简单理解为开启虚拟机)的机器上,对I/O操作(包括DMA和中断)进行重映射。关于什么是重映射,后文将会详细介绍。
  2. 实现方式。SWIOTLB底层是用memcpy()实现的,需要CPU的参与;而Intel IOMMU是通过专门的硬件实现的。

什么是重映射

我们先举例说明重映射的应用场景。以最常见的虚拟机软件VMWare Workstation为例,经常使用它的用户一定会有这样的经历:你在一台Windows机器上,使用VMWare Workstation,开启了一台Linux虚拟机。为了方便表述,本文称此时的物理机(Host,Windows机器)为宿主机,而虚拟机(Guest,Linux机器)仍称为虚拟机
然后,你往机箱上插入一个U盘。U盘就是一个设备,它将会进行I/O操作。这时,系统将会弹出对话框,询问你要把U盘连接到哪台机器,你可以选择连接到Windows机器(宿主机)或是Linux机器(虚拟机)。如果你选择连接到虚拟机,那么该U盘之后的I/O操作,就会触发重映射。

为了理解重映射,我们首先考虑没有启用虚拟化(即没有启动虚拟机)的机器。对于这样的机器,设备发起DMA请求,得到的就是机器物理地址;设备的中断请求,也会被CPU直接接收,而后通过中断向量表,找到对应的中断服务程序,处理该中断请求。这个过程是没有重映射的。

现在,假设一台宿主机上开启了虚拟机(甚至可以开启多个虚拟机),并且设备连接到其中某一台虚拟机。这就意味着,设备能访问到的,只有它所连接的虚拟机的所有资源(一个虚拟机的所有资源,称为一个Domain),而不会访问到其他虚拟机,更不能直接访问宿主机。因此,我们必须确保:

  1. 设备发起DMA请求时,操作系统返回给它的,不能是宿主机物理地址(Host Physical Address,HPA),而只能是虚拟机物理地址(Guest Physical Address,GPA)。Intel IOMMU维护从GPA到HPA的映射关系。
  2. 设备的DMA和中断请求,只能被它所连接的虚拟机接收,而不能被其他虚拟机接收,也不能被宿主机直接接收。

这个过程对于设备来说应当是透明的。从设备的视角来看,它发起DMA请求,得到一个内存物理地址,并向该地址读写内容;或者,它发起中断请求,最终得到某个中断服务程序的响应。这一切都应该正常发生,与设备所连接的是虚拟机还是宿主机无关——事实上,设备只知道自己连接的是一台“机器”,而根本不知道自己连接的机器是虚拟机还是宿主机。

在这个过程中,Intel IOMMU完成如下功能:

  1. DMA重映射:Intel IOMMU截获设备的DMA请求,将它重定位到设备所连接的虚拟机。而后,该虚拟机将DMA请求传递给宿主机,宿主机分配DMA Buffer,并返回用于DMA Buffer的HPA。Intel IOMMU将HPA转换为GPA,而后返回GPA给设备。之后,设备向GPA进行DMA操作,Intel IOMMU又将GPA映射为HPA。
  2. 中断重映射:Intel IOMMU截获设备的中断请求,将它重定位到设备所连接的虚拟机。而后,找到该虚拟机所占有的CPU,将中断请求转发给这些CPU,而后这些CPU找到适用于该虚拟机的中断重映射表(Interrupt Remapping Table,IRT,原理与中断向量表相同,只是每个虚拟机都有一份),通过中断号索引到对应的中断服务程序并执行。

DMA重映射中的I/O页表

对于DMA重映射,我们完全可以仿照虚拟内存机制(将内存虚拟地址转换为内存物理地址)来理解。事实上,Intel IOMMU硬件维护了一套用于DMA重映射的I/O页表机制,它与虚拟内存机制中的多级页表原理基本一致,只有一点不同——其中的顶级I/O页表,并不是直接用于寻址(Address Translation),而是用于将DMA请求映射到不同的虚拟机Domain。从次级I/O页表开始,才是真正的寻址过程。
具体来说,在虚拟化环境下,设备发起DMA请求时,除了常规参数以外,还需要附带一系列标识符(称为Request Identifier或Source ID),包括Bus/Device/Function(具体含义笔者也不是很了解…)。标识符的作用就是让Intel IOMMU确定该设备需要将DMA请求发送到哪个虚拟机Domain。
在这里插入图片描述
Intel IOMMU根据这些标识符,在顶级I/O页表中进行索引,找到对应的次级I/O页表。事实上,顶级I/O页表又可分为两部分——Root Table和Context Table,如下图所示。Intel IOMMU先根据Request Identifier的Bus区段,在Root Table中索引;而后,再根据Device和Function区段,在Context Table中索引,从而找到对应的次级页表(下图中的Second-Level Page Table)。
从次级页表开始,寻址过程就与内存虚拟地址转换为物理地址的过程,完全相同了。
此外,下图中,有两个Context Table都指向了同一个Domain——Domain A。这表明,不同的设备最终连接到同一个虚拟机,这也是合情合理的——一台机器当然可以连接多个设备,不是吗?
在这里插入图片描述
如果上述解释还不够详细,请看《Intel VT-d SPEC》中的原文截图,其中重要语句已用红色下划线标注:
在这里插入图片描述

  • 14
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
这里我提供一个简单的64位RISC-V IOMMU模块的verilog代码示例。这个模块支持基本的输入输出设备和主存之间的数据传输,并提供了虚拟化和内存隔离支持。 ``` module riscv_iommu( input clk, input rst, input [63:0] in_addr, input [63:0] out_addr, input [2:0] access_mode, input [63:0] page_table_base, input [63:0] page_table_mask, output [63:0] data_in, input [63:0] data_out ); /* 定义一些常量 */ localparam PAGE_SIZE = 8192; localparam PAGE_OFFSET_BITS = 13; localparam PAGE_TABLE_SIZE = 4096; /* 定义一些寄存器 */ reg [63:0] mem[1024]; // 主存 reg [63:0] in_data; // 输入数据 reg [63:0] out_data; // 输出数据 reg [63:0] page_table[PAGE_TABLE_SIZE]; // 页表 reg [63:0] page_table_entry; // 页表项 /* 定义一些辅助函数 */ function [63:0] translate_address; input [63:0] addr; input [63:0] page_table_base; input [63:0] page_table_mask; reg [63:0] translated_addr; reg [31:0] page_offset; reg [31:0] page_index; reg [31:0] page_table_index; page_offset = addr[PAGE_OFFSET_BITS-1:0]; page_index = addr[63:PAGE_OFFSET_BITS]; page_table_index = page_index % PAGE_TABLE_SIZE; page_table_entry = page_table[page_table_index]; if (page_table_entry[0]) begin // 检查页表项的有效位 translated_addr = {page_table_entry[62:13], page_offset}; end else begin translated_addr = 64'h0; // 无效地址 end if ((addr & page_table_mask) != (translated_addr & page_table_mask)) begin translated_addr = 64'h0; // 无效地址 end return translated_addr; endfunction /* 主要的IOMMU功能 */ always @(posedge clk) begin if (rst) begin in_data <= 0; out_data <= 0; end else begin case(access_mode) 3'b000: in_data <= data_in; // 读模式 3'b001: mem[in_addr] <= in_data; // 写模式 3'b010: out_data <= mem[out_addr]; // 读取主存中的数据 3'b011: out_data <= data_out; // 直接输出数据 3'b100: begin // 页表读模式 out_data <= page_table[in_addr[31:3]]; end 3'b101: begin // 页表写模式 page_table[in_addr[31:3]] <= data_in; end 3'b110: begin // 虚拟地址翻译 out_data <= translate_address(in_addr, page_table_base, page_table_mask); end endcase end end endmodule ``` 这个模块包含了一个主存数组、一个输入输出端口和一个页表。在读模式下,数据输入端口的数据会被存储在in_data寄存器中;在写模式下,in_data寄存器中的数据会被写入到主存中。在读取主存中的数据时,out_addr指定了要读取的地址;在直接输出数据模式下,data_out端口中的数据会被直接输出到外部。在页表读写模式下,in_addr指定了要读写的页表项地址。在虚拟地址翻译模式下,in_addr指定了要翻译的虚拟地址,page_table_base和page_table_mask分别指定了页表的基地址和掩码。在翻译过程中,模块会根据页表和掩码计算出物理地址,并将其输出到out_data端口中。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值