Windows的设备驱动框架
Windows内核管理层的部件之一是I/O管理模块,有时候也称为I/O子系统。I/O管理模块所管理的对象与活动纵向贯穿管理层、核心层乃至HAL层,所以称之为子系统其实也有道理。I/O管理的主体就是我们所说的设备驱动。很自然地,如果我们沿着纵向考察某项设备的驱动,则一般而言也会分成若干层次。操作系统的一个基本原理就是分层虚拟,即使一种设备的驱动程序全部都在同一个源程序文件中,只要不是特别简单的设备驱动,其设计必然会自觉或不自觉地体现分层虚拟的原理。所以,一项设备的驱动软件常常表现为若干驱动程序的“堆叠(Stack)”。这个堆叠的顶层在管理层中,底层则在HAL层中;愈往上,离具体设备的硬件愈远,就愈抽象,与其他设备的共性就愈多;愈往下,离具体设备的硬件愈近,就愈具体、愈体现出具体设备的个性。不过上层模块与下层模块之间不一定是一对一的关系,而可以是一对多的关系。以文件系统为例,这个堆叠的顶层(大致上)是文件系统,下层是文件系统所在的载体。但是这个载体可以是磁盘,也可以是光盘,还可以是“U盘”。如果是磁盘,则可以是连接在IDE接口上的固定硬盘,也可以是SCSI磁盘。所以,设备驱动逻辑意义上的系统结构其实是一种(倒置的)树状结构,所谓一个堆叠实际上相当于从根节点通往某个特定叶节点的一条路径。
另一方面,设备驱动也是内核中最需要加以动态扩充的部分。这是因为在编译生成系统内核时常常无法确切地知道使用中究竟需要哪一些设备。显然,最好的办法是将各种设备驱动的堆叠做成可以动态安装的程序模块,就像在用户空间可以动态加载DLL一样。Windows正是这么做的,文件扩展名为.sys的模块就是此类可动态装载的内核模块。注意“模块”这个词在不同的语境下有不同的意义。当我们谈论内核管理层中的I/O管理模块时,是指逻辑上相对自成一体的一个部分,也许称之为“板块”更贴切一些。而在谈论.sys模块的时候,则是说一块可动态装载的可执行映像。这种可动态装载的可执行映像可大可小,事实上win32k.sys就是这样一个模块。当然,其他模块就没有这么大了。在实践中,一般都根据具体的需要把一种特定设备的驱动程序堆叠实现成一个.sys模块;或者把一个堆叠中的一层或几层实现成一个.sys模块,实际使用时则由一个或数个.sys模块提供该种设备的驱动程序堆叠。
所以,设备驱动有两个问题,一个是分层的问题,一个是动态装载的问题。
但是程序的分层有概念和形式之分,概念上的分层只是程序员编程的方法问题,当然里面也体现着程序员的技艺和水平;而形式上的分层,则是系统为设备驱动程序的开发定下的模型(Model)和框架(Framework),一方面要求开发者按特定的、体现着程序分层的模型开发设备驱动程序,另一方面则又为符合这种模型的设备驱动程序提供基础设施的支持。打个比方,就好像一个机架,一方面它要求凡是要插入这个机架的模块在形状、尺寸等方面都符合某种规定;而另一方面,只要你符合这样的规定,则机架为你提供电源、通风、模块间通信等基础设施。
对于Windows的设备驱动模块,这个框架定义了:
设备驱动模块以何种形式提供有关的操作(体现为一个含有若干函数指针的数据结构),以及这些操作的范围(打开、关闭、读、写等)。
怎样启动由设备驱动模块提供的特定操作(将“操作码”等参数组装在一个标准格式的数据结构“I/O请求包”即IRP中,以此数据结构作为形式上的调用参数,通过设备驱动框架为此提供的手段IoCallDriver()调用设备驱动模块提供的相应函数指针)。
如果需要,一个设备驱动模块如何启动其(同一堆叠中的)下层模块的操作(仍通过IoCallDriver(),并把“I/O请求包”传到下一层)。
可以从设备驱动程序中调用内核的哪一些函数,访问内核的哪一些变量(Windows的DDK对此做出了规定)。
之所以对于设备驱动模块的界面可以定义一种固定的模型,是因为设备驱动模块所提供的服务有个固定的范围,属于一个固定的集合,不外乎打开、关闭、读、写等操作。相比之下,用户空间的DLL就不像设备驱动模块那么规范,因为一般而言DLL所提供的服务五花八门,并不限于某个固定的集合,因而无法统一到一组固定的函数集合中。