windows内核开发学习笔记十五:IRP结构
IRP(I/O Request Package)
在windows
内核中,有一种系统组件——IRP
,即输入输出请求包。当上层应用程序需要访问底层输入输出设备时,发出I/O
请求,系统会把这些请求转化为IRP
数据,不同的IRP
会启动I/O
设备驱动中对应的派遣函数。
一、IRP
类型
由于IRP
是响应上层应用程序的。可想而知,IRP
类型是与上层对底层设备的访问类型相对应。文件相关的I/O
函数如:CreateFile/ReadFile/WriteFile/CloseHandle
等,操作系统就会将其转为IRP_MJ_CREATE/IRP_MJ_READ/IRP_MJ_WRITE/IRP_MJ_CLOSE
等IRP
类型,这些IRP
再被传送到驱动程序的派遣函数中。
IRP
列表如下:
名称 | 描述 | 调用者 |
---|---|---|
IRP_MJ_CREATE | 请求一个句柄 | CreateFile |
IRP_MJ_CLEANUP | 在关闭句柄时取消悬挂的IRP | CloseHandle |
IRP_MJ_CLOSE | 关闭句柄 | CloseHandle |
IRP_MJ_READ | 从设备得到数据 | ReadFile |
IRP_MJ_WRITE | 传送数据到设备 | WriteFile |
IRP_MJ_DEVICE_CONTROL | 控制操作(利用IOCTL宏) | DeviceIoControl |
RP_MJ_INTERNAL_DEVICE_CONTROL | 控制操作(只能被内核调用) | N/A |
IRP_MJ_QUERY_INFORMATION | 得到文件的长度 | GetFileSize |
IRP_MJ_SET_INFORMATION | 设置文件的长度 | SetFileSize |
IRP_MJ_FLUSH_BUFFERS | 写输出缓冲区或者丢弃输入缓冲区 | FlushFileBuffers |
FlushConsoleInputBuffer | ||
PurgeComm | ||
IRP_MJ_SHUTDOWN | 系统关闭 | InitiateSystemShutdown |
IRP
对应的派遣函数处理过程多数的IRP
都来自于Win32 API
函数,如CreateFile
,ReadFile
,WriteFile
函数等等。
二、IRP数据结构
typedef struct _IRP {
PMDL MdlAddress;
ULONG Flags;
union {
struct _IRP* MasterIrp;
PVOID SystemBuffer;
} AssociatedIrp;
IO_STATUS_BLOCK IoStatus;
KPROCESSOR_MODE RequestorMode;
BOOLEAN PendingReturned;
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;
kd> dt nt!_IRP
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 MdlAddress : Ptr32 _MDL
+0x008 Flags : Uint4B
+0x00c AssociatedIrp : <unnamed-tag>
+0x010 ThreadListEntry : _LIST_ENTRY
+0x018 IoStatus : _IO_STATUS_BLOCK
+0x020 RequestorMode : Char
+0x021 PendingReturned : UChar
+0x022 StackCount : Char
+0x023 CurrentLocation : Char
+0x024 Cancel : UChar
+0x025 CancelIrql : UChar
+0x026 ApcEnvironment : Char
+0x027 AllocationFlags : UChar
+0x028 UserIosb : Ptr32 _IO_STATUS_BLOCK
+0x02c UserEvent : Ptr32 _KEVENT
+0x030 Overlay : <unnamed-tag>
+0x038 CancelRoutine : Ptr32 void
+0x03c UserBuffer : Ptr32 Void
+0x040 Tail : <unnamed-tag>
除去一些简单的成员,例如Type,Size等,我们来看一下各个成员的具体意义:
- MdlAddress : 是一个MDL的指针,当内核层和用户层采用共享内存的结构传递数据的时候,这个MDL就代表共享的内存信息(共享物理内存,通过MDL映射)。这个成员生效的标记为:DO_DIRECT_IO, METHOD_IN_DIRECT 或者METHOD_OUT_DIRECT。MdlAddress 用来描述 user-mode buffer 的 MDL(memory descriptor list),这个域仅用于“direct I/O”。 假如最上层的 device object 的 flags 标志设置为 DO_DIRECT_IO 时:
(1) I/O 建立 IRP_MJ_READ 和 IRP_MJ_WRITE 时使用 MDL。
(2) I/O 建立 IRP_MJ_DEVICE_CONTROL 时假如 control 代码为 METHOD_IN_DIRECT 或 METHOD_OUT_DIRECT,使用 MDL。 MDL 描述 user-mode virtual buffer 也包含对应的 physical address,driver 使用它能尽快地访问 user-mode buffer。
- AssociatedIrp : 这个成员是个联合体,其中存在一个SystemBuffer程序;当内核层使用用户层的数据的时候是通过拷贝数据的方式来实现的话,那么拷贝后的数据就放在了AssociatedIrp.SystemBuffer中了。这个成员生效的标记是DO_BUFFERED_IO或者METHOD_BUFFERED。 AssociatedIrq 是一个 union 成员,它的结构如下: union { struct _IRP *MasterIrp; // 此 IRP 是 associate IRP,它指向 master IRP LONG IrpCount; // 此 IRP 是 master IRP,它指示 associate IRP 的个数 PVOID SystemBuffer; // 此 IRP 是 master IRP,它指向 system buffer } AssociatedIrp; // 用于和 user-mode buffer 进行数据交换。 AssociatedIrq.SystemBuffer 指向 kernel-mode nonpaged 的 data buffer 区域,它使用在下面情形:
(1) 在 IRP_MJ_READ 和 IRP_MJ_WRITE 操作里,假如最上层的 device object 的 flags 提供了 DO_BUFFERED_IO
(2) 在 IRP_MJ_DEVICE_CONTROL 操作里,假如 I/O control code 指示需要 buffer。调用 WriteFile() 或者 DeviceIoControl() 用作输入 data I/O manager 复制 user-mode data buffer 到 kernel-mode data buffer 里。
(3) 在读操作里,I/O manager 复制 kernel-mode data buffer 到 user-mode data buffer 里。
- IoStatus : 返回的状态信息。IoStatus 是一个结构体,包含了两个域:Status 与 Information。IoStatus.Status 接收 NTSTATUS 码,而 IoStatus.Information 是 ULONG 类型, 接收一个确切的值依赖于 IRP 的类型和完成的状态。一个通常的用法是:Information 域保存传送数据的 bytes 数(例如在 IRP_MJ_READ 操作上)。 它的结构类似如下: typedef struct _IO_STATUS_BLOCK { union { ULONG Status; PVOID Pointer; }; ULONG Information; } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
- RequestorMode : UserMode或KernelMode,指定原始I/O请求的来源。驱动程序有时需要查看这个值来决定是否要信任某些参数。UserMode 或者 KernelMode 这两个值之一。
- PendingReturned : Pending 状态,如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING。
- StackCount : 设备栈的数目。
- CurrentLocation : 当前处于哪个设备栈的索引。
- Cancel : IRP是否被取消,如果为TRUE,则表明IoCancelIrp已被调用(该函数用于取消这个请求)。如果为FALSE,则表明没有调用IoCancelIrp函数。
- CancelIrql(KIRQL) : 是一个IRQL值,表明那个专用的取消自旋锁是在这个IRQL上获取的.
- CancelRoutine(PDRIVER_CANCEL) : 是驱动程序取消例程的地址。你应该使用IoSetCancelRoutine函数设置这个域而不是直接修改该域(因为可以原子修改),是 driver 中 IRP cancel routine 的地址,需要使用 IoSetCancelRoutine() 设置,避免直接对它进行设置。
- UserBuffer(PVOID) : 用户层参数的直接地址,设置标记METHOD_NEITHER时候有效,UserBuffer 是保存 user-mode 的 data buffer,与 kernel-mode data buffer 进行数据交换。
- Tail.Overlay 是Tail联合中的一种联合结构,如下:
union
{
struct
{
union
{
KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
struct
{
PVOID DriverContext[4];
};
};
PETHREAD thread;
PCHAR AuxiliaryBuffer;
struct
{
LIST_ENTRY ListEntry;
union
{
struct _IO_STACK_LOCATION *CurrentStackLocation;
ULONG PacketType;
};
};
PFILE_OBJECT OriginalFileObject;
} Overlay;
KAPC Apc;
PVOID CompletionKey;
} Tail;
Tail union 包括三个部分:Overlay,Apc 以及 CompletionKey。
在这个图中,以水平方向从左到右是这个联合的三个可选成员,在垂直方向是每个结构的成员描述:
- Tail.Overlay.DeviceQueueEntry(KDEVICE_QUEUE_ENTRY)和Tail.Overlay.DriverContext(PVOID[4])是Tail.Overlayare内一个未命名联合的两个可选成员(只能出现一个)。I/O管理器把DeviceQueueEntry作为设备标准请求队列中的连接域。当IRP还没有进入某个队列时,如果你拥有这个IRP你可以使用这个域,你可以任意使用DriverContext中的四个指针;Tail.Overlay.DeviceQueueEntry主要用在StartIo相关例程上面。
- Tail.Overlay.ListEntry(LIST_ENTRY)仅能作为你自己实现的私有队列的连接域;这个成员比较重要,因为如果需要自己实现IRP异步执行的队列,那么就需要使用这个成员将IRP挂入到自己的队列中。
三、IRP
对应的派遣函数处理过程
多数的IRP
都来自于Win32 API
函数,如CreateFile
,ReadFile
,WriteFile
函数等等。
一种简单的IRP
派遣函数的实现就是:将该IRP
中的状态置为成功(pIRP->IoStatus =STATUS_SUCCESS
),然后结束该IRP
请求(调用I噢CompleteRequest
函数),并返回成功状态。
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKDispatchRoutin\n"));
return status;
VOID
IoCompleteRequest(
IN PIRP Irp,
IN CCHAR PriorityBoost
);
以ReadFile
为例,处理过程如下:。
ReadFile
调用ntdll中的N他ReadFile
。其中ReadFile
函数是Win32 API
,而NtReadFile
函数是Native API
。ntdll
中的N他ReadFile
进入内核模式,并调用系统服务中的N他ReadFile
函数。- 系统服务函数N他
ReadFile
创建IRP_MJ_WRITE
类型的IRP
,然后将这个IRP
函数发送到对应驱动程序的派遣函数中。 - 在对应的派遣函数中一般会通过
IoCompleteRequest
函数将IRP
请求结束。