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内核原理-同步IO与异步IO

WINDOWS硬件通知应用程序的常方法(五种方式:异步过程调用APC,事件方式VxD,消息方式,异步I/O方式,事件方式WDM)

在 Windows 中的另一个线程上异步启动和取消 I/O 的无竞争方式

vSocket模型详解及select应用详解

Windows I/O模型同步/异步阻塞/非阻塞(转载)

I/O 模型详解