【QEMU-KVM代码分析之三】IO thread源码浅析之main loop

来源:互联网 发布:知乎 一出国就爱国 编辑:程序博客网 时间:2024/05/30 04:56

转载自:http://blog.csdn.net/luo_brian/article/details/8532081

IO thread初始化

Qemu IO thread初始化函数位于main-loop.c:

[cpp] view plaincopy
  1. int qemu_init_main_loop(void)  
  2. {  
  3.     int ret;  
  4.     GSource *src;  
  5.   
  6.     init_clocks();  
  7.     if (init_timer_alarm() < 0) {  
  8.         fprintf(stderr, "could not initialize alarm timer\n");  
  9.         exit(1);  
  10.     }  
  11.   
  12.     ret = qemu_signal_init();  
  13.     if (ret) {  
  14.         return ret;  
  15.     }  
  16.   
  17.     qemu_aio_context = aio_context_new();  
  18.     src = aio_get_g_source(qemu_aio_context);  
  19.     g_source_attach(src, NULL);  
  20.     g_source_unref(src);  
  21.     return 0;  
  • init_clocks(): 依次创建rt_clock, vm_clock, host_clock三个时钟,这3个时钟区别,在qemu-timer.h中有简要的说明,后面讨论qemu时钟和定时器的时候,再详细的分析他们之间的区别。
  • init_timer_alarm(): QEMU-KVM默认支持两种timer, 分别是dynticks timer和unix timer; dynticks timer使用timer_create(CLOCK_REALTIME)创建时钟,然后通过timer_settime()触发SIGALRM信号, 而unix timer使用setitimer来触发SIGALRM信号; 前者的实现会使用rdtsc, 所以精度更高些。QEMU-KVM默认使用dynticks timer.
  • qemu_signal_init(): 该函数首先创建一个signalfd,  然后调用qemu_set_fd_handler2()函数将signalfd加入main loop的select文件句柄集合中;IO thread关注SIGIO, SIGALRM, SIGBUS这3个信号,当IO thread捕获其中一个信号,signalfd因此会变成可读状态,select()函数返回并调用该事件的回调函数sigfd_handler(), sigfd_handler()会调用为该信号注册的回调函数sa_handler。这3个信号中比较特别的是SIGALRM, 因为qemu-kvm的定时器,就是定期触发该信号, 它的回调函数是qemu-timer.c中的host_alarm_handler()。
  • qemu_aio_context: 用于处理aio handler调度的上下文,该调度系统利用了glib的main loop机制,相关背景知识可以参考http://blog.csdn.net/luo_brian/article/details/8540296

IO thread主函数

Qemu IO thread的主函数也位于main-loop.c,  主要考虑到跨平台性,使用select和g_poll轮询系统的IO描述符,并根据测试结果调用相应的回调函数。

Qemu中IO的有些回调函数函数是分上部和下部的,该机制在Linux内核中被广泛使用,主要是为了提高事件相应的及时性。

Qemu中常用的IO描述符有下面几类:

  • block io: 虚拟磁盘相关的io, 为了保证高性能,主要使用aio;
  • signalfd: 前面介绍过,qemu的时钟模拟利用了linux kernel的signalfd, 定期产生SIGALRM信号;
  • eventfd: 主要用于qemu和kvm之间的notifier, 比如qemu的模拟设备可以通过notifier向kvm发送一个模拟中断,kvm也可以通过notifier向qemu报告guest的各种状态;
  • socket: 用于虚拟机迁移,qmp管理等

该函数同时还负责轮询系统中所有的定时器,并调用定时器的回调函数;

 

[cpp] view plaincopy
  1. int main_loop_wait(int nonblocking)  
  2. {  
  3.     int ret;  
  4.     uint32_t timeout = UINT32_MAX;  
  5.   
  6.     if (nonblocking) {  
  7.         timeout = 0;  
  8.     }  
  9.   
  10.     /* poll any events */  
  11.     /* XXX: separate device handlers from system ones */  
  12.     nfds = -1;  
  13.     FD_ZERO(&rfds);  
  14.     FD_ZERO(&wfds);  
  15.     FD_ZERO(&xfds);  
  16.   
  17. #ifdef CONFIG_SLIRP  
  18.     slirp_update_timeout(&timeout);  
  19.     slirp_select_fill(&nfds, &rfds, &wfds, &xfds);  
  20. #endif  
  21.     qemu_iohandler_fill(&nfds, &rfds, &wfds, &xfds);  
  22.     ret = os_host_main_loop_wait(timeout);  
  23.     qemu_iohandler_poll(&rfds, &wfds, &xfds, ret);  
  24. #ifdef CONFIG_SLIRP  
  25.     slirp_select_poll(&rfds, &wfds, &xfds, (ret < 0));  
  26. #endif  
  27.   
  28.     qemu_run_all_timers();  
  29.   
  30.     return ret;  
  31. }  

slirp(user mode networking)

在KVM环境中,为了提高网络的性能一般虚拟机使用vhost-net, 所以slirp基本没有太多实际用途,也建议在编译配置qemu-kvm的时候,使用--disable-slirp将其禁止掉。关于slirp的介绍,可以看一下http://wiki.qemu.org/Documentation/Networking。

IO Handler

用来表示一个IO描述符,其结构定义如下;iohandler.c中定义了一个全局的链表io_handlers,并提供qemu_set_fd_handler()和qemu_set_fd_handler2()函数将一个fd加入到这个链表; 在IO thread主循环中qemu_iohandler_fill()函数负责将io_handlers链表中的所有描述符,加入select测试集合。

[cpp] view plaincopy
  1. typedef struct IOHandlerRecord {  
  2.     IOCanReadHandler *fd_read_poll;  
  3.     IOHandler *fd_read;  
  4.     IOHandler *fd_write;  
  5.     void *opaque;  
  6.     QLIST_ENTRY(IOHandlerRecord) next;  
  7.     int fd;  
  8.     bool deleted;  
  9. } IOHandlerRecord;  
  10.   
  11. static QLIST_HEAD(, IOHandlerRecord) io_handlers =  
  12.     QLIST_HEAD_INITIALIZER(io_handlers);  

AIO Handler

Qemu中的aio主要用于block io, 从而可以提高虚拟磁盘读写的性能。Qemu使用g_poll轮询测试AIO描述符;g_poll是glib的函数,使用方法可以参考GMainLoop的实现原理和代码模型以及glib GMainLoop的手册;

aio_set_fd_handler()用于将一个AioHandler的描述符加入g_poll集合。

[cpp] view plaincopy
  1. typedef struct AioContext {  
  2.     GSource source;  
  3.   
  4.     /* The list of registered AIO handlers */  
  5.     QLIST_HEAD(, AioHandler) aio_handlers;  
  6.   
  7.     /* This is a simple lock used to protect the aio_handlers list. 
  8.      * Specifically, it's used to ensure that no callbacks are removed while 
  9.      * we're walking and dispatching callbacks. 
  10.      */  
  11.     int walking_handlers;  
  12.   
  13.     /* Anchor of the list of Bottom Halves belonging to the context */  
  14.     struct QEMUBH *first_bh;  
  15.   
  16.     /* A simple lock used to protect the first_bh list, and ensure that 
  17.      * no callbacks are removed while we're walking and dispatching callbacks. 
  18.      */  
  19.     int walking_bh;  
  20.   
  21.     /* Used for aio_notify.  */  
  22.     EventNotifier notifier;  
  23. } AioContext;  
  24.   
  25. struct AioHandler {   
  26.     EventNotifier *e;   
  27.     EventNotifierHandler *io_notify;   
  28.     AioFlushEventNotifierHandler *io_flush;   
  29.     GPollFD pfd; int deleted;   
  30.     QLIST_ENTRY(AioHandler) node;  
  31. };  

IO thread同步

Qemu IO thread和vcpu thread使用一个全局共享线程锁来保证同步,函数qemu_mutex_lock_iothread()和qemu_mutex_unlock_iothread()分别用来获取和释放该锁;

当vcpu thread从guest模式退出到host模式的时候,vcpu thread会尝试取得该锁;而IO thread在主循环中,也会不断尝试取得该锁;

思考

  • 使用epoll替换select和g_poll:qemu是一个通用模拟器,在xen和kvm中,用于为hypervisor提供设备模拟服务;同时它也能仿真arm, mips等系统,同时还需要考虑跨平台(windows+linux),所以不少某些Linux系统中的高级函数,qemu的主干没有使用,epoll就是其中之一;select和g_poll的移植性好,但是其O(n)的复杂度以及频繁的描述符拷贝会造成不小的开销,在KVM上下文中,使用epoll应该是比较明智的选择。

  • 同步问题:IO thread和vcpu threads在qemu中共享上下文,所以block io的性能较bare metal相比,即使是使用aio, 还是会有比较大的损耗。Qemu 1.3开始引入dataplane, 其核心思想是把block io从qemu上下文中剥离出去,作为一个单独的线程,这样,block io和vcpu之间不会再有竞争关系,从而可以大幅提高io性能;但是因为dataplane没有qemu上下文,所以也没法很好的支持qcow2等虚拟磁盘格式。
1 0
原创粉丝点击