windowsViaC/C++设备IO之异步设备IO请求

                               异步设备I/O适用于大数据量和高性能的场合,比如服务器。

要使用异步设备I/O,在调用CreateFile来打开或创建一个设备的时候,让参数dwFlagsAndAttributes包括FILE_FALG_OVERLAPPED,这意味着想让打开的设备可以被异步访问。为了发送一个I/O请求给一个设备,也就是让一个I/O请求进入I/O队列,你可以使用ReadFile和WriteFile这两个函数:

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);

当这两个函数被呼叫,系统通过第一个参数hFile,来查看该句柄指明的设备在打开的时候是否使用了FILE_FLAG_OVERLAPPED,如果使用了,这两个函数执行异步设备I/O,
反之,则执行同步设备I/O。当使用异步I/O方式的时候,在调用这两个函数的时候,可以将NULL传递给pdwNumBytes参数,因为不知道何时设备I/O完成,因此使用这个参数没有多大意义。

注意最后一个参数,是一个OVERLAPPED结构的指针:
typedef struct _OVERLAPPED {
   DWORD  Internal;     // 错误代码(出口参数,返回)
   DWORD  InternalHigh; // 传输的数据大小,以字节为单位(出口参数,返回)
   DWORD  Offset;       // 低32位偏移量(入口参数,输入)
   DWORD  OffsetHigh;   // 高32位偏移量(入口参数,输入)
   HANDLE hEvent;       // 事件内核对象句柄(入口参数,输入)
} OVERLAPPED, *LPOVERLAPPED;
该结构包含5个成员,其中的3个——Offset、OffsetHigh、hEvent应该在调用ReadFile和WriteFile之前被初始化,
另外的2个——Internal、InternalHigh会在I/O完成的时候被设备驱动程序所设置,下面细述一下:
 1、Offset、OffsetHigh —— 在使用异步设备I/O来操纵“文件设备”的时候,文件读写指针被忽略,此时I/O的偏移量由OVERLAPPED结构中的Offset和OffsetHigh决定。
  另外,在“非文件设备”中,这两个成员不会被忽略,一般必须要设置为0。
 2、hEvent —— 一个事件内核对象句柄,可以有多种使用方法,后面会讲到。
 3、Internal —— 保存I/O错误码,当你发送一个I/O请求的时候,该参数被设置为STATUS_PENDING,指明没有错误发生,因为操作还没有开始。
     你可以使用HasOverlappedIoCompleted宏来查看一个异步设备I/O是否完成,该结构接受一个OVERLAPPED结构指针,如果I/O请求完成返回TRUE。如果I/O请求仍然没有开始,返回FALSE。
 4、InternalHigh —— 异步I/O请求完成的时候,该成员里保存了传送数据量的字节数。
当异步I/O请求完成之后,你可以接受到一个OVERLAPPED结构的指针。一般可以让一个C++类从OVERLAPPED结构派生,类中加入一些其他信息,使得更容易处理。然后当使用ReadFile和WriteFile函数的时候,可以传递这个C++类对象的指针,当I/O完成之后,接受该结构的时候,可以将其转换为C++类对象,不但可以获得其5个成员,还可以获得类中的其他信息。
 使用异步设备I/O的时候,要注意以下三点:
1、设备驱动程序不一定会按照一个“先进先出”(FIFO)的顺序来处理设备I/O请求,因此如下编码不会保证先读后写:
OVERLAPPED o1 = { 0 };
OVERLAPPED o2 = { 0 };
BYTE bBuffer[100];
ReadFile (hFile, bBuffer, 100, NULL, &o1);   //读
WriteFile(hFile, bBuffer, 100, NULL, &o2);   //写
2、以异步的方式进行I/O请求的是,驱动程序可能会选择同步的方式。
 当你读取一个文件的时候,如果系统发现读取的数据在cache中,且数据有效,那么该I/O请求就不需要驱动程序了,而是直接将cache中的数据复制到你的缓冲区中。
 驱动在某些操作上一直使用同步方式,比如在NTFS格式上的文件压缩,扩展文件长度,添加文件信息等。这个时候,如果ReadFile和WriteFile返回非0值,则表明它以同步方式进行。
 如果返回FLASE,说明发生了一个错误,这个时候可以通过GetLastError来取得信息,如果返回ERROR_IO_PENDING,则说明I/O请求成功提交,但没有完成。
3、数据缓冲区和OVERLAPPED结构在异步I/O请求完成之前不能被移动或释放。
当设备驱动准备处理你的I/O请求的时候,它将数据传送到pvBuffer参数对应的地址上去,并访问OVERLAPPED结构中的Offset等成员。
当I/O请求完成之后,设备驱动更新OVERLAPPED结果中的Internal和InternalHigh成员。
因此,不能在I/O请求完成之前移动或释放数据缓冲区和OVERLAPPED结构,否则,内存数据会被破坏,而且在每次调用ReadFile或WriteFile的时候,都必须分配一个单独的OVERLAPPED结构。
比如,下面的代码是有BUG的:
VOID ReadData(HANDLE hFile)
{
   OVERLAPPED o = { 0 };
   BYTE b[100];
   ReadFile(hFile, b, 100, NULL, &o);
}  //此时缓冲区b和OVERLAPPED结构o都被释放
你可以将一个设备I/O请求取消排队,即撤消该请求。可以有如下方法:
1、在一个线程中调用CancelIo函数,可以取消该线程发送给指定设备有关的所有I/O请求,除了指定的设备是“I/O完成端口”。
BOOL CancelIo(HANDLE hFile);     //参数是设备对象句柄
2、取消与一个设备有关的所有I/O请求,关闭这个设备句柄即可。
3、当一个线程结束,系统自动取消该线程发送的所有I/O请求,除了发送给“I/O完成端口的”I/O请求。
4、如果想取消某一个特定的I/O请求,可以使用CancelIoEx函数,传递一个OVERLAPPED结构指针给它:
BOOL CancelIoEx(HANDLE hFile, LPOVERLAPPED pOverlapped);
该函数可以跨线程使用,也就是在T1线程内发送的I/O请求,可以在T2线程内通过该函数结束之。
因为每个I/O请求都需要一个唯一的OVERLAPPED结构,所以该OVERLAPPED结构就标识了一个I/O请求。
如果传递NULL给CancelIoEx函数的第2个参数,那么就会取消与hFile对应的设备的所有I/O请求。
取消一个I/O请求,该I/O请求会结束,同时错误码被设置为ERROR_OPERATION_ABORTED。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值