qemu是基于事件驱动的,在基于KVM的qemu模型中,每个VCPU对应一个qemu线程,且qemu主线程负责各类事件的监听,这里有一个小的IO监听框架,本节对此进行介绍。数组
struct GArray { gchar *data; guint len; };
Data指向一个GpollFD数组,len表示数组的个数。框架
struct GPollFD { gint fd; gushort events; gushort revents; };
Fd为监听的fd,event为请求监听的事件,是一组bit的组合。Revents为poll收到的事件,根据此断定当前什么事件可用。函数
typedef struct IOHandlerRecord { IOCanReadHandler *fd_read_poll; IOHandler *fd_read; IOHandler *fd_write; void *opaque; QLIST_ENTRY(IOHandlerRecord) next; int fd; int pollfds_idx; bool deleted; } IOHandlerRecord;
该结构是qemu中IO框架的处理单位,fd_read和fd_write为注册的对应处理函数。Next表示该结构会链接在一个全局的链表上,fd是对应的fd,delete标志是否须要从链表中删除该结构。oop
Main-> qemu_init_main_loopui
Main-> main_loop-> main_loop_waitthis
qemu_init_main_loopspa
int qemu_init_main_loop(void) { int ret; GSource *src; init_clocks(); ret = qemu_signal_init(); if (ret) { return ret; } //malloc a globle fd array gpollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD)); //create a aio context qemu_aio_context = aio_context_new(); //get event source from aio context src = aio_get_g_source(qemu_aio_context); //add source to main loop g_source_attach(src, NULL); g_source_unref(src); return 0; }
Qemu中的main loop主要采用 了glib中的事件循环,关于此详细内容,准备后面专门写一小节,本节主要看主体IO框架。线程
该函数主要就分配了一个Garray结构存储全局的GpollFD,在main_loop中的main_loop_wait阶段有两个比较重要的函数:qemu_iohandler_fill,os_host_main_loop_wait和qemu_iohandler_poll,前者把用户添加的fd信息注册到刚才分配的Garray结构中,os_host_main_loop_wait对事件进行监听,qemu_iohandler_poll对接收到的事件进行处理。code
用户添加fd的函数为qemu_set_fd_handler,参数中fd为本次添加的fd,后面分别是对该fd的处理函数(read or write),最后opaque为处理函数的参数。blog
int qemu_set_fd_handler(int fd, IOHandler *fd_read, IOHandler *fd_write, void *opaque) { return qemu_set_fd_handler2(fd, NULL, fd_read, fd_write, opaque); }
可见该函数直接调用了qemu_set_fd_handler2:
int qemu_set_fd_handler2(int fd, IOCanReadHandler *fd_read_poll, IOHandler *fd_read, IOHandler *fd_write, void *opaque) { IOHandlerRecord *ioh; assert(fd >= 0); //if read and write are null,delete if (!fd_read && !fd_write) { QLIST_FOREACH(ioh, &io_handlers, next) { if (ioh->fd == fd) { ioh->deleted = 1; break; } } } else {//find and goto find QLIST_FOREACH(ioh, &io_handlers, next) { if (ioh->fd == fd) goto found; } ioh = g_malloc0(sizeof(IOHandlerRecord)); //insert ioh to io_handlers list QLIST_INSERT_HEAD(&io_handlers, ioh, next); found: ioh->fd = fd; ioh->fd_read_poll = fd_read_poll; ioh->fd_read = fd_read; ioh->fd_write = fd_write; ioh->opaque = opaque; ioh->pollfds_idx = -1; ioh->deleted = 0; qemu_notify_event(); } return 0; }
这里判断若是read和write函数均为空的话就表示本次是要delete某个fd,就遍历全部的io_handlers,对指定的fd对应的IOHandlerRecord标志delete。
不然还有两种状况,添加或者更新。因此首先仍是要从io_handlers找一下,若是找到直接更新,不然新建立一个IOHandlerRecord,而后再添加信息。具体信息内容就比较简单。
在main_loop_wait函数中,经过os_host_main_loop_wait对fd进行监听,固然并非它直接监听,而是经过glib的接口。
当os_host_main_loop_wait返回后,就表示当前有可用的事件,在main_loop_wait函数中,调用了qemu_iohandler_poll函数对fd进行处理。
void qemu_iohandler_poll(GArray *pollfds, int ret) { if (ret > 0) { IOHandlerRecord *pioh, *ioh; QLIST_FOREACH_SAFE(ioh, &io_handlers, next, pioh) { int revents = 0; if (!ioh->deleted && ioh->pollfds_idx != -1) { GPollFD *pfd = &g_array_index(pollfds, GPollFD, ioh->pollfds_idx); revents = pfd->revents; } if (!ioh->deleted && ioh->fd_read && (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR))) { ioh->fd_read(ioh->opaque); } if (!ioh->deleted && ioh->fd_write && (revents & (G_IO_OUT | G_IO_ERR))) { ioh->fd_write(ioh->opaque); } /* Do this last in case read/write handlers marked it for deletion */ if (ioh->deleted) { QLIST_REMOVE(ioh, next); g_free(ioh); } } } }
具体的处理倒也简单,逐个遍历io_handlers,对于每一个GpollFD,取其revents,判断delete标志并校验状态,根据不一样的状态,调用read或者write回调。最后若是是delete的GpollFD,就从链表中remove掉,释放GpollFD。
补充:针对qemu进程中线程数目的问题,从本节能够发现qemu主线程主要负责事件循环,针对每一个虚拟机的VCPU,会有一个子线程为之服务,所以qemu线程数目至少要大于等于1+VCPU数目。
以马内利!
参考资料:
一、qemu 2.7源码