一,IRP的概念(I/O Request Package)
当一个应用程序调用函数去操作某个设备时,比如调用createFile,deviceIOControl,等等时,I/O管理器为此函数创建一个IRP数据结构对象和一个IRP_STACK_LOCATION数据结构对象数组,(数组个数等于驱动程序堆栈上驱动的个数),IRP中的CurrentStackLocation指向IRP_STACK_LOCATION中的某一个元素。 OS 并不会为我们填充好IRP_STACK_LOCATION数组,每一个元素是由上一层驱动负责填充的。OS只负责把IRP包给最上层的驱动程序,至于如何向下层,就是驱动程序自己的事情了。所以下层对就的那些IRP_STACK_LOCATION,完全由上层驱动函数负责填充,OS不管。
驱动程序如何填充一层驱动需要用的IRP_STACK_LOCATION呢?
可以通过调用IoGetNextIrpStackLocation调用得到。其实该函数内部就是返回CurrentStackLocation加1而已。对数组值加1,当然就是得到数组的下一个值了。这样就可以对它进行赋值了。然后调用IoCallDriver(),IoCallDriver()会将irp包中的CurrentStackLocation值加1,然后调用那个DRIVER.
二IRP的结构
typedef struct _IRP {
…
PMDL MdlAddress;
ULONG Flags;
union {
struct _IRP *MasterIrp;
…
PVOID SystemBuffer;
} AssociatedIrp;
LIST_ENTRY ThreadListEntry; //用来将 IRP挂入某个线程的 IrpList队列
IO_STATUS_BLOCK IoStatus; //用来返回操作的完成状况
KPROCESSOR_MODE RequestorMode;
BOOLEAN PendingReturned;
CHAR StackCount;
CHAR CurrentLocation;
…
BOOLEAN Cancel;
KIRQL CancelIrql;
…
PDRIVER_CANCEL CancelRoutine;
PVOID UserBuffer;
union {
struct {
…
union {
KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
struct {
PVOID DriverContext[4];
};
};
…
PETHREAD Thread;
…
LIST_ENTRY ListEntry;
…
} Overlay;
…
} Tail;
} IRP, *PIRP;
IO_STACK_LOCATION结构为:
typedef struct _IO_STACK_LOCATION {
UCHAR MajorFunction;
UCHAR MinorFunction;
UCHAR Flags;
UCHAR Control;
Union
{
…
}Parameters;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
PIO_COMPLETION_ROUTINE CompletionRoutine;
PVOID context;
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;
MajorFunction指示驱动程序应该使用哪个函数来处理IO请求。
MinorFunction 进一步指出该IRP属于哪个主功能类
Flags 表明IO请求类型。
DeviceObject(PDEVICE_OBJECT)是与该堆栈单元对应的设备对象的地址。该域由IoCallDriver函数负责填写。
CompletionRoutine(PIO_COMPLETION_ROUTINE)是一个I/O完成例程的地址,该地址是由与这个堆栈单元对应的驱动程序的更上一层驱动程序设置的。通过调用IoSetCompletionRoutine函数来设置。设备堆栈的最低一级驱动程序并不需要完成例程,因为它们必须直接完成请求。然而,请求的发起者有时确实需要一个完成例程,但通常没有自己的堆栈单元。这就是为什么每一级驱动程序都使用下一级驱动程序的堆栈单元保存自己完成例程指针的原因。
现在对IRP和IO_STACK_LOCATION都有了一个初步的认识。当驱动程序对IRP完成了操作(对各个域的读写)之后,需要调用IoCompleteRequest表明IRP处理已经结束,并将IRP交还给IO管理器。
VOID IoCompleteRequest(
__in PIRP Irp,
__in CCHAR PriorityBoost
);