3、派遣例程的职责
•派遣函数立即完成该IRP。
•把该IRP传递到处于同一堆栈的下层驱动程序。
•排队该IRP以便由这个驱动程序中的其它例程来处理。
每个设备对象都自带一个请求队列对象,下面是使用这个队列的标准方法:
NTSTATUS DispatchXxx(...)
{
...
IoMarkIrpPending(Irp);
IoStartPacket(device, Irp, NULL, NULL);
return STATUS_PENDING;
}
一旦我们调用了IoStartPacket函数,就不要再碰IRP。因为在该函数返回之前,IRP可能已经被完成并且其占用的内存可能被释放,而我们拥有的该IRP的指针也许是无效的。
4、StartIo例程
每处理一个IRP,I/O管理器就调用一次StartIo例程。StartIo的工作是就着手处理IRP。
5、中断服务例程
用IoConnectInterrupt函数“钩住”一个中断,该函数的一个参数就是ISR的地址。一个ISR最可能做的事就是调度DPC例程(推迟过程调用)。而DPC的目的就是让你做某些事情,如调用IoCompleteRequest。
表示. IoCompleteRequest的优先级推进值
推进值常量 | 优先级推进值 |
IO_NO_INCREMENT | 0 |
IO_CD_ROM_INCREMENT | 1 |
IO_DISK_INCREMENT | 1 |
IO_KEYBOARD_INCREMENT | 6 |
IO_MAILSLOT_INCREMENT | 2 |
IO_MOUSE_INCREMENT | 6 |
IO_NAMED_PIPE_INCREMENT | 2 |
IO_NETWORK_INCREMENT | 2 |
IO_PARALLEL_INCREMENT | 1 |
IO_SERIAL_INCREMENT | 2 |
IO_SOUND_INCREMENT | 8 |
IO_VIDEO_INCREMENT | 1 |
不要以专用状态代码STATUS_PENDING来完成一个IRP。派遣例程经常要使用STATUS_PENDING代码作为返回值,但你决不能在IoStatus.Status中设置这个值。
6、完成例程
IoSetCompletionRoutine将把完成例程地址和上下文参数安装到下一个IO_STACK_LOCATION中,即下一层驱动程序将在那个堆栈单元中找到这些参数。因此,最底层的驱动程序不应该安装一个完成例程。
完成例程通常在DISPATCH_LEVEL级和任意线程上下文中被调用,但有时也在PASSIVE_LEVEL或APC_LEVEL级被调用。为了适应大多数情况(DISPATCH_LEVEL),完成例程应存在于非分页内存中,并且仅使用可在DISPATCH_LEVEL级上调用的服务例程。然而,为了适应在低级IRQL上调用该例程的可能情况,完成例程不应调用像KeAcquireSpinLockAtDpcLevel这样的函数,因为这些函数假定开始执行于DISPATCH_LEVEL级上。
在完成例程内部,一个IoGetCurrentIrpStackLocation调用将获得上一层堆栈单元的指针。上层堆栈单元的完成例程不应该依赖任何下层堆栈单元中的内容。为了加强这个规则,IoCompleteRequest在调用完成例程前清除了下一个堆栈单元中的大部分内容。
if (Irp->PendingReturned)
IoMarkIrpPending(Irp);
所有不返回STATUS_MORE_PROCESSING_REQUIRED状态的完成例程都需要这两行代码。
7、取消I/O请求
为了在内核模式中取消一个请求,IRP的创建者需调用IoCancelIrp函数。如果某线程终止时,它发出的请求仍然未完成,则操作系统自动为每个IRP调用IoCancelIrp。用户模式应用程序调用CancelIo函数可以取消给定线程发出的所有未完成的异步操作。IoCancelIrp仅仅是简单地设置IRP的Cancel标志位然后调用IRP的取消例程。即:它并不知道你在是否修改过IRP指针,也不知道你是否正在处理这个IRP,所以它必须依靠一个你提供的取消例程来做大部分IRP取消工作。
关于取消IO更多的关于多CPU的讨论,见参考文献[1]。
[1] Windows驱动程序模型设计