nt驱动 取消列成

1 windows驱动详解内容

在CancelIo的内部会枚举所有没有被完成的IRP,然后依次调用IoCancelIrp。另外,如果应用程序没有调用CancelIo函数,应用程序在关闭设备时同样会自动调用CancelIo。下面的代码演示了如何编写取消例程。

BOOLEAN IoCancelIrp(
  _In_  PIRP Irp
);

The IoCancelIrp routine sets the cancel bit in a given IRP and calls the cancel routine for the IRP if there is one

列子:

VOID
#002 CancelReadIRP(
#003 IN PDEVICE_OBJECT DeviceObject,
#004 IN PIRP Irp
#005 )
#006 {
#007 KdPrint(("Enter CancelReadIRP\n"));
#008 PDEVICE_EXTENSION pDevExt =(PDEVICE_EXTENSION)
#009 DeviceObject->DeviceExtension;
#010 //设置完成状态为STATUS_CANCELLED
#011 Irp->IoStatus.Status =STATUS_CANCELLED;
#012 //设置IRP操作字节数
#013 Irp->IoStatus.Information =0;
#014 //结束IRP请求
#015 IoCompleteRequest( Irp, IO_NO_INCREMENT );
#016 //释放Cancel自旋锁
#017 IoReleaseCancelSpinLock(Irp->CancelIrql);
#018 KdPrint(("Leave CancelReadIRP\n"));
#019 }
#020
#pragma LOCKEDCODE
VOID
  HelloDDKStartIO(
    IN PDEVICE_OBJECT  DeviceObject,
    IN PIRP  Irp 
    )
{
	KIRQL oldirql;
	 DbgPrint("************entry HelloDDKStartIO Irp : %x\n", Irp);

	//获取cancel自旋锁
	IoAcquireCancelSpinLock(&oldirql);
	if (Irp!=DeviceObject->CurrentIrp||Irp->Cancel)
	{
		//如果当前有正在处理的IRP,则简单的入队列,并直接返回
		//入队列的工作由系统完成,在StartIO中不用负责
		 IoReleaseCancelSpinLock(oldirql);
		 DbgPrint("************Leave HelloDDKStartIO Irp : %x    %x   %d \n", Irp,DeviceObject->CurrentIrp,Irp->Cancel);
		return;
	}else
	{
		//由于正在处理该IRP,所以不允许调用取消例程
		//因此将此IRP的取消例程设置为NULL
		IoSetCancelRoutine(Irp,NULL);
		IoReleaseCancelSpinLock(oldirql);
	}

	KEVENT event;
	KeInitializeEvent(&event,NotificationEvent,FALSE);

	//等3秒
	LARGE_INTEGER timeout;
	timeout.QuadPart = -3*1000*1000*10;

	//定义一个3秒的延时,主要是为了模拟该IRP操作需要大概3秒左右时间
	KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,&timeout);

	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;	// no bytes xfered
	IoCompleteRequest(Irp,IO_NO_INCREMENT);
	 DbgPrint("IoCompleteRequest HelloDDKStartIO Irp : %p\n", Irp);


	//在队列中读取一个IRP,并进行StartIo
	IoStartNextPacket(DeviceObject,TRUE);

  DbgPrint("************Leave HelloDDKStartIO Irp : %p\n", Irp);
}
#021 NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
#022 IN PIRP pIrp)
#023 {
#024 KdPrint(("Enter HelloDDKRead\n"));
#025 //获得的设备扩展
#026 PDEVICE_EXTENSION pDevExt =(PDEVICE_EXTENSION)
#027 pDevObj->DeviceExtension;
#028 //设置完成例程
#029 IoSetCancelRoutine(pIrp,CancelReadIRP);
#030 //将IRP设置为挂起
#031 //挂起IRP
#032 IoMarkIrpPending(pIrp);
#033 KdPrint(("Leave HelloDDKRead\n"));
   //返回pending状态
#035 return STATUS_PENDING;
#036 }

应用程序

#include <windows.h>
#include <stdio.h>
#include <process.h>

typedef struct
{
 HANDLE Device; 
 char index;
}pare;

UINT WINAPI Thread(LPVOID context)
{
	
     pare *pareex=(pare *)context;
	printf("Enter Thread %d\n",pareex->index);
	//等待5秒
	OVERLAPPED overlap={0};
	overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
	UCHAR buffer[10];
	ULONG ulRead;
	
	BOOL bRead = ReadFile(pareex->Device,buffer,10,&ulRead,&overlap);

	//可以试验取消例程
	CancelIo(pareex->Device);
	WaitForSingleObject(overlap.hEvent,INFINITE);
	printf("leave Thread %d\n",pareex->index);
	return 0;
}


int main()
{
	int i=0;
	pare pareex[10]={0};

	HANDLE hDevice = 
		CreateFile("\\\\.\\HelloDDK",
					GENERIC_READ | GENERIC_WRITE,
					FILE_SHARE_READ,
					NULL,
					OPEN_EXISTING,
					FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
					NULL );

	if (hDevice == INVALID_HANDLE_VALUE)
	{
		printf("Open Device failed!");
		return 1;
	}

	HANDLE hThread[10];
	for(i=0;i<10;i++)
	{
    pareex[i].index=i;
    pareex[i].Device=hDevice;
    hThread[i] = (HANDLE) _beginthreadex (NULL,0,Thread,&pareex[i],0,NULL);
    
	}
//	hThread[1] = (HANDLE) _beginthreadex (NULL,0,Thread,&hDevice,0,NULL);
	

	//主线程等待两个子线程结束
	WaitForMultipleObjects(i,hThread,TRUE,INFINITE);
	
	//创建IRP_MJ_CLEANUP IRP
	CloseHandle(hDevice);

	printf("process end  %d\n",i);

	return 0;
}

打印信息

Enter DriverUnload
Enter DriverEntry
Leave DriverEntry
Enter HelloDDKDispatchRoutin
 IRP_MJ_CREATE
Leave HelloDDKDispatchRoutin
Enter HelloDDKRead  8ad0cf68
************entry HelloDDKStartIO Irp : 8ad0cf68
Enter HelloDDKRead  8b030f68
Leave HelloDDKRead  8b030f68 
************entry OnCancelIRP Irp : 8b030f68
************ KeRemoveEntryDeviceQueue  OnCancelIRP: 8b030f68
************leave OnCancelIRP Irp : 8b030f68
Enter HelloDDKRead  8b23af68
Leave HelloDDKRead  8b23af68 
************entry OnCancelIRP Irp : 8b23af68
************ KeRemoveEntryDeviceQueue  OnCancelIRP: 8b23af68
************leave OnCancelIRP Irp : 8b23af68
Enter HelloDDKRead  8b130f68
Leave HelloDDKRead  8b130f68 
************entry OnCancelIRP Irp : 8b130f68
************ KeRemoveEntryDeviceQueue  OnCancelIRP: 8b130f68
************leave OnCancelIRP Irp : 8b130f68
Enter HelloDDKRead  8a672f68
Leave HelloDDKRead  8a672f68 
************entry OnCancelIRP Irp : 8a672f68
************ KeRemoveEntryDeviceQueue  OnCancelIRP: 8a672f68
************leave OnCancelIRP Irp : 8a672f68
Enter HelloDDKRead  8aed4f68
Leave HelloDDKRead  8aed4f68 
************entry OnCancelIRP Irp : 8aed4f68
************ KeRemoveEntryDeviceQueue  OnCancelIRP: 8aed4f68
************leave OnCancelIRP Irp : 8aed4f68
Enter HelloDDKRead  8ad66f68
Leave HelloDDKRead  8ad66f68 
************entry OnCancelIRP Irp : 8ad66f68
************ KeRemoveEntryDeviceQueue  OnCancelIRP: 8ad66f68
************leave OnCancelIRP Irp : 8ad66f68
Enter HelloDDKRead  8af6ef68
Leave HelloDDKRead  8af6ef68 
************entry OnCancelIRP Irp : 8af6ef68
************ KeRemoveEntryDeviceQueue  OnCancelIRP: 8af6ef68
************leave OnCancelIRP Irp : 8af6ef68
Enter HelloDDKRead  8adf6f68
Leave HelloDDKRead  8adf6f68 
************entry OnCancelIRP Irp : 8adf6f68
************ KeRemoveEntryDeviceQueue  OnCancelIRP: 8adf6f68
************leave OnCancelIRP Irp : 8adf6f68
Enter HelloDDKRead  8adc6f68
Leave HelloDDKRead  8adc6f68 
************entry OnCancelIRP Irp : 8adc6f68
************ KeRemoveEntryDeviceQueue  OnCancelIRP: 8adc6f68
************leave OnCancelIRP Irp : 8adc6f68
IoCompleteRequest HelloDDKStartIO Irp : 8AD0CF68
************Leave HelloDDKStartIO Irp : 8AD0CF68
Leave HelloDDKRead  8ad0cf68 
Enter HelloDDKDispatchRoutin
 IRP_MJ_CLEANUP
Leave HelloDDKDispatchRoutin
Enter HelloDDKDispatchRoutin
 IRP_MJ_CLOSE
Leave HelloDDKDispatchRoutin
 

应用打印


C:\Documents and Settings\ygc>D:\chapte
Enter Thread 0
Enter Thread 1
Enter Thread 2
Enter Thread 3
Enter Thread 4
Enter Thread 5
Enter Thread 6
Enter Thread 7
Enter Thread 8
Enter Thread 9
leave Thread 4
leave Thread 1
leave Thread 6
leave Thread 8
leave Thread 3
leave Thread 7
leave Thread 2
leave Thread 9
leave Thread 5
leave Thread 0
process end  10

C:\Documents and Settings\ygc>

IoCancelIrp在内部会首先获得该自旋锁,IoCancelIrp会调用取消回调例程,因此,释放该自旋锁的任务就留给了取消回调例程。获得取消自旋的函数是IoAcquire CancelSpinLock函数,而释放取消自旋锁的函数是IoReleaseCancelSpinLock函数
 

在设置取消例程中要注意同步问题是,当退出取消例程时,一定要释放cancel自旋锁,否则会导致系统崩溃。另外,cancel自旋锁是全局自旋锁,所有驱动程序都会使用这个自旋锁,因此,占用自旋锁时间不宜过长

2 在IoStartPacket内设置取消函数

取消函数调用流程图

下面的代码KeLowerIrql,为什么会调

IoReleaseCancelSpinLock(pIrp->CancelIrql);//因为释放,导致取消列成函数不运行在DISPATCH_LEVEL,调用IoStartPacket函数后必须要降低运行IRQ(pass_level)

A driver must hold the system cancel spin lock when calling this routine if the driver uses the I/O manager-supplied device queue in the device object. The driver runs at IRQL = DISPATCH_LEVEL after calling IoAcquireCancelSpinLock until it releases the cancel spin lock with IoReleaseCancelSpinLock.

  KeLowerIrql(oldirql);//其实他要降低IRQL的级别是因为在IoStartPacket函数中如果设备是空闲的话呢会将IRQL的级别调节成DISPATCH_LEVEL

OID OnCancelIRP(PDEVICE_OBJECT DeviceObject, PIRP pIrp){
 2     
 3     UINT32* InputBuffer = (UINT32*)pIrp->AssociatedIrp.SystemBuffer;
 4     DbgPrint("Enter OnCancelIRP:%d!\n", *InputBuffer);
 5     if (pIrp == DeviceObject->CurrentIrp){
 6         //此IRP不在队列中,正在由StartIO处理
 7         DbgPrint("Current IRP OnCancelIRP!\n");
 8         KIRQL oldirql = pIrp->CancelIrql;//保存原先的IRQL
 9         IoReleaseCancelSpinLock(pIrp->CancelIrql);
10         IoStartNextPacket(DeviceObject, TRUE);
11         KeLowerIrql(oldirql);//其实他要降低IRQL的级别是因为在IoStartPacket函数中如果设备是空闲的话呢会将IRQL的级别调节成DISPATCH_LEVEL
12     }
13     else{//当irp != fdo->CurrentIrp的时候,这个很好理解,
14         //就是需求取消的irp还没有被执行,那么也就是说还在队列里面,直接把这个irp从队列里面删除就可以了。
15         DbgPrint("Uncurrent IRP OnCancelIRP!\n");
16         KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,
17             &pIrp->Tail.Overlay.DeviceQueueEntry);
18         IoReleaseCancelSpinLock(pIrp->CancelIrql);
19     }
20     pIrp->IoStatus.Status = STATUS_CANCELLED;
21     pIrp->IoStatus.Information = 0;
22     IoCompleteRequest(pIrp, IO_NO_INCREMENT);
23     DbgPrint("Leave OnCancelIRP:%d!\n", *InputBuffer);
24 }

首先因为I/O管理器在调用取消例程前会先调用IoAcquireCancelSpinLock来获取自旋锁,那么在取消例程里面切记一定要调用IoReleaseCancelSpinLock来释放自旋锁。

取消例程里面有个判断语句if (Irp == DeviceObject->CurrentIrp),

1. 当irp != fdo->CurrentIrp的时候,这个很好理解,就是需求取消的irp还没有被执行,那么也就是说还在队列里面,直接把这个irp从队列里面删除就可以了。

2. 当irp == fdo->CurrentIrp的时候,这是个有趣的时间点,这个时间点处于fdo->CurrentIrp=Irp(IoStartPacket或者IoStartNextPacket)和IoAcquireCancelSpinLock(StartIo例程)之间。因为需要取消的irp已经不在队列中了,那么就无需操作队列。在取消例程里面只要调用IoStartNextPacket就可以了(也就是跳过了当前irp的处理)
https://blog.csdn.net/zj510/article/details/8287712

链接位置:《Windows驱动开发技术详解》之StartIO例程 - _No.47 - 博客园 (cnblogs.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值