3.1.4 IRP与派遣函数
驱动程序的主要功能是负责处理I/O请求,而大部分的I/O请求又是在派遣函数中处理的。IRP的处理机制类似于Windows应用程序中的“消息处理”机制。用户空间对驱动程序的所有I/O请求都是由操作系统将其转化成为一个IRP数据结构,不同的IRP数据会被“派遣”到不同的派遣函数中,而派遣函数则负责处理对应的IRP数据。
IRP具有两个基本属性:MajorFunction与MinorFunction,它们分别记录IRP的主功能和子功能。操作系统就是根据MajorFunction将IRP“派遣”给不同的派遣函数的,在派遣函数中还可以根据MinorFunction继续判断该IRP属于哪种子功能。
一般来说,驱动程序都是在DriverEntry函数中注册派遣函数的。在DriverEntry函数的驱动对象PDRIVER_OBJECT的参数中,有个函数指针数组MajorFunction,这个数组里的不同元素记录着不同派遣函数的地址。通过设置该数组,可以将不同类型的IRP和对应的派遣函数关联起来。例如WinPcap驱动程序DriverEntry函数中的下列代码,就将不同类型的IRP和对应的派遣函数关联起来了。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
……
/设置IRP派遣函数和卸载例程/
DriverObject->MajorFunction[IRP_MJ_CREATE]=NPF_Open;
DriverObject->MajorFunction[IRP_MJ_CLOSE]=NPF_Close;
DriverObject->MajorFunction[IRP_MJ_CLEANUP]=NPF_Cleanup;
DriverObject->MajorFunction[IRP_MJ_READ]=NPF_Read;
DriverObject->MajorFunction[IRP_MJ_WRITE]=NPF_Write;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=
NPF_IoControl;
……
}
大部分的IRP都是因为调用文件I/O处理API而产生的,如CreateFile、ReadFile、WriteFile、CloseHandle等API等函数,会使操作系统产生IRP_MJ_CREATE、IRP_MJ_READ、IRP_MJ_WRITE、IRP_MJ_CLOSE等不同类型的IRP。这些IRP会被传送到驱动程序中,从而调用对应的派遣函数。此外,调用内核的文件I/O处理函数,如ZwCreateFile、ZwReadFile、ZwWriteFile、ZwClose等函数,同样也会产生IRP_MJ_CREATE、IRP_MJ_READ、lRP_MJ_WRITE、IRP_MJ_CLOSE等IRP,并且会将IRP传送到驱动程序中,然后调用对应的派遣函数。
处理IRP最简单的方法就是在相应的派遣函数中,将IRP的状态设置为成功,然后使用函数IoCompleteRequest结束IRP的请求,并让派遣函数返回成功。下面为WinPcap驱动程序中NPF_Close函数的代码,它是处理IRP_MJ_CLOSE类型IRP的派遣函数。
NTSTATUS NPF_Close(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
Irp->IoStatus.Status=STATUS_SUCCESS;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
上述派遣函数中,函数NPF_Close设置IRP的完成状态(Irp->IoStatus.Status)为STATUS_SUCCESS,这样发起请求的API(如CloseHandle)将会返回TRUE。相反,如果将IRP的完成状态设置为不成功,发起I/O请求的API(如CloseHandle)将会返回FALSE。如果返回FALSE,可以使用GetLastError API获得错误代码,所得的错误代码会和IRP设置的状态一致。
除了设置IRP的完成状态外,还要设置该IRP请求的操作字节数(Irp->IoStatus.Information)。在NPF_Close函数中,通常会将操作字节数设置为0。如果是ReadFile产生的IRP,这个字节数就代表从设备中读取了多少个字节。如果是WriteFile产生的IRP,这个字节数就代表对设备写入了多少个字节。
最后函数调用IoCompleteRequest函数结束IRP请求。
函数IoCompleteRequest的原型如下:
VOID IoCompleteRequest(IN PIRP Irp,IN CCHAR PriorityBoost)
上述函数中,参数Irp代表需要被结束的IRP;参数PriorityBoost代表线程恢复时的优先级别,即阻塞的线程以何种优先级恢复运行,一般情况下,其优先级设置为IO_NO_INCREMENT。