Windows异步IO(Asynchronous IO) (一)

题记:最近在学习Windows SDK编程,打算在这里贴出自己的学习总结和心得与大家交流,主要参考资料来自<Windows via C/C++ 5th>和<Programming Windows>。我尽量用英文术语来表达技术概念,方便大家查找其它资料。第一篇从异步IO(Asynchronous IO)说起,以文件IO作为代表。

 

    异步IO是现代操作系统必不可少的特性,它让宝贵的CPU计算资源不会浪费在等待慢速IO上。它的行为方式很直观,用户线程在发送IO请求(Issue IO Request)后不用一直挂起,直到IO完成,而是直接返回继续执行其它任务。在设备驱动(Device Driver)完成IO请求后,会通知用户线程数据传输已完成,可以进行相关操作。Windows异步IO的过程主要有两步:1)向设备驱动发送IO请求,2)设备驱动在完成IO请求后通知用户已完成数据传输,即完成通知(Completion Notification)。这一篇主要介绍第一步:发送IO请求。

 

    Windows SDK中关于文件异步IO的操作,主要涉及这样几个基本函数CreateFile,ReadFile,WriteFile。

    1,HANDLE CreateFile( PCTSTR pszName, DWORD dwDesiredAccess, DWORD dwShareMode, PSECURITY_ATTRIBUTES psa, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hFileTemplate);  

        此函数用于打开一个设备,当然包括文件。当准备发送一个异步IO请求时,必须在dwFlagsAndAttributes参数中指定FILE_FLAG_OVERLAPPED标志,这样告诉系统你想要以异步的方式访问设备。

    2,BOOL ReadFile( HANDLE hFile, PVOID pvBuffer, DWORD nNumBytesToRead, PDWORD pdwNumBytes, OVERLAPPED* pOverlapped); BOOL WriteFile( HANDLE hFile, CONST VOID *pvBuffer, DWORD nNumBytesToWrite, PDWORD pdwNumBytes, OVERLAPPED* pOverlapped); 

        大家都会使用这两个函数进行同步IO,比如:

        #include <windows.h> #include <stdio.h> int main() { HANDLE hFile = NULL; BYTE buff[1024]; DWORD dwRead = 0; hFile = CreateFile(TEXT("data.txt"), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, NULL); ReadFile(hFile, buff, 1024, &dwRead, NULL); printf("%s", buff); return EXIT_SUCCESS; }

        这里的pOverlapped参数设为NULL。而在进行异步IO时,必须通过这个参数给这个IO请求指定一个OVERLAPPED结构,数据成员如下:

        typedef struct _OVERLAPPED { DWORD Internal; // [out] Error code DWORD InternalHigh; // [out] Number of bytes transferred DWORD Offset; // [in] Low 32-bit file offset DWORD OffsetHigh; // [in] High 32-bit file offset HANDLE hEvent; // [in] Event handle or data } OVERLAPPED, *LPOVERLAPPED; 

        其中:

        Internal                    作为输出参数,返回这次IO请求的错误码。

        InternalHigh             作为输出参数,返回这次IO请求的读写的字节数。

        Offset和OffsetHigh    作为输入参数,指定此次IO请求的起始偏移。这个与同步IO不同:对于每个文件核心对象(File Kernel Object),所有IO请求都依次执行,不能重叠(Overlapped名称的由来),每次IO的起始偏移由文件核心对象的文件指针指定。但在异步IO中,多个IO请求可能重叠进行,这样,文件核心对象中的文件指针是没有意义的,所以每个IO请求必须在Overlapped结构中单独指定起始偏移。

        hEvent                      作为输入参数,用于完成通知(Completion Notification)。以后会详细讲到。

        由上面的功能可看出,Overlapped提供的信息几乎覆盖了一个IO请求的方方面面,事实上,每一个IO请求都对应一个Overlapped,它是用户与设备驱动通信的通道。因此,在整个IO请求完成前,Overlapped结构必须一直有效。

        用户在用ReadFile/WriteFile向设备驱动发送IO请求后,必须仔细对函数返回值进行检查:

        1)若返回非0值,表示IO请求立即完成并返回,此时IO请求并没有进入请求队列。这对异步IO请求是完全可能的,因为系统以前可能刚好缓存(cache)了用户此时请求的内容,所以可以可以马上完成请求。

        2)若返回FALSE,则必须马上调用GetLastError进一步判断。如果GetLastError返回ERROR_IO_PENDING,表明此IO请求成功加入请求队列。如果返回其它值,表明此IO请求不能被加入请求队列,下面是常见的加入请求失败的返回值:

        ERROR_INVALID_USER_BUFFER/ERROR_NOT_ENOUGH_MEMORY 表明队列已满,无法加入新的请求。参考代码:

        #include <windows.h> #include <stdio.h> int main() { HANDLE hFile = NULL; BYTE buff[1024]; OVERLAPPED ol = {0}; BOOL bRet = FALSE; DWORD dwCode = 0; hFile = CreateFile(TEXT("data.txt"), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); ol.Offset = 16; //Read data from file starting at byte 16 bRet = ReadFile(hFile, buff, 1024, NULL, &ol); if (bRet) //IO request is completed immediately { } else if ((dwCode = GetLastError()) == ERROR_IO_PENDING) //IO request is queued successfully { } else { //Error Handling here } return EXIT_SUCCESS; }

        我们前面说过,Overlapped结构中的Internal成员是作为此次IO请求的错误码返回,所以我们可以通过检查此变量判断目前IO请求的状态。我们在发送IO请求后,如果此请求被成功加入队列,Overlapped结构中的Internal变量会被设为STATUS_PENDING。事实上Windows为我们提供了一个宏:

        #define HasOverlappedIoCompleted(pOverlapped) / ((pOverlapped)->Internal != STATUS_PENDING) 

        Overlapped提供几乎所有我们需要的信息,这也充分说明了它是用户与设备驱动进行通信的通道。

你可能感兴趣的:(windows,IO,File,null,asynchronous,attributes)