磁盘IO在[qcdio/qcdiskqueue.h]和[qcdio/qcdiskqueue.cc]中实现,基于线程池和消费者/生产者模型。在这两个源文件中实现了基于成块读写的异步磁盘IO。
[qcdio/qcdiskqueue.cpp] class QCDiskQueue::Queue,实现了磁盘文件的异步IO。文件读写的单位是块(支持direct-io)。它会启动给定数量(inThreadCount)的线程处理io请求,支持管理给定数量(mFileCount)的磁盘文件,并且只支持给定数量(inMaxQueueDepth)的请求队列长度。每次读写请求都表示为一个Request,并插入Queue本身的请求队列中,执行线程从请求队列中取下Request并请求,请求执行完成以后,调用请求的回调函数。Queue本身并不保证磁盘请求的执行顺序,也就是说,对于同一个文件发出的请求可能被并行执行,上层用户必须保证发起的请求是可并行的。对于一个由Queue管理的文件,它会为每个工作线程打开一个文件描述符,这样就可以充分发挥读的并行性,同时也意味着每个文件会打开inThreadCount个文件描述符。Queue用数组组织所有的打开的文件,当上层调用Open接口,Queue会返回一个int表示该文件在文件数组中的下标,类似于系统层的文件描述符,后续的读写请求都在该”文件描述符”上进行。Queue使用数组和链表组织所有的读写请求,当上层发起一个请求,首先在数组中分配一个未使用的Request,然后将该request放入请求队列中,并把request的id(request在数组中的下标)返回给调用者,后续该request的操作以requestid为标示。文件的读写由工作线程使用writev完成。每个请求都有一个与之关联的回调函数,该请求失败或者成功会调用该回调函数。由于单个Request的buffer数量是有限的,最多为writev支持的最大数组长度,因此一个请求可能有多个Request组成,由链表链接起来。第一个Request包含了整个请求读写的块数量,工作线程每次会取一个完整的请求的所有Request,因此每个请求都是有一个线程完成的。看代码发现可能的一个bug,Cancel一个请求的时候,代码只是把请求的第一个Request从请求链表中移除,而没有移除所有的Request。在0.4的版本中,Sync接口没有实现,仅仅是一个空读。
[qcdio/qcdiskqueue.cpp] class QCDiskQueue 该类是对QCDiskQueue::Queue进行了简单的封装,同时使用条件变量提供了文件读写的磁盘接口。就是请求发起线程阻塞在那里,等待工作线程完成io操作。
以上就是磁盘异步IO的主要实现