系统的整体结构
WIN2000操作系统是计算机历史上最安全的操作系统,本节介绍WIN2000系统中驱动程序设计者最关心和最感兴趣的部分。
WIN2000的设计目标
有趣的是,原始的NT("New Technology")概念中不包含操作系统环境,直到1989年第一个NT操作系统出现了很长时间后。但是它还保持着原始的设计目标:
兼容性: 尽可能的支持现有的软件和硬件 。
稳定性和可靠性: 操作系统不会因为用户的误操作而损坏,一个用户程序应该不会使操作系统崩溃。
可移植性: 操作系统应当可在尽可能多的当前和未来的平台上运行。
可扩展性: 随着时间的流逝,市场的改变,操作系统应当可以只用添加少的代码就可以支持新的硬件和添加新的功能。
性能: 操作系统应当尽可能大的发挥硬件的效能。
当然,随着时间的流逝,操作系统的设计目标也是改变的,剩下的部分介绍怎样在设计中实现这些目标。
WIN2000的硬件特权级别
为实现稳定性和可靠性的设计目标,WIN2000的设计者使用用户模式服务结构,用户的程序在操作系统的用户模式服务程序中运行。
用户模式中,代码被严格约束在对系统没有损害的范围内。例如: 通过虚拟内存映像,一个程序不能访问其它程序的内存区(两个程序共同定义的公用内存区除外)。硬件I/O指令不能被执行。所有的CPU指令(如CPU中断)不能被执行(在特定的特权级下)。所有的这些被阻止的操作如果想运行,它们必须通过陷阱门来请求操作系统内核。
操作系统内核程序运行在内核模式下,它可以执行所有有效的CPU指令,包括I/O操作,可访问任何程序的内存区,当然是那些没有被翻出内存而被存到磁盘的内存区。
所有的现代的处理器都可以工作在特权级别模式和非特权级别模式下,用户模式工作在非特权级别模式下,内核模式工作在特权级别模式下。而不同的平台和CPU执行特权级别模式是不同的,为了达到可以在这两个模式下运行的目标,操作系统抽象了这两个模式,操作系统总是使用这些抽象的代码来在这两个操作模式下切换。例如: 在Intel平台上,内核模式使用Ring 0指令系统,而用户模式使用了Ring 3指令系统。
我们编写的驱动程序是运行在内核模式下,我们的不正确的代码将危及到WIN2000操作系统的安全,所以,我们必须格外小心我们代码的边界条件,以确保它们不会损坏整个操作系统。
可移植性
为了达到可移植性的目标,NT设计者选择了软件的分层结构。如图1.1所示:
图 1.1:WIN2000系统的分层结构
硬件抽象层(HAL)隔开了硬件平台和操作系统以及驱动程序,驱动程序依赖硬件抽象层的代码和宏去识别硬件的寄存器和总线,有时,驱动程序依赖I/O管理器去分享硬件资源,在以后的章节中将介绍利用硬件抽象层和其它的操作系统服务程序去开发设备无关的驱动程序。
可扩展性
WIN2000的内核也负责系统中所有线程的调度与分配,线程是程序中最小的可独立运行的部分,为保持每个线程的独立性,每个线程的设备上下文必须被保存下来,线程的上下文由CPU寄存器状态(包括一个独立的堆栈和程序计数器),ID(线程ID,也叫线程标识符),特权级别代码,线程地址和其它线程相关信息组成。
调度程序决定哪一个线程何时运行,在单处理机的环境中,一个时间只能有一个线程得到处理机的执行。
在多处理机的环境中,同一时刻不同的线程可能工作在不同的处理机上,是真正的并发执行。调度程序分配时间片给各个线程,高优先权的线程抢先占有时间片。
操作系统的主要任务是调度线程,还有一些必要的工作,如: 内存管理,进程管理,安全管理和I/O管理。这些部分叫做执行部件,它被作为软件模块(除了I/O管理器这个重要部分),随着时间的推移,微软会作一些必要的增加,删除,分离来作为改进或折衷。WIN2000就是后来才加了活动目录服务。
保持小的,干净的内核,外加执行模块是WIN2000的原则,这个原则使WIN2000经过近十年的修改,维护依然是一个流行的操作系统。
性能
分层的软件设计表现出良好的性能,NT的开发团队当然也注意到了:
到目前为止,所有的层别运行在同一硬件模式,即内核模式,因此,中间层的调用比处理器的调用不会多花多少时间,确实,HAL常常使用宏来完成内嵌程序的执行。
有助于集中精力去处理并发,分配尽可能多的线程给任务的不同部分,因为所有的执行部分都是多线程的,帮助程序尽可能少的阻塞,处理器忙等待和中断停机时间。
当用户和系统线程通过驱动程序来请求服务的时候,驱动程序代码阻塞执行是致命的错误,如果这些请求不能快速处理,将会导致设备忙或者很慢,这些请求将排队等候后续处理,幸运的是,I/O管理器使这个过程变的很容易。
执行部件
执行部件提供WIN2000操作系统最基本的服务(除了线程调度),它们的作用非常明显,解释如下:
图1.2 执行部件的组成
系统服务接口
它提供从用户模式到内核模式的入口。它允许内核模式代码安全的调用操作系统服务,依靠这个接口,从用户模式到内核模式可能只是简单的一个CPU指令或者是一个存储于恢复上下文开关。
对象管理器
WIN2000操作系统几乎将所有的服务看成对象提供给用户,例如: 用户模式程序为了使线程同步而向操作系统请求互斥对象,操作系统提供的互斥对象是一个操作系统对象,用户模式程序只能通过句柄来操作它。文件,进程,线程,事件,内存块,注册表的鉴值都是以操作系统对象的形式出现,所有的这些都是被对象管理器创建和销毁。
配置管理器
配置管理器纪录了计算器上所有硬件和软件的安装配置情况,这些信息被纪录在一个叫做注册表的数据库中,设备驱动程序通过注册表可得到它的工作环境信息。
进程管理器
在WIN2000中,进程是线程的工作环境,每个进程包含一个私有地址空间和一个安全标识。线程是最小的可执行实体,一个进程可以包含一个或者多个线程。
进程管理器依靠执行部件的其它部分才能工作,如: 对象管理器和虚拟内存管理器。
虚拟内存管理器
在WIN2000中,程序的地址空间是平坦的,有4GB大小。只有低2GB可以被用户模式的程序访问,也就是说,用户模式的程序占用低2GB空间,如果程序需要动态连接库(DLL),动态连接库也必须在2GB空间内。
高2GB空间被内核模式程序占用,当然,驱动程序也被映像到了这里。
虚拟内存管理器(VMM)管理整个系统的内存,对于一般的用户模式程序,如果访问的虚拟内存地址不在物理内存上,虚拟内存管理器将磁盘上相应的文件映像到内存中,这样,内存在磁盘和有限的RAM之间移动,好象程序可以访问比RAM大的多空间。
本地进程调用
本地进程调用是同一个计算器上不同进程间通讯的机制。驱动程序不需要这个工具。
I/O管理器
I/O管理器将用户模式的I/O操作转变成一系列统一的例程(例行的过程),I/O管理器的一个目标就是使所有的从用户模式到内核模式的访问设备无关,无论程序访问键盘,通讯口,磁盘文件都是一样的。
I/O管理器将用户模式的I/O操作转变成I/O请求包(IRP)的形式传给驱动程序,I/O请求包是I/O管理器将命令的综合。作为用户模式程序于驱动程序的中间层,I/O管理器于驱动程序结合部分的代码将是最重要的部分。
活动目录服务
活动目录服务是WIN2000的新的部分,它给系统资源提供无限的名字空间。以前,标识系统资源的名字被定义在操作系统的有限空间内(硬盘名,打印机名,用户名,文件名等),活动目录是一个统一,安全,标准的方法去标识系统资源,它是基于一个分等级的图,将资源根据实体分成元,树和森林。
基本操作系统的延伸
WIN2000定义了这幺多的服务,它们没有被直接的暴露给用户模式的程序。取而代之的是,WIN2000定义了很多应用程序接口(API),用它们来使用操作系统的服务。应用程序接口在不同的子系统中的形式是不同的,WIN2000包含了以下的子系统:
WIN32子系统: 它是WIN2000固有的应用程序接口模式,所有的其它的子系统都依赖于它去执行它们的工作,因为它是很重要的,所以我们将要在后面详细的讨论。
DOS虚拟器(VDM): 提供一个16位的MS-DOS环境给一些老的16位程序。尽管它承诺了兼容性,但是很多现有的16位程序还是不能完全的运行。这都是因为WIN2000的坚固性和安全性,操作系统将干涉企图直接访问设备和其它系统资源的操作。
窗口子系统(WOW): 提供一个16位的WIN3X环境给一些老的16位Windows程序。有趣的是,在仅有的一个窗口子系统进程中,每一个16位Windows程序都是一个单独线程,它们还互相阻止分享资源,这也正是WIN3X的环境。
POSIX子系统: 提供POSIX1003.1标准的UNIX程序环境,很不幸,这个子系统不能支持其它的标准UNIX程序,这样的话,很多UNIX程序必须在WIN2000下重写。
OS/2子系统: 提供一个16位的OS/2程序执行环境,至少不再需要OS/2的陈述管理器(PM),这个子系统只能工作在80X86平台上。
每个用户程序都必须和相应的子系统结合起来。例如: 程序的应用程序接口函数不能调用其它的子系统,或者使系统性能下降,这些子系统的设计目的是兼容性,而不是速度。
环境子系统(以上的五个都是WIN2000下的环境子系统)是隔离用户模式程序的一般手段,它们的运作是必须的。每一个用户请求都经过本地进程调用传递给这些子系统去加工处理,这些子系统或者直接执行这些请求,或者传递请求给执行部件。
WIN32子系统
这个子系统提供的应用程序接口有以下几个方面:
图形用户接口: 提供给用户可视的窗口,对话框,控件,字体等接口。
控制台I/O: 包括键盘,鼠标,显示器,以及其它子系统。
执行WIN32应用程序接口: 提供应用程序或者其它子系统与执行部件的接口。
因为WIN32子系统在操作系统中的特殊地位和它所提供的高性能,所以它的实现方式与其它的子系统完全不同。它被分成了好几个部分,其中一些工作在用户模式,一些工作在内核模式。通常,WIN32应用程序接口被分成3部分:
USER函数: 管理窗口,菜单,控件,对话框等。
GUI函数: 在物理设备(如: 显示器,打印机)上绘图。
KENEL函数: 管理非GUI资源,例如: 进程,线程,文件,同步服务,KENEL函数接近于执行部件的服务。
WIN NT4.0以后,USER函数和GUI函数被移到内核模式,因此,用户模式的请求用系统服务接口直接送到内核模式。USER函数和GUI函数被安置在WIN32K.SYS中,如图1.3所示:
相反的,KENEL函数依赖于一个标准的服务程序CSRSS.exe去反应用户程序请求。
其它子系统
除了环境子系统之外,还有一些重要的子系统,它们是用户模式的过程:
安全管理子系统: 使用一个变化的过程方法和动态库来管理本地的和远程的安全问题,部分活动目录工作也在这个逻辑子系统中。
服务控制管理器: 管理服务和驱动程序。
RPC服务: 它支持网络的分布式应用程序,通过远程的程序调用,一个计算器可将自己的一部分任务分配给其它网络终端。
图1.3 USER在GUI内核中
内核模式I/O构成
本章的目的是讨论内核模式I/O子系统的设计目的,既然很多种类的驱动器都要讨论,那幺,I/O管理器的分层驱动器也将讨论。
内核模式I/O管理器的设计目标
内核模式I/O管理器的总的设计目标包括:
1.可移植性: 平台之间。
2.可配置: 包括软件和硬件,如WIN2000,包括完全的支持即插即用总线和设备。
3.可抢先和中断: I/O代码应该不会被阻塞。
4.支持多处理器: 同样的I/O代码应可在单处理器下运行,也可在多处理器下运行。
5.基于对象: I/O代码提供的服务应该被封装成定义好的结构中。包括数据于操作。
6. 支持I/O请求包: I/O管理器的请求使用一种独特的格式。
7.支持异步I/O: I/O管理器的请求支持重新请求的模式,完成后,应有一个机制去退出和通知调用者已完成。
除了这些众所皆知的目标外,它们将重点放在代码的重用性上,将驱动程序代码合理的分层。例如: 总线驱动器从特定的驱动器代码被分成一些独立的层,使很多总线共享一部分层别。在很多情况下,不同的代理商提供不同的层别,只有小心的模块化设计才能完成代码重用的目标。
WIN2000驱动器的种类
曾经有一段时间,驱动程序的作者只要理解复杂的新硬件,学会操作系统接口,就可以开始工作了,无论好坏,数天后一个驱动器就会完成。今天,驱动程序的作者必须了解复杂的硬件和I/O的分层子系统,才只不过到了工作描述的阶段,下决心开发一个什幺样的驱动程序都是一个有趣的挑战,下决心去重写或者重用一个层别将是令一个挑战,这一部分描述在硬件世界,操作系统中不同的驱动程序。
在最高层,WIN2000支持用户模式,内核模式两种驱动程序,从名字上来看,用户模式驱动程序是运行在用户模式的系统级代码,例如: 一个为虚拟的硬件或者新的环境子系统所写的虚拟驱动器。既然WINN2000不支持直接访问硬件,虚拟驱动器依赖运行在内核模式的真正的驱动器。本书不将讨论用户模式驱动程序。
内核模式驱动程序使用系统级代码编写,且运行在内核模式下,因为内核模式允许直接访问硬件,这些驱动程序被用来直接控制硬件。
向下移动一层,内核模式驱动程序可被进一步的分成两种,遗留模式的驱动程序和Windows驱动模式的驱动程序(WDM)。遗留模式的驱动程序需要去侦测硬件和与I/O子系统连接,这些是已经由其它文献说明过了。感激的,这些遗留模式的驱动程序可被移植到WIN2000(和WIN98)上。
Windows驱动模式的驱动程序支持即插即用,电源管理,热拔插,自动配置。一个好的Windows驱动模式的驱动程序可以在WIN2000和Win98上使用,虽然微软不保证二进制兼容。最多我们再用WIN98 DDK编译一遍。
再向下移动一层,遗留模式的驱动程序和Windows驱动模式的驱动程序可被进一步的分成三种: 高层,中间层,低层。顾名思义,高层驱动程序依靠中间层和低层驱动程序去完成工作,中间层驱动程序则依靠低层驱动程序去完成工作。
高层驱动程序包括文件系统驱动程序(FSDs),依次的翻译程序请求成为特定的驱动器请求,当低层的驱动程序的服务已经准备好的时候它也是需要的。
微软提供一个安装文件系统工具包(IFS),不在MSDN和其它产品中,是单独出售的。这个安装文件系统工具包需要和驱动程序开发包和其它产品一起才能完成开发。使用这种工具包有很多限制。相关信悉请登禄微软的网站,本书不介绍文件系统的开发。
中间层驱动程序包括磁盘镜像驱动器,类驱动器(Class),迷你驱动器(MINI),过滤驱动器(FILTER)等。这些驱动器位于低层驱动器和高层驱动器之间。例如: 磁盘镜像驱动器接收到文件系统传来的写文件的请求,将它转换成为两个不同的请求传给两个不同的低层驱动器。
类驱动器是对代码重用的一次尝试,因为一种特定设备有多个驱动程序,它们之间大部分是相同的,这些相同的部分被作为一个类驱动器和其它部分分开。例如: 所有的IDE驱动器共享大部分的代码,这样就只用一次编写好这些公用的代码,把它作为一般的类驱动器加载。对于一个指定的设备,我们就只用编写以类驱动器为基础的迷你驱动程序就可以了。
过滤驱动器可以截取程序对存在的驱动程序发出的请求。在请求到达驱动程序之前,它给我们一个更改请求内容的机会。
事实上,在Windows驱动模式的世界中,中间层驱动程序也是由基本的驱动程序所组成,这些驱动程序可能即不是类驱动器,也不是迷你驱动器,但是,它们纵是扮演着转换抽象的I/O请求到低层物理驱动器代码的角色。在DDK的文献中,迷你驱动器和类驱动器的术语有时颠倒了,但从上下文可以分辨出来。
低层驱动器包括硬件和总线的控制器。例如: SCSI适配器是一个低层驱动器,这些低层驱动器和HAL于硬件结合紧密。在WDM中低层驱动器包括物理驱动器的概念,行为。这些物理驱动器与一个或者多个功能驱动器结合。图1.4是WIN2000的分层驱动器总图。
图1.4 WIN2000的分层驱动器
特殊驱动器结构
除了上节介绍的驱动器外,WIN2000还提供一些特殊的驱动器:
视频驱动程序,打印机驱动程序,多媒体驱动程序,网络驱动程序。
视频驱动程序
视频驱动程序非常特殊,因为图形接口经常暴露给用户,系统的速度也是通过这个部分判断的。
WIN2000视频驱动程序构架如图1.5所示,阴影部分为WIN2000所有,视频驱动程序为厂商提供,因为很多视频适配器选用相同的视频芯片,视频芯片制造商提供给视频适配器制造商的是类(CLASS)驱动程序,例如: ET4000 MINI 驱动程序提供给所有使用ET4000芯片的视频适配器,这个芯片的其它硬件环境的驱动程序则是视频适配器自己的驱动代码。
基本的,视频驱动程序在请求画图系统服务的时候可以直接和I/O管理器通讯,这点和一般的I/O构架是不同的。用户模式的代码通常和GDI交互。
GDI函数支持画线,几何图形,文字。因此,GDI好象一个高层驱动器,GDI也依赖I/O管理器和显示驱动来完成工作,显示驱动和GDI的通讯是双向的。GDI可直接调用显示驱动的函数,这里的速度是致关重要的。这些函数是显示驱动的驱动驱动接口(DDI),前缀是Drv,相反的,显示驱动需要GDI中的图形库例程,叫做图形引擎调用(GEC),前缀是Eng。
GDI通过传统分层的设计来访问I/O子系统,GDI使用I/O管理器去调用MINI 驱动程序和视频口的例程,例如: 模式开关命令,它被传给视频口驱动器的是标准的IRP格式,视频口驱动器转换IRP格式为视频请求包格式,然后传给MINI 驱动程序处理。
图1.5 WIN2000视频驱动程序构架
打印机驱动程序
打印机驱动程序与标准的WIN2000驱动程序有以下几点不同:
1.假脱机: 一个打印工作在传送到物理打印机之前先传送到假脱机,因为物理打印机的速度太慢。
2.远程操作: 物理打印机通常被连接到一个远程计算器上,这里用到RPC.
3.不定的打印协议: 不同的打印机当然有不同的打印协议。
假脱机部分如图1.6所示,以后不再重复说明,阴影部分为WIN2000所有,如果假脱机被击活,打印工作先被假脱机纪录进一个文件,假脱机像打印机一样退出队列,而后变的可用,这时数据才被传入本地或者远程的打印机驱动。
图1.5 打印机驱动程序的假脱机
客户端的假脱机部分(winspool.drv 或者 Win32spl.DLL(远程打印时)),是一个以RPC为基础的程序,它连接服务端(Spoolsv.exe)和假脱机的API函数。
服务端的代码依赖路由服务器(spoolss.dll),这个路由服务器连接一个打印提供者,打印提供者是指定的打印机服务或者驱动程序的抽象。打印提供者创建和管理一个指定的打印工作队列。一个简单的打印提供者可以向整个打印机类提供服务,本地,远程,网络的打印提供者都被包含在WIN2000中。
不同的打印机,网络协议有时需要特殊的打印提供者,例如: Novell INC 提供给WIN2000设计了一个直接输出到网络打印服务的打印提供者。
GDI必须转换程序的画图命令成为打印机使用的特殊格式,GDI依靠打印机驱动程序工作, 打印机驱动程序由打印机绘图DLL和打印机接口DLL组成。
打印机绘图DLL负责给指定的打印机转换数据,在WIN2000中,打印机绘图DLL可以放置在用户模式或者内核模式中,用户模式放置在可以产生高的系统可靠性和灵活的配置。
打印机绘图DLL的接口函数的前缀是Drv,这些接口函数在GDI转换命令时被调用。
打印机绘图DLL负责依照用户的参数配置打印机设备,例如: 多进纸盒的打印机需要被设定缺省的纸张大小和进纸盒。打印机绘图DLL通过构建一个或者多个配置窗体的形式提供给用户接口,这些配置窗体上有一些标准的Windows控件,通过它可以方便的配置打印机。
多媒体驱动程序
Windows 2000提供了内核流(KS)去支持多媒体驱动程序像声卡驱动程序,电视卡驱动程序等。KS由过滤驱动程序和功能驱动程序组成,应用软件将KS和方法,事件,等来自组件对象模型(COM)的东西于KS结合,这些机制应用于四种提供给应用程序的KS对象: 过滤器,针脚,时钟,分配算符。每一个提供给应用程序的KS对象都是一个标准的I/O文件对象。
过滤器对象(FILTER)(用以区别过滤器驱动程序)是显露给用户的执行多媒体操作的高层实体,例如: 应用程序可以打开一个声卡的话筒过滤器。
针脚对象(PINS)是FILTER的子对象,它是设备的一个节点(输入或者输出),例如: 一个话筒过滤器可能给输入提供一个针脚对象,一个输出针脚对象然后去获得(读)那个数字化的信号。
时钟对象(CLOCK)给多媒体设备提供(在有请求时钟的情况下)一个实时钟,当时间到的情况下,时钟对象将发送给应用程序一个事件。
分配算符对象(Allocator)给多媒体设备提供一个直接内存接口。这些内存可以通过Allocator去分配或者释放。
Windows 2000拥有一个通用的类驱动器Stream.sys,在多数情况下,必须编写一个MINI驱动器去支持指定的像声卡,摄像机的设备。制造商提供的MINI驱动器利用类驱动器(包括缓冲器和DMA支持)去支持设备指定的动作,类驱动器执行Windows 2000的KS的抽象。
网络驱动程序
网络驱动程序使用ISO的开放的系统互连网罗标准(OSI),这是一个七层的模型,它的顶层是应用软件层,底层是硬件连接和网络的拓扑结构,网络接口卡(NIC)给大多数的平台提供网络的硬件接口,网络驱动程序是写给指定的NIC的驱动程序。
网络驱动程序接口规范(NDIS)给NIC驱动程序提供库支持,通常只允许NIC厂商提供管理硬件特殊细节的MINI驱动程序,更高层的NDIS(中间驱动程序,协议驱动程序)在需要的时候提供媒体转换,过滤。分层的NDIS如图1.7所示。
WIN2000提供一个分层的内核模式软件传输驱动接口(TDI),这个分层的在NDIS层和高层软件抽象像插座和NetBIOS,TDI层使WIN2000的高层结构具有更多的可移植性。
图1.7网络驱动程序接口规范
小结
WIN2000为应用程序的开发提供充足的体系结构,当然,这需要驱动程序开发者花费巨大的心血。WIN2000 I/O处理过程图解是复杂的,但是必须的。这是对理解WIN2000中有什幺驱动器,以及这些驱动器在什幺位置是有益的。