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 10C:\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)