派遣函数IRP
派遣函数是Windows驱动程序中的重要概念。驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请
求是在派遣函数中处理的。
用户模式下所有对驱动程序的I/O请求,全部由操作系统转换为一个叫做IRP数据结构,不同的IRP会被“
派遣”到不同的派遣函数中。
IRP与派遣函数
IRP的处理机制类似于Windows应用程序中的“消息处理”,驱动程序接收到不同的IRP后,会进入不同的
派遣函数,在派遣函数中IRP得到处理。
1.IRP
在Windows内核中,有一种数据结构叫做IRP(I/O Request Package),即输入输出请求包。上层应用程
序与底层驱动程序通信时,应用程序会发出I/O请求。操作系统将I/O请求转化为相应的IRP数据,不同类
型的IRP会被传递到不同的派遣函数中。
IRP有两个基本的重要属性,一个是MajorFunction,另一个MinorFunction,分别记录IRP的主类型和子
类型,操作系统根据MajorFunction将IRP“派遣”到不同的派遣函数中,在派遣函数中还可以继续判断
这个IRP属于哪种MinorFunction。
下面是HelloDDK的DriverEntry中关于派遣函数的注册:
[cpp] view plaincopy
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegisterPath
)
{
NTSTATUS status;
KdPrint(("Enter DriverEntry\n"));
//设置卸载函数
pDriverObject->DriverUnload = HelloDDKUnload;
//设置派遣函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = HelloDDKDispatchRoutine;
//创建驱动设备对象
status = CreateDevice(pDriverObject);
KdPrint(("Leave DriverEntry\n"));
return status;
}
2.IRP的类型
文件I/O的相关函数,如CreateFile,ReadFile,WriteFile,CloseHandle等函数会使操作系统产生出I
RP_MJ_CREATE,IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_CLOSE等不同的IRP。另外,内核中的文件I/O处理
函数,如ZwCreateFile,ZwReadFile,ZwWriteFile,ZwClose,他们同样会产以上IRP。
一下列出了IRP的类型,并对其产生的来源做了说明
IRP类型 来源
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_CREATE 创建设备,CreateFile会产生此IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_CLOSE 关闭设备,CloseHandle会产生此IR
P
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_CLEANUP 清除工作,CloseHandle会产生此IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_DEVICE_CONTROL DeviceControl函数会产生此IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_PNP 即插即用消息,NT驱动不支持次
IRP,WDM驱动才支持次IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_POWER 在操作系统处理电源消息时,产生次
IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_QUERY_INFORMATION 获取文件长度,GetFileSize会产生IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_READ 读取设备内容,ReadFile会产生此
IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_SET_INFORMATION 设置文件长度,GetFileSize会产生IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_SHUTDOWN 关闭系统前会产生此IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_SYSTEM_CONTROL 系统内部产生的控制信息,类似于内核调用DeviceC
ontrol函数
------------------------------------------------------------------------------------------
-----------------------------------------------------
IRP_MJ_WRITE 对设备进行WriteFile时会产生此
IRP
------------------------------------------------------------------------------------------
-----------------------------------------------------
3.对派遣函数的简单处理
大部分的IRP都源于文件I/O处理Win32API,处理这些IRP最简单的方法就是在相应的派遣函数中,将IRP
状态设置为成功,然后结束IRP的请求,并让派遣函数成功返回。结束IRP的请求使用函数IoCompleteRe
quest.。下面代码演示了一种最简单的处理IRP请求的派遣函数。
[plain] view plaincopy
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKDispatchRoutine\n"));
//对一般IRP的简单操作
NTSTATUS status = STATUS_SUCCESS;
//设置IRP完成状态
pIrp->IoStatus = status;
//设置IRP操作了多少字节
pIrp->IoStatus.Information = 0;
//处理IRP
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
KdPrint(("Leave HelloDDKDispatchRputine"));
return status;
}
本例中,派遣函数设置了IRP的完成状态为STATUS_SUCCESS。这样,发起I/O操作请求的Win32API将会返
回TRUE。相反则会返回FALSE。这种情况时,可以使用GetLastError Win32API得到错误代码,所得的错
误代码会和IRP设置的状态一致。
除了设置IRP的完成状态,派遣函数还要设置这个IRP操作了多少字节。
派遣函数将IRP请求结束,这是通过IoCompleteRequest函数完成的。
4.通过设备链接打开设备
要打开设备,必须通过设备名字才能得到该设备的句柄。前面介绍过,每个设备都有设备名称,如Hell
oDDK驱动程序的设备名称为“\\Device\\MyDDKDevice”,但是设备名称无法被用户模式下的应用程序查
询到,设备名只能被内核模式下的程序查询到。在应用程序中需要通过符号链接进行访问。
下面程序演示在用户模式下打开驱动设备:
[cpp] view plaincopy
#include <windows.h>
#include <stdio.h>
int main()
{
HANDLE hDevice =
CreateFile("\\\\.\\HelloDDK",
GENERIC_READ | GENERIC_WRITE,
0, // share mode none
NULL, // no security
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL ); // no template
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Failed to obtain file handle to device: "
"%s with Win32 error code: %d\n",
"MyWDMDevice", GetLastError() );
return 1;
}
CloseHandle(hDevice);
return 0;
}
5.编写一个更通用的派遣函数
在Windows驱动开发中,有一个重要的内核数据结构,IO_STACK_LOCATION,即I/O堆栈,这个数据结构和
IRP紧密相连。
驱动对象会创建一个个设备对象,并将这些设备对象“叠”成一个垂直结构,被称为“设备栈”。IRP会
被操作系统发送到设备栈顶层,如果顶层设备结束了本次IRP的请求,则I/O请求结束,如果不让I/O请求
结束,可以将IRP继续转发到下一层设备。因此,一个IRP可能会被转发多次。为了记录IRP在每层设备中
的操作,IRP会有一个IO_STACK_LOCATION数组,每个IO_STACK_LOCATION元素记录着对应设备中做的操作
。对于本层的IO_STACK_LOCATION,可以通过IoGetCurrentIrpStackLocation函数得到。IO_STACK_LOCA
TION结构中会记录IRP的类型,即IO_STACK_LOCATION中的MajorFuncation子域。
下面代码增加了派遣函数的难度:
[plain] view plaincopy
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKDispatchRoutine\n"));
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//建立一个字符串数组与IRP类型对应起来
static char * irpname[] =
{
"IRP_MJ_CREATE",
"IRP_MJ_CREATE_NAMED_PIPE",
"IRP_MJ_CLOSE",
"IRP_MJ_READ",
"IRP_MJ_WRITE",
"IRP_MJ_QUERY_INFORMATION",
"IRP_MJ_SET_INFORMATION",
"IRP_MJ_QUERY_EA",
"IRP_MJ_SET_EA",
"IRP_MJ_FLUSH_BUFFERS",
"IRP_MJ_QUERY_VOLUME_INFORMATION",
"IRP_MJ_SET_VOLUME_INFORMATION",
"IRP_MJ_DIRECTORY_CONTROL",
"IRP_MJ_FILE_SYSTEM_CONTROL",
"IRP_MJ_DEVICE_CONTROL",
"IRP_MJ_INTERNAL_DEVICE_CONTROL",
"IRP_MJ_SHUTDOWN",
"IRP_MJ_LOCK_CONTROL",
"IRP_MJ_CLEANUP",
"IRP_MJ_CREATE_MAILSLOT",
"IRP_MJ_QUERY_SECURITY",
"IRP_MJ_SET_SECURITY",
"IRP_MJ_POWER",
"IRP_MJ_SYSTEM_CONTROL",
"IRP_MJ_DEVICE_CHANGE",
"IRP_MJ_QUERY_QUOTA",
"IRP_MJ_SET_QUOTA",
"IRP_MJ_PNP",
};
UCHAR type = stack->MajorFunction;
if (type >= arraysize(irpname))
{
KdPrint(("-Unknow IRP ,major type %X\n",type));
}
else
{
KdPrint(("\t%s\n",irpname[type]));
}
//对一般IRP的简单操作,后面会介绍对IRP更复杂的操作
NTSTATUS status = STATUS_SUCCESS;
//完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
KdPrint(("Leave HelloDDKDispatchRoutine\n"));
return status;
}
缓冲区方式读写操作
驱动程序所创建的设备一般会有三种读写方式,一种是缓冲区方式,一种是直接方式,一种是其他方式
。
1.缓冲区方式
IOCreateDevice创建完设备后,需要对设备对象的Flags子域进行设置,设置不同的Flags会导致以不同
的方式操作设备。
设备对象一共可以有三种读写方式,这三种方式的Flags分别对应为DO_BUFFERED_ID,DO_DIRECT_IO和0
,缓冲区方式读写相对简单。
读写操作一般是由ReadFile或者WriteFile函数引起的,这里以WriteFile函数为例进行介绍。WriteFil
e要求用户提供一段缓冲区,并且说明缓冲区的大小,然后WriteFile将这段内存的数据传入到驱动程序
中。
这段缓冲区内存是用户模式的内存地址,驱动程序如果直接引用这段内存是十分危险的。如果以缓冲
区方式读写,操作系统会将应该用程序提供缓冲区的数据复制到内核模式下的地址中,这样无论操作系
统如何切换进程,内核模式的地址都不回改变。IRP派遣函数真正操作的是内核模式下的缓冲区地址,而
不是用户模式下的缓冲区地址。但是这样做会有一定的效率影响。
2.缓冲区设备读写
以缓冲区方式写设备时,操作系统将WriteFile提供的用户模式的缓冲区复制到内核模式地址下,这个地
址由WriteFile创建的IRP的AssociateIrp.SystemBuffer子域记录。
另外,在派遣函数中也可以通过IO_STACK_LOCATION中的Parameters.Read.Length子域知道ReadFile请求
多少字节。通过IO_STACK_LOCATION中的Parameters.Write.Length子域知道WriteFile请求多少字节。
然后,WriteFile和ReadFile指定对设备操作多少字节,并不真正意味着操作了这么多字节。在派遣函数
中,应该设置IRP的子域IoStatus.Information.这个子域记录设备实际操作了多少字节。
下面代码演示了如何利用“缓冲区”方式读设备:
[plain] view plaincopy
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKRead\n"));
//对一般IRP进行处理,后面会介绍对IRP更复杂的处理
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//获得需要读设备的字节数
ULONG ulReadLength = stack->Parameters.Read.Length;
//完成IRP
//设置IRP完成状态
pIrp->IoStatus.Status = status;
//设置IRP操作了多少字节
pIrp->IoStatus.Information = ulReadLength;
memset(pIrp->AssociatedIrp.SystemBuffer,0XAA,ulReadLength);
//处理IRP
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
KdPrint(("Leave HelloDDKRead\n"));
return status;
}
ring3下的程序来读取数据:
[plain] view plaincopy
#include <windows.h>
#include <stdio.h>
int main()
{
HANDLE hDevice =
CreateFile("\\\\.\\HelloDDK",
GENERIC_READ | GENERIC_WRITE,
0, // share mode none
NULL, // no security
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL ); // no template
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Failed to obtain file handle to device: "
"%s with Win32 error code: %d\n",
"MyWDMDevice", GetLastError() );
return 1;
}
UCHAR buffer[10];
ULONG ulRead;
BOOL bRet = ReadFile(hDevice,buffer,10,&ulRead,NULL);
if (bRet)
{
printf("Read %d bytes:",ulRead);
for (int i=0;i<(int)ulRead;i++)
{
printf("%02X ",buffer[i]);
}
printf("\n");
}
CloseHandle(hDevice);
return 0;
}
直接读写方式:
1.直接读取设备:
除了“缓冲区”方式读写设备外,另一种方式是直接方式读写设备。这种方式需要在创建完设备对象后
,在设置设备属性的时候,设置为DO_DIRECT_IO。
和缓冲区读写方式不同,直接读写设备,操作系统会将用户模式下的缓冲区锁住。然后操作系统将这段
缓冲区在内核模式地址再次映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域
的物理内存。无论操作系统如何切换进程,内核模式地址都保持不变。
操作系统先将用户模式的地址锁住后,操作系统用内存描述符(MDL数据结构)记录这段内存。
MDL记录这段虚拟内存,这段虚拟内存的大小存储在mdl->ByteCount里,这段虚拟内存的第一个页地址是
mdl->StartVa,这段虚拟内存的首地址对于第一个页地址的偏移量是mdl->ByteOffset,。因此,这段虚
拟内存的首地址应该是 mdl->StartVa + mdl->ByteOffset。
DDK提供里几个宏方便程序员得到这几个数值:
[plain] view plaincopy
#define MmGetMdlByteCount(mdl) ((Mdl)->ByteCount)
#define MmGetMdlByteOffset(mdl) ((Mdl)->ByteOffset)
#define MmGetMdlVirtualAddress(mdl) ((PVOID)((PCHAR)((Mdl->StartVa) + (Mdl)->ByteOffset))
2.直接读取设备的读写
下面结合代码演示如何编写直接方式设备的派遣函数
[plain] view plaincopy
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKRead\n"));
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ulReadLength = stack->Parameters.Read.Length;
KdPrint(("ulReadLength:%d\n",ulReadLength));
ULONG mdl_length = MmGetMdlByteCount(pIrp->MdlAddress);
PVOID mdl_address = MmGetMdlVirtualAddress(pIrp->MdlAddress);
ULONG mdl_offset = MmGetMdlByteOffset(pIrp->MdlAddress);
KdPrint(("mdl_address:0X%08X\n",mdl_address));
KdPrint(("mdl_length:%d\n",mdl_length));
KdPrint(("mdl_offset:%d\n",mdl_offset));
if (mdl_length!=ulReadLength)
{
//MDL的长度应该和读长度相等,否则该操作应该设为不成功
pIrp->IoStatus.Information = 0;
status = STATUS_UNSUCCESSFUL;
}else
{
//用MmGetSystemAddressForMdlSafe得到MDL在内核模式下的映射
PVOID kernel_address = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePri
ority);
KdPrint(("kernel_address:0X%08X\n",kernel_address));
memset(kernel_address,0XAA,ulReadLength);
pIrp->IoStatus.Information = ulReadLength; // bytes xfered
}
pIrp->IoStatus.Status = status;
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKRead\n"));
return status;
}
其他方式的读写操作:
这里暂时不讨论此种方法。
派遣函数IRP的更多相关文章
- IRP 与 派遣函数
什么是派遣函数: 派遣函数是 WIndows 驱动程序中的重要概念.驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的.也就是说,派遣函数是用来处理驱动程序提交过来的 I ...
- 《Windows驱动开发技术详解》之派遣函数
驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的.用户模式下所有对驱动程序的I/O请求,全部由操作系统转化为一个叫做IRP的数据结构,不同的IRP数据会被“派遣”到不同 ...
- Windows内核-7-IRP和派遣函数
Windows内核-7-IRP和派遣函数 IRP以及派遣函数是Windows中非常重要的概念.IRP 是I/O Request Pocket的简称,意思是I/O操作的请求包,Windows中所有Use ...
- Windows驱动开发-派遣函数
一个简单的派遣函数格式 NTSTATUS DispatchFunction(PDEVICE_OBJECT pDeviceObject, PIRP pIrp) { //业务代码区 //设置返回状态 pI ...
- Windows驱动派遣函数的学习
//派遣处理例程的介绍: //IPR简介: //IRP全称(I/O Request Package),即输入输出请求包.他是windows驱动的重要概念,用户模式下所有对驱动程序的I/O请求,全部由操 ...
- Windows驱动开发-派遣函数格式
NTSTATUS functionName(PDEVICE_OBJECT pDeviceObject, PIRP pIrp) { //业务代码区 //设置返回状态 pIrp->IoStatus. ...
- 派遣例程与IRP结构
提到派遣例程,必须理解IRP(I/O Request Package),即"输入/输出请求包"这个重要数据结构的概念.Ring3通过DeviceIoControl等函数向驱动发出I ...
- IRP派遣操作
IRPTrace工具跟踪IRP 派遣函数(Dispathc Funtion)是windows驱动中的重要概念.驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的.用户模式 ...
- 转---派遣例程与IRP结构
派遣例程与IRP结构 文章出处:http://www.cnblogs.com/zmlctt/p/3978124.html#commentform 提到派遣例程,必须理解IRP(I/O Requ ...
随机推荐
- APICloud上啦加载下拉刷新模块
apicloud有自带的上啦加载下拉刷新,当让也可以用第三方或者在模块库里面找一个使用 一.下拉刷新,一下代码写在 apiready = function (){} 里面 apiready = fun ...
- 前端面试题总结 -vue
1.active-class是哪个组件的属性? vue-router模块的router-link组件. 2.嵌套路由怎么定义? 在 VueRouter 的参数中使用 children 配置,这样就可以 ...
- windows控制台(console)乱码
在cmd中输入 CHCP 65001,实操有效.记录一下 永久设置,代码如下: Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Cons ...
- kvm介绍、安装及创建虚拟机
kvm虚拟化介绍 一.虚拟化分类 1.虚拟化,是指通过虚拟化技术将一台计算机虚拟为多台逻辑计算机.在一台计算机上同时运行多个逻辑计算机,每个逻辑计算机可运行不同的操作系统,并且应用程序都可以在相互独立 ...
- windows的时间同步工具:w32time
windows 客户端 官方文档自己排查可以看一下 如何在 Windows Server 中配置权威时间服务器 Windows Time Service Technical Reference Win ...
- android网络类型之2G-3G切换
在android手机‘设置’-‘移动网络类型’里可以看到有关网络类型的选项,一般默认为3G优先. 如果有需要在程序中切换网络类型的朋友,不妨试试下面的方法.这里提供了几种思路,虽然可能对待 手机的方式 ...
- GitLab权限介绍
访问权限 - Visibility Level 这个是在建立项目时就需要选定的,主要用于决定哪些人可以访问此项目,包含3种 Private - 私有,只有属于该项目成员才有原先查看 Internal ...
- 【Codeforces 229B】Planets
[链接] 我是链接,点我呀:) [题意] [题解] 设dis[i]表示到达i号传送器的最早时刻. 显然,虽然有那么多的出发时刻的限制,但我们还是越早到越好的. 因为你到得越早,出发的时间肯定不会比到达 ...
- 洛谷 P1129 BZOJ 1059 cogs 660 [ZJOI2007]矩阵游戏
题目描述 小Q是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏――矩阵游戏.矩阵游戏在一个N*N黑白方阵进行(如同国际象棋一般,只是颜色是随意的).每次可以对该矩阵进行两种操作: 行交 ...
- 一个使用命令行编译Android项目的工具类
一个使用命令行编译Android项目的工具类 简单介绍 编译apk项目须要使用的几个工具,基本都在sdk中,它们各自是(Windows系统): 1.aapt.exe 资源打包工具 2.android. ...