首页
社区
课程
招聘
[求助]IoCancelIrp疑惑!(已解决)
2013-5-19 23:05 7863

[求助]IoCancelIrp疑惑!(已解决)

2013-5-19 23:05
7863
对于异步IRP的处理方式,就我了解一般是先push irp到一个队列,然后在另一个地方(startio或者专门的线程中)处理,处理时则是先pop出irp,然后再调用IoSetCancelRoutine把完成例程置空;(具体见如下)

线程1:(IRP处理线程)
// some other code
IoAcquireCancelSpinLock(&OldIrql);
Irp = PopIrpFromQueue();
IoSetCancelRoutine(Irp, NULL);
IoReleaseCancelSpinLock(OldIrql);
// do some thing
IoCompleteRequest(Irp, IO_NO_INCREMENT);
// some other code


线程2:(IoCancelIrp内部实现,代码实现参考reactos以及wrk)
IoAcquireCancelSpinLock(&OldIrql);
Irp->Cancel = TRUE;
CancelRoutine = (PVOID)IoSetCancelRoutine(Irp, NULL);
if (CancelRoutine)
{
    // some other code
    CancelRoutine(Irp->Tail.Overlay.CurrentStackLocation->DeviceObject, IRP);
    return ;
}
IoReleaseCancelSpinLock(OldIrql);


那么问题来了,假如线程1在进入IoCompleteRequest内部,调用IoFreeIrp释放IRP后,突然线程调度使线程2执行到了IoAcquireCancelSpinLock处(外部调用IoCancelIrp或者ring3层调wait超时后调用了CancelIo),首先调用Acquire函数肯定不会存在问题,问题是调用了此函数后接下来会对Irp的Cancel以及CancelRoutine成员进行写入操作,那么此时应该会造成写入错误吧?(哪位前辈了解的话,能否帮忙解惑一下,万分感谢!

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞0
打赏
分享
最新回复 (1)
雪    币: 107
活跃值: (73)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
nopnopnop 1 2013-5-20 15:08
2
0
已有结论(又要自问自答了,囧。。。)
不会存在问题,前提是线程2是IoCallDriver以及IoCancelIrp的同一个发起者;

以下分析涉及到的代码参考自reactos及wrk;(假如哪位前辈觉着此结论有问题,请告知下,免得我误人误己,呵呵)

以ring3成的CancelIo为例,它的调用路径为:CancelIo->NtCancelIoFile->IoCancelIrp;
其中在NtCancelIoFile中会调用KeRaiseIrql(APC_LEVEL, &OldIrql);然后遍历此线程中所有相关要取消的IRP列表,并依次调用IoCancelIrp;

而对于异步IRP,其在IofCompleteRequest内部,不会直接调用IoFreeIrp,而是把其放入到APC队列中,如:
/* Initialize the APC */
KeInitializeApc(&Irp->Tail.Apc,
                &Thread->Tcb,
                Irp->ApcEnvironment,
                IopCompleteRequest,
                NULL,
                NULL,
                KernelMode,
                NULL);

/* Queue it */
KeInsertQueueApc(&Irp->Tail.Apc,
                  FileObject,
                  NULL, /* This is used for REPARSE stuff */
                  PriorityBoost);

假如用户层没有注册APC回调,那么IoFreeIrp就在IopCompleteRequest中完成;

而我们知道,APC函数触发时,会把其调度到同一个线程的上下文中才执行,所以当NtCancelIoFile执行KeRaiseIrql(APC_LEVEL, &OldIrql);时,就不需要采用自旋锁考虑多处理器的问题了,且这时如果能得到IRP,那么这个IRP一定有效,即还未调用IoFreeIrp;

其中wrk在NtCancelIoFile的KeRaiseIrql操作前有一句注释说的很明白,具体如下:
//
// Walk the list of IRPs on the thread's pending I/O queue looking for IRPs
// which specify the same file as the FileHandle refers to.  For each IRP
// found, set its cancel flag.  If no IRPs are found, simply complete the
// I/O here.  The only synchronization needed here is to block out all APCs
// for this thread so that no I/O can complete and remove packets from the
// queue.  No considerations need be made for multi-processing since this
// thread can only be running on one processor at a time and this routine
// has control of the thread for now.
//

KeRaiseIrql(APC_LEVEL, &irql);


假如在内核层直接调用IoCancelIrp,理由类似,也均不会出先我问题中所描述担忧情况;
游客
登录 | 注册 方可回帖
返回