首页
社区
课程
招聘
[翻译]Rolling Your Own
发表于: 2008-2-15 13:49 16697

[翻译]Rolling Your Own

2008-2-15 13:49
16697

在这篇文章中我将描述如何自己构造IRP还有I/O管理器提供一些分配和管理IRPs的例程。

  经常听到NT驱动程序开发者问的问题是如何在他们自己所驱动中执行一个I/O操作。这个问题可以总

结为以下两点:当仅可用到一个文件对象但是ZwXXX例程需要一个文件句柄时如何处理I/O;为什么

ZwCreateFile返回的句柄不能用在他们的驱动中。实际的问题是如何在他们的驱动中处理I/O操作,典型

的在多线程上下文中。

  当然,这有很多种可行的方法解决这个问题,但一个重要的方法是在你的驱动中自己构造IRP。这个

IRP包含一个I/O操作在任意上下文中完成所需要的任何信息。IRPs依赖于在任意上下文中都有效的

FILE_OBJECTs和DEVICE_OBJECTs,而不是只在特定进程上下文中有效的Fille Handle。这篇文章描述和示

范了很多方法,每一种方法都是基于DDK的,你不必使用没有文档化的Kernels APIs。

  有很多原因使你需要构建一个IRPs。可能的原因有与文件系统交互执行一个文件的I/O操作,或者是

文件系统支持的内核特征。也许你的驱动要增加一个存在设备的功能,如处理NT容错设备(FTDISK).也许

你有两个协作的驱动需要要它们之间通信。或者也许是你在Windows NT上执行一个物理文件系统需要和

Media Driver或Transport Driver交互。不管什么原因,在Windows NT下完成这些任务最好的方法是自己

构建IPRs。

Allocation
  可以用两种方式分配IRPs。最简单的方法是调用IoAllocateIrp(...)。I/O管理器将用适当的I/O栈单

元(你在调用中指定的)分配一个IPR,这是最简单的并且是大多数分配I/O请求的方法。警告注意:如何你

调用IoAllocateIrp(...)不用调用IoInitializeIrp(...)。在这点上DDK文档导致许多无辜的受害者误入

歧途。
 
  你的驱动可能想要创建自己的IRPs。一个方法是用ExAllocatePool(...)在非分页内存中分配内存。

你通过IoInitializeIrp(...)初始化IRP的形式。你的驱动可以通过IoSizeOfIrp(...)计算出分配的IRP的

大小。只要你在非分页内存中分配了一个IRP,你就能调用IoInitializeIrp设置IRP中的域。请看下面的

例子:
        PIRP MyAllocateIrp(CCHAR NumberOfStackLocations) {
        USHORT IrpSize = IoSizeOfIrp(NumberOfStackLocations);
        Irp = ExAllocatePool(NonPagedPool, IrpSize);
        if (!Irp) {
                return 0; // failureContent provided by OSR Open Systems Resources, Inc.
                }
        IoInitializeIrp(Irp, IrpSize, NumberOfStackLocations);
        return Irp;
        }

  一般地,驱动依赖于I/O管理器分配和管理IRPs。然而这些实例使你的驱动能更有效的分配和管理自

己的IRPs。

  当NT启动的时候,I/O管理器建立两个旁视列表:一个对应于一个I/O栈单元的IPRs,另一个对应于四

个I/O栈单元的IPRs。当你分配大栈单元的IRPs或旁视列表为空时,I/O管理器在非分页内存中分配新的

IRPs。如果你知道你将使用的IRPs多于四个栈单元,你能通过在你自己的自由列表中保持IRPs提高一些效

率。换句话说,你的驱动在第一次运行时能创建一个IRPs池并且保存它在一个私有列表中。当你的驱动需

要IRP时能在这个列表中分配,当I/O操作完成时能返回到这个列表中,从而消除了间接的分配和释放内存

池。我们已经在OSR网站上的"roll.c"展示了这方面的例子代码。

  你怎么确信I/O管理器将IRP返回到你的驱动让它能返回到你的旁视列表中?简单的方法是使用I/O完

成例程。下面展示了I/O完成例程如何实际的工作即使你驱动创建的IRP没有栈单元也能注册一个完成例程



  这是为什么?如果你考虑一下完成例程如何使用,你就会认识到底层驱动被调用不需要完成例程(毕

竟,这将是驱动完成I/O请求).所以,底层的I/O栈单元可以用来存储下层驱动的完成例程。继续这个处理

直到驱动的顶层将给我们一个能把握的额外的完成例程,这个例程对原始的IRP创建者是有效的。

  对于IRP的创建者,设置I/O完成例程等同于中间层驱动的完成例程(简单的调用

IoSetCompletionRoution(...)).我们将在这篇文章在后面描述如何构建完成例程。

Building
  只要你分配了自己的IRP,不管是使用IoAllocateIrp还是从自己的旁视列表中,你必须初始化I/O请

求指示你需要低层驱动的什么服务。当你调用下层驱动执行的时候你的驱动必须做同样的工作。简单的设

置下层驱动的参数块。你的驱动要做的额外工作是初始化自己分配的IPR的其他域。

MdlAddress       
this field will point to the MDL containing the data (if any)

Flags       
any appropriate flags (c.f., ntddk.h for the IRP_ flags)

AssociatedIrp.SystemBuffer       
any data buffer for this I/O request

RequestorMode       
UserMode or KernelMode. Typically, this is UserMode if the arguments being passed should be

validated, KernelMode otherwise.

UserBuffer       
any data buffer for this I/O request

Tail.Overlay.Thread        the PETHREAD for the original requestor

当然,其中的某些域对于你的I/O操作可是不是必须的(如:MdlAddress,AoosciatedIrp.SystemBuffer,

和UserBuffer参数,仅仅是它们中的一个可能被你的驱动使用).当然,你将用到的域用因I/O操作不同而

变化。

  Tail.Overlay.Thread数据结构仅仅用在明确的设备中,如可移动设备,所以系统知道如何操作"错误

的弹出"如当媒体设备没有在驱动中加载的时候abort/retry/cancel对话框的出现。

  IRP有很多不同的标志,告诉下层驱动如何解释这个I/O请求的内容。

        -IRP_NOCA CHE –  data for this I/O request should be read from the actual backing        

media and not from cache.
        -IRP_PAGING_IO – the I/O operation in question is performing paging I/O. This bit        

is used by the Memory Manager.
        -IRP_MOUNT_COMPLETION   –   the   I/O   operation   in   question   is   performing

          a   mount
        operation.
        -IRP_SYNCHRONOUS_API   –   the   API   in   question   expects   synchronous          

behavior.   While synchronous behavior is advised when this bit is set, it is not        

required.
        -IRP_ASSOCIATED_IRP – the IRP in question is associated wit h some larger I/O        

operation.
        -IRP_BUFFERED_IO  – the AssociatedIrp.SystemBuffer field is valid
        -IRP_DEALLOCATE_BUFFER  –  the  system  buffer  was  allocated  from  pool  and         

should  be deallocated by the I/O Manager.
        -IRP_INPUT_OPERATION  –  the  I/O  operation  is  fo r  input.  This  is  used  by  

        the  Memory
        Manager to indicate a page in operation.
        -IRP_SYNCHRONOUS_PAGING_IO  –  the paging operation should complete synchronously.        

This bit is used by the Memory Manager.
        -IRP_CREATE_OPERATION – the IRP represents a file system create operation.
        -IRP_READ_OPERATION – the IRP represents a read operation.
        -IRP_WRITE_OPERATION – the IRP represents a write operation.
        -IRP_CLOSE_OPERATION – the IRP represents a close operation.
        -IRP_DEFER_IO_COMPLETION  –  the  IRP  should  be  process ed  asynchronously.           

While asynchronous behavior is advised when this bit is se, it is not required.

  小心的使用这些标志设置IPR,因为这些标志将对下层驱动处理IRP请求产生一个根本的影响。
  
  如上所述,你的驱动还应该设置下一个I/O栈单元。它仅仅发生的处理第一个栈单元的时候。第一个

栈单元的指针通过IoGetNextIrpStackLocation(...)得到。这个调用返回下一个将要调用的驱动的栈单元

你的驱动要负责初始化这个栈单元的下面这个域:

        MajorFunction       
        the function code for the I/O to be performed

        MinorFunction       
        a minor function code for the I/O. This field should be zero if there is no minor        

function code.

        Flags       
        any  flags  needed  to  modify  the  behavior  of  the  I/O  operation
        (c.f., ntddk.h for the SL_* flags.)

        DeviceObject       
        the device to which your driver will pass the IRP.

  标志域用来修改低层驱动的行为当处理I/O请求的时候。可能的标志如下面所示:表3未画

  最后,你的驱动必须初始化I/O操作指定的参数。如Read Write请求,需要初始化偏移,长度等。

Completion

  有时候,当你构建自己的IRPs时你需要提供一个完成例程。完成例程的特定规则是不明确的。但是如

果你错误的使用它将导致系统崩溃。

  提供完成例程最重要的原因是你可以重新使用I/O操作。在DDK文档中提到的不太重要的原因是你要释

放它。这排除了I/O管理器需要执行I/O完成例程,你通过在你的完成例程中返回

STATUS_MORE_PROCESSING_REQUIRED告诉I/O管理器停止继续向上返回。

  什么时候你不需要使用一个完成例程?当你不需要考虑I/O操作的完成状态,或者你不能在你的完成

例程中释放IRP。后面的情况没有在DDK文档中指出但是非常重要。典型的,I/O管理器为一个线程创建一

个I/O操作,这个IRP存储在线程的链表中(ThreadListEntry域).当线程退出的时候允许NT清除IRP如果你

的驱动有一个完成例程并返回STATUS_MORE_PROCESSING_REQUIRED,这个IRP可能仍然存放于线程的I/O列

表中,这可能产生严重的问题。已经证明I/O管理器的某些函数将创建的IRP添加到线程链表中,而其它的

函数不添加。那么,当构建你的完成例程时,这将是一个好机会检查你的IRP不在线程列表中!

  只要你的完成例程返回STATUS_MORE_PROCESSING_REQUIRED,I/O管理器将停下来等待更多的处理。那

么,你就可以做你想做的任何事情,此时你在I/O操作开始时的线程上下文中,文件句柄和用户地址都是

不必须的。第二点,你不用假设你的完成例程在PASSIVE_LEVEL被调用,它可能在DISPATCH_LEVEL级被调

用,原因是你完成例程的驱动可能是一个DPC例程。将这些记在脑子里,当你设计你自己的完成例程时,

如果你需要在IRP完成后有更多的处理你可能需要设置一个工作例程确保它安全。

Reuse

  我们描述了你的驱动如何在旁视列表中保持IRPs。当你不在需要IRP时你的驱动可以在完成例程中设

置将它返回到你的旁视列表中。然而,你可能需要做些额外的工作在IRP准备重用的时候。

  例如,如果你调用文件系统驱动并指定了一个用户缓冲区(通过设置Irp->Userbuffer),文件系统驱

动可以创建一个MDL描述这个缓冲区。如果是那样,因为你在负责清除IRP,所以你有责任

unmapping,unlocking并且释放与IRP关联的MDL。可以通过MmGetSystemAddressForMdl(...)得到MDL的地

址,通过MmUnmapLockedPages(...)解除映射并通过MmUnlockPages(...)解除锁定页。

Short-cuts
  现在我们已经描述了如何构建自己的IRPs,我们将提到I/O管理器提供的三个短小的调用来使你容易

的完成这些。这些I/O管理器提供的函数不如你自己构建IPRs的通用性好,但它们能快速的构建一个IPR并

且你能在你的驱动中完成初始化工作,这些例程是:
        - IoBuildAsynchronousFsdRequest(...)
        - IoBuildSynchronousFsdRequest(...)
        - IoBuildDeviceIoControlRequest(...)

这三个调用都没有初始化FileObject参数,因此如果你调用文件系统驱动,你的驱动需要设置这个域。既

然你知道如何在你的驱动中构建IRP,你能扩充I/O管理器创建的IPRs来适用于你自己使用。

  前面提到的,在I/O管理器的帮助函数中使用完成例程不是那么容易。在

IoBuildSynchronousFsdRequset(..)和IoBuildDeyiceIoControlRequest(...)中你不能在你的完成例程中

释放IRPs,但是在IoBuildAsynchronousFsdRequest(...)中你可以这样做。这是因为前面的两个例程把

IRP增加到线程的IRP链表中。因为I/O管理器不会在线程中移除IRP,所以唯一的选择是允许完成请求。

  用上面三个函数中的任何一个都可以简单的创建一个IPPs,但是对于你的驱动的请求所需要的帮助例

程仍有限制。IoBuildDeviceIoControlRequest仅用来构建IRP_MJ_DEVICE_CONTROL请求,

IoBuildSynchronousFsdRequset(...)仅用来支持IRP_MJ_INTERNAL_DEVICE_CONTROL,

IoBuildAsynchronousFsdRequest(...)仅对IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_FLUSH_BUFFERS和

IRP_MJ_SHUTDOWN有效。


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

收藏
免费 7
支持
分享
最新回复 (3)
雪    币: 242
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
看过这篇文章还有源代码的例子,看来作者还是有很多保留的。很重要的东西没有给出来。
2008-3-21 15:52
0
雪    币: 101
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
把“Rolling Your Own”也翻译一下
2009-6-30 19:20
0
雪    币: 21
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
挺长呀,呵呵...学习了
2009-7-2 13:05
0
游客
登录 | 注册 方可回帖
返回
//