wdk tips (2): IO_STACK_LOCATION

如前文所述,nt内核的驱动模型没有完全使用函数调用栈,而是自己山寨出来一个IO_STACK_LOCATION,里面保存了驱动调用序列。我们知道函数调用栈的push和pop都是编译器帮忙弄的,你甚至都可以在完全不了解内幕的前提下写代码,但是驱动开发不一样,调用序列要你自己去关心,何时入栈,何时出栈,栈内保留的什么内容,全部都要照顾好,否则BSOD就在前方不远等你。

与IO_STACK_LOCATION有关的函数有以下几个:IoSkipCurrentIrpStackLocation, IoSetNextIrpStackLocation, IoGetNextIrpStackLocation, IoCopyCurrentIrpStackLocationToNext,外加IoCallDriver等往下传irp的函数。

IoCopyCurrentIrpStackLocationToNext

该函数将current location里的内容全部拷贝到next location中去,一般是你设置了CompleteRoutine之后会用。函数实现非常简单,用宏的形式存放在wdm.h中:

 

#define IoCopyCurrentIrpStackLocationToNext( Irp ) { \ 
    PIO_STACK_LOCATION __irpSp; \ 
    PIO_STACK_LOCATION __nextIrpSp; \ 
    __irpSp = IoGetCurrentIrpStackLocation( (Irp) ); \ 
    __nextIrpSp = IoGetNextIrpStackLocation( (Irp) ); \ 
    RtlCopyMemory( __nextIrpSp, __irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine)); \ 
    __nextIrpSp->Control = 0; }

如上所示,其实就是一个RtlCopyMemory。一般在本层驱动需要CompleteRoutine时使用。常见的调用序列如下:

 

irpStack = IoGetCurrentIrpStackLocation(Irp);
//…your code with irpStack
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine( 
     Irp, 
     CompleteRoutine, 
     deviceExtension, 
     TRUE, 
     TRUE, 
     TRUE);
IoCallDriver(deviceExtension->nextLower, Irp);

注意,IoCallDriver会把stack location减一。

IoSkipCurrentIrpStackLocation

该函数的实现就更简单了

 

#define IoSkipCurrentIrpStackLocation( Irp ) { \ 
    (Irp)->CurrentLocation++; \ 
    (Irp)->Tail.Overlay.CurrentStackLocation++; }

也就是把当前的location设置成上一层。该函数一般和IoCallDriver配合使用

 

IoSkipCurrentIrpStackLocation(Irp);//location+1
IoCallDriver(deviceExtension->nextLower, Irp);//location-1

执行完上面两步之后,location正好跟调用者一样,IO_STACK_LOCATION中的内容也不变。Filter driver常用此种手段转发irp:收到一个irp,获取或者修改其数据,继续转发,因为location没变所以上层驱动设置的CompleteRoutine依然会被filter之下的那个驱动call到,filter就跟透明一样。

IoGetNextIrpStackLocation

该函数获取下一层location的指针

 

#define IoGetNextIrpStackLocation( Irp ) (\ 
    (Irp)->Tail.Overlay.CurrentStackLocation - 1 )

获取该指针后可以设置IoControlCode等内容然后传给下层驱动处理,一般也和IoCallDriver配套使用

 

nextStack = IoGetNextIrpStackLocation(Irp);    
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; 
nextStack->Parameters.DeviceIoControl.IoControlCode = code; 
nextStack->Parameters.DeviceIoControl.OutputBufferLength = 0; 
nextStack->Parameters.DeviceIoControl.InputBufferLength = 0;
IoCallDriver(deviceExtension->nextLower, Irp);

IoSetNextIrpStackLocation

我们重点来讲讲IoSetNextIrpStackLocation函数,该函数实现如下:

 

#define IoSetNextIrpStackLocation( Irp ) { \ 
    (Irp)->CurrentLocation++; \ 
    (Irp)->Tail.Overlay.CurrentStackLocation—; }

与IoSkipCurrentIrpStackLocation正好相反,它把location设置成下一级。我们知道IoCallDriver等函数在转发irp的时候会帮我们把location减1,所以需要我们主动把location往下降的时刻很少见,一般是由于此种需求才会用到:你调用IoAllocateIrp生成一个irp,并且需要用到irp的current location。因为刚生成的irp的current location是不可用的所以必须主动往下降一级。正如wdk文档所说,这种需求是极其罕见的,原因有二:1. 大多数情况下你处理的irp是别人传给你的。2. 即使确实要生成一个新的irp,本层所需的数据也不需要存到current location中去,因为IoSetCompleteRoutine可以接受一个context域,每次CompleteRoutine被调到时,该context域也会一起返回给你,所有本层驱动所需的信息完全可以放在context中。但是我们都知道凡事都有个例外,其实还真就有需要用的IoSetNextIrpStackLocation的时刻,我们来看一个例子。

假设我们需要allocate一个新的irp并用同步的方法传递给下层驱动处理,我们先用IoAllocateIrp获取一个irp,设置相应的内容,然后调用IoGetNextIrpStackLocation获取下层location以便将control code设为IRP_MJ_INTERNAL_DEVICE_CONTROL,最后调用IoForwardIrpSynchronously函数同步完成irp。但是问题就来了:IoForwardIrpSynchronously函数内部会做一个IoCopyCurrentIrpStackLocationToNext动作,这个函数会把next location的内容替换成current location里的内容,所以我们设置的control code啥信息全被冲掉了。怎么办呢?既然它会用current的替换next的,那么我们直接把control code放在current里不就完了,IoForwardIrpSynchronously会把它拷到下一层去。愿望是美好的,但残酷的现实就是:irp是新生成的,还没有所谓的current location!这时候IoSkipCurrentIrpStackLocation就会显得很有用:先调用IoSkipCurrentIrpStackLocation将location下降,然后GetCurrentIrpStackLocation获取当前的location,设置control code,最后调用IoForwardIrpSynchronously同步完成irp。

看到这里也许你要发问了:为什么IoForwardIrpSynchronously要主动帮我们做copy to的动作,不是吃饱撑了嘛。问的好,答案就是:没办法只能这么做。我记得咱们说过,nt内核里的io全是异步的,上述所谓同步操作也都是用event模拟出来的:调完IoCallDriver后wait在一个event上,在CompleteRoutine里set event。返回到前面对IoCopyCurrentIrpStackLocationToNext的描述我们知道,既然要用到CompleteRoutine,那么IoCopyCurrentIrpStackLocationToNext操作就是免不的了。

 

update:

另有一种情况也会有用到IoSetNextIrpStackLocation。当你allocate了一个irp,set了CompleteRoutine,调了IoCallDriver,一切都完成等complete时,问题就来了:你设置的CompleteRoutine被调到时传入的DeviceObject为NULL。Why?Because CompleteRoutine是设置在next location中的,而DeviceObject保存在current location中。 当一个irp被create出来之后,它的current location是无效值,所以没地方存放DeivceObject。怎么办?按上面所述方法,借助IoSetNextIrpStackLocation完成。

posted @ 2011-01-19 13:54  gussing  阅读(3336)  评论(2编辑  收藏  举报