驱动开发之四 --- 过滤驱动之一 【译文】

驱动开发之四 --- 过滤驱动之一 【译文】

这是第四篇介绍写设备驱动的文章,本篇我们将介绍设备栈的概念以及设备间的交互。我们将使用前面的例子来进行验证。我们将介绍过滤驱动的概念,并且创建一个过滤驱动,挂接到我们的驱动设备栈。

理论:

什么是设备栈?

谈到栈,通常我们可以想象为摞在一起的一堆物体,一个压另一个的上面。还有就是数据结构中作为一种算法实现的栈,是指先进后出的方法来存储临时对象。这两种描述是有关联的。然而,设备栈既不是指一种算法也不需要和临时对象打交道。因此,用这种摞在一起的一堆物体的简单描述更贴近一些。

设备栈最好的例子就是一堆盘子的类比。盘子一个压在另一个的顶上就像设备栈。另外一个细节要记住的就是我们总是说“设备栈“而不是“驱动栈“。 是否记得在第三篇中,一个驱动中创建了多个设备。这就意味着在一个物理驱动中的所有设备的栈会全部实现。

过滤驱动

一个过滤驱动是指一个驱动挂接在一个设备栈的顶端,在请求到达目标设备之前进行过滤处理。

你可以设想,设备栈中除了最后一个的设备外的其他设备都是过滤器。但这不符合事实。设备栈中的设备除过滤器外,通常都依赖于某个特殊的设备体系。例如,你通常得到靠近设备栈顶层的高层驱动,多数情况下,这些高层的驱动与用户模式的请求进行通讯和交互。

这些设备栈的设备为下层设备中止请求,直到链中的最后一个设备处理这个请求。底层的驱动靠近设备栈底部,就像 “miniport drivers”一样,跟实际的硬件通讯。

最好的例子就是文件系统,顶层的驱动维护着文件和文件系统的概念。他们知道文件存放在磁盘中的位置,底层驱动不知道文件,只是简单的理解请求读取磁盘扇区。他们知道怎样排队这些请求和优化磁盘搜索。但是他们不知道磁盘上的实际内容和怎样解释这些数据。

每一个设备过滤驱动都会被放在设备栈的顶部,就是说,如果在你之后另一个过滤驱动加到设备栈,那么现在它将在你的顶部。你不能保证自己永远在设备栈顶端。

为了加到设备栈,我们需要使用下面的API.

RtlInitUnicodeString(&usDeviceToFilter, L"\\Device\\Example");

NtStatus = IoAttachDevice(pDeviceObject,  

                        &usDeviceToFilter,

                        &pExampleFilterDeviceContext->pNextDeviceInChain); 

实际上,这个API会为了贴上设备,它会打开一个设备句柄,然后再关闭这个句柄。当API企图关闭这个句柄时,我们的驱动就会被挂在了设备栈上。所以我们必须确保IRP_MJ_CLEANUP 和 IRP_MJ_CLOSE 能被正确的处理,并且在他们调用的时候不会出现问题。

还有少许其他的API,其中一个是IoAttachDeviceToStack。这个实际上是IoAttachDevice 在打开一个设备句柄后调用的函数。

IRP处理

IRP被创建后,发送到设备栈的第一个设备。然后这个设备处理IRP,并且完成它或者向下传递到设备栈的下一个设备。IRP的一般规则是,你收到IRP后,你拥有它。如果你把它向下传递到下一个设备,你将不再拥有它,也不能再访问它。最后一个处理IRP的设备必须完成它。

在这个例子中,为了演示,我们创建简单的IRP。这个演示非常简单,我们的驱动会收到发来的IRP. 由于我们控制着所有的终点,在我们的实现中这里可以忽略一些方面。这仅仅是一个非常简单的演示,由于实际上我们完全控制了所有的终点,因此这会让我们实现得更加灵活和确保不出问题。

当我们创建一个IRP时,需要遵循以下几个简单步骤。根据irp的处理,这里有些小的变化。我们将一步一步的仔细的检查。

第一步:创建IRP

很明显,第一步我们需要创建IRP。我们可以使用IoAllocateIrp。下面是一个使用它的例句。

MyIrp = IoAllocateIrp(pFileObject->DeviceObject->StackSize, FALSE);

还有其他的api和宏都可以实现为你创建一个IRP.他们可以更加快捷的创建irp并设置参数。需要注意的一点就是在使用这个函数创建IRP时,要确信这个函数可以在你使用的irql级别下被调用。另一个要检查的就是,谁来释放这个IRP. 是I/O管理器负责管理和释放这个irp还是你自己?

下面是一个可以为我们设置参数的例子。

MyIrp = IoBuildAsynchronousFsdRequest(IRP_MJ_INTERNAL_DEVICE_CONTROL,  

                                       pTopOfStackDevice,

                                       NULL,  

                                       0,  

                                       &StartOffset,

                                       &StatusBlock);

步骤2:设置参数

这一步是根据你需要什么样的功能来决定的。你需要设置FILE_OBJECT和IO_STACK_PARAMETER以及其他。在我们这个例子中,我们不提供FILE_OBJECT,我们设置最少的参数。为什么呢?原因就是这是个简单的例子,并且我们拥有所有的终点。既然我们控制了重点,实际上讲,我们能够用参数做任何我们想要做的事情。就像IOCTL一样,当发送IRP时,我们会知道你需要什么设置什么。实际上我们需要遵守这些规则,让其他驱动可以跟我们会话,但是这里仅仅是为了使这个例子看上去简单。

下面的代码是关于我们怎样设置IRP参数的。

PIO_STACK_LOCATION pMyIoStackLocation = IoGetNextIrpStackLocation(MyIrp);

pMyIoStackLocation->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;

pMyIoStackLocation->Parameters.DeviceIoControl.IoControlCode =

                                  IOCTL_CREATE_NEW_RESOURCE_CONTEXT;

/*

* METHOD_BUFFERED

*

*     Input Buffer = Irp->AssociatedIrp.SystemBuffer

*     Ouput Buffer = Irp->AssociatedIrp.SystemBuffer

*

*     Input Size    =   Parameters.DeviceIoControl.InputBufferLength

*     Output Size   =   Parameters.DeviceIoControl.OutputBufferLength

*

*     Since we are now doing the same job as the I/O Manager,

*     to follow the rules our IOCTL specified METHOD_BUFFERED

*/   

pMyIoStackLocation->Parameters.DeviceIoControl.InputBufferLength   =                                           sizeof(FILE_OBJECT);

pMyIoStackLocation->Parameters.DeviceIoControl.OutputBufferLength = 0;

                  

/*

* This is not really how you use IOCTL's but   

* this is simply an example using

* an existing implementation.   

* We will simply set our File Object as the SystemBuffer.

* Then the IOCTL handler will  

* know it's a pFileObject and implement the code that we

* had here previously.

*/

                  

MyIrp->AssociatedIrp.SystemBuffer = pFileObject;

MyIrp->MdlAddress                  = NULL;

注意,我们设置SystemBuffer来指向我们的文件对象。这样做是不严谨的。在这里,我们应该分配一个缓冲,然后把数据拷贝进来。那样我们能比较安全的让I/O管理器来释放内存或者当我们注销IRP的时候,我们来释放内存。

第三步:向下发送IRP

你需要向下给驱动发送irp. 你仅仅在IoCallDriver里指定DEVICE_OBJECT和IRP就可以了。

无论是你有怎样的DEVICE_OBJECT都可以用。然而,如果你想要从设备栈顶开始执行,最好使用像IoGetRelatedDeviceObject这样的API来找到顶层的设备对象。在我们的例子中,我有一个这样的调用来得到顶层设备。另一个是直接使用我们已经有的设备对象。如果你看调试输出,你就会注意有一个,我们没有经过过滤驱动。这是因为IoCallDriver是非常简单的,只是获得设备对象并且找出合适的函数进行调用。

NtStatus = IoCallDriver(pFileObject->DeviceObject, MyIrp);

第四步:处理和清除IRP

在我们向下发送irp之前有一件事情需要做,那就是创建一个“Completion Routine“。这个例程当IRP完成时,会得到通知。在这个情况下,我可以做一些事情。我们可以允许irp继续,以至于我们可以处理它的参数或者我们注销它。我们也可以让I/O管理器来释放它。这需要依赖我们创建irp的方式。为了回答“谁来释放它“的问题,你需要阅读关于分配IRP的API的ddk文档。错误地实现方法会造成灾难!

这是一个简单的例子,我们自己直接释放它。

IoSetCompletionRoutine(MyIrp, Example_SampleCompletionRoutine,  

                                            NULL, TRUE, TRUE, TRUE);

...

NTSTATUS Example_SampleCompletionRoutine(PDEVICE_OBJECT DeviceObject,  

                                PIRP Irp, PVOID   Context)

{

     DbgPrint("Example_SampleCompletionRoutine \n");

     IoFreeIrp(Irp);

     return STATUS_MORE_PROCESSING_REQUIRED;

}

或许你注意到,有时你看代码检查“STATUS_PENDING”和等待事件发生。在例子中,我们拥有所有的端点,不会发生这个事情。这也就是为什么讲一些细节被忽略了。在下篇文章,我们将详述这些概念。重要的是正好一次消化一部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值