直接内存读取(DMA)和IOMMU
DMA(Direct Memory Access)即为“直接内存读取”的意思,换句话说DMA就是用来传输数据的,它也属于一个外设。只是在传输数据时,无需占用CPU。
DMA要存取的内存地址称为DMA地址(也可称为BUS address)。在DMA技术刚出现的时候,DMA地址都是物理内存地址,简单直接,但缺点是不灵活, 比如,
- 要求物理内存必须是连续的一整块;
- 一些老的32位PCI设备不能访问高位地址(>4G),Linux因此通过Bounce Buffer来实现buffer的拷贝,导致性能下降;;
后来DMAR就出现了。 DMAR意为DMA Remapping, 是Intel为支持虚拟机而设计的I/O虚拟化技术,I/O设备访问的DMA地址不再是物理内存地址,而要通过DMA remapping硬件进行转译,DMA remapping硬件会把DMA地址翻译成物理内存地址,并检查访问权限等等。负责DMA remapping操作的硬件称为IOMMU。做个类比:大家都知道MMU是支持内存地址虚拟化的硬件,MMU是为CPU服务的;而IOMMU是为I/O设备服务的,是将DMA地址进行虚拟化的硬件。
还是用MMU作类比, 当CPU访问一个在地址翻译表中不存在的地址时,就会触发一个fault,Linux kernel的fault处理例程会判断这是合法地址还是非法地址,如果是合法地址,就分配相应的物理内存页面并建立从物理地址到虚拟地址的翻译表项,如果是非法地址,就给进程发个signal,产生core dump。IOMMU也类似,当I/O设备进行DMA访问也可能触发fault,有些fault是recoverable的,有些是non-recoverable的,这些fault都需要Linux kernel进行处理,所以IOMMU就利用中断(interrupt)的方式呼唤内核,
IOMMU虚拟化 (VT-d)
对于虚拟机中的虚拟IO设备,比如网卡,磁盘等,虚拟化层软件(VMM)能够截获虚拟设备的IO请求, 然后将请求进行处理后再转发到物理设备上。我们通常称其为IO虚拟化,相比传统的处理方式,IO虚拟化显然拉长拉长了IO路径,造成延时增加,无法满足一些延时敏感应用的性能要求。
为了提升虚拟机的IO性能,通常最直接的方法就是IO直通(Passthrough), 允许虚拟机直接访问设备,避免VMM带来的性能损耗。VT-d是Intel Virtualization Technology for Directed IO的缩写, 从名字就可以看出,这是专门用来辅助虚拟机直接访问IO设备的技术。
虚拟机中物理地址GPA(Guest Physical Address)并不是物理地址,必须经过虚拟化层>软件的翻译才能获得真正的主机物理地址(Host Physical Address),然而虚拟化层无法通过软件方式截获设备的DMA操作。为了解决这个问题, VT-d技术提供了DMA重映射。
VT-d引入了和内存页表类似的IO页表机制,DMA内存可以通过这个页表进行重映射:因为一般DMA地址必须16M以下的物理地址,或者是通过IOMMU映射获得,所以虚拟化层软件就可以通过映射虚拟机>中的16M以下地址空间以及截获vIOMMU操作来设置并更新这个页表,即GPA到HPA的转换。
Linux DMAR
Linux中查看中断信息的时候可以看到到一些名称为dmarX的设备:
cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 127 0 0 0 IR-IO-APIC-edge timer
1: 2 0 0 0 IR-IO-APIC-edge i8042
...
48: 0 0 0 0 DMAR_MSI-edge dmar0
...
Intel处理器开启IOMMU需要在grub.conf kernel cmdline那行添加intel_iommu=on
, AMD则需要添加iommu=pt iommu=1
,有开启iommu的dmesg日志输出类似如下:
kernel: [ 0.000000] Kernel command line: BOOT_IMAGE=/boot/vmlinuz-4.14.81.bm.7-amd64 root=UUID=708d38e3-f6b2-43ea-a164-6233d1f0d5b6 ro cgroup_enable=memory swapaccount=1 intel_idle.max_cstate=0 processor.max_cstate=0 net.ifnames=1 biosdevname=0 nopti iommu=pt intel_iommu=on crashkernel=1G-:512M quiet crashkernel=384M-:128M
kernel: [ 0.000000] DMAR: IOMMU enabled
内核怎么知道哪些设备需要DMA Remapping呢?是通过ACPI table知道的,因为需要DMA Remapping的设备必须在firmware/BIOS中登记。
只要BIOS中打开了Intel VT-d,我们就总会在kernel messages中看到初始化DMAR table的信息,无论intel_iommu参数是on还是off。
kernel: [ 0.108105] DMAR: DRHD base: 0x000000d37fc000 flags: 0x0
kernel: [ 0.108113] DMAR: dmar0: reg_base_addr d37fc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
kernel: [ 0.108114] DMAR: DRHD base: 0x000000e0ffc000 flags: 0x0
kernel: [ 0.108119] DMAR: dmar1: reg_base_addr e0ffc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
kernel: [ 0.108120] DMAR: DRHD base: 0x000000ee7fc000 flags: 0x0
kernel: [ 0.108124] DMAR: dmar2: reg_base_addr ee7fc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
kernel: [ 0.108125] DMAR: DRHD base: 0x000000fbffc000 flags: 0x0
kernel: [ 0.108129] DMAR: dmar3: reg_base_addr fbffc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
kernel: [ 0.108130] DMAR: DRHD base: 0x000000aaffc000 flags: 0x0
kernel: [ 0.108133] DMAR: dmar4: reg_base_addr aaffc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
kernel: [ 0.108134] DMAR: DRHD base: 0x000000b87fc000 flags: 0x0
kernel: [ 0.108137] DMAR: dmar5: reg_base_addr b87fc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
kernel: [ 0.108138] DMAR: DRHD base: 0x000000c5ffc000 flags: 0x0
kernel: [ 0.108141] DMAR: dmar6: reg_base_addr c5ffc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
kernel: [ 0.108142] DMAR: DRHD base: 0x0000009d7fc000 flags: 0x1
kernel: [ 0.108145] DMAR: dmar7: reg_base_addr 9d7fc000 ver 1:0 cap 8d2078c106f0466 ecap f020df
kernel: [ 0.108146] DMAR: RMRR base: 0x0000006e2cd000 end: 0x0000006e7ccfff
kernel: [ 0.108148] DMAR: RMRR base: 0x0000006f2e2000 end: 0x0000006f2e4fff
kernel: [ 0.108148] DMAR: ATSR flags: 0x0
kernel: [ 0.108149] DMAR: ATSR flags: 0x0
kernel: [ 0.108151] DMAR-IR: IOAPIC id 12 under DRHD base 0xc5ffc000 IOMMU 6
kernel: [ 0.108152] DMAR-IR: IOAPIC id 11 under DRHD base 0xb87fc000 IOMMU 5
kernel: [ 0.108152] DMAR-IR: IOAPIC id 10 under DRHD base 0xaaffc000 IOMMU 4
kernel: [ 0.108153] DMAR-IR: IOAPIC id 18 under DRHD base 0xfbffc000 IOMMU 3
kernel: [ 0.108154] DMAR-IR: IOAPIC id 17 under DRHD base 0xee7fc000 IOMMU 2
kernel: [ 0.108154] DMAR-IR: IOAPIC id 16 under DRHD base 0xe0ffc000 IOMMU 1
kernel: [ 0.108155] DMAR-IR: IOAPIC id 15 under DRHD base 0xd37fc000 IOMMU 0
kernel: [ 0.108156] DMAR-IR: IOAPIC id 8 under DRHD base 0x9d7fc000 IOMMU 7
kernel: [ 0.108156] DMAR-IR: IOAPIC id 9 under DRHD base 0x9d7fc000 IOMMU 7
kernel: [ 0.108157] DMAR-IR: HPET id 0 under DRHD base 0x9d7fc000
kernel: [ 0.108158] DMAR-IR: x2apic is disabled because BIOS sets x2apic opt out bit.
kernel: [ 0.108159] DMAR-IR: Use 'intremap=no_x2apic_optout' to override the BIOS setting.
kernel: [ 0.112463] DMAR-IR: Enabled IRQ remapping in xapic mode
如果你想让kernel中与Intel VT-d有关的软件模块完全关闭,仅仅使用启动参数intel_iommu=off是不够的,而必须重新编译内核–在config中配置CONFIG_DMAR=n,或者用另一种方法:在BIOS中关闭Intel VT-d。