[笔记]深入解析Windows操作系统《二》系统架构
Posted 二次元怪兽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[笔记]深入解析Windows操作系统《二》系统架构相关的知识,希望对你有一定的参考价值。
文章目录
- Ntdll.dll
- 执行体
- 内核
- 设备驱动程序
- 系统进程
- 总结
前言
2.1 需求和设计目标
回到1989年,下面的需求驱动了Windows NT的规范:
- 提供一个真正32位的、抢占式的(preemptive)、可重入的(reentrant)虚拟内存操作系统。
在多种硬件体系架构和平台上运行。 - 可在对称多处理器系统(symmetric multiprocessing systems)上运行,并且能很好地适应处理器的数量。
- 成为一个极好的分布式计算平台,无论是作为网络客户机还是服务器。
- 能够运行大多数己有的16位MS-DOS和Microsoft Windows3.1应用程序。
- 符合政府对于P0SIX1003.1兼容性的要求。
- 符合政府和工业界对于操作系统安全性方面的要求。
- 支持Unicode,从而很容易适应全球市场。
要创建一个满足这些需求的系统,必须做出数千个决定:
为了便于做出这些决定, Windows NT设计小组在项目开始之初选择了下面的设计目标:
-
扩展性
:编写的系统代码必须能够随着市场需求的变化而自如的增长和改变 -
可移植性
:系统必须能运行在多种硬件体系架构上,必须能根据市场的需要,相对容易的迁移到新的体系架构上 -
可靠性和健壮性
系统应该能保护自己,不会因内部故障和外部篡改而不能工作,应用程序应该无法伤害操作系统或其他应用程序 -
兼容性
虽然Windows NT应该扩展已有的技术,但是它的用户界面和API应该与老版本Windows和MS-DOS兼容。而且它也应该能与其他的系统,比如Unix OS2 NetWare 很好的互操作. -
性能
在每一种硬件平台上尽可能运行得更快,对外部的响应尽可能地及时.
2.2 操作系统模型
两种模式:
- 用户模式
- 内核模式
2.3系统架构
基本架构
系统软件层面组成
- 注册表
- 事件日志
- 本地组和本地安全策略
2.4 关键系统组件
Windows系统架构
环境子系统和子系统DLL
子系统启动
子系统是由会话管理器(Session Manager)(Smss.exe)进程启动起来的,子系统的启动信息保存在注册表键
HKLMISYSTEM\\CurrentControlSet\\Control\\Session Manager\\Subsystems
的下面。
Required值列出了系统引导时加载的子系统。
该值有两个字符串:
- Windows
- Debug。
Windows值包含了Windows子系统的文件规范,Csrss.exe,它代表了客户机/服务器运行时子系统(Client/Server Run-Time Subsystem)。
Debug值是空的(因为它被用于内部测试),因此什么也不是。Optional值表明了SUA子系统将会按需启动。注册表值Kmode包含了Windows子系统的内核模式部分的文件名称,Win32k.sys(本章后面将进一步解释)。
Windows子系统
Windows子系统由以下主要的组件构成:
- 对于每个会话,环境子系统进程(Csrss.exe)有一个实例加载三个DLL(Basesrv.dll、Winsrv.dll和Csrsrv.dll),它们包含下列支持:
– 创建或删除进程和线程。
– 对16位DOS虚拟机(VDM)进程的部分支持(仅32位Windows)。sxs (Side-by-side)/Fusion和清单文件(manifest)支持。
– 其他一些函数,比如GetTempFile、DefineDosDevice、ExitWindowsEx,以及几个自然语言支持函数。 - 内核模式设备驱动程序( Win32k.sys)包含下列支持:
– 窗口管理器( window manager),它控制窗口显示,管理屏幕输出,采集来自键盘、鼠标和其他设备的输入,同时也负责将用户消息传递给应用程序。
– 图形设备接口(GDI,Graphics Device Interface),它是专门针对图形输出设备的函数库,其中包括线段、文本和图形的绘制函数,以及图形控制函数。
– DirectX功能的包装函数,Windows对DirectX的支持是在另一个内核驱动程序( Dxgkrnl.sys)中实现的。 - 控制台宿主进程(Conhost.exe),提供了对控制台(字符环境〉应用程序的支持。
- 子系统DLL (比如Kernel32.dll、Advapi32.dll、User32.dll和Gdi32.dll),将已经文档化的Windows API函数,转译成Ntoskrnl.exe和Win32k.sys中恰当的且绝大多数未文档化的内核模式系统服务调用。
- 图形设备驱动程序,与硬件相关的图形显示器驱动程序、打印机驱动程序和视频微端口驱动程序。
控制台窗口宿主进程
在最初的Windows子系统设计中,子系统进程(Csrss.exe)负责管理控制台窗口,每个控制台应用程序(比如Cmd.exe,“命令提示符”程序)与Csrss进行通信。现在,Windows为系统中的每个控制台窗口使用了一个单独的进程:控制台窗口宿主进程(Conhost.exe)。(多个控制台应用程序可以共享同一个控制台窗口,比如在从命令提示符窗口中发起一个新的命令提示符窗口时。默认情况下,第二个命令提示符窗口共享前者的控制台窗口。)
无论何时,只要控制台应用程序向当前会话中正在运行的Csss实例注册其自身,Csss就利用客户进程的安全令牌,而不是Csrss的System令牌来创建Conhost:实例。然后,它映射一个共享内存区,让所有的Conhosti都可以与Csrss共享一部分内存,以便于高效地处理缓冲区(因为这些线程不再运行在Csrss内部了),并且Csrss也在RPC Control对象目录中创建一个命名的异步本地过程调用(ALPC,Asynchronous Local Procedure Call)端口。(有关ALPC的更多信息,参见第3章。)该端口的名称采用console-PID-lpc-handle的格式,其中PID是Conhost进程的进程ID。然后,它向用户应用程序所关联的内核进程结构注册它的PID,因而用户应用程序可以查询此信息,以便打开新创建的ALPC端口。该进程也会创建一个共享内存区对象的映射关系,从而在命令行应用程序和它的Conhost之间可以共享数据。最后,在会话0的BaseNamedObjects目录中创建一个等待事件(名为ConsoleEvent-PID),从而使命令行应用程序和Conhost可以相互之间通知新的缓冲区数据。下面的图显示了一个Conhosti进程有两个句柄已经打开了此ALPC端口和等待事件。
因为Conhost是用用户的凭证来运行的(意味着用户的特权级别),并且也运行在一个与控制台应用程序相关联的进程中,所以,用户界面特权隔离(UIPI,User Interface Privilege Isolation参见第6章“安全性”)安全机制也适用于控制台进程。而且,CPU制约的控制台应用程序可能会被识别为它们背后的控制台宿主进程(若有必要,用户可以杀掉它)。另外还有一种副作用,因为Conhost进程现在运行在Csrss子系统的特别辖区以外,所以,控制台应用程序(它们的窗口实际上属于Conhost)可以完全主题化,可以加载第三方DLL,还可以拥有完全的窗口能力。
UNIX应用子系统
针对UNIX应用程序的子系统(SUA,Subsystem for UNIX-based Application)使得可以在一台运行Windows Server系统,或者Windows客户机系统的企业或旗舰版本的计算机上编译和运行UNIX应用程序。SUA提供了将近2000个UNX函数和300个UNIX类的工具和实用程序。
(有关SUA的更多信息,参见 )
有关Windows如何处理POSIX应用程序运行的更多信息,参见第5章“CreateProcess的流程”一节。
最初的POSIX子系统
POSIX差不多可以看成“a Portable Operating System Interface based on UNIX(一个基于UNX的可移植的操作系统接口)”的缩写,它指的是针对UNX风格的操作系统接口的一组国际标准。POSX标准鼓励厂商实现UNX风格的接口,从而使它们保持兼容,这样程序员们就可以很容易地将他们的应用程序从一个系统迁移到另一个系统上。Windows最初仅仅实现了众多POSIX标准中的一个,即POSIX.1,正式的名称是ISO/IEC 9945-1:1990或者IEEE POSIX标准1003.1-1990.该标准之所以被包含进来,主要是为了满足美国政府在20世纪80年代中后期制定的政府采购要求,即POSX.1兼容性是强制性的,这是在美国标准和技术委员会(National Institute of Standards and Technology)开发的FIPS(Federal Information Processing Standard,联邦信息处理标准)l5l-2中规定的。Windows NT3.5、3.51和4已经被正式测试过,并且通过了FPS151-2的鉴定。
因为POSX.l兼容性是Windows的一个强制性目标,所以,在设计Windows操作系统时,必须要保证所设计的基本系统能够支持POSX.1子系统一一比如fork函数是在Windows执行体中实现的,而针对文件硬链接(hard file link)的支持则在Windows文件系统中。
Ntdll.dll
Ntdll.dll是一个特殊的系统支持库,主要用于子系统DLL。它包含两种类型的函数:
- 系统服务分发存根(stub),它们会调用Windows执行体的系统服务。
- 内部支持函数,供子系统、子系统DLL以及其他的原生映像文件使用。
第一组函数为Windows执行体系统服务提供了接口,在用户模式下可以通过这些接口函数调用Windows执行体的系统服务。这样的函数超过了400个,比如NtCreateFile、NtSetEvent等。
如前所述,这些函数的大多数功能可以通过Windows API来访问得到(然而,有些函数则不然,它们仅被用于操作系统内部)。
对于每一个这样的函数,Ntl包含了一个同名的入口点。函数内部的代码包含了与处理器体系架构相关的模式切换指令,通过该指令可转换到内核模式下,从而调用系统服务分发器(system service dispatcher,第3章将详细介绍)。系统服务分发器在检验某些参数以后,再调用真正的内核模式系统服务,其中包括Ntoskrnl.exe内部的实际代码。
对于每一个这样的函数,Ntdll包含了一个同名的入口点。函数内部的代码包含了与处理器体系架构相关的模式切换指令,通过该指令可转换到内核模式下,从而调用系统服务分发器(system service dispatcher,第3章将详细介绍)。系统服务分发器在检验某些参数以后,再调用真正的内核模式系统服务,其中包括Ntoskrnl.exe内部的实际代码。
Ntdll也包含许多支持函数,比如:
- 映像加载器(以Ldr开头的函数)、
- 堆管理器、
- Windows子系统进程通信函数(以Cs开头的函数)。
Ntdll也包含一般性的运行库例程(以Rtl开头的函数)、对用户模式调试和Windows事件跟踪的支持函数(分别以DbgUi和Ew开头的函数),以及用户模式异步过程调用(APC,Asynchronous Procedure Call)分发器和异常分发器(关于APC和异常,将在第3章中讲述)。最后,你还可以在Nt中发现一个很小的C运行库(CRT)例程的子集,仅限于字符串和标准库中的一些例程,比如nemcpy、strcpy、itoa,等等。
执行体
Windows执行体是Ntoskrnl…exe中的上层(内核是其下层)。执行体包含以下类型的函数:
- 可在用户模式下调用的导出函数。这些函数称为系统服务(system service),并且通过Ntdl被导出。这些服务绝大多数可通过Windows API来访问,或者通过另一个环境子系统的API来访问。然而对于有些服务,通过任何一个文档化的子系统函数都无法访问(这样的例子有ALPC、诸如NtQueryInformationProcess之类的各种查询函数,以及诸如NtCreatePagingFilei这样的专用函数,等等)。
- 可通过DeviceloControli函数来调用的设备驱动程序函数。这为从用户模式到内核模式提供了一个通用的接口,因而在用户模式下可以调用设备驱动程序中并不与读或者写操作关联的函数。
- 只能在内核模式下调用的导出函数,这些函数在WDK中已经文档化。
- 在内核模式下调用,但未在WDK中文档化的导出函数(比如以Inbv开头的、在引导视频驱动程序中调用的函数)。
- 定义为全局符号但是未被导出的函数。这包括在Ntoskrnl内部调用的支持函数,比如以Iop或者M开头的那些函数(分别是I/O管理器内部支持函数和内存管理内部支持函数)。
- 未定义为全局符号,而是在一个模块内部的函数。
执行体包含了以下的主要组件(在本书后续章节中会详细地介绍):
-
配置管理器(configuration manager,在第4章中介绍)
负责实现和管理系统的注册表。 -
进程管理器(process manager,在第5章中介绍)
创建和终止进程和线程。针对进程和线程的底层支持是在Windows内核中实现的;而执行体则在这些低层对象的基础上又加上了额外的语义和功能。 -
安全引用监视器(security reference manager,SRM,在第6章中讲述)
强制在本地计算机上实行安全策略。它守护着操作系统的资源,执行对运行时对象的保护和审计。 -
I/O管理器(I/O manager,在本书下册第8章中解释)
实现了与设备无关的/O功能,负责将/O请求分派到恰当的设备驱动程序以进一步处理。 -
即插即用(PnP)管理器(Plug and Play manager,在本书下册第8章中解释)
的任务是,为了支持某个特定的设备,确定哪些驱动程序是必需的,同时也负责加载这些驱动程序。它在设备枚举过程中,获取到每个设备的硬件资源需求。PP管理器根据每个设备的资源需求,分配适当的硬件资源,比如I/O端口、RQ、DMA通道和内存位置。当系统中的设备变化(增加或者移除设备)时,它还负责发送恰当的事件通知。 -
电源管理器(power manager,在本书下册第8章中解释)
负责协调电源事件,并且产生电源管理/O通知,发送给设备驱动程序。电源管理器可以配置成:当系统空闲时,通过将CPU置于睡眠状态而降低电源消耗。单个设备的电源消耗变化可由设备驱动程序来处理,但是需要电源管理器来协调。 -
Windows驱动程序模型(Windows Driver Model)
的WMI例程(在第4章中解释)允许设备驱动程序发布有关性能和配置的信息,以及接收来自用户模式WMI服务的命令。WMI信息的消费者可以运行在本地机器上,也可以在跨网络的远程机器上。 -
缓存管理器(cache manager,在本书下册第1l章中解释)
提高了基于文件的I/O操作的性能,其做法是,让最近引用过的磁盘数据驻留在主内存中以便快速访问(并且延迟磁盘写操作,在将更新数据发送到磁盘之前先在内存中停留一小段时间)。你将会看到,它利用了内存管理器对映射文件的支持来做到这一点。 -
内存管理器(memory manager,,在本书下册第10章中解释)
实现了虚拟内存。这是一种内存管理方案,它为每个进程提供一个巨大的私有地址空间,其大小可以超过当前可用的物理内存。内存管理器也为缓存管理器提供相应的底层支持。 -
逻辑预取器(logical prefetcher)和Superfetch(在本书下册第l0章中解释)
用于加速系统和进程的启动过程。其做法是,对于要在系统或进程启动过程中引用的数据,它优化这些数据的加载过程。
另外,Windows执行体还包含四组主要的支持函数,以上这些执行体组件会用到这些支持函数。在这些支持函数中,差不多三分之一在WDK中有相应的文档,因为设备驱动程序也要用到它们。以下就是这四大类支持函数:
-
对象管理器(object manager)
,创建、管理和删除Windows执行体对象和抽象数据类型,它们代表了操作系统的资源,比如进程、线程和各种同步对象。第3章将介绍对象管理器。 -
高级LPC设施(ALPC facility,在第3章中解释)
,为同一台机器上的客户机进程和服务器进程传递消息。此外,ALPC也被用作RPC的一个本地传输实现,这里的RPC是指跨网络的客户机进程和服务器进程之间的工业标准通信设施。 -
一组涉及范围广泛的公共运行库函数
,比如字符串处理、算术操作、数据类型转换,以及安全结构处理等。 -
执行体支持例程
,比如系统内存分配(换页的和非换页的内存池)、互锁的内存访问,以及三种特殊类型的同步对象:资源、快速互斥体(fast mutex)和推锁(pushlock)。
执行体还包含了其他多种基础设施例程,在本书后面我们将只是简单地提及其中一部分:
-
内核调试器库(kernel debugger library)
使得内核的调试与支持KD的调试器保持独立,这里KD是指一个可移植的协议,可以承载在各种传输体(比如USB和EEE1394)上。WinDbg和Kd.exe工具实现了KD协议。 -
用户模式调试框架(user-mode debugging framework)
负责向用户模式调试API发送事件,支持断点和单步跟踪代码,以及改变运行线程的执行环境。 -
内核事务管理器(kernel transaction manager)
提供公共的两阶段提交机制供资源管理器(resource manager)。使用,比如事务型注册表(TxR,transactional registry)和事务型NTFS(TxF,transactional NTFS)。 -
超级管理器库(hypervisor library)
是Windows Server2008中Hyper-V栈的一部分,它提供了虚拟机环境的内核支持。当系统知道它在一个客户区(虚拟环境)中运行时,超级管理器库可以优化相应的内核代码。 -
错误修正管理器(errata manager)
为非标准的或非兼容的硬件设备提供绕行的解决方案。 -
驱动程序检验器(Driver Verifier)
为内核模式驱动程序和代码提供可选的一致性检查机制。 -
Windows事件跟踪(Event Tracing for Windows)
为内核模式和用户模式组件提供了许多用于在系统范围内进行事件跟踪的辅助例程。 -
Windows诊断设施(Windows diagnostic infrastructure)
,对基于诊断场景(diagnostic scenario)的系统活动进行智能跟踪。 -
Windows硬件错误体系架构(Windows hardware error infrastructure)
支持例程提供了一个用于报告硬件错误的公共框架。 -
文件系统运行库(file-system runtime library)
为文件系统驱动程序提供了一组公共的支持例程。
内核
内核是由Ntoskrnl.exe中的一组函数以及对于硬件体系架构的低层支持(比如中断和异常分发)构成的。Ntoskrnl.exe中的这组函数提供了一些最为基本的机制,比如线程调度和同步服务,供执行体组件使用:而对硬件的低层支持则随处理器架构的不同而有所区别。内核代码主要是用C编写的,对于那些要用到特殊的处理器指令和寄存器(不容易在C代码中访问)的任务,则保留使用汇编代码的形式。
如同上一部分中提到的各种执行体支持函数一样,内核中的许多函数也在WDK中有相应的文档描述(通过搜索以K打头的函数可以找到),因为在实现设备驱动程序的时候也需要用到它们。
内核对象
内核提供了一组定义明确的、可预知的操作系统低层原语和机制,从而使得执行体中的高层组件可以做它们需要做的事情。内核实现了操作系统的基本机制,并且避免各种策略决定,从而将自己与执行体的其余部分分离开。它几乎将所有的策略决定都留给了执行体,唯一的例外是线程调度和分发,这是由内核自己来实现的。
从内核外部来看,执行体将线程和其他可共享的资源都表示为对象。这些对象需要一些策略开销,比如用以维护它们的对象句柄(object handle),以及用以保护它们的各种安全检查,还有相应的资源配额(当它们被创建时资源配额就会被扣除)。这些开销在内核中是不存在的,内核实现了一组更为简单的对象,称为内核对象(kernel object)。,它们帮助内核控制好中心处理过程,并且支持执行体对象的创建工作。绝大多数执行体层的对象都封装了一个或者多个内核对象,把它们的内核属性合并在一起。
一组称为控制对象(control object)的内核对象建立了有关控制各种操作系统功能的语义。
这包括APC对象、DPC(Deferred Procedure Call,延迟过程调用)对象,以及I/O管理器使用的一些对象,比如中断对象等。
另一组称为分发器对象(dispatcher object)。的内核对象融合了同步的能力,可以改变或者影响线程的调度。分发器对象包括内核线程、互斥体(内部称为突变体即mutant)、事件、内核事件对(event pair)。、信号量(semaphore)、定时器,以及可等待的定时器(waitable timer)。
执行体利用内核函数来创建和维护内核对象实例,并且构建更加复杂的、提供给用户模式的对象。第3章中将更加详细地介绍对象,第5章介绍进程和线程。
内核处理器控制区和控制块(KPCR和KPRCB)
内核使用一个称为处理器控制区(processor control region,KPCR)的数据结构来存放与处理器有关的数据。KPCR包含了基本的信息,例如处理器的中断分发表(DT)、任务状态段
(TSS)和全局描述符表(GDT)。它也包括中断控制器的状态,这是内核与其他模块(比如ACPI驱动程序和HAL)共享的数据。为了便于访问KPCR,在32位Windows上,内核在f寄存器中保存了一个指向KPCR的指针:在x64 Windows系统上,指向KPCR的指针存放在gs寄存器中。在IA64系统上,KPCR总是位于0xe0000000fff0000。
KPCR也包含一个称为内核处理器控块(kernel processor control block,KPRCB)的内嵌数据结构。KPCR是已经文档化的数据结构,因而第三方的驱动程序和其他的Windows内核组件可以使用:与此不同的是,KPRCB是一个私有的数据结构,仅仅Ntoskrnl…exe中的内核代码使用该结构。KPRCB包含了调度信息(比如在该处理器上正在调度的当前线程、下一个执行的线程以及空闲线程)、该处理器的分发器数据库(其中包含了每个优先级的就绪队列)、 DPC队列、CPU厂商和标识符信息(型号(model)、步进(stepping)、速度、特征位)、CPU和NUMA拓扑(节点信息、每个芯片的核、每个核的逻辑处理器,等等)、缓存大小、时间计数信息(比如DPC和中断时间),等等。KPRCB还包含了所有关于该处理器的统计信息,比如/O统计、缓存管理器的统计(相关描述参见本书下册第11章“缓存管理器”)、DPC统计,以及内存管理器的统计(更多信息参见本书下册第10章)。最后,KPRCB有时候也被用来存储一些缓存对齐的、针对每个处理器的数据结构,以便于优化内存访问,尤其是在NUMA系统上。
例如,系统中非换页的和换页的内存池快查表也存储在KPRCB中。
实验:观察KPCR和KPRCB
利用pcr和prcb内核调试器命令,可以查看KPCR和KPRCB的内容。如果在调试器命令中不指定任何标志,那么,调试器默认显示CPU0的信息:否则,可以在调试器命令的后面加上一个CPU编号,从而指定该CPU,例如pcr2.下面的例子显示了pcr和prcb命令的输出。如果系统有正在等待的DPC,也会显示出来。
可以使用dt命令,直接将KPCR和KPRCB数据结构转储出来,因为上述两个调试器命令已经给出了数据结构的地址(在上面的输出中用粗体显示)。例如,如果想要知道处理器的速度,可以用下面的命令来查看MHz域
在这台机器上,处理器以3GHz的速度运行。
硬件支持
内核的另一个主要任务是将执行体和设备驱动程序从Windows所支持的各种硬件体系架构的差异中抽象或隔离出来。这项任务包括处理各种功能(比如中断处理、异常分发和多处理器同步)方面的变化情况。
即便是对这些与硬件相关的功能,在设计内核时也力图使公共代码尽可能最大化。内核支持一组可移植的接口,这组接口的语义
在不同的体系架构上是等同的。而且,实现这组可移植接口的大部分代码在不同的体系架构上也是等同的。
然而,在这组接口中,有些在不同的体系架构上有不同的实现:或是在有些接口的实现中,部分代码与体系架构相关。这些独立于体系架构的接口可以在任何一台机器上被调用,而且无论实现代码是否随体系结构的不同而不同,接口的语义总是相同的。有些内核接口(比如自旋锁例程,将在第3章中讲述)实际上是在HAL(在下一节讲述)中实现的,因为它们的实现即使在同一体系架构族的系统中也可能有所不同。
内核中有一小部分代码涉及与x86有关的接口,之所以需要这部分代码,是为了支持老的MS-DOS程序。这些x86接口并不是可移植的,因为在任何其他体系架构的机器上它们都不可能被调用:而且它们根本不会出现在这样的机器上。例如,与x86相关的代码提供了相应的功能调用,可用来维护全局描述符表(GDT)和局部描述符表(LDT),这正是x86的硬件特性。
在内核中与体系架构相关的代码的另一个例子是,提供“转译缓冲区(translation buffer)”
和“CPU缓存”支持的接口。为了提供这样的支持,不同的体系架构需要不同的代码,因为处理器缓存的实现方式各有不同。
内核中有一小部分代码涉及与x86有关的接口,之所以需要这部分代码,是为了支持老的MS-DOS程序。这些x86接口并不是可移植的,因为在任何其他体系架构的机器上它们都不可能被调用:而且它们根本不会出现在这样的机器上。例如,与x86相关的代码提供了相应的功能调用,可用来维护全局描述符表(GDT)和局部描述符表(LDT),这正是x86的硬件特性。
在内核中与体系架构相关的代码的另一个例子是,提供“转译缓冲区(translation buffer)”和“CPU缓存”支持的接口。为了提供这样的支持,不同的体系架构需要不同的代码,因为处理器缓存的实现方式各有不同。
另一个例子是环境切换。尽管从高层来看,线程选择和环境切换可以使用同样的算法(上一个线程的执行环境被保存起来,新线程的环境被加载进来,然后新线程被启动执行),但在不同的处理器上,具体的实现还是存在体系架构方面的差异。因为执行环境是由处理器的状态(寄存器等)来描述的,所以哪些信息应该被保存或加载,随体系架构的不同而有所不同。
硬件抽象层(HAL)
正如本章开始时所提到的,Windows设计的关键要素之一是,它能被移植到各种不同的硬件平台上。硬件抽象层(HAL)是使得这种可移植性成为可能的一个关键部分。HAL是一个可加载的内核模式模块(Hal.dl),它提供了针对Windows当前运行所在的硬件平台的低层接口。它隐藏了与硬件相关的细节,比如/O接口、中断控制器,以及多处理器通信机制一一任何与体系架构相关或者与机器相关的功能。
所以,Windows内部组件以及用户编写的设备驱动程序并不直接访问硬件:而是当需要获得与平台相关的信息时,通过调用HAL例程来保持可移植性。出于这一原因,这些HAL例程在WDK中也被文档化了。更多有关HAL及其在设备驱动程序中用法的信息,参见WDK。
虽然Windows附带了几个HAL(见表2.4),但是它有能力在引导时检测到应该使用哪个HAL,因而,在早期Windows版本上“试图在不同类型的系统上引导已安装的Windows.系统”的问题便不复存在。
实验:确定当前正在运行哪个HAL
利用WinDbg,可以确定当前正在运行HAL的哪个版本,做法是,打开一个本地内核调试会话,确保已经加载了符号(输入.reload),然后输入1 m mv ha1命令。例如,下面的输出来自于一个运行ACPI HAL的系统:
实验:查看NTOSKRNL和HAL映像的依赖关系
可以利用Dependency Walker.工具(Depends.exe)来检查内核和HAL映像文件的导出和导入表,从而了解它们之间的关系。要在该工具中检查映像文件,可以从File菜单中选择Open命令来打开目标映像文件。
使用该工具来查看Ntoskrnl的依赖性,可能看到如下图所示输出:
注意,Ntoskrnl链接了HAL,而HAL又链接了Ntoskrnl(它们相互使用了对方的函数)。
Ntoskrnli还链接了以下二进制组件:
-
Pshed.dl
,针对特定平台的硬件错误驱动程序。PSHED对底层平台的硬件错误报告设施进行了抽象,做法是,将平台的错误处理机制的细节与操作系统隔离开,仅仅向Windows操作系统暴露一个一致的接口。 -
(仅限于在32位系统上)Bootvid.dll
,引导视频驱动程序。Bootvid提供了在系统启动过程中对VGA命令的支持,以便显示引导文本和引导标志图案。在x64系统上,该库被内联到内核模块中,以避免与内核补丁保护(KPP,Kernel Patch Protection)机制发生冲突。(有关KPP和PatchGuard的更多信息,参见第3章。) -
Kdcom.dll
,内核调试器协议(KD)通信库。 -
Ci.dll
,代码完整性库(有关代码完整性的更多信息,参见第3章)。 -
CIfs.sys
,公共的日志文件系统驱动程序,也用于内核事务管理器(KTM)。(有关KTM的更多信息,参见第3章。)
有关这一工具所显示信息的详细描述,参见Dependency Walker的帮助文件(Depends.hlp)。
设备驱动程序
本书下册第8章才会详细介绍设备驱动程序,这一节先大致介绍一下驱动程序的类型,并说明如何列出系统中已经安装和加载的驱动程序。
设备驱动程序是可加载的内核模式模块(通常以.sys结尾),它们在I/O管理器和相应的硬件之间建立起连接。设备驱动程序运行在内核模式下,位于以下三种执行环境之一:
- 在发起/O功能的用户线程的环境中。
- 在内核模式系统线程的环境中。
- 作为中断的结果(因此它不在任何特定的进程或者线程的执行环境中一一当该中断产生时,无论当前进程或者线程是哪个)。
正如上一节所述,Windows中的设备驱动程序并不直接维护硬件,而是调用HAL中的函数与硬件打交道。驱动程序往往是用C(有时候用C++)来编写的,因此,通过正确地使用HAL例程,驱动程序可以在Windows)所支持的CPU体系架构间进行源代码级的移植,而在同一个体系架构族内则是二进制可移植的。
设备驱动程序有以下几种类型:
-
硬件设备驱动程序(hardware device driver)
通过HAL操纵硬件,从而将输出写到物理设备或网络中,或者从物理设备或网络上接收输入。硬件设备驱动程序也有许多类型,比如总线驱动程序、人机界面驱动程序、大容量存储设备驱动程序,等等。 -
文件系统驱动程序(file system driver)
是指这样的Windows驱动程序:可以接受面向文件的/O请求,并且将这些请求转换成针对某一特定设备的/O请求。 -
文件系统过滤驱动程序(file system filter driver)
,比如那些执行磁盘镜像和加密的驱动程序,或者那些截取I/O请求并执行某些增值处理之后再把I/O传递给下一层的驱动程序。 -
网络重定向器(network redirector)和服务器
,分别指那些将文件系统I/O请求传递给网络上某台机器,或者从网络上接收此类请求的文件系统驱动程序。 -
协议驱动程序(protocol driver)
,实现诸如TCP/IP、NetBEUI和IPX/SPX之类的网络协议。 -
内核流式过滤驱动程序(kernel streaming filter driver)
,这样的驱动程序被串接起来对数据流进行信号处理,比如录制或者播放视频和音频。
要在系统中添加用户编写的内核模式代码,安装驱动程序是唯一的方法,所以有些程序员把编写设备驱动程序当作是一种访问操作系统内部函数和数据结构的简便方法(在用户模式下它们是不可访问的,但是在WDK中有文档以及相应的支持)。例如,Sysinternals的许多工具是由Windows GUI)应用程序和设备驱动程序组成的,其中驱动程序用于收集系统的内部状态,以及调用那些只可在内核模式下访问的函数(从用户模式Windows API中无法访问)。
Windows驱动模型
Windows2000增加了对即插即用、电源选项的支持,同时也扩展了Windows NT的驱动程序模型,新的模型称为WDM(Windows Driver Model,Windows驱动程序模型)。Windows200d及以后的版本都可以运行老的Windows NT4驱动程序,但是因为这些驱动程序不支持即插即用和电源选项,所以运行这些驱动程序的系统在这两方面的能力都会有所退化。
从WDM的角度来看,有以下三种驱动程序。
-
总线驱动程序(bus driver)为总线控制器、适配器、桥或任何带有子设备的设备提供服务
。总线驱动程序是必需的驱动程序,通常Microsoft:会提供此类驱动程序:系统中的每一种总线类型(比如PCI、PCMCIA和USB)都有一个总线驱动程序。第三方也可以编写总线驱动程序来为新的总线提供支持,比如VMEbus、Multibus和Futurebus。 -
功能驱动程序(function driver)是主要的设备驱动程序
,它为相应的设备提供可操作的接口。功能驱动程序也是必需的,除非相应的设备可以按照原始的方式直接使用(指一种特殊的实现,其/O可通过总线驱动程序和总线过滤驱动程序来完成,比如SCSI PassThru)。根据定义,功能驱动程序最了解具体的设备,它往往是唯一能访问与该设备相关的寄存器的驱动程序。 -
过滤驱动程序(filter driver)用来为某一设备(或已有的驱动程序)增加新的功能
,或者修改来自其他驱动程序的/O请求或应答(也常常用来修补那些未能提供正确的硬件资源需求信息的硬件设备)。过滤驱动程序是可选的,可以有任意数目,可以放在功能驱动程序之上或之下,也可以放在总线驱动程序之上。通常,系统原始设备制造商(OEM)或者独立硬件供应商(HV)会提供过滤驱动程序。
在WDM驱动程序环境中,对于设备而言,并不是由单个驱动程序来控制它的所有方面:总线驱动程序负责向PP管理器报告其总线上的设备,而功能驱动程序用于操纵该设备。
在大多数情况下,较低层的过滤驱动程序用于改变设备硬件的行为。例如,若一个设备向其总线驱动程序报告它需要4个I/O端口,但实际需要16个/O端口,那么与该设备相关的低层功能过滤驱动程序可以截获总线驱动程序向PP管理器报告的硬件资源列表,并更新/O端口的数目。
较上层的过滤驱动程序通常为设备提供一些增值特性,比如一个针对键盘的上层过滤驱动程序可以强制加上额外的安全检查。
关于中断处理将在第3章中解释。有关I/O管理器、WDM、即插即用和电源选项的详细介绍,参见本书下册第8章。
Windows驱动程序基础(WDF)
Windows驱动程序基础(WDF,Windows Driver Foundation)简化了Windows!驱动程序的开发,它提供两个框架:内核模式驱动程序框架(KMDF,Kernel–Mode Driver Framework)和用户模式驱动程序框架(UMDF,User-Mode Driver Framework)。开发人员可以用KMDF来为Windows2000SP4及以后的系统编写驱动程序,而UMDF仅支持Windows XP及以后的系统。
KMDF提供了一个简单的WDM接口,向驱动程序开发人员隐藏了WDM的复杂性,开发人员无须修改底层的总线/功能/过滤驱动程序模型。KMDF驱动程序响应它们登记过的事件,并调用KMDF库来完成那些并不特定于它们所管理硬件的工作,比如一般性的电源管理或同步(以前,每个驱动程序必须要实现它自己的一份管理工作)。在有些情况下,200多行的WDM代码可以用一个简单的KMDF函数调用来替代。
UMDF使得某些特定类型的驱动程序(主要是基于USB或其他高延迟协议的总线)可以实现为用户模式的驱动程序,比如摄像机、MP3播放器、移动电话、PDA以及打印机的驱动程序。
UMDF运行每个用户模式驱动程序,本质上这是一个用户模式服务,它使用ALPC与真正访问硬件的内核模式包装驱动程序进行通信。如果一个UMDF驱动程序崩溃了,它的进程将死掉,通常还会重新启动,所以系统不会招致不稳定一一仅仅在宿纳该驱动程序的服务进程重新启动的过程中该设备不可用。最后,UMDF驱动程序是用C+按照COM风格的类和语义来编写的,可以进一步降低程序员编写设备驱动程序的门槛。
实验:查看已安装的设备驱动程序
通过运行Msinfo32程序,可以列出已安装的驱动程序。(要启动该程序,可以单击“开始”
菜单,输入Msinfo32然后按Enter键。)在“系统摘要(System Summary)”下面,展开“软件环境(Software Environment)”,选择“系统驱动程序(System Drivers)”。
下面是一个例子,其中显示了当前已安装的驱动程序的列表:
该窗口显示了注册表中定义的设备驱动程序列表、它们的类型以及它们的状态(正在运行或者已经停止)。设备驱动程序和Windows服务进程是在同一个地方定义的:HKLM SYSTEM\\CurrentControlSet\\Services。通过类型代码可以区分这两者一一例如,类型1是指内核模式设备驱动程序(在注册表中存储的有关设备驱动程序的完整信息列表,参见第4章中的表
4.7)。
或者,也可以利用Process Explorer来列出当前已加载的设备驱动程序。做法是,在Process Explorer中,选择System进程,打开DLL视图。
尚未文档化的接口
在关键的系统映像(比如Ntoskrnl…exe、Hal.dll或Ntdl.dl)中查看导出符号或全局符号的名称或许是非常有启发的一一你可以对诸如“Windowsi能够做什么”以及“今天哪些已被文档化并提供了支持”有一个清晰的概念。当然,知道这些函数的名称并不等于你就可以或者应该调用这些函数一一这些接口并未被文档化,很可能会有变化。我们建议,查看这些函数只是为了对Windows执行的内部功能有更清楚的认识,而非为了绕过那些已经正式支持的接口。
例如,查看Ntdll.dl中的函数列表可以让你了解到,Windows为用户模式子系统DLL提供的所有系统服务,以及相对于每个子系统暴露出来的子集。尽管这些函数中有许多被明确地映射到了文档中定义和支持的Windowsi函数上,但还是有一些函数没有通过Windows API暴露出来。(参见Sysinternals.上的“Inside the Native API”一文。)
相反地,检查一下Windows子系统DLL(比如Kernel32.dl或Advapi.32.dl)的导入表,以及它们调用了Ntdl中的哪些函数,也是非常有意思的。
另一个有趣的、值得转储出来看一看的映像文件是Ntoskrnl…exe一一尽管内核模式设备驱动程序使用的许多导出函数在WDK中已经有文档了,但是仍然有相当多的导出函数尚未被文档化。你可能会发现,看一看Ntoskrnl和HAL的导入表也是十分有意思的:该导入表显示了Ntoskrnl使用了HAL中的哪些函数,以及反过来HAL使用了Ntoskrnl的哪些函数。
表2.5列出了执
以上是关于[笔记]深入解析Windows操作系统《二》系统架构的主要内容,如果未能解决你的问题,请参考以下文章
翻译《深入解析windows操作系统第6版下册》第10章:内存管理