一,前言
昨天我的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函数被调用。
三,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。
3.2关于事件触发
Qemu有2个主处理事件qemu_aio_context和iohandler_ctx
当文件描述符准备就绪、计时器到期、BH被调度时,事件循环将调用响应事件的回调。
我打断点qemu_bh_new进行了捕获,然后能看到qemu_bh_new的初始化cb函数为arm_timer_tick。于是为其也打断点。但是缺少了设置事件呢!然后看了下代码,于是在event_notifier_set函数也打了断点,开始捕获。
初始化注册完成后,然后可以捕获到event_notifier_set,它会写入到fd,这样glic才能扫描到fd。
此函数就是写fd,这样glic能扫描到fd的变化,进而执行初始化注册的callback函数。
(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我也捕获了下。
四,总结
从qemu使用到了解qemu虚化原理,蛮好玩的。关键是学到了一个比较经典的c语言库glib,将来说不定有机会用下。毕竟应用开发现在不是我的重点关注内容,出于好奇,我学习了下其设计思想,qemu源码的探索告一段落。