11.7.3 I/O实现

Windows I/O系统由即插即用服务、电源管理器、I/O管理器和设备驱动模型组成。即插即用服务检测硬件配置上的改变并且为每个设备创建或拆卸设备栈,也会引起设备驱动程序的装载和卸载。功耗管理器会调节I/O设备的功耗状态,以在设备不用的时候降低系统功耗。I/O管理器为管理I/O内核对象以及如IoCallDrivers和loCompleteRequest等基于IRP的操作提供支持。但是,支持Windows I/O所需要的大部分工作都由设备驱动程序本身实现。

1.设备驱动程序

为了确保设备驱动程序能和Windows Vista的其余部分协同工作,微软公司定义了设备驱动程序需要符合的WDM(Windows驱动程序模型)。WDM被设计成能在Windows 98系统上运行,也能在从Windows 2000开始的基于NT的系统上运行。WDM允许开发人员编写与两类系统都兼容的驱动程序。微软公司还提供了一个用于帮助驱动程序开发人员编写符合模型的驱动程序的开发工具箱(Windows驱动程序开发工具箱)。大部分Windows驱动程序的开发过程都是先复制一份合适的简单的驱动程序,然后修改它。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

微软公司也提供一个驱动程序验证器,用以验证驱动程序的多个行为以确保驱动程序符合Windows驱动程序模型的结构要求和I/O请求的协议要求、内存管理等。操作系统中带有此验证器,管理员可能通过运行verifier.exe来控制驱动程序验证器,验证器允许管理员配置要验证哪些驱动程序以及在怎样的范围(多少资源)内验证这些驱动程序。

即使有所有的驱动程序开发和验证支持,在Windows中写一个简单的驱动程序仍然是非常困难的事情,因此微软建立了一个叫做WDF(Windows驱动程序基础)的包装系统,它运行在WDM顶层,简化了很多更普通的需求,主要和驱动程序与电源管理和即插即用操作之间的正确交互有关。

为了进一步简化编写驱动程序,也为了提高了系统的健壮性,WDF包含UMDF(用户模式驱动程序架构),使用UMDF编写的驱动程序作为在进程中执行的服务。还有KMDF(内核模式驱动程序架构),使用KMDF编写的驱动程序作为在内核中执行的服务,但是也使得WDM中的很多细节变得不可预料。由于底层是WDM,并且WDM提供了驱动程序模型,因此,本节将主要关注WDM。

在Windows中,设备是由设备对象描述的。设备对象也用于描述硬件(例如总线),软件抽象(例如文件系统、网络协议),还可以描述内核扩展(例如病毒过滤器驱动程序)。上面提到的这些设备对象都是由Windows中的设备栈来组织的,见前面的图11-16。

I/O操作从I/O管理器调用可执行API IoCallDriver程序开始,IoCallDriver带有指向顶层设备对象和描述I/O请求的IRP的指针。这个例程可以找到与设备对象联合在一起的驱动程序。在IRP中指定操作类型通常都符合前面讲过的I/O管理器系统调用,例如创建、读取和关闭。

图11-38表示的是一个设备栈在单独一层上的关系。驱动程序必须为每个操作指定一个进入点。IoCallDriver从IRP中获取操作类型,利用在当前级别的设备栈中的设备对象来查找指定的驱动程序对象,并且根据操作类型索引到驱动程序分派表去查找相应驱动程序的进入点。最后会把设备对象和IRP传递给驱动程序并调用它。

阅读 ‧ 电子书库
图 11-38 设备栈中的单独一层

一旦驱动程序完成处理IRP描述的请求后,它将有三种选择。第一,驱动程序可以再一次调用IoCallDriver,把IRP和设备栈中的下一个设备对象传递给相应的驱动程序。第二,驱动程序也可以声明I/O请求已经完成并返回到调用者。第三,驱动程序还可以在内部对IRP排队并返回到调用者,同时声明I/O请求仍未处理。后一种情况下,如果栈上的所有驱动都认可挂起行为且返回各自的调用者,则会引起一次异步I/O操作。

2.I/O请求包

图11-39表示的是IRP中的主要的域。IRP的底部是一个动态大小的数组,包含那些被设备栈管理请求的域,每个驱动程序都可以使用这些域。在完成一次I/O请求的时候,这些设备栈的域也允许驱动程序指定要调用哪个例程。在完成请求的过程中,按倒序访问设备栈的每一级,并且依次调用由每个应用程序指定的完成例程。在每一级,驱动程序可以继续执行以完成请求,也可以因为还有更多的工作要做从而决定让请求处于未处理状态并且暂停I/O的完成。

阅读 ‧ 电子书库
图 11-39 I/O请求包的主要域

当I/O管理器分配一个IRP时,为了分派一个足够大的IRP,它必须知道这个设备栈的深度。在建立设备栈的时候,I/O管理器会在每一个设备对象的域中记录栈的深度。注意,在任何栈中都没有正式地定义下一个设备对象是什么。这个信息被保存在栈中当前驱动程序的私有数据结构中。事实上这个栈实际上并不一定是一个真正的栈。在每一层栈中,驱动程序都可以自由地分配新的IRP,或者继续使用原来的IRP,或者发送一个I/O操作给另一个设备栈,或者甚至转换到一个系统工作线程中继续执行。

IRP包含标志位、索引到驱动程序分派表的操作码、指向内核与用户缓冲区的指针和一个MDL(内存描述符列表)列表。MDL用于描述由缓冲区描述的物理内存框,也就是用于DMA操作。有一些域用于取消和完成操作。当I/O操作已经完成后,在处理IRP时用于排列这个IRP到设备中的域会被重用。目的是给用于在原始线程的上下文中调用I/O管理器的完成例程的APC控制对象提供内存。还有一个连接域用于连接所有的外部IRP到初始化它们的线程。

3.设备栈(Device Stack)

Windows Vista中的驱动程序可以自己完成所有的任务,如图11-40所示的打印机驱动程序。另一方面,驱动程序也可以堆叠起来,即一个请求可以在一组驱动程序之间传递,每个驱动程序完成一部分工作。图11-40也给出了两个堆叠的驱动程序。

阅读 ‧ 电子书库
图 11-40 Windows允许驱动程序堆叠起来操作设备。这种堆叠是通过设备对象(Device Object)来表示的

堆叠驱动程序的一个常见用途是将总线管理与控制设备的功能性工作分离。因为要考虑多种模式和总线事务,PCI总线上的总线管理相当复杂。通过将这部分工作与特定于设备的部分分离,驱动程序开发人员就可以从学习如何控制总线中解脱出来了。他们只要在驱动栈中使用标准总线驱动程序就可以了。类似地,USB和SCSI驱动程序都有一个特定于设备的部分和一个通用部分。Windows为其中的通用部分提供了公共的驱动程序。

堆叠设备驱动程序的另一个用途是将过滤器驱动程序(filter driver)插入到驱动栈中。我们已经讨论过文件系统过滤器驱动程序的使用了,该驱动程序插入到文件系统之上。过滤器驱动程序也用于管理物理硬件。在IRP沿着设备栈(Device Stack)向下传递的过程中,以及在完成操作(completion operation)中IRP沿着设备栈中各个设备驱动程序指定的完成例程(completion routine)向上传递的过程中,过滤器驱动程序会对所要进行的操作进行变换。例如,一个过滤器驱动程序能够在将数据存放到磁盘上时对数据进行压缩,或者在网络传输前对数据进行加密。将过滤器放在这里意味着应用程序和真正的设备驱动程序都不必知道过滤器的存在,而过滤器会自动对进出设备的数据进行处理。

内核态设备驱动程序是影响Windows的可靠性和稳定性的严重问题。Windows中大多数内核崩溃都是由设备驱动程序出错造成的。因为内核态设备驱动程序与内核及执行层使用相同的地址空间,驱动程序中的错误可能破坏内核数据结构,甚至更糟。其中的有些错误之所以产生,部分原因是为Windows编写的设备驱动程序的数量极其庞大,部分原因是设备驱动程序由缺乏经验的开发者编写。当然,为了编写一个正确的驱动程序而涉及的大量设备细节也是造成驱动程序错误的原因。

I/O模型是强大而且灵活的,但是几乎所有的I/O都是异步的,因此系统中会大量存在竞态条件(race condition)。从Win9x系统到基于NT技术的Windows系统,Windows 2000首次增加了即插即用(的功能)和电源管理设施。这对要正确地操纵在处理I/O包过程中涉及的驱动器的驱动程序提出了很多要求。PC机用户常常插上/拔掉设备,把笔记本电脑合上盖子装入公文包,而通常不考虑设备上那个小绿灯是否仍然亮着(表示设备正在与系统交互)。编写在这样的环境下能够正确运行的设备驱动程序是非常具有挑战性的,这也是开发WDF(Windows Driver Foundation)以简化Windows驱动模型的原因。

电源管理器集中管理整个系统的电源使用。早期的电源管理包括关闭显示器和停止磁盘旋转以降低电源消耗。但是,我们需要延长笔记本电脑在电池供电情况下的使用时间。我们还会涉及长时间无人看管运行的桌面计算机的电源节约,以及节省为现今存在的巨大的服务器群提供能源的昂贵花费(像微软、Google这样的公司将服务器群建在水电站附近以降低费用)。当我们面临以上问题时,情况迅速变得复杂起来。

更新一些的电源管理设施可以在系统没有被使用的时候,通过切换设备到后备状态甚至通过使用软电源开关(soft power switch)将设备完全关闭来降低部件功耗。在多处理器中,可以通过关闭不需要的CPU和降低正在运行的CPU的频率来减少功耗。当一个处理器空闲的时候,由于除了等待中断发生之外,该处理器不需要做任何事情,它的功耗也相应减少了。

Windows支持一种特殊的关机模式——休眠,该模式将物理内存复制到磁盘,然后把电力消耗降低到很低的水平(笔记本电脑在休眠状态下可以运行几个星期),电池的消耗也变得十分缓慢。因为所有的内存状态都被写入磁盘,我们甚至可以在笔记本电脑休眠的时候为其更换电池。从休眠状态重新启动时,系统恢复已保存的内存状态并重新初始化设备。这样计算机就恢复到休眠之前的状态,而不需要重新登录,也不必重新启动所有休眠前正在运行的应用程序和服务。尽管Windows设法优化这个过程(包括忽略在磁盘中已备份而在内存中未被修改的页面及压缩其他内存页面以减少对I/O操作的需求),对于一个有几个GB内存的笔记本电脑或桌面机来说,仍然需要花费数秒钟的时间来进入休眠状态。

另一种可选择的模式是待机模式,电源管理器将整个系统降到最低的功率状态,仅使用足够RAM刷新的功率。因为不需要将内存复制到磁盘,进入待机状态比进入休眠状态的速度更快。但是待机状态不像休眠状态那么可靠。因为如果在待机状态遇到桌面机掉电,笔记本电脑更换电池,或者由于驱动程序故障使得设备切换到低功耗状态后无法重新初始化等情况,系统将无法恢复到待机前的状态。在开发Windows Vista的过程中,微软和很多硬件设备厂商合作,花费了极大的努力改进待机模式的操作。他们也终止了允许应用软件禁止系统进入待机模式这一习惯(有时疏忽的用户没有等到指示灯熄灭就把笔记本电脑放进公文包,从而导致笔记本电脑过热)。

有很多关于WDM(Windows Driver Model)和WDF(Windows Driver Foundation)的有用的书(Cant,2005;Oney,2002;Orwick&Smith,2007;Viscarola等人,2007)。