IoGetDeviceObjectPointer函数从下层的设备对象名称来获得下层设备指针。该函数造成了对下层设备对象以及下层设备对象所对应的文件对象的引用。如果本层驱动在卸载之前对下层的设备对象的引用还没有消除,则下层驱动的卸载会被停止。因此必须要消除对下层设备对象的引用。
但是程序一般不会直接对下层设备对象的引用减少。原因在于当文件对象的引用减小后,设备对象的引用也会相应自动减小。因此只要减少对文件对象的引用就可以减少文件对象和设备对象两个对象的引用。而如果单独再减少对设备对象的引用,会导致两次减少对设备对象的引用。但是,如果在本驱动的卸载例程之前减少了对文件对象的引用,从而导致设备对象的引用降为0,则有可能导致下层驱动对象的过早删除。因此,如果想在卸载例程之外减小对文件对象的引用,又要在减少对文件对象的引用之前对设备对象增加一次额外的引用,然后在减少对文件对象的引用之后再减小对设备对象的引用。
事实上,IoGetDeviceObjectPointer返回的并不是下层设备对象的指针。而是该设备堆栈中顶层的设备对象的指针。当然,IRP会一层一层的转发下去,因此下层驱动会得到该IRP。
IoGetDeviceObjectPointer在内部干了如下的操作。ZwCreateFile ObReferenceObjectByHandle ZwClose IoGetRelatedDeviceObject。在IoGetDeviceObjectPointer后如果调用了IoCallDriver发送私有IRP,然后又调用ObDereferenceObject,这一系列的操作会引发如下的IRP被发送到目标设备对象。IRP_MJ_CREATE IRP_MJ_CLEANUP IRP_MJ_DEVICECONTROL IRP_MJ_CLOSE。当有文件句柄被创建时,IRP_MJ_CREATE被发送;当所有文件句柄关闭时,IRP_MJ_CLEANUP被发送;IRP_MJ_DEVICECONTROL对应私有IRP发送;IRP_MJ_CLOSE则是ObDereferenceObject后文件对象的引用为0后被发送。由此可看出,IRP_MJ_CLEANUP是文件对象句柄计数为0时被发送,而IRP_MJ_CLOSE是文件对象的引用计数为0时发送。
对象的生命周期
一个内核对象有两种方式被引用。一个是指针,一个是句柄。当创建了一个新的指向该对象的指针时,内核的对象管理器就将该对象的引用计数增加1。当创建了一个新的指向该对象的句柄时,对象管理器不仅将该对象的引用计数加1还要将该对象的句柄技术加1。当一个对象的引用计数为0后,该对象就自动被对象管理器删除。注意,句柄计数为0并不意味着引用计数为0。但当句柄计数为0后,就不能再通过文件名称来操作该对象了。ZwClose在减小句柄计数的同时也减小了引用计数。而诸如IoGetDeviceObjectPointer的函数会在函数内部增加对对象指针,因此增加了对对象的引用。
对象可分为暂时对象和永久对象。在对象被创建时可以指定。永久对象就是对象被创建时对象管理器就将引用计数加1了的对象。这种对象总可以通过文件名来访问。永久对象可以被转化为暂时对象