同步IO是指:线程在发起IO请求后会被挂起,IO完成后继续执行。

  异步IO是指:线程发起IO请求后并不会挂起而是继续执行。IO完毕后会得到设备驱动程序的通知。

  一.异步准备与OVERLAPPED结构

  (1).为了以异步的方式来访问设备,必须先调用CreateFile,并在dwFlagsAndAttributes参数中指定FILE_FLAG_OVERLAPPED标志来打开设备。该标志告诉系统要以异步的方式来访问设备。

  为了将I/O请求加入设备驱动程序的队列中,必须使用ReadFile和WriteFile函数:

  

HANDLE CreateFile(
LPCTSTR lpFileName, // 文件名/设备路径 设备的名称
DWORD dwDesiredAccess, // 访问方式
DWORD dwShareMode, // 共享方式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全描述符指针
DWORD dwCreationDisposition, // 创建方式
DWORD dwFlagsAndAttributes, // 文件属性及标志
HANDLE hTemplateFile // 模板文件的句柄
);

  当调用ReadFile,WriteFile这两个函数中任何一个时,函数会检查hFile参数标识的设备是否用FILE_FLAG_OVERLAPPED标志打开的。如果打开设备时指定了这个标志,那么函数会执行异步设备I/O。

BOOL WINAPI ReadFile(
_In_ HANDLE hFile,
_Out_ LPVOID lpBuffer,
_In_ DWORD nNumberOfBytesToRead,
_Out_opt_ LPDWORD lpNumberOfBytesRead,
_Inout_opt_ LPOVERLAPPED lpOverlapped
); BOOL WINAPI ReadFile(
_In_ HANDLE hFile,
_Out_ LPVOID lpBuffer,
_In_ DWORD nNumberOfBytesToRead,
_Out_opt_ LPDWORD lpNumberOfBytesRead,
_Inout_opt_ LPOVERLAPPED lpOverlapped
);

  

  (2).再来看OVERLAPPED结构:

  

typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
} DUMMYSTRUCTNAME;
PVOID Pointer;
} DUMMYUNIONNAME; HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;

  1.Internal成员:这个成员用来保存已处理的I/O请求的错误码.

     InternalHigh成员:当异步I/O请求完成的时候,这个成员用来保存已传输的字节数。

  2..Offset和OffsetHigh成员,构成一个64位的偏移量,它们表示当访问文件的时候应该从哪里开始进行I/O操作。每个文件内核对象都有一个与之相关联的文件指针。在执行异步I/O的时候,系统会忽略文件指针。这是为了避免在对同一个对象进行多个异步调用的时候出现混淆,所有异步I/O请求必须在OVERLAPPED结构中指定起始偏移量。非文件设备会忽略这两个参数,必须将其初始化为0,否则I/O请求会失败。

  (3.)异步设备IO注意事项

    1:异步IO不会按照你的投递顺序来执行,驱动会选择他认为最快的方式来组合这些投递

    2:错误处理,以文件IO为例,当我们投递一个异步ReadFile()时,设备驱动程序可能会以同步方式执行,例如如果设备驱动程序发现要读取的数据在文件缓冲里时,就不会投递这个异步设备IO,而是直接将数据复制进我们的缓冲区

    3.如果IO是同步方式执行,ReadFile()和WriteFile()返回非零值,如果是异步或者出现错误,返回FALSE,调用GetLastError()获得错误码,如果返回的是ERROR_IO_PENDING,那么IO请求已经被成功地加入了队列。

  二.接收IO请求完成的方法  

Windows提供了4种不同的技术方法来得到I/O完成的通知。

技术 概要
通知一个设备内核对象 当一个设备同时有多个IO请求的时候,该方法不适用。允许一个线程发送一个IO请求,另一个线程处理之。
通知一个事件内核对象 允许一个设备同时有多个IO请求。允许一个线程发送一个IO请求,另一线程处理之。
警告IO 允许一个设备同时有多个IO请求。必须在同一个线程中发送并处理同一个IO请求。
IO完成端口 允许一个设备同时有多个IO请求。允许一个线程发送一个IO请求,另一个线程处理之。该方法伸缩性好,而且性能高。

  

  1.触发设备内核对象

  (1)Read/WriteFile在将I/O请求添加到队列之前,会先将对象设为未触发状态。当设备驱动程序完成了请求之后,会将设备内核对象设为触发状态,线程可以通过WaitForSingalObject或WaitForMultiObject来检查一个异步IO请求是否完成。

  (2)不能向同一个设备发出多个IO请求。

  2.触发事件内核对象

  (1)在每个I/O请求的OVERLAPPED结构体的hEvent创建一个用来监听该请求完成的事件对象。当一个异步I/O请求完成时,设备驱动程序会调用SetEvent来触发事件。驱动程序仍然会像从前一样,将设备对象也设为触发状态,因为己经有了可用的事件对象,所以可以通过SetFileCompletionNoticationModes(hFile,FILE_SKIP_SET_EVENT_ON_HANDLE)来告诉操作系统在操作完成时,不要触发文件对象。

  3.使用可提醒IO 

  (1)创建线程时,会同时创建一个与线程相关联的APC队列(异步过程调用),可以告诉设备程序驱动程序在I/O完成时,为了将通知信息添加到线程的APC队列中,需要调用ReadFileEx和WriteFileEx函数。

  (2)ReadFile/WriteFileEx函数与Read/WriteFile最大的不同在于最后一个参数,这是一个回调函数(也叫完成函数)的地址,当*Ex发出一个I/O请求时,这两个函数会将回调函数的地址传给设备驱动程序。当设备驱动程序完成I/O请求的时候,会在发出I/O请求的线程的APC队列中添加一项。该项包含了完成函数的地址以及发出I/O请求时使用的OVERLAPPED的地址。

  对于这种异步设备I/O方式,确实没什么用。重要的是微软为可提醒IO构建的基础设施——APC(Asynchronous Procedure Call),异步过程调用。

  当创建一个线程的时候,系统会为线程维护一个APC队列,该队列中的项目想要得到执行,线程必须处于可提醒等待状态,即使用SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx等函数,并且把最后一个参数设置为TRUE。当线程处于可提醒等待状态时,线程就会执行APC队列中的APC,之后执行过的APC就会清除队列,再进行下一次执行APC(如果APC队列中还有未执行的APC)。

  在这一小节中,作者的用意就是不要使用可提醒I/O方式进行异步设备I/O——因为可提醒I/O的两大缺陷:回调函数的累赘和线程的无负载均衡机制。

// Overlapped.cpp : 定义控制台应用程序的入口点。

#include "stdafx.h"
#include <windows.h>
#include <iostream>
/*
Internal成员:这个成员用来保存已处理的I/O请求的错误码.
InternalHigh成员:当异步I/O请求完成的时候,这个成员用来保存已传输的字节数。
在当初设计OVERLAPPED结构的时候,Microsoft决定不公开Internal和InternalHigh成员(名副其实)。随着时间的推移,Microsoft认识到这些成员包含的信息会对开发人员有用,因此把它们公开了。但是,Microsoft没有改变这些成员的名字,这是因为操作系统的源代码频繁地用到它们,而Microsoft并不想为此修改源代码。
*/
using namespace std; #define PAGE_SIZE 0x1000
void Sub_1(); //ReadFile 异步操作
void Sub_2(); //ReadFileEx
DWORD WINAPI Sub_1ThreadProcedure(LPVOID ParameterData);
DWORD WINAPI Sub_2ThreadProcedure(LPVOID ParameterData);
OVERLAPPED __Overlapped = { 0 };
char __BufferData[4] = {0}; int main()
{ //Sub_1(); //触发事件内核对象
Sub_2(); //可提醒IO
}
void Sub_1()
{
BOOL IsOk = FALSE;
DWORD ReturnLength = 0;
HANDLE FileHandle = CreateFile(L"ReadMe.txt", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (FileHandle == INVALID_HANDLE_VALUE)
{
int LastError = GetLastError();
goto Exit;
}
//当一个异步IO请求完成的时候,驱动程序检查OVERLAPPED结构的hEvent成员是否为NULL
//如果hEvent不为NULL,那么驱动程序会调用SetEvent来触发事件,这时候就是使用事件对象来检查一个设备操作是否完成,而不是等待设备(文件)对象
__Overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); //绝对要创建
HANDLE ThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Sub_1ThreadProcedure,
(LPVOID)FileHandle, 0, NULL); if (__BufferData == NULL)
{
goto Exit;
}
IsOk = ReadFile(FileHandle, __BufferData, 4, &ReturnLength, &__Overlapped); //事件必须创建
if (IsOk == FALSE)
{
int LastError = GetLastError(); if (LastError == ERROR_IO_PENDING)
{
//成功
printf("ERROR_IO_PENDING\r\n"); //重叠I/O返回标志
}
}
WaitForSingleObject(ThreadHandle, INFINITE);
Exit:
if (FileHandle != NULL)
{
CloseHandle(FileHandle);
FileHandle = NULL;
}
printf("\r\n");
return;
}
DWORD WINAPI Sub_1ThreadProcedure(LPVOID ParameterData)
{
HANDLE FileHandle = (HANDLE)ParameterData;
BOOL IsOk = FALSE;
DWORD ReturnLength = 0;
while (1)
{
IsOk = WaitForSingleObject(__Overlapped.hEvent, INFINITE);
IsOk -= WAIT_OBJECT_0;
if (IsOk == 0)
{
IsOk = GetOverlappedResult(FileHandle, &__Overlapped, &ReturnLength, INFINITE); if (IsOk==TRUE)
{
int i = 0;
for (i = 0; i < ReturnLength; i++)
{
printf("%c", __BufferData[i]);
}
__Overlapped.Offset += ReturnLength;
ReadFile(FileHandle, &__BufferData, 4, &ReturnLength, &__Overlapped);
} else
{
//数据完毕
break;
} }
else
{
return 0;
}
} return 0;
} void Sub_2()
{
BOOL IsOk = FALSE;
HANDLE FileHandle = CreateFile(L"ReadMe.txt", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (FileHandle == INVALID_HANDLE_VALUE)
{
int LastError = GetLastError();
goto Exit;
} //__Overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); //不能提供该事件
HANDLE ThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Sub_2ThreadProcedure,
(LPVOID)FileHandle, 0, NULL); if (__BufferData == NULL)
{
goto Exit;
}
IsOk = ReadFileEx(FileHandle, __BufferData, 4, &__Overlapped, NULL);
if (IsOk == FALSE)
{
int LastError = GetLastError(); if (LastError == ERROR_IO_PENDING)
{
//成功
printf("ERROR_IO_PENDING\r\n"); //重叠I/O返回标志
}
}
WaitForSingleObject(ThreadHandle, INFINITE);
Exit:
if (FileHandle != NULL)
{
CloseHandle(FileHandle);
FileHandle = NULL;
}
printf("\r\n");
return;
} DWORD WINAPI Sub_2ThreadProcedure(LPVOID ParameterData)
{
HANDLE FileHandle = (HANDLE)ParameterData;
DWORD ReturnLength = 0;
BOOL IsOk = FALSE;
while (1)
{
IsOk = GetOverlappedResult(FileHandle, &__Overlapped, &ReturnLength, TRUE);
//当一个可提醒IO完成时,设备驱动程序不会试图去触发一个事件对象
//IsOk = WaitForSingleObject(__Overlapped.hEvent, INFINITE);
if (IsOk == TRUE)
{
int i = 0;
for (i = 0; i < ReturnLength; i++)
{
printf("%c", __BufferData[i]);
} __Overlapped.Offset += ReturnLength;
ReadFileEx(FileHandle, &__BufferData, 4, &__Overlapped, NULL);
}
else
{
return 0;
}
} return 0;
}

  

    

// Overlapped.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std;
VOID CALLBACK CompletionRoutine(
_In_ DWORD ErrorCode,
_In_ DWORD ReturnLength,
_Inout_ LPOVERLAPPED Overlapped);
HANDLE __FileHandle = NULL;
char __BufferData[20] = {0};
int main()
{
BOOL IsOk = FALSE;
OVERLAPPED Overlapped = { 0 };
__FileHandle = CreateFile(L"ReadMe.txt", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, NULL);
if (__FileHandle == INVALID_HANDLE_VALUE)
{
int LastError = GetLastError();
goto Exit;
}
IsOk = ReadFileEx(__FileHandle, __BufferData, 4,&Overlapped,
(LPOVERLAPPED_COMPLETION_ROUTINE)CompletionRoutine);
if (IsOk == FALSE)
{
int LastError = GetLastError();
if (LastError == ERROR_IO_PENDING)
{
//成功
}
}
Exit:
SleepEx(0,TRUE);
if (__FileHandle != NULL)
{
CloseHandle(__FileHandle);
__FileHandle = NULL;
}
printf("Input AnyKey To Exit\r\n");
getchar();
return 0;
}
VOID CALLBACK CompletionRoutine(
_In_ DWORD ErrorCode,
_In_ DWORD ReturnLength,
_Inout_ LPOVERLAPPED Overlapped
)
{
if (ErrorCode == ERROR_SUCCESS)
{
int i = 0;
for (i = 0; i < ReturnLength; i++)
{
printf("%c", __BufferData[i]);
} Overlapped->Offset += ReturnLength;
ReadFileEx(__FileHandle, __BufferData, 4, Overlapped,
(LPOVERLAPPED_COMPLETION_ROUTINE)CompletionRoutine);
}
else if (ErrorCode==ERROR_HANDLE_EOF)
{
//数据完成
printf("\r\n");
}
else
{ }
}

  

异步设备IO OVERLAPPED结构(设备内核对象 事件内核对象 可提醒IO)的更多相关文章

  1. 异步IO模型和Overlapped结构

    .NET中的 Overlapped 类 异步IO模型和Overlapped结构(http://blog.itpub.net/25897606/viewspace-705867/) 数据结构 OVERL ...

  2. 《Windows核心编程系列》九谈谈同步设备IO与异步设备IO之同步设备IO

    同步设备IO 所谓同步IO是指线程在发起IO请求后会被挂起,IO完成后继续执行. 异步IO是指:线程发起IO请求后并不会挂起而是继续执行.IO完毕后会得到设备的通知.而IO完成端口就是实现这种通知的很 ...

  3. 【RT-Thread笔记】IO设备模型及GPIO设备

    RTT内核对象--设备 RT-Thread有多种内核对象,其中设备device就是其中一种. 内核继承关系图如下: 设备继承关系图如下: device对象对应的结构体如下: 其中,设备类型type有如 ...

  4. Linux字符设备驱动基本结构

    1.Linux字符设备驱动的基本结构 Linux系统下具有三种设备,分别是字符设备.块设备和网络设备,Linux下的字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中某一数据,读取数据 ...

  5. linux块设备驱动---相关结构体(转)

    上回最后面介绍了相关数据结构,下面再详细介绍 块设备对象结构 block_device 内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区.如果该结构代表一个分区,则其成 ...

  6. 设备文件三大结构:inode,file,file_operations

    驱动程序就是向下控制硬件,向上提供接口,这里的向上提供的接口最终对应到应用层有三种方式:设备文件,/proc,/sys,其中最常用的就是使用设备文件,而Linux设备中用的最多的就是字符设备,本文就以 ...

  7. Linux设备文件三大结构:inode,file,file_operations

    驱动程序就是向下控制硬件,向上提供接口,这里的向上提供的接口最终对应到应用层有三种方式:设备文件,/proc,/sys,其中最常用的就是使用设备文件,而Linux设备中用的最多的就是字符设备,本文就以 ...

  8. 【Linux开发】linux设备驱动归纳总结(一):内核的相关基础概念

    linux设备驱动归纳总结(一):内核的相关基础概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  9. Linux设备驱动开发详解-Note(5)---Linux 内核及内核编程(1)

    Linux 内核及内核编程(1) 成于坚持,败于止步 Linux 2.6 内核的特点 Linux 2.6 相对于 Linux 2.4 有相当大的改进,主要体现在如下几个方面. 1.新的调度器 2.6 ...

随机推荐

  1. Server SQL Modes

    The MySQL server can operate in different SQL modes, and can apply these modes differently for diffe ...

  2. 20165327 2017-2018-2 《Java程序设计》第8周学习总结

    20165327 2017-2018-2 <Java程序设计>第8周学习总结 教材内容总结 第十二章 (一)教材学习内容总结 线程是比进程更小的执行单位.一个进程在其执行过程中,可以产生多 ...

  3. WPF自定义漂亮的按钮样式

    首先打开 Microsoft Visual Studio 2008 ,新建一个WPF项目,在上面随便放几个按钮: 然后给各个按钮设置不同的背景颜色: 设置好之后就是这样啦: 然后我们就开始在 App. ...

  4. Linux系统常见内核问题修复(转发)

    Linux系统常见内核问题修复(转发) 常见Linux系统破坏修复 http://blog.csdn.net/jmilk/article/details/49619587

  5. Bipartite Segments CodeForces - 901C (区间二分图计数)

    大意: 给定无向图, 无偶环, 每次询问求[l,r]区间内, 有多少子区间是二分图. 无偶环等价于奇环仙人掌森林, 可以直接tarjan求出所有环, 然后就可以预处理出每个点为右端点时的答案. 这样的 ...

  6. pytorch初步学习(一):数据读取

    最近从tensorflow转向pytorch,感受到了动态调试的方便,也感受到了一些地方的不同. 所有实验都是基于uint16类型的单通道灰度图片. 一开始尝试用opencv中的cv.imread读取 ...

  7. loj#2353. 「NOI2007」 货币兑换 斜率优化

    题意略 题解:可以列出dp方程\(dp[i]=max(dp[j]*{\frac{a[i]*c[j]+b[i]}{a[j]*c[j]+b[j]}}\),化简可以得到\(\frac{dp[i]}{b[i] ...

  8. 第一阶段——站立会议总结DAY04

    1.昨天做了什么:未取得一点进步. 2.今天准备做什么:准备在网上搜索一下微信个人中心的相关信息,代码资料. 3.遇到的困难:界面简陋,还是对于深层次的,比如逻辑,还是不怎么会用.

  9. C++的面试题

    1.什么是类? 类是具有相同属性和相同的方法的对象的集合,它是一种既包含数据又包含函数的抽象数据类型. 对象是类进行实体化后的产物,是一个实体. 在C++中也是先声明一个类类型,用类去定义若干个同类型 ...

  10. Leetcode 1014. 在 D 天内送达包裹的能力

    1014. 在 D 天内送达包裹的能力  显示英文描述 我的提交返回竞赛   用户通过次数197 用户尝试次数272 通过次数203 提交次数538 题目难度Medium 传送带上的包裹必须在 D 天 ...