在 Windows 内核中的请求基本上是通过 I/O Request Packet 完成的。
I/O manager ---> Dispatch routine ---> StartIo routine ---> ISR ---> DPC routine ---> I/O manager
IRP 的生存期从调用 I/O manager function 建立 IRP 开始,你可以使用下面 4 个 function 来建立一个新的 IRP:
建立 synchronous IRP
使用 IoBuildSynchronousFsdRequest() 或 IoBuildDeviceIoControlRequest() 来建立同步的 IRP,同步 IRP 是属于创建者线程。 由它有一个物主,由此有下面的一系列结果:
必须在 PASSIVE_LEVEL 里调用这两个函数,特别是不能在 APC_LEVEL 级别上。因为:在获得 fast mutex 后进入到 APC_LEVEL,然后 I/O manager不能提交 special APC routine 去处理所有 complete 处理。
PIRP Irp = IoBuildSycnhronousFsdRequest(...);
ExAcqurireFastMutex(...);
NTSTATUS status = IoCallDriver(...);
if (status == STATUS_PENDING)
{
KeWaitForSingleObject(...); // 错误:不要这样做
}
ExReleaseFastMutex(...);
在上面的代码里,使用 KeWaitForSingleObject() 等待会进入死锁:当完成执行 IoCompleteRequest(),这个 APC routine 运行将设置 event,
因为已经在 APC_LEVEL 级别上,APC routine 不能运行去设置 event signled。
假如你需要发送一个 synchronous IRP 到其它 driver,考虑下面的选择:
这两个函数所建立的 IRP 为下表所示:
support function IRP 类型
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IoBuildSynchronousFsdRequest() IRP_MJ_READ
IRP_MJ_WRITE
IRP_MJ_FLUSH_BUFFERS
IRP_MJ_SHUTDOWN
IRP_MJ_PNP
IRP_MJ_POWER(仅用于 IRP_MN_POWER_SEQUENCE)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IoBuildDeviceControlRequest() IRP_MJ_DEVICE_CONTROL
IRP_MJ_INTERNAL_DEVICE_CONTROL
建立异步 IRP
IoBuildAsynchronousFsdRequest() 和 IoAllocateIrp() 这两上函数建立异步的 IRP,这些可以建立 IRP 如下表所示:
support function IRP 类型
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IoBuildAsynchronousFsdRequest() IRP_MJ_READ
IRP_MJ_WRITE
IRP_MJ_FLUSH_BUFFERS
IRP_MJ_SHUTDOWN
IRP_MJ_PNP
IRP_MJ_POWER(仅用于 IRP_MN_POWER_SEQUENCE)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IoAllocateIrp() 任何(但必须初始化 Marjor function 表)
异步 IRP 不属于创建者线程,当 IRP 完成后,I/O manager 不调用 APC 以及不清理 IRP。考虑下面的问题:
当建立一个 IRP 后,可以使用 IoGetNextIrpStackLocation() 来获得 first stack location,然后需要对 stack location 进行初始化。
如果是使用 IoAllcateIrp() 建立的需要填写相关的 MajorFunction 表。
PEDEVICE_OBJECT DeviceObject;
PIO_STACK_LOCATION stack = IoGetNextIrpStackLoction(Irp);
stack->MajorFunction = IRP_MJ_Xxx;
// ... stack 初始化代码
NTSTATUS status = IoCallDriver(DeviceObject, Irp);
IoGetNextIrpStackLocation() 是一个宏用来获得当前的 stack location,它的定义如下:
#define IoGetNextIrpStackLocation(Irp) ((Irp)->Tail.Overlay.CurrentStackLocation - 1)
IoCallDriver完成的内容
IoCallDriver() 看起来像下面:
NTSTATUS IoCallDriver(PDEVICE_OBJECT DeviceObject,
PIRP Irp)
{
IoSetNextIrpStackLocation(Irp);
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
Stack->DeviceObject = DeviceObject;
ULONG fcn = Stack->MajorFunction;
PDRVIER_OBJECT Driver = DeviceObject->DriverObject;
return (*Driver->MajorFunction[fcn]))(DeviceObject, Irp);
}
IoCallDriver() 简单地调用 stack 指针对应的 driver 的 dispatch routine。
location device objects
除了使用 IoAttachDeviceToDeviceStack() 外,可以使用另外的两个方法:IoGetDeviceObjectPointer() 和 IoGetAttachedDeviceReference()
NTSTATUS IoGetDeviceObjectPointer(
IN PUNICODE_STRING ObjectName,
IN ACCESS_MASK DesiredAccess,
OUT PFILE_OBJECT *FileObject,
OUT PDEVICE_OBJECT *DeviceObject
);
假如你知道 device object 的名字,那么你可以使用这个函数 IoGetDeviceObjectPointer() 得到 device object,像下面的用法:
PUNICODE_STRING devname;
ASSESS_MASK access;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
NTSTATUS status;
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
status = IoGetDeviceObjectPointer(devname, access, &FileObject, &DeviceObject);
这个函数返回两个指针:一个指向 FILE_OBJECT, 一个指向 DEVICE_OBJECT。
PIRP Irp = IoXxx(...);
PIO_STACK_LOCATION Stack = IoGetNextIrpStackLocation(Irp);
ObReferenceObject(FileObject);
Stack->FileObject = FileObject;
IoCallDriver(DeviceObject, Irp);
ObDereferenceObject(FileObject);
IoGetDeviceObjectPointer() 执行一些步骤来定位返回的两个指针:
一个 Dispatch routine 的原型,看起来像下面:
NTSTATUS DispatchXxx(PDEVICE_OBJECT fdo, PRIP Irp)
{
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
...
return STATUS_Xxx;
}
IRP 的完成
完成一个 IRP 必须填允 IRP 的 IoStatus 域内的 Status 与 Information 值,然后调用 IoCompleteRequest() 函数。 这个 Status 值是在 NTSTATUS.h 文件里定义的 status code 之一。而 Information 值依赖于 IRP 的类型而定,大多时候当 IRP 失败后Information 设置为 0 ,当 IRP 引发数据的传送操作,通常设置 Information 值为传送的字节数。