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
函数等等。
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 :
+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 :
+0x038 CancelRoutine : Ptr32 void
+0x03c UserBuffer : Ptr32 Void
+0x040 Tail :
除去一些简单的成员,例如Type,Size等,我们来看一下各个成员的具体意义:
(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。
(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 里。
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。
在这个图中,以水平方向从左到右是这个联合的三个可选成员,在垂直方向是每个结构的成员描述:
三、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
函数。ReadFile
创建IRP_MJ_WRITE
类型的IRP
,然后将这个IRP
函数发送到对应驱动程序的派遣函数中。IoCompleteRequest
函数将IRP
请求结束。