从前面MMIO的处理可以看到,每次访问MMIO都会导致虚拟机退了到QEMU中。很多时候多个MMIO操作,这个时候可以先将前面的MMIO操作保存起来,等到最后一个MMIO的时候,再一起退出到QEMU中处理,这就是coalesced MMIO。目前只支持对写操作的coalesced MMIO。
首先介绍QEMU中部分Coalesced MMIO相关的API。
函数memory_region_add_coalescing()将MR中偏移为offset大小为size的内存区域设置为coalesced mmio区域。
void memory_region_add_coalescing(MemoryRegion *mr,
hwaddr offset,
uint64_t size)
函数memory_region_clear_coalescing()清除掉MR中的coalesced mmio区域。
void memory_region_clear_coalescing(MemoryRegion *mr)
函数memory_region_set_flush_coalesced()将mr->flush_coalesced_mmio设置为true。
void memory_region_set_flush_coalesced(MemoryRegion *mr)
函数memory_region_clear_flush_coalesced()首先会flush掉之前保存起来还未完成的MMIO操作,然后将mr->flush_coalesced_mmio设置为false。
void memory_region_clear_flush_coalesced(MemoryRegion *mr)
函数memory_region_set_coalescing()首先将之前可能创建的coalesced MMIO去掉,然后再对整个MR区域设置为coalesced mmio区域。
void memory_region_set_coalescing(MemoryRegion *mr)
在实现coalesced MMIO之前,虚拟机中对MMIO的每次访问,会立即陷入到EL2,最终通过QEMU中定义读写操作函数进行模拟。
通常需要将MR的某段内存区域设置为coalesced MMIO后,后续对该段内存区域中的一个或多个连续写会被保存到ring中,不会对每个写都立即陷入到EL2,而是在下一次读或对其他区域写之前将保存在ring中的MMIO操作flush完成。
以调用函数memory_region_add_calescing()将MR中一段内存区域设置为coalesced MMIO为例,讲述此过程。
通过系统调用KVM_REGISTER_COALESCED_MMIO往KVM注册coalesced MMIO,过程如下:
(1)分配kvm_coalesced_mmio_dev;
(2)初始化该设备的操作回调,对于coalesced MMIO,仅定义write回调;
对于写回调coalesced_mmio_wirte(),当对coalesced MMIO写时,将数据缓存到ring中;
(3)将coalesced MMIO设备注册到系统中,类似于GIC/ITS;
(4)将设备添加到kvm->coalesced_zones中;
当对coalesced MMIO作写访问时,仍然会陷入到EL2 host上,调用coalesced_mmio_write()将数据缓存到ring中。
当对coalesced MMIO作读访问时,仍然会陷入到EL2 host上(不作任何操作),返回到EL2 QEMU中,首先会flush掉之前缓存的coalesced MMIO操作,然后再执行QEMU中对应的读操作;
对其他MMIO区域的读写访问时,首先陷入到EL2 host上,然后返回到EL2 QEMU中,先flush掉之前缓存coalesced MMIO操作,再执行QEMU中对应的读写操作。
因此对于coalesced MMIO区域的真正写操作,是在对coalesced MMIO区域读操作或对其他MMIO域的读写访问时发生的,它会依次将ring中缓存的coalesced MMIO进行处理,过程如下: