软件结构比硬件来的复杂很多。因为它包含了许多从表面上看不到的层次。比如总线驱动、功能驱动、过滤驱动等。套用社会学的话,这体现了功能应用中的分工和统筹。下面我们逐层来看它们。
总线驱动
总线驱动位于驱动栈的最低层,处理复杂的任务,必须资源分配,子设备管理。作为下层驱动,负责处理上层驱动发下来的请求。USB设备中的总线驱动主要有两类:控制器驱动、HUB驱动;另外还有一个端口驱动。
1)控制器驱动:Ushohci.sys、Usbuhci.sys、Usbehci.sys。
首先解释一下HCI,它是主机控制接口(Host ControlInterface)的缩写。前后一共有三种HCI协议出现:USB 1.1时代,有OHCI(开发HCI)协议和UHCI(通用HCI)协议;USB2.0时代,有EHCI(扩展HCI)协议。三个协议分别对应了上面的三个驱动程序。因为USB是向后兼容的,所以UsbEHC.sys中也包含了UsbOhci和UsbUhci的功能。请看下图。
图1 总线设备
上图中设备1、3是控制器设,从名称上就可以区别它们的不同:Universal Host Controller和Enhanced Host Controller。所以设备1的驱动程序是USBUhci.sys,设备3的驱动程序是USBEhci.sys。
这说明同一台主机特别是笔记本电脑中,1.1和2.0的控制器并存,像笔记本电脑中的键盘通过内置USB接口与系统连接,其数据吞吐量小,只需要1.1的控制器就能满足;而暴露在外供用户使用的接口则需要2.0。
2)Hub驱动:UsbHub.sys。Hub驱动是所有USB设备的父驱动。下图描绘了这一景况:
图2
上图中看到,Hub驱动的子设备要不是独立设备,如左侧图;要么是一个含有多个子设备的父设备,如右侧图。Hub驱动只为直系子设备创建唯一的物理设备对象。上图中,Hub驱动为设备1和设备2创建物理设备对象,但并不为设备2的子设备创建物理设备对象。
3)Port驱动:UsbPort.sys。这是个框架驱动,比较复杂,很少人会用到。而上面的Ushohci.sys、Usbuhci.sys、Usbehci.sys其实都是他的微端口驱动。对于这么偏门的框架,不提也罢。
系统类驱动
所以出现类驱动,体现了USB总线在应用上的繁荣景象。只有用得多了,才有被归类的可能。就像人类社会中有几百个国家,几千个民族,正是体现了人类这个团体的多样性与繁荣。只有在“多”的基础上,分类才是有必要的;少数人的小群体,再怎么独立特行,也都不足以被分类,甚至定义为“XX民族”。
USB设备包含很多的通用的功能类,比如:USB 集线器设备,USB HID设备,USB音频设备,USB MIDI设备,USB存储设备。为了让开发工作变得更加简单,Windows操作系统为他们提供了系统驱动程序。
大部分时候,系统类驱动就是功能驱动。下表是系统提供的USB类驱动。
Vista、
0x0B
2K、2K3、2K8
0x09
2K、2K3、2K8
0x03
2K、2K3、2K8
0x08
2K、2K3、2K8
打印设备
Usbprint.sys
XP、Vista、0x06
2K、2K3、2K8
媒体传输设备0x06
XP、Vista、0x01
2K、2K3、2K8
(CDC)
Usbser.sys
XP、Vista、 (UVC)
Usbvideo.sys
Vista
表USB类驱动
功能驱动
不是所有的USB设备都有类驱动,但功能驱动却是它们唯一的身份证。没有功能驱动,设备就不足以在系统中存在。它的作用是为设备创造一个独一无二的内核设备对象(DEVICE_OBJCET),并因此而在需要的时候,系统能够通过此内核设备对象找到它。
如果要让用户层也能够知道并使用USB设备,功能驱动更加不可少。它为设备在用户程序可见的名字空间中,为它起一个别名,这个别名可以是一个符号链接,也可以是一个由GUID定义的设备Interface。通过对这个别名进行操作,也就是对设备本身进行操作。
OK,上面的话说得太满了,有例外的!唯一的例外是以RAW 模式驱动的设备。这种设备直接由总线驱动来驱动其工作,不需要功能驱动。这种例子真的不多见,也许只有很很底层的控制器设备、Hub设备之类,才会这样做。对于RAW模式驱动的设备,当收到IRP_MN_QUERY_CAPABILITIES查询请求的时候,在返回的DEVICE_CAPABILITIES结构体中,必须将RawDeviceOK位设置为TRUE。
建议读者在需要的时候使用WinOBJ.exe工具查看系统空间中的设备与别名。
父驱动与混合设备
通过一定的设置,USB设备中的每个接口可以拥有不同的Class和Protocol定义,从而实现:一个设备,多个功能。这种设备被称为混合设备。混合设备的前提是拥有多个接口,对单接口设备谈“混合”是没有意义的。
满足了如下两个条件的多接口USB设备,被系统认为是混合设备:
1. 设备描述符中,设备类的值为0:(bDeviceClass, bDeviceSubClass, bDeviceProtocol) = (0,0, 0);
2. 只有唯一的配置描述符,即设备描述符中:(bNumConfigurations) = (1)
从WinXP SP2以后,还支持另外一种混合设备的判别方式,称作:Interface Association Descriptor(IAD)。其实IAD描述符是用来组织“接口组(Interfaces Group)”的。配置描述符中可以有多个IAD存在,如果将某两个接口将组成接口组,那么首先这两个接口必须是紧挨着的,其次,必须有一个IAD描述符位于这两个接口描述符的前面,也必须是紧挨着的,IAD描述符中的bFirstInterface用来描述接口组中的第一个接口ID,bInterfaceCount用来描述接口组中包含多少个接口。这样接口集合: [bFirstInterface, bFirstInterface+bInterfaceCount)为一个接口组。
当然,能够用上IAD的设备,一定是有多接口存在了,否则就是多此一举了。IAD描述符的识别和实现是通用父驱动完成的,所以有IAD支持的设备,都被认作混合设备。而识别设备是否有IAD支持,是通过设备描述符中的如下值判断的:
(bDeviceClass, bDeviceSubClass, bDeviceProtocol) = (0xEF, 0x02,0x01)
IAD普及率不是很广,一个原因就是XP sp2以后的操作系统版本才对它支持,这样如果把设备插入到Win 2000甚至 XP SP1上,都不能被正确识别。这样,厂商可能必须为不同的系统写两套驱动程序。
我们下面来说说当一个多接口混合设备插入电脑后,系统是如何识别它,并为它加载驱动的。这里大家要注意到一点,就是系统是如何把一个设备,通过多接口,识别为多个物理设备的。
一开始,系统PNP管理器安装常规,读取并分析USB设备描述符,然后为它分配如下设备ID:
USB/VID_vvvv&PID_pppp&REV_rrrr
16进制数,分别代表了厂商ID,idVendor/idProduct/ bcdDevice)
和兼容ID:
PNP管理器首先按照常规,根据上述的设备ID为设备寻找合适的驱动程序:根据设备ID到注册表的设备安装信息库(对应于Enum和Class两个键)中进行搜索,如果找到了安装记录,就根据记录中的信息加载驱动程序。
问题是如果找不到合法的记录怎么办?这时候就用的着兼容ID了,兼容ID“USB/COMPOSITE”是在系统中有注册记录的,并且就对应着通用父设备驱动(USBCCGP.sys)。于是,系统为混合设备加载通用父设备驱动。读者可到目录Windows/Inf下查看usb.inf文件,此文件中包含了通用父设备驱动的安装信息。
通用父设备驱动通过分析配置描述符,完成两个动作:首先为每个USB接口分配一个的设备ID和兼容ID;然后为每个USB接口创建一个物理设备对象(Physical Device Object)。设备ID形式如下:
USB/VID_vvvv&PID_pppp&REV_rrrr&MI_mm
16位数字表示的接口号USB/CLASS_cc
USB/CLASS_cc&SUBCLASS_ss&PROT_pp
16进制数。分别对应于接口描述符中的:<font style="font-size: 9pt;" color="black" face=""">bInterfaceClass/bInterfaceSubClass/ bInterfaceProtocol)
通用父设备驱动为每个接口创建了物理设备对象后,将接口的设备ID、兼容ID信息提交给PNP管理器,PNP管理器就有责任为这些“虚假的”物理设备安装驱动。仍然重复上面的过程:根据每个接口的设备ID和兼容ID在注册表的设备安装信息库中搜索,试图找到相关的安装记录,如果找到了,就为接口加载相应的驱动程序。如果找不到,系统就会弹出“发现新设备”的对话框,启动驱动安装向导。此时用户需为这些接口手动安装驱动。
所以,对于多接口的混合设备(compositedevice),其设备驱动的安装分为两个过程,先尝试安装混合驱动,如果找不到,就默认安装通用父设备驱动;接下来通用父设备驱动为每个接口分配设备ID,并要求系统为每个接口启动PNP过程。
最后,那么来说,在USB混合设备中,一定是X个接口对应于X个功能设备(有一个物理设备对象)吗?一般情况下是这样的,但也有例外,这就是接口组:在符合一定条件的情况下,多个接口中的某些接口可以被组合为一个接口组,一个接口组代表一个功能,父设备驱动只为之创建一个物理设备对象。
接口组的详情,大家看后面的章节内容。
过滤驱动
过滤驱动无处不在。在很多时候,它被称作Hook,是一种Hack手段;很多时候它又是必不可少的,这种技术甚至被操作系统自己使用。没有哪一个杀防毒软件不使用过滤驱动,我们在使用网上银行的时候,会用到一些安全控件,基本上都借助了过滤驱动的技术。
过滤驱动可以位于任何一层驱动的上面,或下面。过滤的对象也包括已经存在于系统中的其他的过滤驱动。当它位于某层驱动(D驱动)上面的时候,所有目标发往D驱动的请求,都首先被它截取;当它位于某层驱动下面的时候,所有和D驱动相关的从更底层驱动反馈回来的的“完成消息”都预先被过滤驱动截取。这正是它威力强大的原因所在。对于被过滤的驱动来说,过滤驱动简直就是它的先知了。
但使用过滤驱动,要很慎重。很容易把系统搞得很不稳定。读者在写过滤驱动的时候,要明白这样一件事:你想过滤谁,得先了解谁;好像你追求一个人,要先认识这个人。否则死机蓝屏都会与你不期而遇。
USB驱动栈、设备栈
请大家不要把这里的“栈”理解成“程序堆栈”的那个栈,朋友们要回到这个字最简单的本意来理解它,而不是想象成一个数据结构。就看成草垛柴堆一样。
驱动栈、设备栈本质上是并行概念。驱动之间的联系是通过设备对象进行的,所以驱动之间是间接联系的,驱动栈多少也就只是概念上的,他用来表示一个设备能够在系统中识别、运行,从上到下*需要哪些驱动程序支持。
设备栈则是由据可查的。系统中每个DevNode就表现一个设备栈。可以这样理解,多个设备栈,串联成了驱动栈。
使用WinDBG的!devnode命令,可以列举系统中的设备树。下图截取了CY001相关片段。
图3 CY001 DevNode片段
上图中红色框标出的三个DevNode,正好对应于三个内核驱动,建立了CY001的驱动栈。从上到下分别是控制器驱动(usbEHCI),集线器驱动(usbHUB),和功能驱动(CY001)。下图以CY001为例,更加清晰详细地描绘了驱动栈、设备栈的面貌。
图4 CY001的驱动栈和设备栈
上图是单接口CY001设备的驱动栈、设备栈全图。也适用于所有其他的单接口USB设备。如果是多接口混合设备,就要多一个通用父驱动,稍微复杂一点。
最上面是可能存在的过滤驱动,因为只是“可能存在”,所以都用虚框表示。其实过滤驱动可以存在于设备栈的任何一个位置,而不仅仅是最上层。笔者不可能尽皆画全,以上层过滤为例,能说明问题也就可以了。
过滤驱动生成的过滤设备对象,挂载到CY001驱动生成的功能设备对象上;这样所有发送给CY001功能设备对象的请求,过滤设备对象总是先得到。根集线器生成了CY001驱动的物理设备对象。三个设备连在一起,就是CY001驱动程序的设备栈。
但还没有完,根集线器驱动并不是最底层驱动,他必须得到控制器驱动的支持,于是加上可能存在的过滤驱动,由此形成中间的那条设备栈。
仍旧未结束,控制器驱动也不是直接和系统交互的,而是通过更底层的系统总线如PCI、ACPI进行的。这样,右侧的第三条设备栈也形成了。
由此也可以看出设备栈、驱动栈之间的关系了。驱动栈是形而上的,设备栈是形而下的。