Windows异步I/O详解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows异步I/O详解相关的知识,希望对你有一定的参考价值。
当我们对文件进行读写时,线程本该是阻塞的,即线程在等待读写操作的结束,这种方式称为同步I/O。Windows在系统层为我们提供了一种高效的机制——异步IO。
异步IO提供了这样一种功能:当你读取文件时,读取函数会立刻返回,读取任务转交给系统底层自动处理,这样文件的读取操作就不会阻塞线程。IO操作完成时,由系统发出完成通知。
介绍三种完成通知:
1.直接等待句柄(文件等待对象)
2.等待OVERLAPPED中的hEvent句柄(等待事件对象)
3.异步调用(APC调用)
主要函数:CreateFile();ReadFile();WriteFile();
数据成员:OVERLAPPED结构体。
一个句柄如果是以异步IO的方式打开,那这个句柄就具有了两个特性:
1.文件变为可等待的对象(即具有激发态和非激发态)
2.文件指针失效(需要通过OVERLAPPED结构体中的的Offect表示读取或写入的位置,而不能用SetFilePointer()这样的函数。)
代码实例:(VS2015控制台)
准备:Debug目录下创建123.exe并保存数据
1.直接等待句柄
#include "stdafx.h"
#include <windows.h>
typedef struct _MYOVERLAPPED{
OVERLAPPED ol;
HANDLE hFile;
PBYTE pBuf;
int nIndex;
}MYOVERLAPPED,*PMYOVERLAPPED;
DWORD WINAPI ThreadProc(LPVOID lParam) {
PMYOVERLAPPED pol = (PMYOVERLAPPED)lParam;
WaitForSingleObject(pol->hFile, INFINITE);
for (int i=0;i<10;i++)
{
printf("%d:%d \n", pol->nIndex,pol->pBuf[i]);
}
printf("读完了!\n");
return 0;
}
int main()
{
// 1. 异步IO标记
// 有了这个标记 该文件就变为可等待的内核对象
// 后面的read write函数就变为非阻塞的
HANDLE hFile = CreateFile(L"123.txt", GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL| FILE_FLAG_OVERLAPPED, NULL);
// 2. 文件读取
PMYOVERLAPPED pol = new MYOVERLAPPED{};
pol->ol.Offset = 0x0;// 从偏移0x100这个位置开始读
// pol->hEvent == NULL; 系统读取完成后,会把我的hFile变为有信号状态
pol->hFile = hFile;
pol->pBuf = new BYTE[0x1000]{};
pol->nIndex =1;
ReadFile(hFile,
pol->pBuf,
0x1000,
NULL,//实际读取的个数,由OVERLAPPED结构体指定
(LPOVERLAPPED)pol);
HANDLE hThread = CreateThread(NULL, NULL, ThreadProc, pol, NULL, NULL);
PMYOVERLAPPED pol2 = new MYOVERLAPPED{};
pol2->ol.Offset = 0x0;// 从偏移0x100这个位置开始读
// pol->hEvent == NULL; 系统读取完成后,会把我的hFile变为有信号状态
pol2->hFile = hFile;
pol2->pBuf = new BYTE[0x1000]{};
pol2->nIndex = 2;
ReadFile(hFile,
pol2->pBuf,
0x1000,
NULL,//实际读取的个数,由OVERLAPPED结构体指定
(LPOVERLAPPED)pol2);
HANDLE hThread2 = CreateThread(NULL, NULL, ThreadProc, pol2, NULL, NULL);
// ......干其他事
WaitForSingleObject(hThread, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
return 0;
}
2.等待事件对象
#include "stdafx.h"
#include <windows.h>
typedef struct _MYOVERLAPPED {
OVERLAPPED ol;
HANDLE hFile;
PBYTE pBuf;
int nIndex;
}MYOVERLAPPED, *PMYOVERLAPPED;
DWORD WINAPI ThreadProc(LPVOID lParam) {
PMYOVERLAPPED pol = (PMYOVERLAPPED)lParam;
printf("开始等待......\n");
WaitForSingleObject(pol->ol.hEvent, INFINITE);
for (int i = 0; i < 10; i++)
{
printf("%d:%02x \n", pol->nIndex, pol->pBuf[i]);
}
printf("读完了!\n");
return 0;
}
int main()
{
// 1. 异步IO标记
// 有了这个标记 该文件就变为可等待的内核对象
// 后面的read write函数就变为非阻塞的
HANDLE hFile = CreateFile(L"..\\Debug\\123.exe", GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
// 2. 文件读取
PMYOVERLAPPED pol = new MYOVERLAPPED{};
pol->ol.Offset = 0x100;// 从偏移0x100这个位置开始读
pol->ol.hEvent = CreateEvent(NULL,NULL,FALSE,NULL); //系统读取完成后,会把我的hFile变为有信号状态
pol->hFile = hFile;// 被无视
pol->pBuf = new BYTE[0x1000]{};
pol->nIndex = 1;
ReadFile(hFile,
pol->pBuf,
0x1000,
NULL,//实际读取的个数,由OVERLAPPED结构体指定
(LPOVERLAPPED)pol);
HANDLE hThread = CreateThread(NULL, NULL, ThreadProc, pol, NULL, NULL);
PMYOVERLAPPED pol2 = new MYOVERLAPPED{};
pol2->ol.Offset = 0x200;// 从偏移0x100这个位置开始读
pol2->ol.hEvent = CreateEvent(NULL, NULL, FALSE, NULL); //系统读取完成后,会把我的hFile变为有信号状态
pol2->hFile = hFile;// 被无视
pol2->pBuf = new BYTE[0x1000]{};
pol2->nIndex = 2;
ReadFile(hFile,
pol2->pBuf,
0x1000,
NULL,//实际读取的个数,由OVERLAPPED结构体指定
(LPOVERLAPPED)pol2);
HANDLE hThread2 = CreateThread(NULL, NULL, ThreadProc, pol2, NULL, NULL);
// ......干其他事
WaitForSingleObject(hThread, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
return 0;
}
3.异步调用(APC调用)
异步调用提供了这样一个机制:你可以在读取文件或者写入文件的时候,提供一个回调函数,当读写任务完成时,就会调用这个回调函数。
#include "stdafx.h"
#include <windows.h>
typedef struct _MYOVERLAPPED {
OVERLAPPED ol;
HANDLE hFile;
PBYTE pBuf;
int nIndex;
}MYOVERLAPPED, *PMYOVERLAPPED;
// 提交任务的线程处理,其他线程看着
VOID CALLBACK FileIOCompletionRoutine(
_In_ DWORD dwErrorCode,
_In_ DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
) {
PMYOVERLAPPED pol = (PMYOVERLAPPED)lpOverlapped;
for (int i = 0; i < 10; i++)
{
printf("%d:%02x \n", pol->nIndex, pol->pBuf[i]);
}
printf("读完了!\n");
}
int main()
{
// 1. 异步IO标记
// 有了这个标记 该文件就变为可等待的内核对象
// 后面的read write函数就变为非阻塞的
HANDLE hFile = CreateFile(L"..\\Debug\\123.exe", GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
// 2. 文件读取
PMYOVERLAPPED pol = new MYOVERLAPPED{};
pol->ol.Offset = 0x100;// 从偏移0x100这个位置开始读
// hEvent被无视 hFile也被无视
//pol->ol.hEvent = CreateEvent(NULL, NULL, FALSE, NULL); //系统读取完成后,会把我的hFile变为有信号状态
pol->hFile = hFile;// 被无视
pol->pBuf = new BYTE[0x1000]{};
pol->nIndex = 1;
ReadFileEx(hFile,
pol->pBuf,
0x1000,
(LPOVERLAPPED)pol,
FileIOCompletionRoutine);// 完成后直接调用该回调函数,不用等待文件句柄/事件对象
PMYOVERLAPPED pol2 = new MYOVERLAPPED{};
pol2->ol.Offset = 0x200;// 从偏移0x100这个位置开始读
//pol2->ol.hEvent = CreateEvent(NULL, NULL, FALSE, NULL); //系统读取完成后,会把我的hFile变为有信号状态
//pol2->hFile = hFile;// 被无视
pol2->pBuf = new BYTE[0x1000]{};
pol2->nIndex = 2;
ReadFileEx(hFile,
pol2->pBuf,
0x1000,
(LPOVERLAPPED)pol2,
FileIOCompletionRoutine);
// FileIOCompletionRoutine有系统调用
// 哪个线程执行该函数呢
// 哪个线程read/write 哪个线程执行
// ......干其他事
// 忙完了 想起来还有两个函数等着我呢
// CPU检测到当前线程的APC队列里有函数需要执行
// 就去执行该函数,执行完返回
// 只有当第2个参数是TRUE才去执行
SleepEx(200, TRUE);
// WaitForSingleObjectEx()
return 0;
}
以上是关于Windows异步I/O详解的主要内容,如果未能解决你的问题,请参考以下文章
WINDOWS硬件通知应用程序的常方法(五种方式:异步过程调用APC,事件方式VxD,消息方式,异步I/O方式,事件方式WDM)