7、I/O Request Package,输入输出请求包
1)基本概念
IRP 的全名是I/O Request Package,即输入输出请求包,它是Windows 内核中的一种非常重要的数据结构。上层应用程序与底层驱动程序通信时,应用程序会发出I/O 请求,操作系统将相应的I/O 请求转换成相应的IRP,不同的IRP 会根据类型被分派到不同的派遣例程中进行处理。
IRP有两个基本的属性,即MajorFunction 和MinorFunction,分别记录IRP 的主类型和子类型。操作系统根据MajorFunction 决定将IRP 分发到哪个派遣例程,然后派遣例程根据MinorFunction 进行细分处理。
IRP的概念类似于Windows 应用程序中“消息”的概念。在Win32 编程中,程序由“消息”驱动,不同的消息被分发到不同的处理函数中,否则由系统默认处理。
文件I/O的相关函数例如CreateFile、ReadFile、WriteFile、CloseHandle等分别会引发操作系统产生 IRP_MJ_CREATE、IRP_MJ_READ、IRP_MJ_WRITE、IRP_MJ_CLOSE等不同的IRP,这些IRP会被传送到驱动程序的相应派遣例程中。
表 IRP常见类型
typedef struct _IO_STATUS_BLOCK {
NTSTATUS Status;
ULONG Information;
}IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
2)IRP处理
以前知道:在DriverEntry中为不同的IRP设置相应的派遣例程。在派遣例程中处理IRP最简单做法就是将IRP的状态设置为成功,然后结束IRP请求并返回成功,同时还要记得设置这个IRP请求操作了多少字节。
我们在派遣函数中设置IRP的完成状态为STATUS_SUCCESS,发起I/O请求的Win32 API才能返回TRUE,否则Win32 API将返回FALSE,在这个时候可以通过GetLastError获得错误代码,这个错误代码会和此时IRP 被设置的状态一致。
如我们以前描述的派遣函数:
/************************************************************************
* 函数名称:HelloDDKDispatchRoutine
* 功能描述:对读IRP进行处理
* 参数列表:
pDevObj:功能设备对象
pIrp:从IO请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKDispatchRoutine\n"));
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKDispatchRoutine\n"));
return status;
}
3)IRP与IO_STACK_LOCATION
开发一个驱动要有可能要处理各种IRP。我们先学习应用程序和驱动交互而产生的IRP。应用程序为了和驱动通信,首先必须打开设备。然后发送或者接收信息,最后关闭它;这至少需要三个IRP:第一个是打开请求,第二个发送或者接收信息,第三个是关闭请求。
IRP的种类取决于主功能号。主功能号就是前面的说的DRIVER_OBJECT中的分发函数指针数组中的索引,打开请求的主功能号是IRP_MJ_CREATE,而关闭请求的主功能号是IRP_MJ_CLOSE。
如果写有独立的处理IRP_MJ_CREATE和IRP_MJ_CLOSE的分发函数,就没有必要自然判断IRP的主功能号。使用一个函数处理所有的IRP,那么首先就要得到IRP的主功能号。IRP的主功能号在IRP的当前栈空间中。
IRP总是发送给一个设备栈,到每个设备上的时候拥有一个“当前栈空间”来保存在这个设备上的请求信息。
NTSTATUS MyDispatchFunction(PDEVICE_OBJECT device,PIRP irp)
{
// 获得当前irp调用栈空间
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status = STATUS_UNSUCCESSFUL;
swtich(irpsp->MajorFunction)
{
// 处理打开请求
case IRP_MJ_CREATE:
……
break;
// 处理关闭请求
case IRP_MJ_CLOSE:
……
break;
// 处理设备控制信息
case IRP_MJ_DEVICE_CONTROL:
……
break;
// 处理读请求
case IRP_MJ_READ:
……
break;
// 处理写请求
case IRP_MJ_WRITE:
……
break;
default:
…
break;
}
return status;
}
4)打开和关闭
在一些有同步限制的驱动中(比如每次只允许一个进程打开设备)编程要更加复杂一点,现在忽略这些问题。
简单的返回一个IRP成功(或者直接失败)是三部曲,如下:
·设置irp->IoStatus.Information为0。
·设置irp->IoStatus.Status的状态。如果成功则设置STATUS_SUCCESS,否则设置错误码。
·调用IoCompleteRequest (irp,IO_NO_INCREMENT),这个函数完成IRP。
以上三步完成后,直接返回irp->IoStatus.Status即可。
NTSTATUS
MyCreateClose(
IN PDEVICE_OBJECT device,
IN PIRP irp)
{
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest (irp,IO_NO_INCREMENT);
return irp->IoStatus.Status;
}
当然,在前面设置分发函数的时候,应该加上:
DriverObject->MajorFunctions[IRP_MJ_CREATE] = MyCreateClose;
DriverObject->MajorFunctions[IRP_MJ_CLOSE] = MyCreateClose;
在应用层,打开和关闭这个设备的代码如下:
HANDLE device=CreateFile("\\\\.\\MyCDOSL",
GENERIC_READ|GENERIC_WRITE,0,0,
OPEN_EXISTING,
FILE_ATTRIBUTE_SYSTEM,0);
if (device == INVALID_HANDLE_VALUE)
{
// …. 打开失败,说明驱动没加载,报错即可
}
// 关闭
CloseHandle(device);