阅读:3709回复:16
请问:设备堆栈
对于一个物理设备而言,总线驱动将会枚举每个设备并为每个设备创建一个PDO,再在其上堆叠其它的设备对象,但是假如是一个虚拟设备,比如操作一个内存块的驱动程序,他的设备堆栈中是否有PDO,如果有,这个PDO是由谁创建的?如果没有,是不是说一个堆栈可以只由FDO及FiDO组成?
请赐教,谢谢! |
|
最新喜欢:![]() |
沙发#
发布于:2003-12-19 20:49
虚拟的设备可以有,也可以没有PDO。
如果你需要PDO(这样做的好处是有很多事件可以利用系统的默认处理,而且有很多Kernel API需要PDO),那么你在Driver处理IRP_MJ_PNP->IRP_MN_QUERY_DEVICE_RELATIONS的代码里加入你自己的PDO; 如果不需要,你就只要创建FDO以便和应用通讯就行了。 |
|
|
板凳#
发布于:2003-12-21 18:18
在WDM即插即用驱动中,设备对象在AddDevice过程中创建,这个过程提供了一个PDO,对于一个虚拟设备而言,好像也可以使用即插即用,那么这个PDO是怎么回事?在《WDM驱动程序开发指南》(应该没记错)中的WDM1例子就是在AddDevice中将自己Attach到PDO上的,它好像就是操作一个内存块的虚拟设备驱动程序。
PS:我是新手,谁能告诉我怎么给分(分不多,见谅!) |
|
地板#
发布于:2003-12-21 22:33
在系统调用你的AddDevice时,实际上是枚举程序给你创建了一个PDO(比如Unknown Bus或其它非即插即用总线就是这么干的),这个PDO实际上代表你的这个驱动程序,随后系统会对这个PDO发出PNP事件,你也可以主动地向PNP管理器报告PNP事件(比如用IoInvalidateDeviceRelations或IoReportXXX等等),PNP管理器就会重新发出IRP_MN_QUERY_DEVICE_RELATIONS,这时你就有机会报告你的虚拟设备(IRP_MN_QUERY_DEVICE_RELATIONS要求返回的是PDO,换句话说,只要是在这个时候返回给系统的任何DeviceObject都会被系统认为是PDO,再进一步,就是说除了在IO Stack中的位置不同以外,FDO,PDO等等并没有什么其它不同,当然,如果你想用一个FDO做参数去调用PDO相关的Kernel API,你还是会失败的)。
接下来,系统又会对每个你刚刚在IRP_MN_QUERY_DEVICE_RELATIONS中返回的每个新的DeviceObject(注意,是新的)调整用一次AddDevice,里面的那个PDO参数就是你刚刚返回给系统的那个,呵呵。 至于第一次AddDevice,只要系统加载你的驱动,那这个方法就会被调用,这样,一些非PNP的设备也可以写得“好象”支持PNP一样。 >> 在WDM即插即用驱动中,设备对象在AddDevice过程中创建, >> 这个过程提供了一个PDO,对于一个虚拟设备而言,好像也 >> 可以使用即插即用,那么这个PDO是怎么回事?在《WDM驱动 >> 程序开发指南》(应该没记错)中的WDM1例子就是在AddDevice >> 中将自己Attach到PDO上的,它好像就是操作一个内存块的虚拟 >> 设备驱动程序。 一种情况是NT式的驱动,没有AddDevice这一说,所以你要在 DriverEntry里直接CreateDeviceObject,那就没有PDO或FDO之 分,所有的事情都需要你自己去控制(废话,那时候还没有PNP 呢,当然也没有AddDevice,呵呵),如果你不提供AddDevice例 程就和这个效果是一样的,也就是你说的---没有PDO。 第二种情况就是最常见的那种情况(呵呵,我估计也就是你想问 的那种情况):你的虚拟设备驱动程序总要挂在某个枚举程序上 启动(一般情况下是Unknown总线枚举程序),这个枚举程序不管 3721会先为你创建一个PDO,然后拿着这个PDO来调用你的 AddDevice(根据我的经验,它好象不只调一次,而是不断地调, 起到AddDevice失败为止,但我在DDK文档里没有找到对这个的说 明,不知道哪位知道哪里有对这个的解释?),看起来就好象支 持PNP一样,那么不言而喻---系统(准确地说是“枚举程序”) 为你创建PDO。 第三种情况就是你自己的驱动扮演枚举程序的角色,那就是刚开始 我说的:利用IRP_MN_QUERY_DEVICE_RELATIONS返回你自己的PDO, 这种情况下---PDO是你创建的。 第四种... ...我还没想到,呵呵。:-) >> PS:我是新手,谁能告诉我怎么给分(分不多,见谅!) 呵呵,不用给分,你可以留着问更有意义的问题(就象当年在 “V星系”时一样,我不知道分是干什么用的,呵呵 :-) PS:我是不是把简单问题给复杂化了?抱谦。:-) |
|
|
论坛版主
![]() |
地下室#
发布于:2003-12-22 17:25
这位兄弟,我问一个问题,你说的枚举程序(就是PNP MANAGER吧)创建PDO,那么这个动作应该是在ADDDEIVCE之前吧,那么在这个函数调用之前系统如何知道有一个虚拟驱动需要PNP MANAGER为其创建PDO?是否是KERNEL在调用需要加载的驱动的时候,遇到一个ADDDEVICE先判断它的PDO是否存在,如果不存在先建立一个给他用?
要不就是整个驱动的装载都是PNP MANAGER在做?他在装载前现看这个驱动对应的PDO是否存在,不再就创建一个 |
|
5楼#
发布于:2003-12-22 19:50
>> 这位兄弟,我问一个问题,你说的枚举程序(就是PNP MANAGER
>> 吧) 不一定(个人意见,PnP Manager并不能算一个枚举程序,因为真 正的“设备”枚举过程并不是它做的),还包括其它的程序,比 如我提到的Unknown Bus,当然更典型的还有其它的“非PnP总线” 驱动程序(比如ISA)。 >> 创建PDO,那么这个动作应该是在ADDDEIVCE之前吧 当然,要不AddDevice里的PDO参数从哪儿来呢? >> 那么在这个函数调用之前系统如何知道有一个虚拟驱动需要PNP >> MANAGER为其创建PDO? 一般情况下PnP Manager并不创建PDO,但是有例外情况,比如非 PnP总线驱动通过程序使用诸如IoReportDetectedDevice之类的方 法向PnP Manager报告非PnP设备,但是没有给出PDO,这时PnP Manager会为这个设备创建一个PDO。 “一个虚拟驱动”所驱动的设备并非是普通的PnP设备,所以这类 设备的枚举也不会在一个“普通的总线驱动”里进行(我个人的习 惯是把虚拟设备挂在Unknown Bus上(偷懒的做法),当然你可以 自己写一个“虚拟总线”,其实“Unknown Bus”就是一个虚拟总 线的例子,另一个很好的例子就是DDK里给出的Toaster Bus)。 现在来说“系统为什么知道”,很简单,因为有人举报。:-) 在我的例子里,就是Unknown Bus Driver枚举挂在它下面的所有设 备,并使用IoReportDetectedDevice(我没反汇编看过,但我想更 可能它用的是IoInvalidateDeviceRelations)向PnP Manager报告 这个设备。 我现在越来越觉得Unknown Bus很可能就是PnP Manager本身(未经 证实,听过请马上忘掉:-) >> 是否是KERNEL在调用需要加载的驱动的时候,遇到一个 >> ADDDEVICE先判断它的PDO是否存在,如果不存在先建立一个给 >> 他用? 怎么说呢?是and不是。 加载驱动是Setup和PnP Manager要做的事,而且创建PDO跟AddDevice 似乎并没有很直接的关系(创建PDO是发现设备时的事,而AddDevice 是加载相应设备驱动程序之后的事),所以我觉得你这句话应该这 样说: PnP Manager得到一个发现新设备的报告时(无论是通过 IoReportDetectedDevice或是PNP_RELATIONSHIP),先判断这个设备 的驱动是否已经加载,如果是,就调用它的AddDevice,否则就加载 它,并调用它的AddDevice。 这里说“新设备”很重要,因为只要是新设备,那么它就一定尚未 创建PDO(或者已经由总线驱动创建了,但尚未建立devnode),所 以PnP Manager根本无需判断。 >> 要不就是整个驱动的装载都是PNP MANAGER在做?他在装载前现 >> 看这个驱动对应的PDO是否存在,不再就创建一个 同上,PnP Manager只根据需要加载驱动,一种情况是如果没有报 告相应的设备存在,它永远也不会加载这个驱动。另一种情况是 有报告,但是使用的是IoReportDetectedDevice并且没有PDO,那 它就建一个(还有一种情况是有报告也有PDO,呵呵... ...)。 以上纯属个人理解,如有不对之处还请高人不要给面子,指出来, 这样当个反面教材,我可以有进步,大家还可以加深理解,何乐 而不为呢?呵呵... ... |
|
|
6楼#
发布于:2003-12-23 19:30
to cool-net:
多谢这位热心大哥的详实指点,除了感激外,我实在找不出更好的词汇形容我对您的敬意,感动挥泪ing……………… 说实在话,我在这之前根本不知道Unknown Bus的存在,亦不知如何将一个虚拟设备驱动挂到该总线上(我说过我是新手吧;)),在平时学习的时候参阅的源代码都是一些通过SCM动态装载的KMD驱动,真正用到PnP的几乎没有,因此这方面的知识比较欠缺,看来还需努力呀! 当然,同时感谢其它兄弟的热心关注! 趁这个机会再问个问题: 在I/O Manager构造IRP时必须知道设备堆栈的总层数,以便于构造足够的IO_STACK_LOCATION(下面简称为IO堆栈)存放各层的参数,它是如何知道IO堆栈的个数的?特别的,当系统运行时可能随时有一些过滤驱动插入一层,I/O Manager会不会也为插入的一层多申请一个IO堆栈?如果不是的话,这时过滤驱动也会用掉一个IO堆栈,那么下层的岂非不够用? 再次感谢!!! [编辑 - 12/23/03 by doogle_v] |
|
论坛版主
![]() |
7楼#
发布于:2003-12-23 20:25
I/O管理器在构造IRP的时候可能会从设备树上得知一个IRP发向的目标驱动堆栈的驱动程序个数,那么堆栈中驱动个数就是需要的IRP堆栈个数的最小值(猜测,这个问题可能还要cool-net来回答:))。
I/O管理器维持了一个空闲IRP池,一个是有一个堆栈位置的IRP,另外是有三个堆栈的,如果使用1个堆栈位置,I/O管理器就从拥有1个堆栈的空闲IRP中分配;如果使用2和3,使用后者;如果使用的堆栈个数大于3,会从NONPAGABLE POOL中分配。 |
|
8楼#
发布于:2003-12-23 20:30
to cool-net: 每个设备在系统中都会有一个称做devnode数据结构表示,这里面保存了完整的Device Stack,系统要用到的所有信息它都可以从devnode里得到(BTW:devnode是PnP Manager在设备的枚举过程中建起来的,所有的devnode形成系统中的device tree,在系统启动的最后阶段,PnP Manager还要通过遍历device tree以找到那些已经发现,但还没有启动的设备并启动它)。 希望能解答你的疑惑,呵呵。:-) |
|
|
9楼#
发布于:2003-12-23 21:31
那就是说,假如系统运行时加入的过滤驱动用掉一个IO_STACK_LOCATION,最下层就少掉一个,最下层的驱动如何处理这种情况的?是不是最下层驱动的上层驱动(不知道怎么表述,次底层驱动?)就应该发现这种情况,并提早返回?但假如这样的话,请求岂不是无法完整地完成了?
[编辑 - 12/23/03 by doogle_v] |
|
论坛版主
![]() |
10楼#
发布于:2003-12-23 21:33
:)刚才不是说了嘛,上层发下来以前就会保证足够,即使发到最下层也不会用完
|
|
11楼#
发布于:2003-12-23 21:49
但是在电子工业出版社的《Windows防火墙与网络封包拦截技术》中的TDI例子中作者明确的进行Irp->CurrentLocation的检查,并且在注释中解释是由于会发生IO堆栈不够用的情况,但如你所言,这种情况应该是不可能发生的喽!
代码片断如下: // 这是一个不得以的做法,当Irp->CurrentLocation == 1时,表示 // 底层设备已经没有IRP堆栈可用,如果继续转发,将导致系统崩 // 溃,所以提早以错误的方式返回,这样造成某些请求被中断, // 例如网上邻居出现不能连接其它计算机的情况。…… if (Irp->CurrentLocation == 1) { NTSTATUS result = STATUS_INVALID_DEVICE_REQUEST; Irp->IoStatus.Status = result; Irp->IoStatus.Information = 0; IoCompeleteRequest(Irp, IO_NO_INCREMENT); return result; } [编辑 - 12/23/03 by doogle_v] |
|
12楼#
发布于:2003-12-23 22:00
我想了想,《Windows防火墙与网络封包拦截技术》一书中提供的这个TDI例子好像是一个KMD,而不是一个WDM驱动,我想这个可能是它检查Irp->CurrentLocation的原因所在。总结一下,也就是说,对于一个WDM驱动而言,不可能发生IO堆栈不够用的情况,而对于KMD驱动,在运行时加入的设备对象则可能造成IO堆栈的缺失,YES or NO?
对 wxl_50685330 的不敬之处,还望原谅! |
|
13楼#
发布于:2003-12-23 22:11
如果所有的程序代码都正确的话,我想这种情况的确不应该发生。
在你给的例子里,我不知道作者在转发什么东西,或者他是如何销耗IO Stack的,在一个驱动要向下层驱动转发IRP时,要么自行分配(比如使用IoAllocateIrp或IoBuildDeviceIoControlRequest之类的),要么使用IoSkipCurrentIrpStackLocation或 IoCopyCurrentIrpStackLocationToNext为下面的设备建立Stack Location,不知道他所说的“不得以”是什么意思。 |
|
|
14楼#
发布于:2003-12-23 22:33
我解释一下这个例子如何将创建的设备对象Attach到设备堆栈,其实方法和编写一般的过滤驱动基本相同:
1 调用IoGetDeviceObjectPointer得到系统TCP设备的句柄和相关信息; 2 调用IoCreateDevice创建自己的过滤设备; 3 调用IoAttachDeviceToDeviceStack将自己的设备挂接到系统的TCP设备之上; 4 把各个设备的相关信息保存到设备扩展中; 就这么简单,然后在分发例程PacketDispatch中接收所有的IRP,在调用了上帖中的代码片断后把IO堆栈复制到下层,设置完成例程后再调用IoCallDriver,就搞定了。其中转发的就是一些IRP_MJ_XXX,比较特殊的是其中一个IRP_MJ_INTERNAL_DEVICE_CONTROL/TDI_ACCEPT | TDI_LISTEN等等。至于IO堆栈会不会真的缺失,我没有实践过,不敢发表什么言论。 ps:过几天就要考试了,暂时放下驱动,好好复习去! |
|
论坛版主
![]() |
15楼#
发布于:2003-12-23 22:46
:)啥子敬不敬喔,大家在这儿来就是讨论感兴趣的东西嘛,说错了正常啊,(我说我自己哈,不是说你)都是对得还讨论啥子喃?说错了大家纠正三,要不然咋个共同进步嘛?对不兄弟们
你说的情况它自己要用IOCALLDRIVER那么到是有可能,因为I/O MANAGER管理器不可能预见到你会把IRP再发到什么地方,如果你把它发到另外一个设备堆栈中,就可能发生堆栈不够,这样你就需要自己判断了 |
|
16楼#
发布于:2004-02-27 20:14
>> 这位兄弟,我问一个问题,你说的枚举程序(就是PNP MANAGER PCI主总线就是你所说的UNKNOWEN BUS的一种,哈哈。。 以下引用ONEY 的WDM BOOK: “递归枚举 首先,PnP管理器有一个内建的驱动程序,它与一个实际不存在的根总线相对应。根总线概念性地把计算机与所有那些不能用电子方式声明自己存在的设备连接起来,这包括主硬件总线(如PCI)。根总线驱动程序从注册表中获取有关计算机的信息。而这些关于计算机本身的注册表信息是由Windows 2000系统安装程序初始化的。安装程序通过运行一个精心制作的硬件检测程序以及向用户提出一些适当的问题来获取这些信息。所以,根总线驱动程序有足够的信息为主总线创建PDO。 然后,主总线的功能驱动程序用电子方式枚举自己的硬件。 ” |
|