10.5 接收I/O请求完成的通知
(1)I/O请求被加入设备驱动程序的队列,当请求完成以后,设备驱动也要负责通知我们I/O请求己经完成。
(2)可以用4种方法来接收I/O请求己经完成的通知
技术 |
特点 |
触发设备内核对象 |
①允许一个线程发出I/O请求,另一个线程对结果进行处理。 ②当向一个设备同时发出多个I/O请求的时候,这种方法是不能用的,因为等待函数中等待的是同一个内核对象,只要任何一个I/O请求完成时都会被触发,却没办法区别是哪个请求的完成触发了内核对象。 |
触发事件内核对象 |
①允许一个线程发出I/O请求,另一个线程对结果进行处理。 ②允许我们向一个设备同时发出多个I/O请求的时候。(因为每个请求都通过pOverlapped与一个事件相关联) |
使用可警告I/O |
①发出I/O请求的线程必须对结果进行处理,因为这是通过线程的APC队列来实现的,而APC队列是线程独有的。 ②允许我们向一个设备同时发出多个I/O请求的时候。 |
使用I/O完成端口 |
①允许一个线程发出I/O请求,另一个线程对结果进行处理。 ②允许我们向一个设备同时发出多个I/O请求的时候。 ③这项技术具有高度的伸缩性和最佳的灵活性 |
10.5.1 通过触发设备内核对象来通知I/O处理己完成
(1)Read/WriteFile在将I/O请求添加到队列之前,会先将对象设为未触发状态。当设备驱动程序完成了请求之后,会将设备内核对象设为触发状态。
(2)使用这种方法不能达到异步调用的好处,因为发出请求以后,要立即等待请求的完成,这跟同步调用效果是一样的。
【示例代码】——在实际的代码中,用这种方式来获取通知,因为没能真正体现异步的好处,也不能处理多个I/O请求。
//以异步方问访问hFile设备(传入参数FILE_FLAG_OVERLAPPED) HANDLE hFile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...); BYTE bBuffer[100]; OVERLAPPED o = { 0 }; o.Offset = 345; //从第346个字节开始读取数据 BOOL bReadDone = ReadFile(hFile, bBuffer, 100, NULL, &o); //添加异步I/O请求 DWORD dwError = GetLastError(); //ReadFile异步调用会立即,返回值为FALSE,GetLastError为ERROR_IO_PENDING表示 //请求正在被处理 if (!bReadDone && (dwError == ERROR_IO_PENDING)){ //I/O请求正在被异步处理,等待I/O请求完成的通知 WaitForSingleObject(hFile, INFINITE); //等待的是设备内核对象! bReadDone = TRUE; } if (bReadDone){ //读取完成后,数据被写入bBuffer,状态信息写入pOverlapped结构体中 //o.Internal包含IO错误代码 //o.InternalHigh包含己经传输的字节数 //bBuffer包含读取到的数据 } else{ //出现错误,可查看dwError以获得更多信息 }
【示例程序2】——用来说明设备内核对象不能处理多个IO请求
//以异步方问访问hFile设备(传入参数FILE_FLAG_OVERLAPPED) HANDLE hFile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...); //异步I/O读请求 BYTE bReadBuffer[10]; OVERLAPPED oRead = { 0 }; oRead.Offset = 0; ReadFile(hFile, bReadBuffer, 100, NULL, &o); //添加异步I/O读请求 //异步I/O写请求 BYTE bWriteBuffer[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; OVERLAPPED oWrite = { 0 }; oWrite.Offset = 10; WriteFile(hFile, bWriteBuffer,_countof(bWriteBuffer), NULL, &oWrite); ... WaitForSingleObject(hFile, INFINITE); //hFile被触发可以是读操作完成或写完成。 //我们不知道为什么完成:读?写?或两者都是?
10.5.2 触发事件内核对象
(1)在每个I/O请求的OVERLAPPED结构体的hEvent创建一个用来监听该请求完成的事件对象。当一个异步I/O请求完成时,设备驱动程序会调用SetEvent来触发事件。
(2)驱动程序仍然会像从前一样,将设备对象也设为触发状态,因为己经有了可用的事件对象,所以可以通过SetFileCompletionNoticationModes(hFile,FILE_SKIP_SET_EVENT_ON_HANDLE)来告诉操作系统在操作完成时,不要触发文件对象。
(3)以下代码是故意那样设计的。实际应用中,可用一个循环来等待I/O请求完成。
【示例程序】——利用事件对象处理多个IO请求
//以异步方问访问hFile设备(传入参数FILE_FLAG_OVERLAPPED) HANDLE hFile = CreateFile(..., FILE_FLAG_OVERLAPPED, ...); //异步I/O读请求 BYTE bReadBuffer[10]; OVERLAPPED oRead = { 0 }; oRead.Offset = 0; oRead.hEvent = CreateEvent(...); //创建一个监听读的事件对象 ReadFile(hFile, bReadBuffer, 100, NULL, &o); //添加异步I/O读请求 //异步I/O写请求 BYTE bWriteBuffer[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; OVERLAPPED oWrite = { 0 }; oWrite.Offset = 10; oWrite.hEvent = CreateEvent(...);//创建一个监听写的事件对象 WriteFile(hFile, bWriteBuffer, _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: break;//读完成 case 1: break;//写完成 }