异步I/O请求: 当线程发出一个异步I/O请求时,这个请求被传递给设备驱动程序,由后者负责完成实际的I/O操作。 当驱动程序在等待设备响应的时候,应用程序的线程并没有因为等待I/O请求完成而被挂起,线程会继续运行并执行其他任务。
将异步I/O请求加入队列,有驱动程序完成对列中 I/O 请求的处理,并通知应用程序I/O操作已完成。
异步方式访问设备:
CreateFile(,,,) 在dwFlagsAndAttributes参数指定FILE_FLAG_OVERLAPPED标志来打开设备,告诉其用异步的方式访问设备。
将I/O请求加入到设备驱动程序的队列:
通过调用ReadFile, WriteFile 函数(会检测 设备文件是否标记了 FILE_FLAG_OVERLAPPED,若标记 则 异步,否则同步)将I/O请求放入队列。
重叠结构(OVERLAPPED):
执行异步设备I/O的时候,需为pOverlapped参数传递一个已初始化的OVERLAPPED结构.
“overlapped”意思是执行I/O请求的时间与线程执行其他任务的时间是重叠的。
typedef struct _OVERLAPPED {
DWORD Internal; //[out] error code
DWORD InternalHigh; //[out] number of bytes transferred
DWORD Offset; //[int] low 32it offset
DWORD OffsetHigh; //[int] high 32it offset
HANDLE hEvent; //[int] evernt handle or data
} OVERLAPPED, *LPOVERLAPPED;
其中offset OffsetHigh 用于 表明访问文件应从哪里开始I/O操作。
每个文件内核对象都有与之相关的文件指针,用于表明读取起始位置。每次操作完成后,os会自动更新文件指针。
同步I/O时 会接着上次的文件指针,异步I/O 无先后顺序,所以需指明其实偏移量。
异步I/O请求完成时,后收到一个OVERLAPPED结构的地址,及请求时的那个。有时为了获取更多的返回信息(如发送请求使用的设备句柄),这时可以对OVERLAPPED封装为C++类,使其能携带更多的信息。
异步设备I/O注意事项:
1. 驱动程序不必以先入先出的方式来处理队列中的I/O请求
1. 驱动程序不必以先入先出的方式来处理队列中的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 以同步方式执行时, ReadFile ,WriteFile 会返回非零值。 异步方式时,出错时 返回False, 需GetLastError判断,是出错还是 加入了队列,为处理完ERROR_IO_PENDING。
3.请求完成之前 不能移动或销毁发出的 数据缓存和OVERLAPPED结构
必须为每一个I/O请求 分配并初始化一个不同的OVERLAPPED结构
取消队列中的设备I/O请求:
BOOL CancelIoEx ( HANDLE hFile, LPOVERLAPPED poverlapped); poverlapped为NULL时,取消hFile上所有I/O请求
BOOL CancelIoEx ( HANDLE hFile, LPOVERLAPPED poverlapped); poverlapped为NULL时,取消hFile上所有I/O请求
接受I/O请求完成通知
Windows提供4中不同方式来接受完成通知
技术 | 简介 |
触发设备内核对象 |
一个线程发出I/O请求,另一个线程出结果进行处理。
当向一个设备同时发出多个I/O请示时,则无能为力。因为设备只有一个,其处于激发状态与否,只能表示一个请求的状态。
|
触发事件内核对象 |
一个线程发出I/O请求,另一个线程出结果进行处理。
可以向一个设备同时发出多个I/O请求(为每个请求建立相应的事件对象)
|
使用可提醒I/O | 可以向一个设备同时发出多个I/O请求,发出请求的线程处理结果 |
使用I/O完成端口 |
一个线程发出I/O请求,另一个线程出结果进行处理。
可以向一个设备同时发出多个I/O请求
|
例:
1.
触发设备内核对象
HANDLE hFile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...);
BYTE bBuffer[100];
OVERLAPPED o = {0};
o.Offset = 345;
bool bReadDone = ReadFile(hFile, bBuffer, 100, NULL, &o);
DWORD dwError = GetLastError();
if (!bReadDone && (dwError == ERROR_IO_PENDING))
{//waiting for it to complete
WaitForSingleObject(hFile, INFINITE); //挂起,等待完成请求,此时 驱动程序会将 设备内核设为触发状态
bReadDone = TRUE;
}
if(bReadDone)
{
//finished
}
else
{
//error occurred; See dwError
}
2.触发事件内核对象
当对一个设备同时进行多个异步I/O请求时,必须为每个请求创建不同的事件对象,并将OVERLAPPED结构的最后一个成员hEven 初始化为一个事件内核对象。然后发送请求--ReadFile, WriteFile
当需要检测I/O请求的完成状态时,可以通过调用WaitForMultipleObjects,并传入相关联的时间句柄 即可。
HANDLE hFile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...);
BYTE bReadBuffer[100];
OVERLAPPED oRead = {0};
o.Offset = 0;
oRead.hEvent = CreateEvent(...);
ReadFile(hFile, bReadBuffer, 100, NULL, &oRead);
BYTE bWriteBuffer[100] = {......};
OVERLAPPED oWrite = {0};
o.Offset = 0;
oWrite.hEvent = CreateEvent(...);
WriteFile(hFile, bReadBuffer, _countof(bWriteBuffer), 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;
}
}