Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3569463
  • 博文数量: 205
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7385
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-23 18:56
个人简介

将晦涩难懂的技术讲的通俗易懂

文章分类

全部博文(205)

文章存档

2024年(8)

2023年(9)

2022年(4)

2021年(12)

2020年(8)

2019年(18)

2018年(19)

2017年(9)

2016年(26)

2015年(18)

2014年(54)

2013年(20)

分类: LINUX

2018-08-26 09:46:50

qemu-kvm mmio的模拟

——lvyilong316

MMIOPIO的区别

I/O作为CPU和外设交流的一个渠道,主要分为两种,一种是Port I/O,一种是MMIO(Memory mapping I/O) 

前者就是我们常说的I/O端口,它实际上的应该被称为I/O地址空间。 对于x86架构来说,通过IN/OUT指令访问。PC架构一共有655368bitI/O端口,组成64KI/O地址空间,编号从0~0xFFFF。连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个32bit的端口。I/O地址空间和CPU的物理地址空间是两个不同的概念,例如I/O地址空间为64K,一个32bitCPU物理地址空间是4G 

MMIO占用CPU的物理地址空间,对它的访问可以使用CPU访问内存的指令进行。一个形象的比喻是把文件用mmap()后,可以像访问内存一样访问文件、同样,MMIO是用访问内存一样的方式访问I/O资源,如设备上的内存。MMIO不能被cache(有特殊情况,如VGA)。 

Port I/OMMIO的主要区别在于:

(1)     前者不占用CPU的物理地址空间,后者占有(这是对x86架构说的,一些架构,如IA64port I/O占用物理地址空间)。

(2)     前者是顺序访问。也就是说在一条I/O指令完成前,下一条指令不会执行。例如通过Port I/O对设备发起了操作,造成了设备寄存器状态变化,这个变化在下一条指令执行前生效。uncacheMMIO通过uncahce memory的特性保证顺序性。

(3)     使用方式不同。 

由于port I/O有独立的64KI/O地址空间,但CPU的地址线只有一套,所以必须区分地址属于物理地址空间还是I/O地址空间。早期的CPU有一个M/I针脚来表示当前地址的类型,后来似乎改了。刚才查了一下,叫request command line,没搞懂,觉得还是一个针脚。

IBM PC架构规定了一些固定的I/O端口,ISA设备通常也有固定的I/O端口,这些可以通过ICH(南桥)的规范查到。PCI设备的I/O端口和MMIO基地址通过设备的PCI configure space报告给操作系统,这些内容以前的帖子都很多,可以查阅一下。 

通常遇到写死在I/O指令中的I/O端口,如果不是ISA设备,一般都是架构规定死的端口号,可查阅规范。

qemu-kvm中的MMIO

我们知道X86体系结构上对设备进行访问可以通过PIO方式和MMIO(Memory Mapped I/O)两种方式进行, 那么QEMU-KVM具体是如何实现设备MMIO访问的呢?

MMIO是直接将设备I/O映射到物理地址空间内,虚拟机物理内存的虚拟化又是通过EPT机制来完成的, 那么模拟设备的MMIO实现也需要利用EPT机制.虚拟机的EPT页表是在EPT_VIOLATION异常处理的时候建立起来的, 对于模拟设备而言访问MMIO肯定要触发VM_EXIT然后交给QEMU/KVM去处理,那么怎样去标志MMIO访问异常呢? 查看Intel SDM知道这是通过利用EPT_MISCONFIG来实现的.那么EPT_VIOLATIONEPT_MISCONFIG的区别是什么?

EXIT_REASON_EPT_VIOLATION is similar to a "page not present" pagefault.

EXIT_REASON_EPT_MISCONFIG is similar to a "reserved bit set" pagefault.

EPT_VIOLATION表示的是对应的物理页不存在,而EPT_MISCONFIG表示EPT页表中有非法的域.

那么这里有2个问题需要弄清楚.

KVM如何标记EPTMMIO类型 ?

hardware_setup时候虚拟机如果开启了ept支持就调用ept_set_mmio_spte_mask初始化shadow_mmio_mask 设置EPT页表项最低3bit为:110b就会触发ept_msconfig110b表示该页可读可写但是还未分配或者不存在,这显然是一个错误的EPT页表项).

点击(此处)折叠或打开

  1. static void ept_set_mmio_spte_mask(void)

  2. {

  3.     /*

  4.      * EPT Misconfigurations can be generated if the value of bits 2:0

  5.      * of an EPT paging-structure entry is 110b (write/execute).

  6.      */

  7.     kvm_mmu_set_mmio_spte_mask(VMX_EPT_RWX_MASK,

  8.                    VMX_EPT_MISCONFIG_WX_VALUE);

  9. }

同时还要对EPT的一些特殊位进行标记来标志该spte表示MMIO而不是虚拟机的物理内存,例如这里

(1)set the special mask:  SPTE_SPECIAL_MASK

(2)reserved physical address bits:  the setting of a bit in the range 51:12 that is beyond the logical processor’s physic

我们可以通过以两个函数对比一下kvmMMIO pte的处理:

点击(此处)折叠或打开

  1. void kvm_mmu_set_mmio_spte_mask(u64 mmio_mask, u64 mmio_value)

  2. {

  3.     BUG_ON((mmio_mask & mmio_value) != mmio_value);

  4.     shadow_mmio_value = mmio_value | SPTE_SPECIAL_MASK;

  5.     shadow_mmio_mask = mmio_mask | SPTE_SPECIAL_MASK;

  6. }

  7. EXPORT_SYMBOL_GPL(kvm_mmu_set_mmio_spte_mask);

  8.  

  9. static void kvm_set_mmio_spte_mask(void)

  10. {

  11.     u64 mask;

  12.     int maxphyaddr = boot_cpu_data.x86_phys_bits;

  13.  

  14.     /*

  15.      * Set the reserved bits and the present bit of an paging-structure

  16.      * entry to generate page fault with PFER.RSV = 1.

  17.      */

  18.      /* Mask the reserved physical address bits. */

  19.     mask = rsvd_bits(maxphyaddr, 51);

  20.  

  21.     /* Set the present bit. */

  22.     mask |= 1ull;

  23.  

  24. #ifdef CONFIG_X86_64

  25.     /*

  26.      * If reserved bit is not supported, clear the present bit to disable

  27.      * mmio page fault.

  28.      */

  29.     if (maxphyaddr == 52)

  30.         mask &= ~1ull;

  31. #endif

  32.  

  33.     kvm_mmu_set_mmio_spte_mask(mask, mask);

  34. }

KVM在建立EPT页表项之后设置了这些标志位再访问对应页的时候会触发EPT_MISCONFIG退出了,然后调用handle_ept_misconfig-->handle_mmio_page_fault来完成MMIO处理操作。

QEMU如何标记设备的MMIO

这里以e1000网卡模拟为例,设备初始化MMIO时候时候注册的MemoryRegionIO类型(不是RAM类型)

点击(此处)折叠或打开

  1. static void

  2. e1000_mmio_setup(E1000State *d)

  3. {

  4.     int i;

  5.     const uint32_t excluded_regs[] = {

  6.         E1000_MDIC, E1000_ICR, E1000_ICS, E1000_IMS,

  7.         E1000_IMC, E1000_TCTL, E1000_TDT, PNPMMIO_SIZE

  8.     };

  9.     // 这里注册MMIO,调用memory_region_init_io,mr->ram = false!!!

  10.     memory_region_init_io(&d->mmio, OBJECT(d), &e1000_mmio_ops, d,

  11.                           "e1000-mmio", PNPMMIO_SIZE);

  12.     memory_region_add_coalescing(&d->mmio, 0, excluded_regs[0]);

  13.     for (i = 0; excluded_regs[i] != PNPMMIO_SIZE; i++)

  14.         memory_region_add_coalescing(&d->mmio, excluded_regs[i] + 4,

  15.                                      excluded_regs[i+1] - excluded_regs[i] - 4);

  16.     memory_region_init_io(&d->io, OBJECT(d), &e1000_io_ops, d, "e1000-io", IOPORT_SIZE);

  17. }

结合QEMU-KVM内存管理知识我们知道, QEMU调用kvm_set_phys_mem注册虚拟机的物理内存到KVM相关的数据结构中的时候 会调用memory_region_is_ram来判断该段物理地址空间是否是RAM设备, 如果不是RAM设备直接return

点击(此处)折叠或打开

  1. static void kvm_set_phys_mem(KVMMemoryListener *kml,

  2.                              MemoryRegionSection *section, bool add)

  3. {

  4.     ......

  5.     if (!memory_region_is_ram(mr)) {

  6.         if (writeable || !kvm_readonly_mem_allowed) {

  7.             return; // 设备MR不是RAM但可以写,那么这里直接return不注册到kvm里面

  8.         } else if (!mr->romd_mode) {

  9.             /* If the memory device is not in romd_mode, then we actually want

  10.              * to remove the kvm memory slot so all accesses will trap. */

  11.             add = false;

  12.         }

  13.     }

  14.     ......

  15. }

对于MMIO类型的内存QEMU不会调用kvm_set_user_memory_region对其进行注册, 那么KVM会认为该段内存的pfn类型为KVM_PFN_NOSLOT 进而调用set_mmio_spte来设置该段地址对应到spte 而该函数中会判断pfn是否为NOSLOT标记以确认这段地址空间为MMIO

点击(此处)折叠或打开

  1. static bool set_mmio_spte(struct kvm_vcpu *vcpu, u64 *sptep, gfn_t gfn,

  2.               kvm_pfn_t pfn, unsigned access)

  3. {

  4.     if (unlikely(is_noslot_pfn(pfn))) {

  5.         mark_mmio_spte(vcpu, sptep, gfn, access);

  6.         return true;

  7.     }

  8.  

  9.     return false;

  10. }

总结

MMIO是通过设置spte的保留位来标志的. 虚拟机内部第一次访问MMIOgpa时,发生了EPT_VIOLATION然后check gpa发现对应的pfn不存在(QEMU没有注册),那么认为这是个MMIO,于是set_mmio_spte来标志它的spte是一个MMIO 后面再次访问这个gpa时就发生EPT_MISCONFIG了,进而愉快地调用handle_ept_misconfig -> handle_mmio_page_fault -> x86_emulate_instruction 来处理所有的MMIO操作了.

阅读(8190) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~