文章目录

前言

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系统架构

基本架构

[笔记]深入解析Windows操作系统《二》系统架构_系统服务

[笔记]深入解析Windows操作系统《二》系统架构_应用程序_02

系统软件层面组成

  • 注册表
  • 事件日志
  • 本地组和本地安全策略

2.4 关键系统组件

Windows系统架构

[笔记]深入解析Windows操作系统《二》系统架构_应用程序_03

环境子系统和子系统DLL

子系统启动

子系统是由会话管理器(Session Manager)(Smss.exe)进程启动起来的,子系统的启动信息保存在注册表键

HKLMISYSTEM\CurrentControlSet\Control\Session Manager\Subsystems

的下面。

[笔记]深入解析Windows操作系统《二》系统架构_windows_04

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端口和等待事件。

[笔记]深入解析Windows操作系统《二》系统架构_系统服务_05


因为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,也会显示出来。

[笔记]深入解析Windows操作系统《二》系统架构_系统服务_06


[笔记]深入解析Windows操作系统《二》系统架构_设备驱动程序_07


可以使用dt命令,直接将KPCR和KPRCB数据结构转储出来,因为上述两个调试器命令已经给出了数据结构的地址(在上面的输出中用粗体显示)。例如,如果想要知道处理器的速度,可以用下面的命令来查看MHz域

[笔记]深入解析Windows操作系统《二》系统架构_windows_08


在这台机器上,处理器以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.系统”的问题便不复存在。

[笔记]深入解析Windows操作系统《二》系统架构_系统服务_09

实验:确定当前正在运行哪个HAL

利用WinDbg,可以确定当前正在运行HAL的哪个版本,做法是,打开一个本地内核调试会话,确保已经加载了符号(输入.reload),然后输入1 m mv ha1命令。例如,下面的输出来自于一个运行ACPI HAL的系统:

[笔记]深入解析Windows操作系统《二》系统架构_系统服务_10


[笔记]深入解析Windows操作系统《二》系统架构_系统服务_11

实验:查看NTOSKRNL和HAL映像的依赖关系

可以利用Dependency Walker.工具(Depends.exe)来检查内核和HAL映像文件的导出和导入表,从而了解它们之间的关系。要在该工具中检查映像文件,可以从File菜单中选择Open命令来打开目标映像文件。

使用该工具来查看Ntoskrnl的依赖性,可能看到如下图所示输出:

[笔记]深入解析Windows操作系统《二》系统架构_应用程序_12


注意,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操作系统《二》系统架构_应用程序_13


该窗口显示了注册表中定义的设备驱动程序列表、它们的类型以及它们的状态(正在运行或者已经停止)。设备驱动程序和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列出了执行体组件通常会用到的大多数函数名称前缀。这些主要的执行体组件,其中每个也都会使用前缀的变化形式来标记内部函数一一前缀的第一个字母后面跟一个ⅰ(代表internal,即内部的),或者整个前缀后面跟一个p(代表private,即私有的)。例如,Ki代表内核的内部函数,而Psp指内部的进程支持函数。

[笔记]深入解析Windows操作系统《二》系统架构_应用程序_14


[笔记]深入解析Windows操作系统《二》系统架构_windows_15

系统进程

以下系统进程会出现在每一个Windows系统中(其中有两个一一Idle进程和System进程一并不是完整的进程,因为它们并不是在运行用户模式的可执行文件):

  • Idle进程(它为每个CPU包含一个对应的线程,占用空闲的CPU时间)。
  • System进程(包含大多数内核模式系统线程)。
  • 会话管理器(Smss.exe)。
  • 本地会话管理器(Lsm.exe)。
  • Windows子系统(Csrss.exe)。
  • 会话0初始化(Wininit.exe)。
  • 登录进程(Winlogon.exe)。
  • 服务控制管理器(Services.exe)和它创建的子服务进程(比如系统提供的通用服务宿主进程Svchost…exe)。
  • 本地安全认证服务器(Lsass.exe)。

为了理解这些进程的关系,查看一下系统的进程“树”,也即进程之间的父/子关系,是很有帮助的。看一看哪个进程创建了其他进程,将有助于理解每个进程是从哪里来的。图25的屏幕截图显示了从Process Monitor的引导跟踪日志得到的进程树。利用Process Monitor可以看到哪些进程已经退出(用模糊的图标来指示)。

[笔记]深入解析Windows操作系统《二》系统架构_系统服务_16


接下来的几部分将讲述图2.5中显示的关键系统进程。这些部分简要地说明了进程启动的顺序,本书下册第13章将详细地描述在Windows引导和启动过程中涉及的步骤。

系统空闲进程

图2.5中列出的第一个进程是系统空闲进程(Ide)。在第5章中会介绍,进程是由它们的映像文件名称来标识的。然而,这个进程(以及名为System的进程)并没有运行一个实际的用户模式映像文件(也就是说,在Windows目录下没有名为“System Idle Process.exe”的文件)。
而且,在不同的工具中,该进程的显示名称也不尽相同(由于实现细节的原因)。表2.6列出了空闲进程的一些名称(其进程D为0)。第5章中会详细地解释空闲进程。

[笔记]深入解析Windows操作系统《二》系统架构_应用程序_17


[笔记]深入解析Windows操作系统《二》系统架构_设备驱动程序_18

System进程和系统线程

System进程(进程ID为4)是某种特殊线程的母体,这种特殊线程只能在内核模式下运行,称为内核模式系统线程(kernel-mode system thread)。系统线程具备普通用户模式线程的所有属性和环境(比如硬件环境、优先级,等等),但是不同的地方在于,它们只在内核模式下运行系统空间中加载的代码,无论这些代码是在Ntoskrnl.exe中,还是在任何其他加载进来的设备驱动程序中。而且,系统线程没有用户进程地址空间,因此,任何的动态存储需求,都必须从操作系统的内存堆中分配,比如从一个换页的或者非换页的内存池中分配。

系统线程是通过PsCreateSystemThread函数(WDK中有文档说明)来创建的,该函数只能从内核模式中调用。Windows以及各种设备驱动程序在系统初始化阶段创建系统线程,以便执行各种要求线程环境的操作,比如发出和等待IO或其他对象,或者查询(poll))一个设备。例如,内存管理器使用系统线程来实现诸如“将脏页面写到页面文件或者映射文件中”“将进程在内存中换进/换出”之类的功能。内核会创建一个称为平衡集管理器(balance set manager)的系统线程,它每秒钟被唤醒一次,从而有可能发出各种与调度和内存管理相关的事件。缓存管理器也使用系统线程来实现“预读(read-ahead)”和“滞后写(write-behind)”的IO。文件服务器设备驱动程序(Srv2.sys)利用系统线程来响应那些“针对已共享磁盘分区上的文件数据”的网络I/O请求。甚至软盘驱动程序也有一个系统线程来查询软盘设备(在这种情况下,定期查询更加有效,因为一个靠中断驱动的软盘驱动程序要消耗大量的系统资源)。有关特定系统线程的进一步信息,见相应组件的章节。

在默认情况下,系统线程是属于System进程的,但是,设备驱动程序可以在任何一个进程中创建系统线程。例如,Windows子系统设备驱动程序(Win32k.sys)在规范的显示驱动程序(Canonical Display Driver,Cdd.dl)中创建一个系统线程,这里Cdd.dll是Windows子系统进程(Csrss.exe)的一部分,因而新线程可以很容易地访问该进程用户模式地址空间中的数据。

在诊断或者进行系统分析时,若能够将一个系统线程的执行过程映射回驱动程序上,甚至映射到包含该代码的子例程,一定非常有用。例如,在一个负载很重的文件服务器上,System进程很有可能会消耗掉相当可观的CPU时间。但是,仅仅知道“System进程运行时‘某个系统线程’正在运行”,是不足以确定哪个设备驱动程序或者操作系统组件正在运行的。

所以,如果System进程中的线程正在运行,则首先要确定哪些线程正在运行(例如,通过性能监视器)。一旦找到了当前正在运行的一个或者多个线程,就可以查看一下该系统线程是在哪个驱动程序中开始执行的(至少可以知道可能是哪个驱动程序创建了这个线程),或者检查一下所涉及的线程的调用栈(或至少当前的地址),通过调用栈可以知道该线程当前正在哪里执行。
下面的实验演示了这两项技巧。

实验:将系统线程映射到一个设备驱动程序

在这个实验中,我们会看到,如何将System进程中的CPU活动映射到产生该活动的相应的系统线程(以及它所在的驱动程序)。这很重要,因为当System进程正在运行时,你必须在线程粒度上才能真正理解当前正在发生的事情。在这个实验中,我们将在你的机器上产生文件服务器的活动,以此来生成系统线程活动。(文件服务器驱动程序,Sv2.sys,创建系统线程来处理所接收到的文件/O请求。有关该组件的更多信息,参见第7章。)

1.打开一个命令提示符窗口。
2.通过一个网络路径来访问你的C驱动器,列出整个C驱动器的所有目录。例如,如果你的计算机名称是COMPUTERI,则输入dir \computer1\c$/s(这里的s开关指示列出所有的子目录)。
3.运行Process Explorer,双击System进程。
4.单击Threadsi选项卡。
5.按照“CSwitch Delta”(环境切换的差量)列来排序。你应该看到,有一个或者多个线程正在Sv2.sys中运行,如下图所示。

[笔记]深入解析Windows操作系统《二》系统架构_应用程序_19


如果你看到一个系统线程正在运行,但不确定对应的驱动程序是哪个,那么,请单击Module按钮,然后将会弹出文件属性对话框。如上图那样,当Srv2.sys中的线程被加亮显示时单击Module按钮,就会得到下面的显示结果。

[笔记]深入解析Windows操作系统《二》系统架构_系统服务_20

会话管理器

会话管理器(%SystemRoot%\System32.Smss.exe)是系统中创建的第一个用户模式进程。这一进程由负责完成执行体和内核初始化工作最后阶段的内核模式系统线程创建。

Smss启动时,会检查自己是第一个实例(主Smss),还是主Smss为了创建会话而启动起来的一个实例。(如果存在命令行参数,则是后一种情形。)通过在引导过程中以及在终端服务会话的创建过程中创建多个Smss实例,Smss可以同时创建多个会话(最多4个并发会话,再为除去一个CPU外每个额外的CPU加上一个额外的会话)。这一能力增强了终端服务器系统的登录性能,在这种终端服务器系统上,同时会有许多个用户连接上来。一旦一个会话完成了初始化,该会话的Smss副本便终止。因而结果是,只有初始的Smss.exe进程仍然是活动的。(关于终端服务的描述,参见第1章的“终端服务及多个会话”一节。)

主Smss执行下面的一次性初始化步骤:

  1. 将该进程和初始线程标记为“关键的(critical)”。(如果一个被标记为“关键的”的
    进程或线程退出,则Windows崩溃。更多信息参见第5章。)
  2. 将进程的基本优先级提升到11。
  3. 如果系统支持动态增加热处理器,则允许自动更新处理器亲和性,这样,如果新的处
    理器被加入进来,新的会话将可以利用这些新加入的处理器。(有关动态增加处理器的更多信息,参见第5章。)
  4. 创建相应的命名管道和邮件槽,用于Smss、Csrss、Lsm(本章后文将介绍)之间的通
    信。
  5. 创建ALPC端口接收命令。
  6. 根据HKLMISYSTEM\CurrentControlSet\ControllSession Manager\Environment中的定
    义,创建系统范围的环境变量。
  7. 根据HKLM\SYSTEMI\CurrentControlSet\ControlSession Manager\DOS Devices中的定
    义,在对象管理器名字空间的\Global??目录下为该注册表键中定义的设备创建符号链接。
  8. 在对象管理器名字空间中创建\Sessions根目录。
  9. 运行HKLMSYSTEMCurrentControlSet\Contro\Session Manager’BootExecute中的程
    序。(默认是Autochk.exe,执行磁盘检查。)
  10. 根据HKLM\SYSTEMCurrentControlSetlControlSession Manager\PendingFileRename
    Operations中指定的信息,处理尚未完成的文件改名操作。
  11. 初始化页面文件(paging file)。
  12. 初始化注册表的其余部分(HKLM Software、SAM和Security储巢 〔hive))。
  13. 运行HKLMISYSTEM\CurrentControlSet\ControllSession ManagerlSetupExecute中的程
    序。
  14. 打开已知DLL (HKLMISYSTEMICurrentControlSet\ControlSession ManagerlKnown
    DLLs),将它们映射为永久内存区(映射文件)。
  15. 创建一个线程来响应会话创建请求。
  16. 创建Smss来初始化会话0(非交互会话)。17.创建Smss来初始化会话1(交互会话)。

一旦这些步骤完成,Smss将永远在会话0的Csrss…exe实例的句柄上等待。因为Csrss被标记为关键进程(参见第5章),所以,如果Csss退出,这一等待操作将永远不会完成,因为此时系统会崩溃。

会话启动的Smss实例将完成以下事项:

  1. 调用NtSetSystemInformation,请求建立起内核模式的会话数据结构。这又进而调用到内部的内存管理器函数MmSessionCreate,它建立起会话虚拟地址空间,其中包含该会话的换页内存池,以及由Windows-子系统的内核模式部分(Win32k.sys)和其他的会话空间设备驱动程序所分配的属于每个会话的数据结构(更多细节参见本书下册第10章)。
  2. 为该会话创建子系统进程(默认情况下,为子系统进程Csrss.exe)。
  3. 创建Winlogon实例(对于交互会话)或者Wininit实例(对于会话0)。关于这两个进程
    的更多信息,参见本章后面的内容。

然后,这一中间Smss进程退出(留下子系统进程和Winlogon或Wininit成为无父进程)。

Windows初始化进程(Wininit.exe)

Wininit.exe进程执行下面的系统初始化功能:

  • 将自己标记为“关键的”,因而,如果它过早地退出,并且系统是在调试模式下引导起来的,那么,它将会在调试器中断下来(若不然,系统将崩溃)。
  • 初始化用户模式调度设施。
  • 创建%windir%\temp文件夹。
  • 创建一个窗口站( Winsta0)和两个桌面(Winlogon和Default),以便会话0中的进程可以在其中运行。
  • 创建Services.exe(服务控制管理器,SCM)。关于SCM,本章稍后简要介绍,更多细节参见第4章。
  • 启动Lsass.exe(本地安全认证子系统服务器)。有关Lsass的更多信息,参见第6章。
  • 启动Lsm.exe(本地会话管理器)。本章后面的“本地会话管理器(Lsm.exe)”小节将
    简要介绍。
  • 一直等待,直至系统停机。

服务控制管理器(SCM)

回顾本章前面提到过的,Windows中的“服务( service)”既可以指服务器进程,也可以指设备驱动程序。这一节讲的服务是指用户模式的进程。服务就像UNIX中的“守护进程(daemon process)”或者VMS中的“分派进程(detached process)”,这些进程可以被配置成能在系统引导时自动启动起来,而不要求有交互式的登录过程,也可以被手工启动(比如,通过运行“服务(Services)”管理工具,或者调用Windows的StartService函数)。在通常情况下,这些服务并不与登录用户进行交互,尽管在特殊条件下这也是有可能的(参见第4章)。

服务控制管理器是一个特殊的系统进程,它运行的映像文件是%SystemRoot%System32Services.exe,它负责启动、停止服务进程,也负责与这些服务进程进行交互。所谓服务程序,实际上只是调用了一些特殊Windows函数的Windows映像文件。它们通过这些特殊的Windows函数,与服务控制管理器进行交互,以便执行诸如此类的动作:注册一个服务的成功启动、响应状态请求,以及暂停或停止一个服务。Windows服务定义在注册表的HKLMISYSTEMCurrentControlSetlServices下。

记住,服务有三种名称:在系统中看到的正在运行的进程名称、注册表中的内部名称,以及在“服务”管理工具中给出的显示名称(并不是所有的服务都有显示名称—一如果一个服务没有显示名称,则显示内部名称)。在Windows中,服务还可以有一个描述域( description field),以进一步详细说明该服务所做的事情。
要想从一个服务进程映射到该进程所包含的服务,可以使用tlist ls或tasklist /svc命令。然而,请注意,服务进程和所运行的服务之间并不总是一一对应的,因为有的服务与其他的服务共享一个进程。在注册表中,一个服务的类型代码指明了该服务是运行它自己的进程,还是与同一映像中的其他服务共享一个进程。

有许多Windows组件是以服务的方式来实现的,比如Print Spooler(假脱机打印)、事件日志、任务计划,以及各种网络组件。有关服务的更多细节,参见第4章。

实验:列出当前安装的服务

为了列出当前系统中所安装的服务,请从控制面板中选择“管理工具(AdministrativeTools)”,然后选择“服务(Services)”。你应该会看到如下所示输出:

[笔记]深入解析Windows操作系统《二》系统架构_windows_21


要想看到有关一个服务的详细属性,请在这个服务上右键单击,然后选择“属性(Properties)”。例如,下面是Print Spooler服务(在上图中被加亮显示)的属性:

[笔记]深入解析Windows操作系统《二》系统架构_应用程序_22


注意,“可执行文件的路径(Path to executable)”域指明了包含这一服务的程序。记住,有的服务与其他的服务共享同一个进程一一从服务到进程的映射关系并不总是一一对应的。

实验:检查服务进程内部的有关服务的细节

Process Explorer可以加亮显示那些包含.一个或在一个包含有服务的进程上双击,你单中选择“Configure Colors”就可以配置成这样)。如架仕一个媒于展个昭务,显示了该服将会看到有一个Services选项卡,具中列出 孩进E以风术该服冬的文本信息(如果有的话),务的注册表键的名称、管理员可见的显示名称,以及描服务的个运行在Svstcm账号下的对于Svchost服务,还会列出实现该服务的DLL路径。例如,一个运行在System账号下的

Svchost.exe进程中所包含的服务如下图所示:

[笔记]深入解析Windows操作系统《二》系统架构_windows_23

本地会话管理器(Lsm.exe)

本地会话管理器(Lsm.exe)管理本地机器上的终端服务器会话的状态。它通过ALPC端口SmSs WinStationApiPort向Smss发送请求启动新的会话(例如,创建Csrss和Winlogon进程),比如当用户在资源管理器(Explorer)中选择“切换用户(Switch User)”时。Lsm也跟Winlogon和Csrss。
进行通信(通过一个本地系统RPC)。它通知Csrss诸如建立连接、断开连接、终止等事件,也广播系统消息。

对于以下的事件,它接收Winlogon的通知:

  • 登录(Logon)和注销(Logoff)。
  • Shell/启动和终止。
  • 连接到一个会话。
  • 与一个会话断开连接。
  • 锁住或解锁桌面。

WinLogon、LogonUl和UserInit

Windows登录进程(%SystemRoot%System32\Winlogon.exe)处理交互式用户的登录和注销。当安全注意序列(SAS,Secure Attention Sequence)组合键被按下时,Winlogon就会接到一个用户登录请求。在Windows上默认的SAS是组合键Ctrl+Alt+Delete。使用SAS是为了保护用户免受那些模拟登录过程的口令窃听程序欺骗,因为用户模式应用程序不可能截取这一键盘序列。
登录过程的身份识别和认证是通过一种称为凭证提供者(credential provider)的DLL来实现的。标准的Windows凭证提供者实现了默认的Windows认证接口:口令和智能卡。不过,开发人员可以提供他们自己的凭证提供者来实现其他的身份识别和认证机制—一比如基于声波纹(voice print)的方法,或者像指纹阅读器这样的生物采集设备——以替换Windows标准的用户名/口令方法。因为Winlogon是系统依赖的一个关键系统进程,所以,凭证提供者和显示登录对话框的UI都运行在Winlogon的一个子进程中,称为LogonUI。当Winlogon检测到SAS时,它就启动这一进程,该进程会初始化凭证提供者。一旦用户输入了凭证,或者取消了登录界面,LogonUI进程就终止。
而且,Winlogon可以加载那些需要执行二级认证的附加网络提供者DLL。这种能力使得多个网络提供者可以在正常的登录过程中一次采集到所有的身份识别和认证信息。
一旦获取到用户名和口令,就可以将它们送到本地安全认证服务器进程(%SystemRoot%lSystem32\Lsass.exe,第6章中讲述)进行认证。LSASS调用适当的认证包(实现为DLL的形式)执行实际的验证工作,比如检查该口令是否与存储在活动目录或者SAM(属于注册表的一部分,其中包含了关于本地用户和组的定义)中的口令相符。

在成功地完成了认证以后,LSASS调用安全引用监视器中的一个函数(例如,NtCreateToken),以生成一个访问令牌对象,该访问令牌对象包含了当前用户的安全轮廓(security profile)。如果系统使用了UAC(用户账户控制),并且正在登录的用户是管理员组的成员或者具有管理员特权,那么,LSASS将创建该令牌的另一个受限版本。然后,Winlogon利用此访问令牌来创建该用户会话中的初始进程。这一(或这些)初始进程被存储在注册表键HKLMISOFTWAREMicrosoft\Windows NTICurrentVersion\Winlogon下的注册表值Userinit中(默认是Userinit.exe,不过,在该注册表值中可以列出多个映像名称。)

Userinit执行用户环境的一些初始化工作(比如运行登录脚本、应用组策略),然后在注册表中查找Shell值(在上一段提到的Winlogon键的下面),并且创建一个进程来运行系统定义的外壳(shell)程序(默认是Explorer.exe)。然后,Userinit退出。这正是Explorer.exe在进程树中没有父进程的原因—一它的父进程已经退出,而且如第1章中所解释的那样,tlist将所有父进程已不在运行的进程左对齐。(看待这一现象的另一种方法是: Explorer是Winlogon的孙子进程。)

Winlogon不仅在用户登录和注销的时候是活动的,无论何时,只要它截取到键盘的SAS就是活动的。例如,在你登录进来后,若按下了Ctrl+Alt+Delete组合键,则Windows安全屏幕会出现,提示注销、启动任务管理器、锁定工作站、关闭系统等选项。Winlogon和LogonUI是负责处理这一交互过程的进程。

关于登录过程中各个步骤的完整描述,参见本书下册第13章的“Smss、Csrss和Wininit"一节。有关安全认证的更多细节,参见第6章。关于在跟LSASS交互时可调用函数(以Lsa开头的函数)的详细信息,参见Windows SDK中的文档。

总结

在本章中,我们概括地看了看Windows的总体系统架构。我们检查了Windows的关键组件,看到了它们是如何相互关联起来的。

在下一章,我们将更加详细地看一看这些组件所赖以建立起来的核心系统机制,比如对象管理器(object manager)和同步(synchronization)。