驱动理解备忘录

驱动设备层次结构

NT式以及WDM驱动都可以分层,即设备创建时,先创建底层PDO(物理设备对象,检测到设备时由PNP管理器创建),随后创建高层FDO(功能设备对象,IoCreateDevice创建)。每个PDO上必须有一个FDO来操作。可以通过IoAttachDeviceToDeviceStack将FDO附加到其他设备对象上。

垂直遍历设备对象: 底层设备对象寻找上一层设备对象,通过对象结构体内AttachedDevice成员从下往上寻找上一层设备对象。若想从上向下寻找,可以在自定义设备扩展中记录下层设备对象地址。

水平遍历设备对象: 同一驱动程序创建出的所有设备对象就是同一水平的设备对象。通过成员NextDevice可遍历所有同一个驱动创建出的所有设备对象。同一水平的设备对象的DriverObject指向同一驱动对象。

驱动设备IRP处理

驱动设备通过驱动中的MajorFunction数组设置对应的IRP主类型的派遣例程。在进入DriverEntry前,该数组所有元素被设置为函数_IopInvalidDeviceRequest(该例程会转发电源管理相关IRP,随后设置IRP错误码0xC0000010【指定的请求不是目标设备的有效操作】完成并返回该IRP的错误码)。IRP的常用处理方式有:

完成IRP: 通过IoCompleteRequest完成IRP,完成前需要设置IRP完成状态(pIrp->IoStatus.Status)以及IRP信息(pIrp->IoStatus.Information)。该函数第二个参数为调用线程恢复时的优先级别。通常设置为IO_NO_INCREMENT。

在同步操作时,用户层会创建一个事件传到派遣函数中并等待,IoCompleteRequest在完成该IRP时,会设置该事件。
异步操作时,若通过事件进行异步,IoCompleteRequest完成时会设置该事件,若通过完成例程进行异步操作时(例如ReadFileEx),IoCompleteRequest在完成IRP时会将该例程插入到用户APC队列中。当调用线程进入警惕模式后,该例程执行。

挂起IRP: 当异步读写设备时,设备例程首先要做一些不耗时的简单处理,随后需要将IRP通过IoMarkIrpPending函数挂起,最后返回STATUS_PENDING。

转发IRP: 分层驱动对IRP做完处理后,如果需要继续下发,则需要通过IoCallDriver函数,该函数会将当前设备堆栈下移。若不做任何操作,可以使用IoSkipCurrentIrpStackLocation上移堆栈继续重复使用当前堆栈。

若进行过操作,则需要使用内核宏IoCopyCurrentIrpStackLocationToNext将当前设备栈复制到下一层。

调用IoCallDriver后驱动会失去对IRP的控制,所以返回值当前过滤层需要返回最终层返回的结果,即该函数的返回值。

驱动设备IRP例程

取消例程: IRP取消例程通过IoSetCancelRoutine设置,第二个参数为例程地址,为NULL时删除原先例程。驱动中可以调用IoCancelIrp函数触发取消例程,该函数内部通过一个自旋锁进行同步,所以在取消例程中需要释放自旋锁。通过IoAcquireCancelSpinLock获取自旋锁,IoReleaseCancelSpinLock函数释放自旋锁。

应用层通过CancelIo取消IRP请求,函数内部会枚举所有未完成的IRP并依次调用IoCancelIrp,应用程序在关闭设备时也会调用CancelIo。

完成例程: IRP完成例程是通过在当前设备堆栈CompletionRoutine成员中注册实现的。当IRP完成后,一层层堆栈向上弹出,依次查询该成员是否为空,非空则会调用。使用完成例程时,只能使用IoCopyCurrentIrpStackLocationToNext复制设备栈而不能跳过。可通过IoSetCompletionRoutine设置完成例程。

一般完成例程返回STATUS_SUCCESS或STATUS_CONTINUE_COMPLETION(等价),此时对IRP任何设置都会引发崩溃。另一种情况是返回STATUS_MORE_PROCESSING_REQUIRED,会解除IRP完成状态并重新获得IRP控制权。之后在注册完成例程的函数中需要重新处理该IRP,完成或者继续下发。

设置完成例程是通过设置IRP、复制当前堆栈到下一层、最后转发实现的。如果没有设置完成例程,底层在返回Pending时,会自动将堆栈Control成员的SL_PENDING_RETURNED设置到当前堆栈。但如果设置完成例程后,系统则不会进行该操作。所以当底层返回Pending时,完成例程中需要对Irp->PendingReturned进行检测,如果为真,则需要调用IoMarkIrpPending设置当前堆栈。

驱动设备IO栈

驱动设备的IRP请求会从上层设备依次下发到底层设备。每一层设备都会有一个IO_STACK_LOCATION结构,该结构记录着对应设备的操作。获取本层设备IO栈可以通过IoGetCurrentIrpStackLocation函数获取。IO设备栈中可以获取IRP的类型、请求的参数等数据。

驱动设备读写

驱动设备创建后,可通过设备结构成员Flags设置读写方式。读写主要有三种方式:缓冲区方式、直接方式和其他方式:

缓冲区方式读写: DO_BUFFERED_IO。缓冲区模式下,系统会分配内核模式下的内存,大小为Write/Read写入读取的字节数,该内存地址可从IRP的AssociatedIrp.SystemBuffer获取。无论读写操作,都会发生用户模式与内核模式地址数据复制。通过当前IO栈成员Parameters.Read/Write.Length可获取读写字节数,操作完成后分配的内存会把数据复制回去并销毁。

直接方式读写: DO_DIRECT_IO。直接模式下,系统会将用户模式下的缓冲区锁定,随后通过内存描述符表(MDL,pIrp->MdlAddress)获取虚拟内存地址(MmGetMdlVirtualAddress(mdl))和大小(MmGetMdlByteCount(mdl)),最后通过MmGetSystemAddressForMdlSafe得到MDL在内核模式下的映射。

其他方式: 即不设置Flag的默认方式,该模式下内存地址为用户模式地址,即pIrp->UserBuffer。其他与DO_BUFFERED_IO相似。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值