异步设备IO OVERLAPPED结构(设备内核对象 事件内核对象 可提醒IO)
同步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)的更多相关文章
- 异步IO模型和Overlapped结构
.NET中的 Overlapped 类 异步IO模型和Overlapped结构(http://blog.itpub.net/25897606/viewspace-705867/) 数据结构 OVERL ...
- 《Windows核心编程系列》九谈谈同步设备IO与异步设备IO之同步设备IO
同步设备IO 所谓同步IO是指线程在发起IO请求后会被挂起,IO完成后继续执行. 异步IO是指:线程发起IO请求后并不会挂起而是继续执行.IO完毕后会得到设备的通知.而IO完成端口就是实现这种通知的很 ...
- 【RT-Thread笔记】IO设备模型及GPIO设备
RTT内核对象--设备 RT-Thread有多种内核对象,其中设备device就是其中一种. 内核继承关系图如下: 设备继承关系图如下: device对象对应的结构体如下: 其中,设备类型type有如 ...
- Linux字符设备驱动基本结构
1.Linux字符设备驱动的基本结构 Linux系统下具有三种设备,分别是字符设备.块设备和网络设备,Linux下的字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中某一数据,读取数据 ...
- linux块设备驱动---相关结构体(转)
上回最后面介绍了相关数据结构,下面再详细介绍 块设备对象结构 block_device 内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区.如果该结构代表一个分区,则其成 ...
- 设备文件三大结构:inode,file,file_operations
驱动程序就是向下控制硬件,向上提供接口,这里的向上提供的接口最终对应到应用层有三种方式:设备文件,/proc,/sys,其中最常用的就是使用设备文件,而Linux设备中用的最多的就是字符设备,本文就以 ...
- Linux设备文件三大结构:inode,file,file_operations
驱动程序就是向下控制硬件,向上提供接口,这里的向上提供的接口最终对应到应用层有三种方式:设备文件,/proc,/sys,其中最常用的就是使用设备文件,而Linux设备中用的最多的就是字符设备,本文就以 ...
- 【Linux开发】linux设备驱动归纳总结(一):内核的相关基础概念
linux设备驱动归纳总结(一):内核的相关基础概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- Linux设备驱动开发详解-Note(5)---Linux 内核及内核编程(1)
Linux 内核及内核编程(1) 成于坚持,败于止步 Linux 2.6 内核的特点 Linux 2.6 相对于 Linux 2.4 有相当大的改进,主要体现在如下几个方面. 1.新的调度器 2.6 ...
随机推荐
- Server SQL Modes
The MySQL server can operate in different SQL modes, and can apply these modes differently for diffe ...
- 20165327 2017-2018-2 《Java程序设计》第8周学习总结
20165327 2017-2018-2 <Java程序设计>第8周学习总结 教材内容总结 第十二章 (一)教材学习内容总结 线程是比进程更小的执行单位.一个进程在其执行过程中,可以产生多 ...
- WPF自定义漂亮的按钮样式
首先打开 Microsoft Visual Studio 2008 ,新建一个WPF项目,在上面随便放几个按钮: 然后给各个按钮设置不同的背景颜色: 设置好之后就是这样啦: 然后我们就开始在 App. ...
- Linux系统常见内核问题修复(转发)
Linux系统常见内核问题修复(转发) 常见Linux系统破坏修复 http://blog.csdn.net/jmilk/article/details/49619587
- Bipartite Segments CodeForces - 901C (区间二分图计数)
大意: 给定无向图, 无偶环, 每次询问求[l,r]区间内, 有多少子区间是二分图. 无偶环等价于奇环仙人掌森林, 可以直接tarjan求出所有环, 然后就可以预处理出每个点为右端点时的答案. 这样的 ...
- pytorch初步学习(一):数据读取
最近从tensorflow转向pytorch,感受到了动态调试的方便,也感受到了一些地方的不同. 所有实验都是基于uint16类型的单通道灰度图片. 一开始尝试用opencv中的cv.imread读取 ...
- 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] ...
- 第一阶段——站立会议总结DAY04
1.昨天做了什么:未取得一点进步. 2.今天准备做什么:准备在网上搜索一下微信个人中心的相关信息,代码资料. 3.遇到的困难:界面简陋,还是对于深层次的,比如逻辑,还是不怎么会用.
- C++的面试题
1.什么是类? 类是具有相同属性和相同的方法的对象的集合,它是一种既包含数据又包含函数的抽象数据类型. 对象是类进行实体化后的产物,是一个实体. 在C++中也是先声明一个类类型,用类去定义若干个同类型 ...
- Leetcode 1014. 在 D 天内送达包裹的能力
1014. 在 D 天内送达包裹的能力 显示英文描述 我的提交返回竞赛 用户通过次数197 用户尝试次数272 通过次数203 提交次数538 题目难度Medium 传送带上的包裹必须在 D 天 ...