前言: 简单回顾一下前文,《内存映射技术分析》描述了虚拟内存的管理、内存映射;《物理内存管理》介绍了物理内存管理。《内存回收》介绍了一下PFRA内存回收。 上述三篇,简单建立Linux的内存管理模型,下面开始分析MMIO技术。 分析: 1,MMIO MMIO,即Memory mapping I/O;在x86上,CPU如果想要和外部交互数据,一种是使用in、out类型的端口访问的指令;一种是mov类型的读写内存的指令。对于前者来讲,就是PortIO(PIO);对于后者,就是MMIO。(这里说明一下,ARM上只有MMIO,简单了一些~) 所以,如果CPU和外部的设备交互,也逃不开这两种方式。对于PIO,如前文《PIO技术分析》中所说,CPU只要截获VM(Virtual Machine)的in、out指令,就可以知道CPU想要访问设备,那么用软件来模拟硬件的行为,就可以让VM觉得自己有设备。 对于MMIO,则相对复杂一点。首先来看一下VM中的iomem的layout,在VM中敲cat /proc/iomem:
2,System RAM 说明一下,作者给VM配置了6G的RAM。如上图中的System RAM: 00001000-0009fbff : System RAM 00100000-bffd7fff : System RAM -->这段大小约3G,地址范围在约低0G~3G的范围内。 100000000-1bfffffff : System RAM -->这段大小约3G,在4G~7G的范围内。 原因就是中间有1G的地址空间给了PCI、IOAPIC、LAPIC等使用。 在qemu-2.8.0-rc4/hw/i386/pc_piix.c中:
可见,在qemu中,就已经提前把内存分块,并留下了3G~4G的1G的物理地址空间。
那么问题来了,为什么不让内存连续在一起呢?内存连在一起的好处在于memory block之间没有hole,可以节省一些page struct(spare memory的情况下,节省一点计算量)。因为qemu并不知道将来要运行的VM的操作系统是否支持64bit,在32bit下,地址空间只有0~4G,那么可以减少一些内存,但是不能没有PCI,APIC等。
3,PCI Address 在VM中敲lspci:
以00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)为例,对应的是iomem中: febc0000-febdffff : 0000:00:03.0 febc0000-febdffff : e1000 00:03.0表示PCI地址:bus 0x00,device 0x03, function 0x00。 febc0000-febdffff表示网卡的MMIO的地址。 e1000是常用的虚拟网卡。 此时,CPU可以通过访问febc0000-febdffff之间的地址和e1000网卡交互。需要注意的是,这里是VM的物理地址,操作系统加载之后,跑在protected mode下,是访问虚拟地址的。所以还需要做一次mapping。 4,misconfig 继续上文3步,如果CPU访问了febc0000-febdffff,就说明在访问网卡设备了。所以,CPU需要截获虚拟机访问的具体地址,并发生了异常,从VM-mode下退出来,让qemu继续处理,模拟硬件的行为即可。这就是MMIO下的设备模拟过程,CPU截获MMIO的是misconfig异常。 在intel的官方文档中:EPT misconfiguration. An attempt to access memory with a guest-physical address encountered a misconfigured EPT paging-structure entry. 结合上面的地址信息,PCI的地址空间确实也不在内存的地址空间范围内。 5,KVM_EXIT_MMIO 如果发生了misconfig,KVM会返回KVM_EXIT_MMIO的exit reason。 6,handle mmio qemu-2.8.0-rc4/kvm-all.c中kvm_cpu_exec函数处理KVM_EXIT_MMIO:
根据地址,找到对应的设备。再继续模拟设备的行为。 后记: 设备的模拟,无论是PIO,还是MMIO,思路都差不多:CPU能够感知到访问了设备,并根据访问的具体的地址,找到对应的设备,最后模拟出来硬件的行为。 区别在于,两种方式下的处理不同,明显MMIO的路径要长一些,而且更加复杂一些。 Virtio设备本质上也是一个PCI设备(例如例子中的fe000000-fe003fff : virtio-pci-modern,就是作者挂载的Virtio-Blk),在Guest中需要kick Host,默认会使用PIO,而非MMIO。原因就是PIO的性能略好于MMIO。作者也实验过,在PIO VS MMIO的对比中,MMIO和PIO的性能都是ns级别的,二者相差不是很多,总体来说,PIO的数据略好于MMIO。