完成例程

在将IRP发送给底层驱动或者其他驱动前,可以对IRP设置-一个完成例程。一旦底层
驱动将IRP完成后,IRP完成例程立刻被触发。通过设置完成例程可以方便地使程序员了
解其他驱动对IRP进行的处理。

完成例程是驱动程序编写中的一种常用技巧。 当驱动程序调用自己的底层驱动,可以通过完成例程获得通知。不管是调用自
己的底层驱动或者调用其他驱动,都是使用内核函数loCallDriver.

当loCallDriver将IRP的控制权交给被动驱动时,有两种情况。第一种情况, 即调用
的设备是同步完成这个IRP的,从loCallDriver返回的时刻,即代表此IRP已经完成。

第二种情况,就是调用的设备是异步操作,IoCallDriver 会立刻返回loCallDriver, 但此时并
没有真正的完成IRP…

在第二种情况下,调用loCallDriver前,先对IRP注册一个完成例程,当底层驱动或
者其他驱动完成此IRP时,此完成例程立刻被调用。其实注册IRP的完成例程就是在当前
的堆栈(IO_STACK_LOCATION)中的CompletionRoutine 子域。

IRP 完成后,一层层堆栈向上弹出,如果遇到IO_STACK_LOCATION CompletionRoutine非空,则调用这个函数,另外传进这个完成例程的是I0_ STACK_ LOCATION 的子域Context。

如果使用完成例程,就不能使用内核宏loSkipCurrentIrpStackLocation,即不能将本层
IRP作为下层I/O堆栈。而必须使用IoCopyCurrentIrpStackLocationToNext,将本层I/O堆
栈拷贝到下一层的I/O堆栈中。对于初级程序员来说不必关心以上细节,DDK已经提供了
内核宏,即IoSetCompletionRoutine,其声明如下:

void IoSetCompletionRoutine(
  PIRP                   Irp,		//要设置的IRP
  PIO_COMPLETION_ROUTINE CompletionRoutine, //设置的IRP完成函数,如果为NULL,则代表没有IRP完成函数.
  PVOID  				 Context,//传给完成函数的参数
  BOOLEAN 				 InvokeOnSuccess,	//指定是否IRP被成功完成后进入完成函数
  BOOLEAN                InvokeOnError,	//指定是否IRP被错误完成后进入完成函数
  BOOLEAN                InvokeOnCancel	//指定是否IRP被取消完成后进入完成函数
);

当IRP处于某个设备栈时,IRP被IoCompleteRequest完成的时候,会一层层出栈。 出
栈的时候如果遇到该栈有完成例程,则调用该栈的完成例程。因此,完成例程可以作为通
知IRP完成的一个标志。并且,在完成例程中可以很清楚地知道IRP的完成情况,例如,
IRP的status、读写的情况,操作字节的数量等。另外,在完成例程中,提供了一个机会重
新获取对IRP的“控制”。
当调用loCallDriver 之后,当前的驱动就失去了对IRP的控制,如果这时候设置IRP
的属性,会引起系统的崩溃。完成例程只有两种返回的可能,一种STATUS_SUCCESS
或STATUS_ CONTINUE COMPLETION。其实这两个状态数值一样,是等价的,STATUS_CONTINUE COMPLETION是STATUS_ SUCCESS的别名。这种情况下,驱动不会再得到
IRP的控制。另一种情况是完成例程返回STATUS_ MORE PROCESSING _REQUIRED,这时候本层设备堆栈会重新获得IRP的控制权,并且设备栈不会向上弹出,也就是向上“回
卷"设备栈停止。此时可以选择再次向底层发送IRP。

假设有这样一个设备栈,IRP传递的方向是从设备栈顶传到设备栈底,

在这里插入图片描述

那么在IRP处理完成后是从设备栈底开始往上返回的,处理流程如下图.
在这里插入图片描述

传播Pending位
每当低级驱动完成IRP后,将IRP的堆栈向上回卷时,底层I/O堆栈中Control 域的
SL_PENDING_RETURNED位必须被传播到上一-层。如果本层没有设置完成例程,那么这种传播是自动的,即不用程序员指定。如果本层堆栈设置了完成例程,则这种传播需要程序员自己实现。例如,调用IoCallDriver低层驱动时,返回的是STATUS PENDING。如果没有设置完成例程,需要如下编写代码:

//向下复制堆栈
IoCopyCurrentIrpStacklocationToNext(Irp);
//向下传递IRP
status=IoCallDriver(nextDevice, Irp);
//直接返回底层驱动状态
return status ;

此时没有调用IoMarkIrpPending,是因为没有设置完成例程,底层驱动会自动将堆栈
的Control域的SL PENDING_ RETURNED位复制到本层堆栈。
然而如果是设置了完成例程,传播Pending位的任务就留给了程序员完成。对于设置
了完成例程,下面的代码似乎是正确的:

//向下复制准栈
IoCopyCurrentIrpstackLocationToNext( Irp );
//向下传递IRP
status = IoCallDriver( nextDevice, Irp );
//下面的代码是有错误的! 
//因为调用IoCallDriver后,IRP就不在归本层所有,因此再操作IRP会导致错误
if(status ==STATUS_PENDING){
IoMarkIrpPending( Irp );
}

//直接返回底层驱动状态
return status ;

由于调用IoCallDriver后,IRP就不再归当前堆栈所有,因此以上的代码有一点问题。
为此,传播Pending位的任务就留给了完成例程。以下的代码是正确的:

NTSTATUS
  MyIoCompletion(
    IN PDEVICE_OBJECT  DeviceObject,
    IN PIRP  Irp,
    IN PVOID  Context
    )
{
	//进入此函数标志底层驱动设备将IRP完成
	KdPrint(("Enter MyIoCompletion\n"));
    if (Irp->PendingReturned) 
	{	
		//如果底层驱动是否有Pending位  如果有则当前堆栈也设置Pending位
        IoMarkIrpPending( Irp );
    }
    //完成函数的其他附加行为
	return STATUS_SUCCESS;//同STATUS_CONTINUE_COMPLETION

}

完成例程返回STATUS_SUCCESS
当IRP被IoComplectcRequest完成时,IRP就会沿着一-层层的设备 堆栈向上回卷。如果
途经遇到某设备堆栈的完成例程,则进入该完成例程。完成例程如果返回
STATUS_ SUCCESS (别名是STATUS_CONTINUE_COMPLETION),则继续向上回卷。此时的完成例程仅仅就是一个通知,表明IRP已完成。

NTSTATUS
  MyIoCompletion(
    IN PDEVICE_OBJECT  DeviceObject,
    IN PIRP  Irp,
    IN PVOID  Context
    )
{
	
	//进入此函数标志底层驱动设备将IRP完成 
	KdPrint(("Enter MyIoCompletion\n"));
	if (Irp->PendingReturned)
	{
		//如果底层驱动是否有Pending位  如果有则当前堆栈也设置Pending位  在本层堆栈设置了完成例程,需要自己手动传递,
		IoMarkIrpPending(Irp);
	}
	//完成函数的其他附加行为

	//设置这个标志,驱动不会再得到IRP的控制权 STATUS_SUCCESS 就是一个通知,表明IRP已完成
	return STATUS_SUCCESS;//同STATUS_CONTINUE_COMPLETION
}

#pragma PAGEDCODE
NTSTATUS IrpReadProc(IN PDEVICE_OBJECT pDevObj,
								 IN PIRP pIrp) 
{
	DbgPrint("设备栈顶层设备不处理此IRP  转发给下一层处理\n");
	NTSTATUS status = STATUS_SUCCESS;

	//得到设备扩展
	PDEVICE_EXTENSION pDeviceExtension = pDeviceObject->DeviceExtension;

	//将当前I/O堆栈拷贝至底层堆栈
	IoCopyCurrentIrpStackLocationToNext(pIrp);

	//设置完成例程
	IoSetCompletionRoutine(pIrp,		
						   MyIoCompletion,	//完成例程
						   NULL,	//传递给完成例程的参数
						   TRUE,
						   TRUE,
						   TRUE
						   );

	//调用底层驱动
	status = IoCallDriver(pDeviceExtension->AttachedDeciveObjectPointer, pIrp);

	//当调用IoCallDriver后,并且完成例程返回的是STATUS_SUCCESS IRP就不再属于派遣函数,就不能对IRP进行操作了.
	if (status==STATUS_PENDING)
	{
		DbgPrint("STATUS_PENDING\n");
	}
	
	//将状态设置为挂起
	status = STATUS_PENDING;
	return status;
}

完成例程返回STATUS_MORE_PROCESSING_REQUIRED
当IRP被IoCompleteRequest完成时,IRP就会沿着一-层层的设 备堆栈向上回卷。如果
途经遇到某设备堆栈的完成例程,则进入该完成例程。完成例程如果返回STATUS_MORE PROCESSING_ REQUIRED,则停止向上回卷。这时的本层堆栈又重新获得IRP的
控制,并且该IRP 从完成状态又变成了未完成状态,需要再次完成,即需要再次执行
IoCompleteRequest。
重新获得的IRP可以再次发往底层驱动,也可以自己标志完成,即调用IoComplete
Request。

以下给出了使用完成例程的例子,该完成例程返回STATUS_MORE_PROCESSING_REQUIRED。在调用loCallDriver 之后,当前设备想等待底层驱动将设备完成后再继续执行。因此,loCallDriver 初始化了个事件,并将事件指针传递给了完成例程。IRP 被完成后进入完成例程,并触发事件。
这种技巧被广泛应用于驱动程序的编写中,代码如下:

NTSTATUS
  MyIoCompletion(
    IN PDEVICE_OBJECT  DeviceObject,
    IN PIRP  Irp,
    IN PVOID  Context
    )
{

	if (Irp->PendingReturned == TRUE) 
	{
		//设置事件
		KeSetEvent((PKEVENT)Context,IO_NO_INCREMENT,FALSE);
	}

	return STATUS_MORE_PROCESSING_REQUIRED;
}

#pragma PAGEDCODE
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
								 IN PIRP pIrp) 
{
	KdPrint(("DriverB:Enter B HelloDDKRead\n"));
	NTSTATUS ntStatus = STATUS_SUCCESS;
	//将自己完成IRP,改成由底层驱动负责

	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;

	//本层堆栈也可能会有一个完成函数,如果直接使用本层堆栈的话,设置完成函数的时候回覆盖掉.所以都是先把堆栈复制到下一层,然后设置完成函数到下一层堆栈中
	IoCopyCurrentIrpStackLocationToNext(pIrp);

	KEVENT event;
	//初始化事件
	KeInitializeEvent(&event, NotificationEvent, FALSE);

	//设置完成例程  最后三个参数指明了 在 成功  发生错误 或者取消的情况下都会调用完成例程.
	IoSetCompletionRoutine(pIrp,MyIoCompletion,&event,TRUE,TRUE,TRUE);

	//调用底层驱动
    ntStatus = IoCallDriver(pdx->TargetDevice, pIrp);

	if (ntStatus == STATUS_PENDING)
	{
		KdPrint(("IoCallDriver return STATUS_PENDING,Waiting ...\n"));
		KeWaitForSingleObject(&event,Executive,KernelMode ,FALSE,NULL);
		ntStatus = pIrp->IoStatus.Status;
	}

	//虽然在底层驱动已经将IRP完成了,但是由于完成例程返回的是
	//STATUS_MORE_PROCESSING_REQUIRED,因此需要再次调用IoCompleteRequest!
	IoCompleteRequest (pIrp, IO_NO_INCREMENT);

	KdPrint(("DriverB:Leave B HelloDDKRead\n"));

	return ntStatus;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值