Windows和Linux下编写异步I/O的模型探讨
需要首先说明的是异步I/O和多路复用同步I/O(如通过select函数在连接池中选择任一完成的端口)是有本质差异的,虽然两种方式都能提高效率。
异步I/O的思想是在一个端口支持异步读写,读写异步的系统调用需要OS支持,比如Read、Write有异步的实现版本,则用户调用其Read、Write异步版本,如果端口暂时不可用,会立即返回到用户代码。可以针对同一个端口准备多个异步读写操作,充分利用端口资源以及减少用户在等待端口可用过程中被挂起的时间。
一、Windows下的异步I/O的模型
通过OVERLAPPED structure和WaitForMultipleObjects/WaitForMultipleEvents实现。
示例代码如下:
HANDLE hfile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...);
BYTE bBuffer[10];
OVERLAPPED oRead = { 0 };
oRead.Offset = 0;
oRead.hEvent = CreateEvent(...);
ReadFile(hfile, bBuffer, 10, NULL, &oRead);
OVERLAPPED oWrite = { 0 };
oWrite.Offset = 10;
oWrite.hEvent = CreateEvent(...);
WriteFile(hfile, "Jeff", 5, NULL, &oWrite);
HANDLE h[2];
h[0] = oRead.hEvent;
h[1] = oWrite.hEvent;
DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);
switch (dw _ WAIT_OBJECT_0) {
case 0: // Read completed
break;
case 1: // Write completed
break;
}
二、Linux下的异步I/O的模型
在Linux下不存在OVERLAPPED structure及WaitForMultipleObjects/WaitForMultipleEvents等系统调用,所以需要自行封装一些函数,以方便实现异步操作。
本课题基于Linux在内核实现了POSIX1003.1b标准定义的异步I/O函数。基本思想是当一个进程有异步I/O请求时,就为该进程创建一个队列来排队其所有的异步请求,并为该队列创建一个内核线程来完成队列中实际的I/O操作。异步I/O系统调用所做的工作只是将I/O请求加入调用进程的队列,如果调用该功能的进程是第一次发出异步I/O请求,则先要为该进程创建排队异步I/O请求的队列和相应的内核线程,然后进程直接返回而不用挂起等待I/O完成。当实际的I/O操作完成时,内核发一个信号通知进程I/O完成。
2.1 异步请求控制块aiocb
该结构是异步I/O系统调用的主要参数,其中包含了异步操作所需的所有信息,是最基本也是最重要的数据结构,包括要进行的操作(读或写)及其优先级,被请求的文件描述符、偏移量、字节数,缓冲区地址等。
Struct aiocb
{
/*下面7项成员的值由用户提供,用来确定异步I/O请求的各个参数*/
intaiofildes;/*文件描述符*
/intaiolioopcode;/*I/O操作类型*/
intaioreqprio;/*优先级*/
void* aiobuf;/*缓冲区*/
size_t aionbytes;/*字节数*/
struct sigevent aiosigevent;/*信号量*/
loff_t aiooffset;/*文件偏移量*/
/*下面这些是内部成员,用户不需提供*/
Kaio_key_t aiokey;
Unsigned long aiotimes;/*实际I/O完成的时间*/
int aioerror;/*aioerror的返回值*/
ssize_t aioreturn;/*aioreturn的返回值*/
}
2.2 内核异步请求控制块kaiocb
该结构是提供给内核的异步I/O信息,包含结构aiocb中的所有信息以及其它一些相关的数据结构。
Structkaiocb
{
Struct aiocb* kaiouaiocb;/*用户提供的aiocb*/
Struct file* kaiofilp;
void* kaiobuf;
size_t kaionbytes;
loff_t kaiooffset;
int kaiocmd;/*I/O操作:read|write|fsync*/
int kaioerror;
int kaioid;/*kaiocb描述符*/
struct kaiocb* kaiohnext;/*hash表后指针*/
struct kaiocb** kaiohpprev;/*hash表前指针*/
struct liocb *kaioliocb;/*ListI/O控制块指针*/
struct waitqueue **kaiosuspendwait;
struct taskstruct *kaiotask;/*提出请求的进程*/
struct kaio_list_head_kaio_ioq;/*异步请求队列头指针*/
void*kaiokq;/*异步请求队列*/
struct sigevent kaiosigevent;/*通知信号量*/
unsigned long kaiotimes[AIOTIMES];
#definek aiosettime(k,x)(k)->kaiotimes[(x)]=kaiotime()
}
2.3 异步请求队列
该队列是一个双向循环链表。每个有异步请求的进程创建一个异步请求队列,该进程的所有异步请求都在该队列中排队。
Struct kaio_queue
{
Spinlock_t kaioq_lock;
Structkaiolisthead kaioq;
Int kaioqref;
Int threads;
}
2.4 算法
以异步读aioread为例,该函数的功能是根据异步I/O控制块aiocb所提供的信息将指定的文件内容拷贝到指定的缓冲区中。
算法 aioread
输入:异步I/O、控制块aiocb
输出:成功返回0,失败返回-1
{
if(进程第一次发出异步请求,还没有异步请求队列)
为之创建一个异步请求队列kq;
if(当前的异步请求数目大于系统规定的最大值)
出错返回;
if(aiocb指定的文件不合法)
出错返回;
根据aiocb设置内核异步I/O控制块kiocb各个域的值;
调用kaiocbenqueue将请求入队;
if(入队操作失败返回)
出错返回;
函数返回;
}
算法 kaiocbenqueue
输入:异步请求队列kq、内核异步I/O控制块kaiocb
输出:成功返回0,失败返回相应的错误号
{
将kaiocb插入异步请求队列kq的相应位置;
if(kq还没有进行具体I/O操作的内核服务线程)
调用kernelthread(handleio,kaiocb,0)为之创建一个内核线程; (其中handleio是线程的执行函数);
函数返回;
}
算法 handleio
输入:内核异步I/O控制块kaiocb
输出:成功返回0,失败返回相应的错误号
{
while(队列不空)
{
执行文件底层的I/O操作fop->read;
操作完成后将该请求从队列中删除;
}
给进程kaiocb->kaiotask发信号kaiocb->kaiosigevent,通知进程I/O操作完成;
函数返回;
}
(转自:http://hi.baidu.com/gamedot/item/44070c34f64179d16c15e9da )