IRP通过KDevice的分发例程进入驱动程序。驱动程序可以立即处理它并返回状态信息,或者是将它排队并返回STATUS_PENDING。
当一个IRP被排队,驱动程序应该使这个IRP可以取消,因为如果这个IRP的发起者提出一个取消请求,I/O管理器应该可以通知驱动程序响应这个请求。如果一个IRP拥有一个取消例程,则它是可以取消的。取消例程可以通过KDevice::QueueIrp, KIrp::TestAndSetCancelRoutine, 和KIrp::SetCancelRoutine进行设置。当驱动程序用KDevice::QueueIrp来排队IRP时,必须决定这个IRP是否可以取消。而用KDriverManagedQueue::QueueIrp排队的IRP总是可以取消的。
如果当一个请求已经被处理到驱动程序不能再取消它时,必须将这个请求的取消例程设置为NULL(用KIrp::TestAndSetCancelRoutine或KIrp::SetCancelRoutine)。当一个IRP可以取消时驱动程序不能完成它。如果一个IRP的取消标志被设置时就没有必要清除取消例程。
下面是完成取消例程的步骤:
在KDevice子类的声明中,使用DEVMEMBER_CANCELIRP宏把取消例程声明为设备类(即KDevice的子类,译注)的成员函数。一些驱动开发者可能会发现用不同的取消成员函数处理不同种类的IRP或过程的不同阶段是很有用的。
class MyDevice : public KDevice
{
public:
MyDevice(void);
. . .
DEVMEMBER_CANCELIRP(MyDevice, Cancel)
};
当你用KDevice::QueueIrp排队一个IRP时,将第二个参数设置为LinkTo(Cancel),这样可以连接IRP和设备类的静态函数(即取消例程,译注),这个函数是设备类的声明里用DEVMEMBER_CANCELIRP宏声明的非静态成员。
QueueIrp( I, LinkTo(Cancel) );
书写取消成员函数。当系统调用它时,它保存系统范围内的取消自旋锁,必须在完成这个请求之前释放。取消自旋锁在取消例程返回后不能继续保存。
VOID MyDevice::Cancel(KIrp I)
{
if ( PIRP(I) == CurrentIrp() )
{
CancelSpinLock::Release(I->CancelIrql);
I.Information() = 0;
I.Status() = STATUS_CANCELLED;
NextIrp(I);
}
else
{
KDeviceQueue dq(DeviceQueue());
BOOLEAN bRemoved = dq.RemoveSpecificEntry(I);
CancelSpinLock::Release(I->CancelIrql);
if ( bRemoved )
{
I.Information() = 0;
I.Complete(STATUS_CANCELLED);
}
}
}
在你的StarIo函数内,你必须检查取消标志并且清除取消例程。使用KIrp::TestAndSetCancelRoutine函数来完成:
VOID MyDevice::StartIo(KIrp I)
{
if ( ! TestAndSetCancelRoutine(LinkTo(Cancel), NULL, CurrentIrp() )
return;
. . .
// normal IRP processing
. . .
}
注意,强烈建议你给QueueIrp传递非空取消例程,但这不是必须的。如果你排队一个空取消历程的IRP,你必须始终在保存全局取消自旋锁时,在StartIo函数的开始部分测试取消标志。你可以调用TestAndSetCancelRoutine函数并把前两个参数设置为NULL完成这个功能。如果函数返回FALSE,你必须用STATUS_CANCELLED状态结束IRP,因为排队时没有有效的取消例程。