随便记记,没有章法。
VTTBR_EL2和TTBR1_EL2有啥区别?
VTTBR_EL2是内存虚拟化中stage2页表的基地址存放的寄存器,高16位存放了VMID,用于提高VM TLB性能;
TTBR1_EL2,是指在VHE开启的情况下host OS可以在EL2运行,这时候内核使用的页表基地址就存放在这里;
设备模拟分为软件模拟和直接assign。前者通过stage 2 entry 设置为fault由hypervisor完成模拟,后者由hypervisor将物理设备内存直接映射到VM。
当stage 2 fault产生时hypervisor需要知道触发fault的地址,读or写,属于哪个设备,哪个寄存器,size大小。HPFAR_EL2负责保存触发fault的IPA,ESR_EL2存放了出错的详细信息。问题:怎么知道是哪个设备触发的fault?
有些系统寄存器经常访问,如果每次都trap会影响性能,所以arm提供了相应的virtual寄存器,比如VPIDR_EL2,是MIDR_EL1的virtual版;VMPIDR_EL2是VPIDR_EL1的virtual版。
HFGRTR_EL2, Hypervisor Fine-Grained Read Trap Register,这是一个非常重要的寄存器,负责控制guest在读系统寄存器的时候的行为,是否trap。
ARM DDI 0487中D19.2以H开头的系列寄存器可以控制在guest读写执行某些敏感指令的行为。
enable HCR_EL2.IMO中断会route到EL2。还由FMO,AMObit也有同样的作用。
HCR_EL2的E2H控制vhe的行为,如果打开则由TGE决定当前是VM还是host,1是host,表面host kernel在EL2,也就不需要virtual interrupt了。所以在VM中E2H=1, TGE=0, IMO=FMO=AMO=1,VM=1
中断虚拟化有两种方式,HCR模拟,VI,VF,VSE可以发送虚拟中断;使用vgic;
中断控制器的模拟
GIC的虚拟化只支持cpu interface,对GICD, GICR, GITS的模拟只能依靠MMIO通过data abort来软件模拟,一般是KVM会使用in-kernel的方式模拟。
kvm_arm_gicv3_realize会真正实现gic。通过ioctl KVM_DEV_ARM_VGIC_GRP_ADDR注册gicd gicr的地址到kvm。
kvm_set_irq通过ioctl KVM_IRQ_LINE向VM注入中断。KVM_SIGNAL_MSI可以注入msi中断。KVM_SET_GSI_ROUTING可以设置gsi 路由
创建虚拟机
打开/dev/kvm得到kvm_fd,然后使用ioctl create vm;创建一个vm实例。
创建vcpu
qemu在cpu的realizefn(arm_cpu_realizefn)里面会初始化vcpu。初始化vcpu主要在qemu_init_vcpu里面。最重要的是创建一个vcpu thread,运行kvm_vcpu_thread_fn,在这个函数中,在VMfd上调用ioctl KVM_CREATE_VCPU创建vcpu,kvm会初始化对应vcpu,接着线程进入while循环,
do { if (cpu_can_run(cpu)) { r = kvm_cpu_exec(cpu); if (r == EXCP_DEBUG) { cpu_handle_guest_debug(cpu); } } qemu_wait_io_event(cpu); } while (!cpu->unplug || cpu_can_run(cpu));
如果现在还处于stop状态就等着,如果可以运行就执行kvm_cpu_exec。在这个函数中会使用ioctl KVM_RUN真正是虚拟机进入guest运行。
qemu在arm64虚拟机上创建内存的调用链:
#0 ram_block_add (new_block=0x555556a64d00, errp=0x7fffffffdd40) at ../softmmu/physmem.c:1975 #1 0x0000555555e3a61b in qemu_ram_alloc_internal (size=size@entry=2147483648, max_size=max_size@entry=2147483648, resized=resized@entry=0x0, host=host@entry=0x0, ram_flags=ram_flags@entry=0, mr=mr@entry=0x555556c926e0, errp=0x7fffffffddb0) at ../softmmu/physmem.c:2180 #2 0x0000555555e3d307 in qemu_ram_alloc (size=size@entry=2147483648, ram_flags=ram_flags@entry=0, mr=mr@entry=0x555556c926e0, errp=errp@entry=0x7fffffffddb0) at ../softmmu/physmem.c:2200 #3 0x0000555555e33cad in memory_region_init_ram_flags_nomigrate (mr=mr@entry=0x555556c926e0, owner=owner@entry=0x555556c92680, name=name@entry=0x5555569bd720 "mach-virt.ram", size=2147483648, ram_flags=0, errp=errp@entry=0x7fffffffde20) at ../softmmu/memory.c:1563 #4 0x0000555555af6b0e in ram_backend_memory_alloc (errp=0x7fffffffde20, backend=0x555556c92680) at ../backends/hostmem-ram.c:33 #5 ram_backend_memory_alloc (backend=0x555556c92680, errp=0x7fffffffde20) at ../backends/hostmem-ram.c:20 #6 0x0000555555af6c20 in host_memory_backend_memory_complete (uc=<optimized out>, errp=0x7fffffffde70) at ../backends/hostmem.c:336 #7 0x0000555555ed60d7 in user_creatable_complete (uc=0x555556c92680, errp=errp@entry=0x5555569b2670 <error_fatal>) at ../qom/object_interfaces.c:28 #8 0x00005555558f7cb7 in create_default_memdev (errp=0x5555569b2670 <error_fatal>, path=<optimized out>, ms=0x555556bf8400) at ../hw/core/machine.c:1287 #9 machine_run_board_init (machine=0x555556bf8400, mem_path=<optimized out>, errp=0x5555569b2670 <error_fatal>) at ../hw/core/machine.c:1326 #10 0x0000555555aef986 in qemu_init_board () at ../softmmu/vl.c:2493 #11 qmp_x_exit_preconfig (errp=<optimized out>) at ../softmmu/vl.c:2589 #12 0x0000555555af325a in qmp_x_exit_preconfig (errp=<optimized out>) at ../softmmu/vl.c:2584 #13 qemu_init (argc=<optimized out>, argv=<optimized out>, envp=<optimized out>) at ../softmmu/vl.c:3586 #14 0x000055555588f33b in qemu_main (argc=<optimized out>, argv=<optimized out>, envp=<optimized out>) at ../softmmu/main.c:37 #15 0x00007ffff7629d90 in __libc_start_call_main (main=main@entry=0x555555888e30 <main>, argc=argc@entry=20, argv=argv@entry=0x7fffffffe248) at ../sysdeps/nptl/libc_start_call_main.h:58 #16 0x00007ffff7629e40 in __libc_start_main_impl (main=0x555555888e30 <main>, argc=20, argv=0x7fffffffe248, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe238) at ../csu/libc-start.c:392 #17 0x000055555588f265 in _start ()
创建arm64虚拟机的命令行:
/root/qemu/build/qemu-system-aarch64 \ -m 2048 \ -cpu cortex-a57 \ -smp 2 \ -M virt \ -nographic \ -serial mon:stdio \ -device virtio-scsi-device \ -drive if=none,file=jammy-server-cloudimg-arm64.img,id=hd0 \ -device virtio-blk-device,drive=hd0 \ -bios /root/qemu/pc-bios/edk2-aarch64-code.fd \
memory分配之后需要通知kvm,还有后续的内存更改通知,这由MemeoryListener来完成。
对于kvm有:
void kvm_memory_listener_register(KVMState *s, KVMMemoryListener *kml, AddressSpace *as, int as_id, const char *name) { 。。。 kml->listener.region_add = kvm_region_add; kml->listener.region_del = kvm_region_del; kml->listener.commit = kvm_region_commit; kml->listener.log_start = kvm_log_start; kml->listener.log_stop = kvm_log_stop; kml->listener.priority = MEMORY_LISTENER_PRIORITY_ACCEL; kml->listener.name = name;
向kvm注册mmeory region只会添加hva和ipa的联系,并不会让kvm操作stage2的页表,更不会分配内存。此时stage2的页表只有一个pgd,只有在发生stage2的page fault的时候才会route到kvm,kvm这个时候才会去创建s2的页表。
kvm_handle_guest_abort负责解决s2页表缺失的问题。调用链是:
建立页表最重要的是找到ipa和对应的phy_addr。ipa是出错信息中拿到的,物理地址是通过hva拿到的,但是复杂的情况是这个时候可能还没有分配物理内存,所以根本不存在物理地址。hva_to_pfn可以解决这个问题,首先使用快速路径,在内存已经分配的情况下,可以直接拿到这个地址,如果没有分配就进入慢速路径,使用get_user_pages函数分配内存,返回物理地址。如果这样还不够后面还有进一步分操作。拿到物理地址之后就可以使用kvm_pgtable_stage2_map去建立页表了。
对于mmio就简单很多,凡是没有注册过的ipa,一旦被访问就视为IO区域,一般由userspace处理。
int io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa) { 。。。 /* Now prepare kvm_run for the potential return to userland. */ run->mmio.is_write = is_write; run->mmio.phys_addr = fault_ipa; run->mmio.len = len; vcpu->mmio_needed = 1; 。。。 if (is_write) memcpy(run->mmio.data, data_buf, len); vcpu->stat.mmio_exit_user++; run->exit_reason = KVM_EXIT_MMIO; return 0; }
忽略in-kernel处理的情况,一般mmio会由VMM去处理。
int kvm_cpu_exec(CPUState *cpu) {
。。。 switch (run->exit_reason) { case KVM_EXIT_IO: /* Called outside BQL */ kvm_handle_io(run->io.port, attrs, (uint8_t *)run + run->io.data_offset, run->io.direction, run->io.size, run->io.count); ret = 0; break; case KVM_EXIT_MMIO: /* Called outside BQL */ address_space_rw(&address_space_memory, run->mmio.phys_addr, attrs, run->mmio.data, run->mmio.len, run->mmio.is_write); ret = 0; break; case KVM_EXIT_IRQ_WINDOW_OPEN: ret = EXCP_INTERRUPT; break; case KVM_EXIT_SHUTDOWN: qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); ret = EXCP_INTERRUPT; break; case KVM_EXIT_UNKNOWN: fprintf(stderr, "KVM: unknown exit, hardware reason %" PRIx64 "\n", 。。。
qemu会监控VM trap,如果需要处理就从kvm->run中获取信息,主要包括退出原因,处理完之后再次调用KVM_RUN ioctl进入guest。
标签:KVM,run,..,虚拟化,kvm,笔记,entry,armv8,cpu From: https://www.cnblogs.com/banshanjushi/p/17944080