Qemu AIO:

struct LinuxAioState {
    AioContext *aio_context;
    /* 在event_notifier_init中初始化,第一个参数是允许的最大的异步IO的个数 */
    io_context_t ctx;
    /* 这个可以是eventfd或是pipe两种形式。只是对这两种形式的一个简单封装。在event_notifier_init中初始化 */    
    EventNotifier e;    
    /* io queue for submit at batch */
    LaioQueue io_q;                         
    /* I/O completion processing */                            
    QEMUBH *completion_bh;                                      
     struct io_event events[MAX_EVENTS];                         
     int event_idx;                                             
     int event_max;    /* 把iocb结构挂载这个链表上。 并非每一次IO都要触发submit,aio_do_submi这里只是一个 生产者,而ioq_submit是消费者? */
};


读写数据的调用流程
bdrv_driver_pwritev 
------
raw_co_prw 
      | ------> aio_get_linux_aio 
      |                      |                                                     
      |                      |  ------> laio_init                                        
      |                      |                       |  -----> event_notifier_init(创建eventfd)  
      |                      |   -----> laio_attach_aio_context (把eventfd加入到epoll中,并把eventfd与qemu_laio_completion_cb关联起来。等下该eventfd有数据了就使用这个回调处理)
      |  
      |  -------> laio_co_submit    (老版本没有启用AIO,所以不会走到这里,走的是线程池方式)
      |                        |
      |                        |  ------> laio_do_submit
      |                        |                     |
      |                        |                     | ------> io_prep_pwritev(拼装iocb,用于aio)
      |                        |                     |
      |                        |                     | ------> io_set_eventfd(把event fd放到iocb中。等下发给iocb内核,内核做完了以后IO,然后通过eventfd_write,这样用户态便能通过read读取到结果了)
      |                        |                     |
      |                        |                     | ------> ioq_submit(最后调用io_submit把多个iocb一起发给内核)
      |                        |
      |                        | ------> qemu_coroutine_yield(为什么这里需要yield呢? 因为laio_do_submit函数的返回值并非此次IO的结果。所以它必须等到内核通知此次IO结果。qemu_laio_process_completion中会填充此次IO的结果,并调用qemu_coroutine_enter重新跳转到这里执行。这里就退到该协程的调用者处。 )
也就是说使用了AIO以后就没有线程池了,只有再flush的时候才会使用到线程池
读写完成的调用流程
qemu_laio_completion_cb
                 | 
                 | ------> event_notifier_test_and_clear(使用read清除掉eventfd里面的数据)
                 |
                 | ------> io_getevents(获取io结果)
                 |
                 | ------> qemu_laio_process_completion(处理IO结果)                                                       
                 |  
                 | ------> qemu_coroutine_enter(回到 laio_co_submit处继续执行)

 ioq_submit的结果qemu已经在qemu_laio_process_completion中处理了,但并不代表它真正地写入到了物理磁盘,它其实只是写到磁盘的缓存中(打开文件的时候传入的是O_DIRECT;如果是O_SYNC的话就不用sync了)。 因此还需要把数据flush到磁盘中。那能否再打开文件的时候使用O_SYNC/O_DSYNC让它直接写入到磁盘呢。反正已经是异步的了,我们不应该再来一次flush系统调用带来的上下文切换的开销
bdrv_driver_pwritev 
      | ------> bdrv_co_flush
      |                     | ------> raw_aio_flush 
      |                     |                     |                                                     
      |                     |                     |  ------> paio_submit  
      |                     |                      | 
      |                     |                      | ------> thread_pool_submit_aio
      |                     |                                               |
      |                     |                                               | ------> spawn_thread 没有idle thread的时候才会去创建一个新的aio线程,aio线程会调用aio_worker -> handle_aiocb_flush                              
      |                     | ------> qemu_coroutine_yield (回退到其调用者) 
bdrv_co_io_em_complete 重新enter到哪里去了呢?



qemu_bh_schedule机制
http://www.kuqin.com/linux/20120908/330333.html

 

qemu磁盘 qemu ahci_初始化