Windows驱动开发WDM (7)- 异步IRP
来源:互联网 发布:中世纪2优化9蒙古出现 编辑:程序博客网 时间:2024/05/16 04:31
同步IRP是很简单的,比如caller调用DeviceIoControll,那么DeviceIoControll的IRP会发到相应的驱动,驱动把这个IRP完成,然后caller的DeviceIoControll才返回。同步的缺点很明显,比如驱动需要花10秒处理这个IRP,那么caller就得等待10秒钟,有时候这是个浪费。这是个很常见的问题,解决方案就是采用异步的方式。
异步IRP
caller和驱动通信的方式主要就是3个API, ReadFile,WriteFile和DeviceIoControl。这3个函数都支持异步方式(OVERLAPPED)。当采用异步方式的时候,这3个函数会立刻返回,同时得到错误码997(io pending).这个时候caller就可以去做其他事情了,通过WaitForSingleObject等待驱动的处理返回。
如果要使用异步方式,CreateFile打开设备的时候,就需要设置一个标志FILE_FLAG_OVERLAPPED,比如:
HANDLE hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);
驱动实现异步方式处理
将驱动代码稍作改变,当驱动接收到caller编码请求的时候,启动一个线程,然后在IRP处理函数里面调用IoMarkIrpPending,并且返回STATUS_PENDING,这样等于IRP_MJ_DEVICE_CONTROL处理函数立刻返回。
NTSTATUS HelloWDMIOControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp){KdPrint(("Enter HelloWDMIOControl\n"));PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);//得到IOCTRL码ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;NTSTATUS status;ULONG info = 0;switch (code){case IOCTL_ENCODE:{//启动一个工作线程(用户线程,表示属于发起DeviceIoControl的那个进程)HANDLE hThread;PsCreateSystemThread(&hThread, 0, NULL, NtCurrentProcess(), NULL, EncodingThread, Irp);//设置IRP为挂起状态,在工作线程里面会完成这个Irpstatus = STATUS_PENDING;Irp->IoStatus.Status = status;Irp->IoStatus.Information = info;IoMarkIrpPending(Irp);}break;default:status = STATUS_INVALID_VARIANT;Irp->IoStatus.Status = status;Irp->IoStatus.Information = info;IoCompleteRequest(Irp, IO_NO_INCREMENT);break;}KdPrint(("Leave HelloWDMIOControl\n"));return status;}
从上面的代码可以看到当驱动接到IOCTL_ENCODE的请求时,开启一个线程,然后调用IoMarkIrpPending函数,并且返回STATUS_PENDING的错误码,也就是说IRP_MJ_DEVICE_CONTROL的派遣函数立刻返回了(没有做任何caller要求的事情),caller要求的事情将在新创建的线程里面完成。
内核模式下,通过函数PsCreateSystemThread可以创建线程。内核模式下有两种线程:用户线程和系统线程。用户线程意思是说这个线程属于caller的相应进程,系统线程属于系统进程(一个特殊的进程,可以用任务管理器看到,通常进程ID是4)。这个例子里面使用了用户线程,通过PsCreateSystemThread的第四个参数决定,这里使用了NtCurrentProcess得到当前caller进程。
看一下工作线程函数EncodingThread:
void EncodingThread(IN void* pContext){//模拟延时3秒KdPrint(("Start to wait for encoding, 3s\n"));KEVENT event;KeInitializeEvent(&event, NotificationEvent, FALSE);LARGE_INTEGER timeout;timeout.QuadPart = -3 * 1000 * 1000 * 10;//负数表示从现在开始计数,KeWaitForSingleObject的timeout是100ns为单位的。KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);//等待3秒KdPrint(("Start to Encode\n"));PIRP Irp = (PIRP)pContext;PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);//得到输入缓冲区大小ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;//得到输出缓冲区大小ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;//获取输入缓冲区,IRP_MJ_DEVICE_CONTROL的输入都是通过buffered io的方式char* inBuf = (char*)Irp->AssociatedIrp.SystemBuffer;for (ULONG i = 0; i < cbin; i++)//将输入缓冲区里面的每个字节和m亦或{inBuf[i] = inBuf[i] ^ 'm';}//获取输出缓冲区,这里使用了直接方式,见CTL_CODE的定义,使用了METHOD_IN_DIRECT。所以需要通过直接方式获取out bufferKdPrint(("user address: %x, this address should be same to user mode addess.\n", MmGetMdlVirtualAddress(Irp->MdlAddress)));//获取内核模式下的地址,这个地址一定> 0x7FFFFFFF,这个地址和上面的用户模式地址对应同一块物理内存char* outBuf = (char*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);ASSERT(cbout >= cbin);RtlCopyMemory(outBuf, inBuf, cbin);//完成irpIrp->IoStatus.Status = STATUS_SUCCESS;Irp->IoStatus.Information = cbin;IoCompleteRequest(Irp, IO_NO_INCREMENT);KdPrint(("Encode thread finished\n"));}
使用KeWaitForSingleObject等待3秒,然后处理相应的IRP,包括编码和IRP完成。也就是说当caller发起一个DeviceIoControl的时候,驱动会使用IoMarkIrpPending函数设置当前Irp为pending状态,同时开启一个线程,在线程里面等待3秒,然后处理请求,并且完成这个irp。
caller测试例子
// TestWDMDriver.cpp : Defines the entry point for the console application.//#include "stdafx.h"#include <windows.h>#define DEVICE_NAME L"\\\\.\\HelloWDM"int _tmain(int argc, _TCHAR* argv[]){//设置overlapped标志,表示异步打开HANDLE hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);if (hDevice != INVALID_HANDLE_VALUE){char* inbuf = "hello world";char outbuf[12] = {0};DWORD dwBytes = 0;OVERLAPPED ol = {0};ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);BOOL b = DeviceIoControl(hDevice, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_IN_DIRECT, FILE_ANY_ACCESS), inbuf, 11, outbuf, 11, &dwBytes, &ol);printf("DeviceIoControl returned with Overlapped mode. ret value: %d, last error: %d, operated bytes: %d\n", b, GetLastError(), dwBytes);DWORD dwStart = GetTickCount();WaitForSingleObject(ol.hEvent, INFINITE);//等待驱动处理完编码要求//将输出buffer的数据和'm'亦或,看看是否能够得到初始的字符串。for (int i = 0; i < 11; i++){outbuf[i] = outbuf[i] ^ 'm';}printf("Verify encode result, outbuf: %s, used: %d ms\n", outbuf, GetTickCount() - dwStart);CloseHandle(hDevice);}elseprintf("CreateFile failed, err: %x\n", GetLastError());return 0;}
首先使用CreateFile异步打开这个设备,然后传递一个OVERLAPPED结构给驱动,使用WaitForSingleObject等待驱动完成编码请求,最后打印log来验证异步操作是否成功。ok,看一下结果。
如图中的注释,异步操作成功了。caller的DeviceIoControl立刻返回了并且得到997的错误码。然后WaitForSingleObject花费了3秒钟才返回,返回的buffer数据里面装了驱动编码后的数据,通过将这些数据再次和'm'亦或得到的原始传入字符串。一切正常。和同步方式相比,异步方式下DeviceIoControl会立刻返回,然后有需要的话,caller可以做其他事情,用WaitForSingleObject(或者WaitForMultipleObjects)查看驱动是否完成了处理。
总体来讲,异步方式可以提升系统的性能,但是代码就要复杂一些。
完整代码下载:http://download.csdn.net/detail/zj510/4813345
DDK编译驱动,VS2008编译调用例子。
- Windows驱动开发WDM (7)- 异步IRP
- Windows驱动开发WDM (9)- StartIO例程(串行化处理IRP)
- Windows驱动开发WDM (16)- 完成例程 (重新获得IRP控制权)
- Windows 7驱动开发系列(四)--WDM模型介绍
- WDM驱动之IRP处理:取消IRP
- WDM驱动之IRP处理:取消IRP
- WINDOWS WDM驱动开发基础
- 驱动开发(13)IRP 的异步完成和 CancelRoutine
- Windows驱动开发WDM (13)- 过滤驱动
- Windows驱动开发WDM (13)- 过滤驱动
- Windows 7驱动开发系列(五)--WDM驱动设计原则
- Windows驱动开发——WDM驱动
- Windows驱动开发——WDM驱动
- Windows驱动开发WDM (1) - 基本结构
- Windows驱动开发WDM (3)- 设备内存读写方式
- Windows驱动开发WDM (4)- 缓冲区方式例子
- Windows驱动开发WDM (6)- 中断请求级别
- Windows驱动开发WDM (8)- 内核同步对象
- Linux 硬件信息查看 dmidecode
- 设置UIButton字体大小
- How to add AD attribute?
- 1、如何处理频繁快速点击事件
- javascript 判断两个日期的大小
- Windows驱动开发WDM (7)- 异步IRP
- Android.mk简介
- magento采集-magento火车头采集-magento教程
- Android ADB server didn’t ACK解决方案
- Android 动画框架详解,第 1 部分
- 【学会感恩,孝敬父母,欢迎转载,学习】
- 过quick_updates大批量复制产品
- Oracle行列转换应用
- 深入体验JavaWeb开发内幕——JDBC高级之数据库连接池