2025年qemu kvm 内存虚拟化

qemu kvm 内存虚拟化一 qemu 中物理内存的注册 cpu register physical memory 调用 cpu notify set memory cpu notify set memory 调用 kvm client set memory kvm client set memory 调用 kvm set phys mem

大家好,我是讯享网,很高兴认识大家。

一、qemu中物理内存的注册
cpu_register_physical_memory调用cpu_notify_set_memory
cpu_notify_set_memory调用kvm_client_set_memory
kvm_client_set_memory调用kvm_set_phys_mem
kvm_set_phys_mem调用kvm_set_user_memory_region
kvm_set_user_memory_region调用的kvm_vm_ioctl进入内核
内核中会调用kvm_vm_ioctl_set_memory_region最终调用到kvm_set_memory_region函数
kvm_set_memory_region函数中有如下代码:
738 ____slots->memslots[mem->slot] = new;
739 ____old_memslots = kvm->memslots;
740 ____rcu_assign_pointer(kvm->memslots, slots);
741 __synchronize_srcu_expedited(&kvm->srcu);
因此函数
kvm_set_memory_region本质是创建并填充了一个临时kvm_memslots结构,并把其赋值给kvm->memslots(全局的)。

二、处理用户态虚拟的地址(主要考虑tlb不能命中的情况)
1、查物理tlb如果不能命中会调用host中do_kvm_tlbmiss
2、do_kvm_tlbmiss会先判断地址是IO地址还是访存的地址,如果是访存地址,会进一步查guest tlb表,如果查guest tlb还没有命中,就会把guest tlb miss异常注入到guest系统中,guest kernel会根据页表来填充guest tlb,当guest调用TLBWI特权指令时,会再次陷入host中,调用do_kvm_cpu异常处理
3、在do_kvm_cpu中模拟TLBWI指令,先填充guest tlb 表项,在调用kvmmips_update_shadow_tlb来更新物理tlb(shadow tlb)
4、在kvmmips_update_shadow_tlb中,通过gfn_to_page和page_to_phys两个函数将gpa转化成hpa,再将hpa填充到物理tlb中
5、gfn_to_page函数(我想讲的重点)这个函数会调用到gfn_to_hva
6、gfn_to_hva调用gfn_to_memslot和gfn_to_hva_memslot
gfn_to_memslot代码如下:
859 struct kvm_memory_slot *gfn_to_memslot(struct kvm *kvm, gfn_t gfn)
860 {
861 ____int i;
862 ____struct kvm_memslots *slots = kvm_memslots(kvm);
863
864 ____for (i = 0; i < slots->nmemslots; ++i) {
865 ________struct kvm_memory_slot *memslot = &slots->memslots[i];
866
867 ________if (gfn >= memslot->base_gfn
868 ________ && gfn < memslot->base_gfn + memslot->npages)
869 ____________return memslot;
870 ____}
871 ____return NULL;
872 }
代码中首先调用kvm_memslots获得slots,kvm_memslots代码如下:
255 static inline struct kvm_memslots *kvm_memslots(struct kvm *kvm)
256 {
257 ____return rcu_dereference_check(kvm->memslots,
258 ____________srcu_read_lock_held(&kvm->srcu)
259 ____________|| lockdep_is_held(&kvm->slots_lock));
260 }
本质是return kvm->memsolts。
gfn_to_hva_memslot代码如下:
935 static unsigned long gfn_to_hva_memslot(struct kvm_memory_slot *slot, gfn_t gfn)
936 {
937 ____return slot->userspace_addr + (gfn - slot->base_gfn) * PAGE_SIZE;
938 }
由此看来gpa到hva的关键是slot->userspace_addr,其在qemu中kvm_set_user_memory_region中通过qemu_safe_ram_ptr函数赋值。
qemu_safe_ram_ptr代码如下:
2945 void *qemu_safe_ram_ptr(ram_addr_t addr)
2946 {
2947 RAMBlock *block;
2948
2949 QLIST_FOREACH(block, &ram_list.blocks, next) {
2950 if (addr - block->offset < block->length) {
2951 return block->host + (addr - block->offset);
2952 }
2953 }
2954
2955 fprintf(stderr, “Bad ram offset %” PRIx64 “\n”, (uint64_t)addr);
2956 abort();
2957
2958 return NULL;
2959 }
因此得找到block->host,在qemu的qemu_ram_alloc_from_ptr函数中赋值,在该函数中有这么一句话new_block->host = qemu_vmalloc(size);从host系统中分配一个hva地址。

结论:
综合以上分析可以看出,在qemu中调用qemu_ram_alloc主要是分配RAMBlock结构,并将其插入ram_list.blocks链表,它的本质上分配了一个hva地址,把它放到RAMBlock结构host域;调用cpu_register_physical_memory主要填充struct kvm结构的slots域,它的本质是将一个gha地址与hva地址对应起来,将hva放在slot->userspace_addr中,将gha放在slot->base_gfn中。quma通过上面两个函数就把一段gha的空间映射成一段hva空间。

三、console显示过程(基于cirrusfb)
先看一个函数栈:
2 [<51164>] cirrusfb_imageblit+0xa0/0x284
3 [<3ce5c>] bit_putcs+0x3dc/0x48c
4 [<6eb8c>] do_update_region+0x148/0x1a4
5 [<705f4>] update_region+0xb4/0xdc
6 [<393bc>] fbcon_switch+0x5b8/0x61c
7 [<70ef4>] redraw_screen+0x188/0x2a8
8 [<72c84>] take_over_console+0x368/0x3cc
9 [<36030>] fbcon_takeover+0x108/0x188
10 [<60204>] notifier_call_chain.isra.1+0x40/0x90
11 [<60540>] __blocking_notifier_call_chain+0x48/0x68
12 [<2ee8c>] register_framebuffer+0x2b0/0x2dc
13 [<0f4b4>] cirrusfb_pci_register+0x608/0x6c4
14 [<2650c>] pci_device_probe+0x60/0xa0
15 [<89008>] driver_probe_device+0x108/0x1f0
16 [<8915c>] __driver_attach+0x6c/0xa4
17 [<879f8>] bus_for_each_dev+0x54/0xa0
18 [<881ec>] bus_add_driver+0xf0/0x310
19 [<89838>] driver_register+0xe0/0x194
20 [<26214>] __pci_register_driver+0x5c/0x11c
21 [<86710>] cirrusfb_init+0x164/0x198
22 [<70c18>] do_one_initcall+0xbc/0x204
23 [<70ecc>] kernel_init+0x16c/0x244
24 [<189e8>] kernel_thread_helper+0x10/0x18
从函数栈可以看出,register_framebuffer会触发一个FB_EVENT_FB_REGISTERED事件,调用函数fbcon_fb_registered,该函数中调用fbcon_takeover来接管操作console的函数,从此之后console的操作,就会调用下面函数
3281 static const struct consw fb_con = {
3282 _.owner_______= THIS_MODULE,
3283 ____.con_startup _______= fbcon_startup,
3284 ____.con_init ______= fbcon_init,
3285 ____.con_deinit ________= fbcon_deinit,
3286 ____.con_clear _____= fbcon_clear,
3287 ____.con_putc ______= fbcon_putc,
3288 ____.con_putcs _____= fbcon_putcs,
3289 ____.con_cursor ________= fbcon_cursor,
3290 ____.con_scroll ________= fbcon_scroll,
3291 ____.con_bmove _____= fbcon_bmove,
3292 ____.con_switch ________= fbcon_switch,
3293 ____.con_blank _____= fbcon_blank,
3294 ____.con_font_set ______= fbcon_set_font,
3295 ____.con_font_get ______= fbcon_get_font,
3296 __.con_font_default_= fbcon_set_def_font,
3297 ____.con_font_copy _____= fbcon_copy_font,
3298 ____.con_set_palette ___= fbcon_set_palette,
3299 ____.con_scrolldelta ___= fbcon_scrolldelta,
3300 ____.con_set_origin ____= fbcon_set_origin,
3301 ____.con_invert_region _= fbcon_invert_region,
3302 ____.con_screen_pos ____= fbcon_screen_pos,
3303 ____.con_getxy _____= fbcon_getxy,
3304 ____.con_resize = fbcon_resize,
3305 __.con_debug_enter__= fbcon_debug_enter,
3306 __.con_debug_leave__= fbcon_debug_leave,
3307 };
我们不防以fbcon_putcs函数为例,进一步分析,其代码如下:
1256 static void fbcon_putcs(struct vc_data *vc, const unsigned short *s,
1257 ____________int count, int ypos, int xpos)
1258 {
1259 ____struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
1260 ____struct display *p = &fb_display[vc->vc_num];
1261 ____struct fbcon_ops *ops = info->fbcon_par;
1262
1263 ____if (!fbcon_is_inactive(vc, info))
1264 ________ops->putcs(vc, info, s, count, real_y(p, ypos), xpos,
1265 ____________ get_color(vc, info, scr_readw(s), 1),
1266 ____________ get_color(vc, info, scr_readw(s), 0));
1267 }
它需要调用info->fbcon_par->putcs(info 的数据结构是struct fb_info),info->fbcon_par初始化在函数是fbcon_set_bitops,函数如下:
404 void fbcon_set_bitops(struct fbcon_ops *ops)
405 {
406 ____ops->bmove = bit_bmove;
407 ____ops->clear = bit_clear;
408 ____ops->putcs = bit_putcs;
409 ____ops->clear_margins = bit_clear_margins;
410 ____ops->cursor = bit_cursor;
411 ____ops->update_start = bit_update_start;
412 ____ops->rotate_font = NULL;
413
414 ____if (ops->rotate)
415 ________fbcon_set_rotate(ops);
416 }
因此会继续调用bit_putcs,其最终会调用到info->fbops->fb_imageblit(info, image);(info 的数据结构是struct fb_info),info->fbops的初始化函数是cirrusfb_set_fbinfo中,该函数中有info->fbops = &cirrusfb_ops;一句话,cirrusfb_ops结构如下:
1973 static struct fb_ops cirrusfb_ops = {
1974 _.owner___= THIS_MODULE,
1975 __.fb_open__= cirrusfb_open,
1976 ____.fbrelease= cirrusfb_release,
1977 __.fb_setcolreg_= cirrusfb_setcolreg,
1978 __.fb_check_var_= cirrusfb_check_var,
1979 ____.fb_setpar= cirrusfb_set_par,
1980 ____.fb_pan_display = cirrusfb_pan_display,
1981 __.fb_blank_= cirrusfb_blank,
1982 __.fb_fillrect__= cirrusfb_fillrect,
1983 __.fb_copyarea__= cirrusfb_copyarea,
1984 __.fb_sync__= cirrusfb_sync,
1985 __.fb_imageblit_= cirrusfb_imageblit,
1986 };
因此最终会调用到cirrusfb_imageblit函数。

上面一个过程就是一个console写操作,最终调到cirrusfb驱动中cirrusfb_imageblit过程。

四、xserver 显示(基于fbmem)

xserver 下普通的显卡驱动,通常会直接操作寄存器,具体操作就是,先mmap(/dev/mem)io空间的基址,再通过基址加偏移的方式,操作寄存器。
但fbmem是个例外,其不用操作既存器,而是通过ioctl(/dev/fb0),把这些操作丢给内核去做。
但是两者在都没有加速的情况下,framebuffer操作方式是相同的,都是将framebuffer区域通过mmap映射到用户态,然后交给xorg中其他代码处理映射后地址。

五、结论

qemu中cirrus_linear_writeb函数在console下调用很多次而在Xserver不被调用原因如下:

首先在xserver下,我们将framebuffer区域(0x开始的一段区域)mmap成了guest虚拟地址(通过调用/dev/fb0 mmap函数),也就说在xserver所有frambuffer的操作,都是通过这个gva.
其次qema中,在函数map_linear_vram中,通过cpu_register_physical_memory(s->vga.map_addr, s->vga.map_end - s->vga.map_addr, s->vga.vram_offset);(将0x这个gpa和一个hva建立起了联系)
因此在xserver下整个framebuffer操作全部成了内存操作,不是IO操作,过程是gva->gpa->hva->hpa,不会回到qemu中,当然也就不可能访问qemu中的cirrus_linear_writeb函数了。
再次在console下,访console操作最终会调用到cirrusfb_imageblit函数,在cirrusfb_imageblit中有这么一句话memcpy(info->screen_base, image->data, size);其中info->screen_base就是0xremap后(IO空间),因此会回到qemu中,调用cirrus_linear_writeb。
最后,为什么这么关注cirrus_linear_writeb函数,因为在qemu中操作framebuffer表现在往s->vga.vram_ptr中写或从s->vga.vram_ptr读,(s->vga.vram_ptr就是我们说的hva),通过2242 s->vram_offset = qemu_ram_alloc(NULL, “vga.vram”, vga_ram_size);
2243 s->vram_ptr = qemu_get_ram_ptr(s->vram_offset);得到。只有在cirrus_linear_writeb函数中,才在往s->vga.vram_ptr这个hva写后,通过cpu_physical_memory_set_dirty将这个区域标记,而在我们更新屏幕是,dirty的区域是我们更新的判断条件。

小讯
上一篇 2025-01-17 18:20
下一篇 2025-02-18 17:07

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/46692.html