Qemu2.8虚拟机源码分析—Apple的学习笔记

一,前言

昨天我的blogqemu虚化原理入门--Apple的学习笔记中描述过,要探秘下qemu的源码设计。自己看源码,然后网上也看了源码分析。找到了关键的事件触发机制glib库的使用,是qemu的main_loop使用的设计方案。网上查了下原来glib是gtk+的底层库。而我在了解glib的时候,感觉它用到了QT信号和槽的概念进行事件触发,而事件触发是通过pollfd来绑定的。天呢,我在学习qemu,怎么感觉又和图形图像学习相关了,真心觉得知识都是相通的,核心设计思想都是一样的。

二,glib基础

Glib是一个c语言库,它是著名的gtk+的底层。里面很多api,比如g_print就是此库封装的printf函数。Glib官网有详细的API使用。

1. Ubuntu安装glib库

sudo apt-get install libglib2.0-doc

APP使用glib库编译方法,引用头文件#include

gcc main.c $(pkg-config --cflags --libs glib-2.0) -o hello

2. Glib的事件触发设计基础
2.1) GmainLoop,Gcontext和Gsource。
Gsource是最小单位和fd的某个事件绑定。
Gcontext是各个Gsource的合集容器。通过g_source_attach将source加入到容器中。
GmainLoop是用于周期调用处理分发事件的主对象。所以Gcontext需要绑定到GmainLoop。
2.2)事件遍历的框架函数
prepare遍历_GMainContext拥有的事件源,计算下次轮训间隔,并检测事件源是否已经就绪。
query筛选出需要进行poll的事件源。
check检测事件源是否已经就绪。
dispatch遍历并调用事件源的GSourceFuncs->dispatch函数进行分发。(初始化时候注册的回调函数)

3. Glib事件触发机制应用调试
我参考了一个blog对glic的应用代码。这样我大改了解了调试方法,qemu的源码调试可以依样画葫芦了。但是这个代码中应该有bug,我理解没有写入事件的时候不会调用dispatch函数,但是此代码的dispatch会一直被调用,导致运行此代码的cpu负载接近100%。
编译此代码的命令为

gcc -g qemu_main_loop.c $(pkg-config --cflags --libs glib-2.0 gthread-2.0) -o qemu_main_loop

GDB调试,能看到callback函数被调用。


image.png

三,qemu2.8源码分析

使用2.8源码是因为选一个代码量少些的来学习其主要设计。昨天已经看过HW中硬件驱动注册的方法和linux的驱动注册方法类似。主要是qemu的main_loop函数我昨天没有看懂,导致main_loop我不知道如何调试了,原因是qemu使用了glib库,所以我今天才先学习了下glib库的事件分发机制。找到一个分析的比较好的参考blog,另外qemu为AIO建立了线程池,而这些事件都绑定了fd(时间,IO读写等)。这样都连接起来了。
3.1 关于fd
我在ubuntu16.04的虚拟机中调试的时候发现一共有9个fd,然后我就在aio_set_fd_handler打断点。因为此函数中有g_source_add_poll(&ctx->source, &node->pfd);就是把fd和gsource关联起来。然后再向上推导,这个fd是如何来的,能搜索到qemu_signal_init函数里面初始化的。从里面可以看到信号绑定到了signalfd。

static int qemu_signal_init(void)
{
    int sigfd;
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIG_IPI);
    sigaddset(&set, SIGIO);
    sigaddset(&set, SIGALRM);
    sigaddset(&set, SIGBUS);

    pthread_sigmask(SIG_BLOCK, &set, NULL);
    sigdelset(&set, SIG_IPI);
    sigfd = qemu_signalfd(&set);
    if (sigfd == -1) {
        fprintf(stderr, "failed to create signalfd\n");
        return -errno;
    }
    fcntl_setfl(sigfd, O_NONBLOCK);
    qemu_set_fd_handler(sigfd, sigfd_handler, NULL, (void *)(intptr_t)sigfd);
    return 0;
}

qemu_signalfd-> qemu_signalfd_compat,然后创建了管道,并且创建了线程。

static int qemu_signalfd_compat(const sigset_t *mask)
{
    struct sigfd_compat_info *info;
    QemuThread thread;
    int fds[2];
    info = malloc(sizeof(*info));
    if (info == NULL) {
        errno = ENOMEM;
        return -1;
    }
    if (pipe(fds) == -1) {
        free(info);
        return -1;
    }
    qemu_set_cloexec(fds[0]);
    qemu_set_cloexec(fds[1]);
    memcpy(&info->mask, mask, sizeof(*mask));
    info->fd = fds[1];
    qemu_thread_create(&thread, "signalfd_compat", sigwait_compat, info, QEMU_THREAD_DETACHED);
    return fds[0];
}

在创建新的线程前,使用了pthread_sigmask,将信号屏蔽了。

void qemu_thread_create(QemuThread *thread, const char *name,
                       void *(*start_routine)(void*),
                       void *arg, int mode)
{
    sigset_t set, oldset;
。。。。。。
    /* Leave signal handling to the iothread.  */
    sigfillset(&set);
    pthread_sigmask(SIG_SETMASK, &set, &oldset);
    err = pthread_create(&thread->thread, &attr, start_routine, arg);
    if (err)
        error_exit(err, __func__);
    if (name_threads) {
        qemu_thread_set_name(thread, name);
    }
    pthread_sigmask(SIG_SETMASK, &oldset, NULL);
    pthread_attr_destroy(&attr);
}

捕获了下, 可以看到不同的fd,并且绑定的callback函数。比较关键的信号处理函数就是sigfd_handler。

image.png

3.2关于事件触发
Qemu有2个主处理事件qemu_aio_context和iohandler_ctx
当文件描述符准备就绪、计时器到期、BH被调度时,事件循环将调用响应事件的回调。
我打断点qemu_bh_new进行了捕获,然后能看到qemu_bh_new的初始化cb函数为arm_timer_tick。于是为其也打断点。但是缺少了设置事件呢!然后看了下代码,于是在event_notifier_set函数也打了断点,开始捕获。
image.png

初始化注册完成后,然后可以捕获到event_notifier_set,它会写入到fd,这样glic才能扫描到fd。
image.png

此函数就是写fd,这样glic能扫描到fd的变化,进而执行初始化注册的callback函数。
image.png

(gdb) c
Continuing.
[Switching to Thread 5174.5184]

Thread 4 "qemu-system-arm" hit Breakpoint 3, event_notifier_set (e=0x80e2b7dc)
    at util/event_notifier-posix.c:108
108         ret = write(e->wfd, &value, sizeof(value));
(gdb) bt
#0  event_notifier_set (e=0x80e2b7dc) at util/event_notifier-posix.c:108
#1  0x80507799 in aio_notify (ctx=0x80e2b760) at async.c:331
#2  0x80507399 in qemu_bh_schedule (bh=0x80e2ac68) at async.c:162
#3  0x8051281b in qemu_notify_event () at main-loop.c:138
#4  0x805137c4 in timerlist_notify (timer_list=0x80e2ae00) at qemu-timer.c:274
#5  0x80513b0a in timerlist_rearm (timer_list=0x80e2ae00) at qemu-timer.c:400
#6  0x80513bf3 in timer_mod_ns (ts=0x8125c910, expire_time=4061638620)
    at qemu-timer.c:428
#7  0x80513d1a in timer_mod (ts=0x8125c910, expire_time=4061638620)
    at qemu-timer.c:458
#8  0x8034073b in ptimer_reload (s=0x8125c8d0, delta_adjust=0)
    at hw/core/ptimer.c:116
#9  0x80340c36 in ptimer_run (s=0x8125c8d0, oneshot=0) at hw/core/ptimer.c:271
#10 0x8044ba50 in arm_timer_write (opaque=0x8125c890, offset=8, value=226)
    at hw/timer/arm_timer.c:127
#11 0x8044bdd6 in sp804_write (opaque=0x8125be08, offset=8, value=226, size=4)
    at hw/timer/arm_timer.c:251
#12 0x80112371 in memory_region_write_accessor (mr=0x8125c04c, addr=8, 
    value=0x8c58cb30, size=4, shift=0, mask=4294967295, attrs=...)
    at /work/qemu2_8/qemu-2.8.0/memory.c:526
#13 0x801125cd in access_with_adjusted_size (addr=8, value=0x8c58cb30, size=4, 
    access_size_min=1, access_size_max=4, 
    access=0x80112260 , mr=0x8125c04c, attrs=...)
    at /work/qemu2_8/qemu-2.8.0/memory.c:592
#14 0x80114cbb in memory_region_dispatch_write (mr=0x8125c04c, addr=8, 
    data=226, size=4, attrs=...) at /work/qemu2_8/qemu-2.8.0/memory.c:1323
#15 0x8011b5a3 in io_writex (env=0x80e7b834, iotlbentry=0x80e85d84, val=226, 
    addr=2692784136, retaddr=2928977987, size=4)
    at /work/qemu2_8/qemu-2.8.0/cputlb.c:534
#16 0x8011e2ca in io_writel (env=0x80e7b834, mmu_idx=3, index=40, val=226, 
    addr=2692784136, retaddr=2928977987)
    at /work/qemu2_8/qemu-2.8.0/softmmu_template.h:265
#17 0x8011e5c0 in helper_le_stl_mmu (env=0x80e7b834, addr=2692784136, val=226, 
    oi=35, retaddr=2928977987)
    at /work/qemu2_8/qemu-2.8.0/softmmu_template.h:300
#18 0xae94a843 in code_gen_buffer ()
#19 0x800c770a in cpu_tb_exec (cpu=0x80e77688, itb=0xacfd57b8)
    at /work/qemu2_8/qemu-2.8.0/cpu-exec.c:164
#20 0x800c832e in cpu_loop_exec_tb (cpu=0x80e77688, tb=0xacfd57b8, 
    last_tb=0x8c58cfb4, tb_exit=0x8c58cfb8, sc=0x8c58cfc4)
    at /work/qemu2_8/qemu-2.8.0/cpu-exec.c:544
#21 0x800c85cb in cpu_exec (cpu=0x80e77688)
    at /work/qemu2_8/qemu-2.8.0/cpu-exec.c:638
#22 0x800fc59e in tcg_cpu_exec (cpu=0x80e77688)
---Type  to continue, or q  to quit---
    at /work/qemu2_8/qemu-2.8.0/cpus.c:1117
#23 0x800fc80a in qemu_tcg_cpu_thread_fn (arg=0x80e77688)
    at /work/qemu2_8/qemu-2.8.0/cpus.c:1197
#24 0xb7944295 in start_thread (arg=0x8c58db40) at pthread_create.c:333
#25 0xb786e0ae in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:114
(gdb) c
Continuing.
[Switching to Thread 5174.5174]

Thread 1 "qemu-system-arm" hit Breakpoint 4, arm_timer_tick (opaque=0x8125c890)
    at hw/timer/arm_timer.c:146
146     arm_timer_state *s = (arm_timer_state *)opaque;
(gdb) bt
#0  arm_timer_tick (opaque=0x8125c890) at hw/timer/arm_timer.c:146
#1  0x80507204 in aio_bh_call (bh=0x8125c8b8) at async.c:87
#2  0x80507283 in aio_bh_poll (ctx=0x80e2b760) at async.c:115
#3  0x80514c57 in aio_dispatch (ctx=0x80e2b760) at aio-posix.c:303
#4  0x805075d3 in aio_ctx_dispatch (source=0x80e2b760, callback=0x0, 
    user_data=0x0) at async.c:254
#5  0xb7b94ee9 in g_main_context_dispatch ()
   from target:/lib/i386-linux-gnu/libglib-2.0.so.0
#6  0x80512b16 in glib_pollfds_poll () at main-loop.c:215
#7  0x80512c29 in os_host_main_loop_wait (timeout=0) at main-loop.c:260
#8  0x80512d01 in main_loop_wait (nonblocking=1) at main-loop.c:508
#9  0x802ab1b8 in main_loop () at vl.c:1966
#10 0x802b2b14 in main (argc=14, argv=0xbffff624, envp=0xbffff660) at vl.c:4684

qemu_thread_create我也捕获了下。


image.png

四,总结

从qemu使用到了解qemu虚化原理,蛮好玩的。关键是学到了一个比较经典的c语言库glib,将来说不定有机会用下。毕竟应用开发现在不是我的重点关注内容,出于好奇,我学习了下其设计思想,qemu源码的探索告一段落。

你可能感兴趣的:(Qemu2.8虚拟机源码分析—Apple的学习笔记)