10、Windows驱动开发技术详解笔记(6) 基本语法回顾

时间:2021-11-28 05:55:29

7I/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的相关函数例如CreateFileReadFileWriteFileCloseHandle等分别会引发操作系统产生 IRP_MJ_CREATEIRP_MJ_READIRP_MJ_WRITEIRP_MJ_CLOSE等不同的IRP,这些IRP会被传送到驱动程序的相应派遣例程中。

10、Windows驱动开发技术详解笔记(6) 基本语法回顾

IRP常见类型

typedef struct _IO_STATUS_BLOCK {

NTSTATUS Status;

ULONG Information;

}IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

2IRP处理

以前知道:在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;

}

3IRPIO_STACK_LOCATION

开发一个驱动要有可能要处理各种IRP。我们先学习应用程序和驱动交互而产生的IRP。应用程序为了和驱动通信,首先必须打开设备。然后发送或者接收信息,最后关闭它;这至少需要三个IRP:第一个是打开请求,第二个发送或者接收信息,第三个是关闭请求。

IRP的种类取决于主功能号。主功能号就是前面的说的DRIVER_OBJECT中的分发函数指针数组中的索引,打开请求的主功能号是IRP_MJ_CREATE,而关闭请求的主功能号是IRP_MJ_CLOSE

如果写有独立的处理IRP_MJ_CREATEIRP_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.Information0

·设置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);