IRP的同步
应用层对设备的同步与异步操作
以WriteFile为例,一般的同步操作是调用WriteFile完成后,并不会返回,应用程序会在此处暂停,一直等到函数将数据写入文件中并正常返回,而异步操作则是调用WriteFile后会马上返回,但是操作系统有另一线程在继续执行写的操作,这段时间并不影响应用程序的代码往下执行,一般异步操作都有一个事件用来通知应用程序,异步操作的完成,以下图分别来表示同步和异步操作:
在调用这些函数时可以看做操作系统提供一个专门的线程来处理,然后如果选择同步,那么应用层线程会等待底层的线程处理完成后接着执行才执行后面的操作,而异步则是应用层线程接着执行后面的代码,而由操作系统来通知,因此一般来说异步相比较与同步少去了等待操作返回的过程,效率更高一些,但是选择同步还是异步,应该具体问题具体分析
同步操作设备
如果需要对设备进行同步操作,那么在使用CreateFile时就需要以同步的方式打开,这个函数的第六个参数dwFlagsAndAttributes是同步和异步操作的关键,如果给定了参数FILE_FLAG_OVERLAPPED则是异步的,否则是同步的。一旦用这个函数指定了操作方式,那么以后在使用这个函数返回的句柄进行操作时就是该中操作方式,但是这个函数本身不存在异步操作方式,一来这个函数没有什么耗时的操作,二来,如果它不正常返回,那么针对这个设备的操作也不能进行。
一般像WriteFile、ReadFile、DeviceIoControl函数最后一个参数lpOverlapped,是一个OVERLAPPED类型的指针,如果是同步操作,需要给这个参数赋值为NULL
异步操作方式
设置Overlapped参数实现同步
一般能够异步操作的函数都设置一个OVERLAPPED类型的参数,它的定义如下
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
对于这个参数在使用时,其余的我们都不需要关心,一般只使用最后一个hEvent成员,这个成员是一个事件对象的句柄,在使用时,先创建一个事件对象,并设置事件对象无信号,并将句柄赋值给这个成员,一旦异步操作完成,那么系统会将这个事件设置为有信号,在需要同步的地方使用Wait系列的函数进行等待即可。
int main()
{
HANDLE hDevice =
CreateFile("test.dat",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
NULL );
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Read Error\n");
return 1;
}
UCHAR buffer[BUFFER_SIZE];
DWORD dwRead;
//初始化overlap使其内部全部为零
OVERLAPPED overlap={0};
//创建overlap事件
//设置事件采用自动赋值的方式,且初始化为无信号,这样操作系统才能在异步操作完成时自动给其赋值为有信号
overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
//这里没有设置OVERLAP参数,因此是异步操作
ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap);
//做一些其他操作,这些操作会与读设备并行执行
//等待读设备结束
WaitForSingleObject(overlap.hEvent,INFINITE);
CloseHandle(hDevice);
return 0;
}
使用完成函数来实现异步操作
异步函数是在异步操作完成时由操作系统调用的函数,所以我们可以在需要同步的地方等待一个同步对象,然后在异步函数中将这个对象设置为有信号。
使用异步函数必须使用带有Ex的设备操作函数,像ReadFileEx,WriteFileEx等等,Ex系列的函数相比于不带Ex的函数来说,多了最后一个参数,LPOVERLAPPED_COMPLETION_ROUTINE 类型的回调函数,这个函数的原型如下:
VOID CALLBACK FileIOCompletionRoutine(
__in DWORD dwErrorCode,
__in DWORD dwNumberOfBytesTransfered,
__in LPOVERLAPPED lpOverlapped
);
第一个参数是一个错误码,如果异步操作出错,那么他的错误码可以由这个参数得到,第二个参数是实际操作的字节数对于Write类型的函数来说这个就是实际读取的字节数,第三个是一个异步对象。在使用这个方式进行异步时Ex函数中的OVERLAPPED参数一般不需要为其设置事件句柄,只需传入一个已经清空的OVERLAPPED类型的内存地址即可。
当我们设置了该函数后,操作系统会将这个函数插入到相应的队列中,一旦完成这个异步操作,系统就会调用这个函数,Windows中将这种机制叫做异步过程调用(APC Asynchronous Produre Call);这种机制也不是一定会执行,一般只有程序进入警戒状态时才会执行,想要程序进入警戒状态需要调用带有Ex的等待函数,包括SleepEx,在其中的bAlertable设置为TRUE那么当其进入等待状态时就会调用APC队列中的函数,需要注意的是所谓的APC就是系统借当前线程的线程环境来执行我们提供的回调函数,是用当前线程环境模拟了一个轻量级的线程,这个线程没有自己的线程上下文,所以在回调函数中不要进行耗时的操作,否则一旦原始线程等到的它的执行条件而被唤醒,而APC例程还没有被执行完成的话,就会造成一定的错误。下面是使用这种方式进行异步操作的例子:
VOID CALLBACK MyFileIOCompletionRoutine(
DWORD dwErrorCode, // 对于此次操作返回的状态
DWORD dwNumberOfBytesTransfered, // 告诉已经操作了多少字节,也就是在IRP里的Infomation
LPOVERLAPPED lpOverlapped // 这个数据结构
)
{
SetEvent(lpOverlapped->hEvent);
printf("IO operation end!\n");
}
int main()
{
HANDLE hDevice =
CreateFile("test.dat",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
NULL );
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Read Error\n");
return 1;
}
UCHAR buffer[BUFFER_SIZE];
//初始化overlap使其内部全部为零
//不用初始化事件!!
OVERLAPPED overlap={0};
//这里没有设置OVERLAP参数,因此是异步操作
overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine);
//做一些其他操作,这些操作会与读设备并行执行
printf("在此处可以执行其他操作\n");
//进入alterable,只是为了有机会执行APC函数
SleepEx(1000, TRUE);
//在此处进行同步,只有当读操作完成才关闭句柄
WaitForSingleObject(overlap.hEvent, INFINITE);
CloseHandle(hDevice);
return 0;
}
在最后SleepEx让线程休眠而使其有机会执行APC例程,然后使用WaitForSingleObject来等待事件,我们在APC例程中将事件置为有信号,这样只有当异步操作完成,才会返回,利用这个可以在关键的位置实现同步,在这里按理来说可以直接用WaitForSingleObjectEx来替换这两个函数的调用,但是不知道为什么使用WaitForSingleObjectEx时,即使我没有设置为有信号的状态它也能正常返回,所以为了体现这点,我是使用了SleepEx和WaitForSingleObject两个函数。
IRP中的同步和异步操作
上述的同步和异步操作必须得到内核的支持,其实所有对设备的操作最终都会转化为IRP请求,并传递到相应的派遣函数中,在派遣函数中可以直接结束IRP,或者让派遣函数返回,在以后的某个时候处理,由于应用层会等待派遣函数返回,所以直接结束IRP的方式可以看做是同步,而先返回以后处理的方式可以看做是异步处理。
在CreateFile中没有异步的方式,所以它会一直等待派遣函数调用IoCompleteRequest结束,所以当调用CreateFile打开一个自己写的设备时需要编写一个用来处理IRP_MJ_CREATE的派遣函数,并且需要在函数中结束IRP,否则CreateFile会报错,之前本人曾经犯过这样的错误,没有为设备对象准备IRP__MJ_CREATE的派遣函数,结果CreateFile直接返回-1.
对于ReadFile和WriteFile来说,它们支持异步操作,在调用这两个函数进行同步操作时,内部会生成一个事件并等待这个事件,这个事件会和IRP一起发送的派遣函数中,当IRP被结束时,事件会被置为有信号,这样函数中的等待就可以正常返回。而异步操作就不会产生这个事件。而是使用函数中的overlapped参数,这时它内部不会等待这个事件,而由程序员自己在合适的位置等待。
而调用带有Ex的I/O函数则略有不同,他不会设置overlapped参数中的事件,而是当进入警告模式时调用提供的APC函数。
在派遣函数中可以调用IoCompleteRequest函数来结束IRP的处理或者调用IoMarkIrpPending来暂时挂起IRP,将IRP进行异步处理。该函数原型如下:
VOID
IoMarkIrpPending(
IN OUT PIRP Irp
);
下面的例子演示了如何进行IRP的异步处理
typedef struct IRP_QUEUE_struct
{
LIST_ENTRY IRPlist;
PIRP pPendingIrp;
}IRP_QUEUE, *LPIRP_QUEUE;
NTSTATUS DefaultDispatch(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
NTSTATUS status;
PIO_STACK_LOCATION pIrpStack;
pIrpStack = IoGetCurrentIrpStackLocation(Irp);
switch(pIrpStack->MajorFunction)
{
case IRP_MJ_READ:
{
PLIST_ENTRY pQueueHead;
LPIRP_QUEUE pQueue;
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_PENDING;
pQueue = (LPIRP_QUEUE)ExAllocatePoolWithTag(PagedPool, sizeof(IRP_QUEUE), TAG);
if(pQueue != NULL)
{
pQueue->pPendingIrp = Irp;
pQueueHead = (PLIST_ENTRY)(DeviceObject->DeviceExtension);
InsertHeadList(pQueueHead, &(pQueue->IRPlist));
}
IoMarkIrpPending(Irp);
return STATUS_PENDING;
}
break;
case IRP_MJ_CLEANUP:
{
PLIST_ENTRY pQueueHead;
LPIRP_QUEUE pQueue;
PLIST_ENTRY pDelete;
pQueueHead = (PLIST_ENTRY)(DeviceObject->DeviceExtension);
if(NULL != pQueueHead)
{
while(!IsListEmpty(pQueueHead))
{
pDelete = RemoveHeadList(pQueueHead);
pQueue = CONTAINING_RECORD(pDelete, IRP_QUEUE, IRPlist);
IoCompleteRequest(pQueue->pPendingIrp, IO_NO_INCREMENT);
ExFreePoolWithTag(pQueue, TAG);
pQueue = NULL;
}
}
}
default:
{
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
break;
}
}
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING uDeviceName;
UNICODE_STRING uSymbolickName;
UNREFERENCED_PARAMETER(DriverObject);
RtlInitUnicodeString(&uDeviceName, DEVICE_NAME);
RtlInitUnicodeString(&uSymbolickName, SYMBOLIC_NAME);
IoDeleteSymbolicLink(&uSymbolickName);
IoDeleteDevice(DriverObject->DeviceObject);
DbgPrint("GoodBye World\n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
int i = 0;
PDEVICE_OBJECT pDeviceObject;
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->DriverUnload = DriverUnload;
status = CreateDevice(DriverObject, &pDeviceObject);
for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION + 1; i++)
{
DriverObject->MajorFunction[i] = DefaultDispatch;
}
DbgPrint("Hello world\n");
return status;
}
NTSTATUS CreateDevice(PDRIVER_OBJECT pDriverObject,PDEVICE_OBJECT *ppDeviceObject)
{
NTSTATUS status;
PLIST_ENTRY pIrpQueue = NULL;
UNICODE_STRING uDeviceName;
UNICODE_STRING uSymbolickName;
RtlInitUnicodeString(&uDeviceName, &DEVICE_NAME);
RtlInitUnicodeString(&uSymbolickName, SYMBOLIC_NAME);
if(NULL != ppDeviceObject)
{
status = IoCreateDevice(pDriverObject, sizeof(LIST_ENTRY), &uDeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, ppDeviceObject);
if(!NT_SUCCESS(status))
{
return status;
}
(*ppDeviceObject)->Flags |= DO_BUFFERED_IO;
status = IoCreateSymbolicLink(&uSymbolickName, &uDeviceName);
if(!NT_SUCCESS(status))
{
IoDeleteDevice(*ppDeviceObject);
*ppDeviceObject = NULL;
return status;
}
pIrpQueue = (PLIST_ENTRY)((*ppDeviceObject)->DeviceExtension);
InitializeListHead(pIrpQueue);
return status;
}
return STATUS_UNSUCCESSFUL;
}
在上述代码中,定义一个链表用来保存未处理的IRP,然后在DriverEntry中创建一个设备对象,将链表头指针放入到设备对象的扩展中,在驱动的IRP_MJ_READ请求中,将IRP保存到链表中,然后直接调用IoMarkIrpPending,将IRP挂起。一般的IRP_MJ_CLOSE用来关闭内核创建的内核对象,对应用层来说也就是句柄,而IRP_MJ_CLEANUP用来处理被挂起的IRP,所以在这我们需要对CLEANUP的IRP进行处理,在处理它时,我们从链表中依次取出IRP,调用IoCompleteRequest直接结束并清除这个节点。对于其他类型的IRP则直接结束掉即可。
在应用层,利用异步处理的方式多次调用ReadFile,最后再IrpTrace工具中可以看到,有多个显示状态位Pending的IRP。
在处理IRP时除了调用IoCompleteRequest结束之外还可以调用IoCancelIrp来取消IRP请求。这个函数原型如下:
BOOLEAN
IoCancelIrp(
IN PIRP Irp
);
当调用这个函数取消相关的IRP时,对应的取消例程将会被执行,在DDK中可以使用函数IoSetCancelRoutine,该函数可以通过第二个参数为IRP设置一个取消例程,如果第二个参数为NULL,那么就将之前绑定到IRP上的取消例程给清除。函数原型如下:
PDRIVER_CANCEL
IoSetCancelRoutine(
IN PIRP Irp,
IN PDRIVER_CANCEL CancelRoutine
);
在调用IoCancelIrp函数时系统在内部会获取一个名为cancel的自旋锁,然后进行相关操作,但是自旋锁的释放需要自己来进行,一般在取消例程中进行释放操作。这个自旋锁可以通过函数IoAcquireCancelSpinLock来获取,通过IoReleaseCancelSpinLock来释放,下面是一个演示如何使用取消例程的例子。
//取消例程
VOID CancelReadIrp(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
IoReleaseCancelSpinLock(Irp->CancelIrql);
}
//IRP_MJ_READ 处理
case IRP_MJ_READ:
{
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_PENDING;
IoSetCancelRoutine(Irp, CancelReadIrp);
IoMarkIrpPending(Irp);
return STATUS_PENDING;
}
在R3层可以利用CancelIO,来使系统调用取消例程。
这个API传入的是设备的句柄,当调用它时所有针对该设备的被挂起的IRP都会调用对应的取消例程,在这就不需要像上面那样保存被挂起的IRP,每当有READ请求过来时都会调用case里面的内容,将该IRP和取消例程绑定,每当有IRP被取消时都会调用对应的取消例程,就不再需要自己维护了。另外在取消时,系统会自己获取这个cancel自旋锁,并提升对应的IRQL,IRP所处的IRQL被保存在IRP这个结构的CancelIrql成员中,而调用IoReleaseCancelSpinLock函数释放自旋锁时需要的参数正是这个IRP对应的IRQL,所以这里直接传入Irp->CancelIrql
IRP的同步的更多相关文章
- 《Windows驱动开发技术详解》之IRP的同步
应用程序对设备的同步异步操作: 大部分IRP都是由应用程序的Win32 API函数发起的.这些Win32 API本身就支持同步和异步操作.例如,ReadFile.WriteFile和DeviceIoC ...
- ASP.NET 中的 Async/Await 简介
本文转载自MSDN 作者:Stephen Cleary 原文地址:https://msdn.microsoft.com/en-us/magazine/dn802603.aspx 大多数有关 async ...
- ASP.NET 上的 Async/Await 简介
原文链接 大多数有关 async/await 的在线资源假定您正在开发客户端应用程序,但在服务器上有 async 的位置吗?可以非常肯定地回答“有”.本文是对 ASP.NET 上异步请求的概念性概述, ...
- Windows的同步I/O和异步I/O
同步I/O操作 执行步骤 1. 程序通过FileStream对象来打开磁盘文件,然后调用Read方法(内部调用Win32 ReadFile函数),从文件中读取数据.这时,线程从托管代码转 ...
- IRP小结 0x01 IRP & IO_STACK_LOCATION(结合WRK理解)
写博客整理记录一下IRP相关的知识点,加深一下印象. 所有的I/O请求都是以IRP的形式提交的.当I/O管理器为了响应某个线程调用的的I/O API的时候,就会构造一个IRP,用于在I/O系统处理这个 ...
- CPU与IRP的一些相关函数
VOID KiAdjustIrpCredits ( VOID )其中 Number = KeNumberProcessors;Prcb = KiProcessorBlock[Index]; 多核情况下 ...
- 内核事件KEVENT(同步)
转载请您注明出处:http://www.cnblogs.com/lsh123/p/7358702.html 一.驱动程序与驱动程序的事件交互 IoCreateNotificationEvent ...
- Windows内核原理-同步IO与异步IO
目录 Windows内核原理-同步IO与异步IO 背景 目的 I/O 同步I/O 异步I/O I/O完成通知 总结 参考文档 Windows内核原理-同步IO与异步IO 背景 在前段时间检查异常连接导 ...
- 64位内核开发第十二讲,进程监视,ring3跟ring0事件同步.
一丶同步与互斥详解,以及实现一个进程监视软件. 1.用于线程同步的 KEVENT 事件很简单分别分为 事件状态. 以及事件类别. 事件状态: 有信号 Signaled 无信号 Non-signaled ...
随机推荐
- hdu 1233 还是畅通project(kruskal求最小生成树)
还是畅通project Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Tota ...
- Linux网络编程--wireshark分析TCP包头的格式
摘要: 本文简介了TCP面向连接理论知识,具体讲述了TCP报文各个字段含义.并从Wireshark俘获分组中选取TCP连接建立相关报文段进行分析. 一.概述 TCP是面向连接的可靠传输 ...
- 中国十大B2C电商站点开发语言调查
中国B2C电商站点市场占有率排名例如以下 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I ...
- JavaScript 定义类的最佳写法——完整支持面向对象(封装、继承、多态),兼容所有浏览器,支持用JSDuck生成文档
作者: zyl910 [TOC] 一.缘由 由于在ES6之前,JavaScript中没有定义类(class)语法.导致大家用各种五花八门的办法来定义类,代码风格不统一.而且对于模拟面向对象的三大支柱& ...
- Oracle中主键、外键、索引、序列、唯一性约束的创建
1.主键的创建 方法一:直接在sql语句中声明字段主键约束 create table table_name (id type[length] constraint pk_name primary ke ...
- 支持国内版Office 365的PowerShell模块现已发布
作者:陈希章 发表于2017年5月12日 上一篇文章我详细介绍了如何在PowerShell中访问到Office 365的Graph API,在文章结尾处我留了一个问题,希望有朋友可以根据那个思路,尝试 ...
- 输入url会发什什么
从输入url到页面加载完成发生了什么 整体来说有几个基本的点: 1.浏览器的地址栏输入url并按下回车 2.浏览器查找当前url是否存在缓存,并比较缓存是否过期 3.DNS解析url对应的IP 4.根 ...
- python作用域与命名空间
什么是命名空间 比如有一个学校,有10个班级,在7班和8班中都有一个叫“小王”的同学,如果在学校的广播中呼叫“小王”时,7班和8班中的这2个人就纳闷了,你是喊谁呢!!!如果是“7班的小王”的话,那么就 ...
- 查看当前支持的shell,echo -e相关转义符,一个简单shell脚本,dos2unix命令把windows格式转为Linux格式
/etc/shells [root@localhost ~]# more /etc/shells /bin/sh /bin/bash /sbin/nologin /usr/bin/sh /usr/bi ...
- Android 环境搭建、基础窗口window/Mac
1.五步搞定Android开发环境部署--非常详细的Android开发环境搭建教程 2.Android开发学习之路--MAC下Android Studio开发环境搭建 4.Android常用开发工具以 ...