分类: WINDOWS
2008-01-12 17:10:46
硬件抽象层(HAL)
正如本章 开始时所提到的,Windows设计的关键要素之一是,它能被移植到各种不同的硬件平台上。硬件抽象层是使得这种移植性成为可能的一个关键部分。HAL是 一个可加载的、内核模式的模块,它提供了针对Windows当前运行所在的硬件平台的低层接口。它隐藏了与硬件相关的细节,比如I/O接口、中断控制器, 以及多处理器通信机制——任何与体系结构相关或者与机器相关的功能。
所以, Windows内部组件以及用户编写的设备驱动程序并不直接访问硬件;相反,当它们需要获得与平台相关的信息时,它们可以通过调用HAL例程来保持可移植 性。出于这一原因,针对这些HAL例程,也有相应的文档包含在Windows DDK中。要想找到更多有关HAL及其在设备驱动程序中用法的信息,请参考DDK。
虽然 Windows随带了几个HAL(见表2.6),但是在安装的时候只有一个HAL被选中,并且被拷贝到系统磁盘上,其文件名为Hal.dll(其他的操作 系统,比如VMS,在系统引导的时候选择一个等价于HAL的模块)。因此,如果支持两个不同处理器的HAL有所不同的话,你就不能这样假设:一个x86已 安装系统的系统磁盘也可以在另一个处理器上引导。
表2.6 在\Windows\Driver Cache\i386\Driver.cab中的x86 HAL列表
HAL文件名 |
所支持的系统 |
Hal.dll |
标准PC |
Halacpi.dll |
高级配置和电源接口(ACPI) PC |
Halapic.dll |
高级可编程中断控制器(APIC) PC |
Halaacpi.dll |
APIC ACPI PC |
Halmps.dll |
多处理器PC |
Halmacpi.dll |
多处理器ACPI PC |
Halborg.dll |
Silicon图形工作站(仅仅在Windows 2000中才有,市场上已经不再销售) |
Halsp.dll |
Compaq SystemPro (仅仅在Windows XP中才有) |
注 从Windows Server 2003开始,在Windows基本系统中不再随带与特定厂商相关的HAL。
实验:查看Windows中包含的基本HAL
要想检查Windows中包含的HAL,请打开\Windows\Driver Cache下面某个体系结构文件夹中的Driver.cab文件。(例如,对于x86系统来说,该文件名是\Windows\Driver Cache\i386\Driver.cab。)滚动到以Hal为开头的文件处,你应该可以看到表2.6中列出的那些文件。
实验:确定你当前正在运行哪个HAL
有以下两种方法可以确定你当前正在运行哪个HAL。
1. 打开文件\Windows\Repair\Setup.log,搜索Hal.dll,检查等号后面的文件名。该文件名是指在安装介质上从Driver.cab中提取出来的HAL的名字。
2. 在设备管理器中(在你的桌面上右键点击“我的电脑(My Computer)”),选择“属性(Properties)”,然后点击“硬件(Hardware)”标签,再点击“设备管理器(Device Manager)”),看一看在“计算机(Computer)”设备类型下面的“driver”的名字。例如,下面的屏幕拷贝是从某一个运行ACPI HAL的系统中截取下来的:
实验:查看NTOSKRNL和HAL的映像相依性
你 可以利用相依性检查工具(Dependency Walker tool,Depends.exe)来检查内核和HAL映像文件的导出和导入表,从而了解它们之间的关系。至于此相依性检查工具,你可以从Windows 支持工具箱或者Platform SDK中找到。要想在该工具中检查一个映像文件,你可以从File菜单中选择Open命令,来打开目标映像文件。
如果你使用该工具来查看Ntoskrnl的相依性,下图是你可能看到的一个输出例子:
注 意,Ntoskrnl链接了HAL,而HAL又链接了Ntoskrnl(它们相互使用了对方的函数) 。Ntoskrnl也链接了Bootvid.dll,这是用来实现GUI启动屏幕的引导视频驱动程序。在Windows XP及其以后,你在列表中还会看到一个附加的DLL:Kdcom.dll。该DLL包含了内核调试器的基础设施代码,在过去,这些代码是 Ntoskrnl.exe的一部分。
有关相依性检查工具的详细描述信息,请参考Dependency Walker的帮助文件(Depends.hlp)。
设备驱动程序
虽然要到第9章才介绍设备驱动程序,不过,这一部分先大致介绍一下驱动程序的类型,并且解释一下如何列出你的系统中已经安装和加载的驱动程序。
设备驱动程序是可加载的内核模式模块(通常以.sys结尾),它们在I/O管理器和相应的硬件之间建立起链接。设备驱动程序运行在内核模式下,位于以下三种执行环境之一:
n 在发起I/O功能的用户线程的环境中;
n 在内核模式系统线程的环境中;
n 作为一个中断的结果(因此它不在任何特定的进程或者线程的执行环境中 ——当该中断产生时,无论当前进程或者线程是哪个)。
正如上一部分所说的,Windows中的设备驱动 程序并不直接维护硬件,相反,它们调用HAL中的函数与硬件打交道。驱动程序往往是用C(有时候用C++)来编写的,因此,通过正确地使用HAL例程,驱 动程序可以在Windows所支持的CPU体系结构之间进行源代码级的移植,而在同一个体系结构族内则是二进制可移植的。
设备驱动程序有以下几种类型。
n 硬件设备驱动程序(hardware device drivers)通过HAL操纵硬件,从而将输出写到物理设备或网络中,或者从物理设备或网络上接收输入。硬件设备驱动程序也有许多类型,比如总线驱动程序、人机界面驱动程序、大容量存储器驱动程序,等等。
n 文件系统驱动程序(file system drivers)是指这样的Windows驱动程序:可以接受面向文件的I/O请求,并且将这些请求转换成针对某一特定设备的I/O请求。
n 文件系统过滤器驱动程序(file system filter drivers),比如那些执行磁盘镜像和加密的驱动程序,或那些截取了I/O请求并且执行某些增值处理之后再传递给下一层的驱动程序。
n 网络重定向器和服务器(network redirectors and servers),分别是指那些将文件系统I/O请求传递给网络上某一台机器,或者从网络上接收此类请求的文件系统驱动程序。
n 协议驱动程序(protocol drivers)实现了诸如TCP/IP、NetBEUI和IPX/SPX之类的网络协议。
n 内核流式过滤器驱动程序(kernel streaming filter drivers),这样的驱动程序被串接起来,以便对数据流进行信号处理,比如录制或者播放视音频。
要想在系统中添加用户编写的内核模式代码,安装驱动程序是惟一的方法,所以有些程序员把编写设备驱动程序当做是一种访问操作系统内部函数和数据结构的简便方法(在用户模式下是不可访问的,但是在DDK中有文档以及相应的支持)。例如,来自的许多工具是由一个Windows GUI应用程序和一个驱动程序组成的,其中驱动程序用来收集内部的系统状态,以及调用那些只可在内核模式下访问(从用户模式中无法访问)的Windows API。
Windows驱动程序模型
Windows 2000增加了对即插即用、电源选项的支持,同时也扩展了Windows NT的驱动程序模型,新的模型称为WDM(Windows Driver Model,Windows驱动程序模型)。Windows 2000及以后的版本都可以运行老的Windows NT 4驱动程序,但是因为这些驱动程序不支持即插即用和电源选项,所以运行这些驱动程序的系统在这两方面的能力都会有所退化。
从WDM的角度来看,有以下三种驱动程序。
n 总线型驱动程序(bus driver), 它为总线控制器、适配器、桥或任何带有子设备的设备提供服务。总线驱动程序是必需的驱动程序,通常Microsoft会提供此类驱动程序;系统中的每一种 总线类型(比如PCI、PCMCIA和USB)都有一个总线驱动程序。第三方也可以编写总线驱动程序来为新的总线提供支持,比如VMEbus、 Multibus和Futurebus。
n 功能型驱动程序(function driver) 是主要的设备驱动程序,它们为相应的设备提供了可操作的接口。功能型驱动程序也是必需的,除非相应的设备可以直接使用(其I/O可通过总线驱动程序和总线 过滤型驱动程序来完成,比如SCSI PassThru)。根据定义,功能型驱动程序最了解某一特定的设备,而且它往往是惟一能访问与该设备相关的寄存器的驱动程序。
n 过滤型驱动程序(filter driver) 用来为某一设备(或已有的驱动程序)增加新的功能,或者修改来自其他设备的I/O请求或应答(也常常用来修补那些未能提供正确的硬件资源需求信息的硬件设 备)。过滤型驱动程序是可选的,可以有任意数目,可以放在一个功能型驱动程序之上或之下,也可以放在总线型驱动程序之上。通常,系统原始设备制造商 (OEM)或者独立硬件销售商(IHV)会提供过滤型驱动程序。
在WDM驱动程序环境中,对于一个设备而言,并不是由单个驱动程序来控制它的所有方面:一个总线型驱动程序负责向PnP管理器报告其总线上的设备,而一个功能型驱动程序操纵该设备。
在大多数 情况下,低层次的过滤型驱动程序改变设备硬件的行为。例如,若一个设备向其总线型驱动程序报告它需要4个I/O端口,但它实际却需要16个I/O端口,那 么一个与该设备相关的低层次功能过滤型驱动程序可以截获总线型驱动程序向PnP管理器报告的硬件资源列表,并更新一下I/O端口的数目。
上层的过滤型驱动程序通常为一个设备提供一些增值特性,比如一个针对键盘的上层过滤型驱动程序可以强制加上安全检查。
关于中断处理将在第3章中解释。有关I/O管理器、WDM、即插即用和电源选项的详细介绍,请参考第9章。
实验:查看已安装的设备驱动程序
通 过运行Msinfo32程序,你就可以列出当前系统中已安装的驱动程序:在“开始(Start)”菜单中,选择“运行(Run)”,然后输入 “Msinfo32”并回车,打开“系统信息”程序,从树图中展开“软件环境”,选择“系统驱动程序”。下面是一个例子,其中显示了当前已安装的驱动程序 的列表:
该窗口显示了注册表中定义的设备驱动程序列表、它们的类型以及它们的状态(正在运行或者已经停止)。设备驱动程序和Windows服务进程都是在同一个地方被定义的:HKLM\ SYSTEM\CurrentControlSet\Services。然而,它们是通过一个类型代码来区分的——例如,类型1是指内核模式的设备驱动程序(在注册表中存储的有关设备驱动程序的完整信息列表,请参见表4.7)。
另 外一种方法是,你可以利用Drivers实用工具(Drivers.exe,在Windows 2000资源工具箱中)或者Pstat实用工具(Pstat.exe,在Windows XP支持工具箱、Windows Server 2003支持工具箱、Windows 2000资源工具箱,以及Platform SDK中均包含了该工具)列出当前系统中已经加载的设备驱动程序。下面是Drivers实用工具的一部分输出:
C:\>drivers
ModuleName Code Data Bss Paged Init LinkDate
----------------------------------------------------------------
ntoskrnl.exe 429184 96896 0 775360 138880 Tue Dec 07 18:41:11 1999
hal.dll 25856 6016 0 16160 10240 Tue Nov 02 20:14:22 1999
BOOTVID.DLL 5664 2464 0 0 320 Wed Nov 03 20:24:33 1999
ACPI.sys 92096 8960 0 43488 4448 Wed Nov 10 20:06:04 1999
WMILIB.SYS 512 0 0 1152 192 Sat Sep 25 14:36:47 1999
pci.sys 12704 1536 0 31264 4608 Wed Oct 27 19:11:08 1999
isapnp.sys 14368 832 0 22944 2048 Sat Oct 02 16:00:35 1999
compbatt.sys 2496 0 0 2880 1216 Fri Oct 22 18:32:49 1999
BATTC.SYS 800 0 0 2976 704 Sun Oct 10 19:45:37 1999
intelide.sys 1760 32 0 0 128 Thu Oct 28 19:20:03 1999
PCIIDEX.SYS 4544 480 0 10944 1632 Wed Oct 27 19:02:19 1999
pcmcia.sys 32800 8864 0 23680 6240 Fri Oct 29 19:20:08 1999
ftdisk.sys 4640 32 0 95072 3392 Mon Nov 22 14:36:23 1999
----------------------------------------------------------------
Total 4363360 580320 0 3251424 432992
每一个已被加载的内核模式组件(Ntoskrnl、HAL以及设备驱动程序)都被列出来了,其中也包括每个映像中各个区域的大小。
Pstat 实用工具也可以显示已被加载的驱动程序列表,但是要等它显示了进程列表和每个进程中的线程之后才显示驱动程序列表。有一个重要的信息只有Pstat才能显 示,Drivers工具并没有显示出来,那就是每个模块在系统空间中的加载地址。正如我们稍后将要解释的那样,为了将正在运行的系统线程映射到它们所在的 设备驱动程序中,此加载地址是必需的。
看一看尚未文档化的接口
在 关键的系统映像中查看那些导出符号或全局符号的名字或许是非常有启发的——你可以对“Windows可能会做些什么样的事情”以及“今天那些已被文档化并 提供了很好支持的技术”有一个大致的概念。当然,仅仅因为你知道了这些函数的名字并不意味着你可以或者应该调用这些函数——接口并未被文档化,很可能会有 变化。我们的建议是,你纯粹是为了对Windows执行的内部功能有所认识才查看这些函数,而并非是为了绕过那些正式支持的接口。
例 如,查看Ntdll.dll中的函数列表可以让你了解到,Windows为用户模式子系统DLL提供的所有系统服务,以及相对于每个子系统暴露出来的子 集。尽管这些函数中有许多被明确地映射到文档中定义和支持的Windows函数,但还是有几个函数没有通过Windows API暴露出来(请参考上的“Inside the Native API”一文)。
相反地,检查一下Windows子系统DLL(比如Kernel32.dll或Advapi32.dll)的导入表,以及它们调用了Ntdll中的哪些函数也是非常有意思的。
另一个有趣的、值得转储的映像文件是Ntoskrnl.exe——尽管内核模式设备驱动程序使用的许多导出函数在Windows DDK中已经有了文档了,但是仍然有相当多的函数尚未被文档化。你可能会发现,看一看Ntoskrnl和HAL的导入表也是十分有意思的;该导入表显示了Ntoskrnl使用了HAL中哪些函数,以及反过来HAL使用了Ntoskrnl的哪些函数。
表2.7列出了执行体组件常用的绝大多数函数名前缀。这些主要的执行体组件,每个都会使用前缀的变形方式——前缀的第一个字母后面跟一个i(代表internal即内部的),或者整个前缀后面跟一个p(代表private即私有的)来标记内部函数。例如,Ki代表内部内核函数,而Psp指内部进程支持函数。
表2.7 常用的前缀
前缀 |
组件 |
Cc |
高速缓存管理器(Cache manager) |
Cm |
配置管理器(Configuration manager) |
Ex |
执行体支持例程(Executive support routines) |
FsRtl |
文件系统驱动程序运行库(File system driver run-time library) |
Hal |
硬件抽象层(Hardware abstraction layer) |
Io |
I/O管理器(I/O manager) |
Ke |
内核(Kernel) |
Lpc |
本地过程调用(Local procedure call) |
Lsa |
本地安全认证(Local Security Authority) |
Mm |
内存管理器(Memory manager) |
Nt |
Windows系统服务(绝大多数被导出为Windows函数)(Windows system services) |
Ob |
对象管理器(Object manager) |
Po |
电源管理器(Power manager) |
Pp |
PnP管理器(PnP manager) |
Ps |
进程支持(Process support) |
Rtl |
运行库(Run-time library) |
Se |
安全性(Security) |
Wmi |
Windows管理规范(Windows Management Instrumentation) |
Zw |
以Nt开头的系统服务入口点的镜像,它把原先的访问模式设置为内核模式,从而消除了参数的有效性检查过程,因为Nt系统服务只有当原来的访问模式为用户模式时才进行参数检查 |
如果你理解了Windows系统例程的命名规范,就可以很容易地将这些导出函数的名称解析出来。一般的格式是:
<前缀><操作><对象>
在这一格式中,“前缀”部分是导出该例程的内部组件;“操作”部分说明了在对象或者资源上做了什么工作;“对象”部分标记了是在什么上进行操作的。
例如,ExAllocatePoolWithTag是一个负责从换页的或者非换页的池里进行内存分配的执行体支持例程。KeInitializeThread则是分配并建立内核线程对象的例程。
系统进程
以下的系统进程会出现在每一个Windows系统中(其中两个——空闲进程和System进程——并不是完整的进程,因为它们并不是在运行一个用户模式的可执行文件):
n 空闲(Idle)进程(每个CPU一个线程,占用空闲的CPU时间);
n System进程(包含大多数内核模式系统线程);
n 会话管理器(Smss.exe);
n Windows子系统(Csrss.exe);
n 登录进程(Winlogon.exe);
n 服务控制管理器(Services.exe)和它创建的子服务进程(比如系统提供的通用服务宿主进程Svchost.exe);
n 本地安全认证服务器(Lsass.exe)。
为 了理解这些进程的关系,查看一下系统的进程“树”是很有帮助的。也就是说,进程之间是父/子关系。看一看哪个进程创建了其他哪个进程,将有助于理解每个进 程是从哪里来的。图2.6的屏幕截图显示了部分进程树,其中前面少数几个系统进程有相应的注释(comment)。(Process Explorer允许你为每个进程加上一条注释,然后以可选的方式显示在屏幕上,作为窗口中的一列。)
图2.6 初始的系统进程树
接下去的几部分将讲述图2.6中显示的关键系统进程。尽管这些部分简要地说明了进程启动的顺序,但是,第5章将会详细地描述在Windows引导和启动过程中涉及的步骤。
空闲进程
图2.6中列出的第一个进程是系统空闲进程。正如我们将在第6章中介绍的那样,进程是由它们的映像文件名来标记的。
然而,这个进程(以及名为System的进程)并没有运行一个实际的用户模式映像文件(也就是说,在\Windows目录下没有名为“System Idle Process.exe”的文件)。而且,在不同的工具中,该进程的显示名称是不同的(由于实现细节的原因)。表2.8列出了空闲进程的一些名称(进程ID为0)。第6章中详细解释了空闲进程。
表2.8 进程ID为0在各种工具中的名称
实用工具 |
进程ID为0的名称 |
任务管理器 |
System Idle Process |
Process Viewer (Pviewer.exe) |
Idle |
Process Status (Pstat.exe) |
Idle Process |
Process Explode (Pview.exe) |
System Process |
Task List (Tlist.exe) |
System Process |
QuickSlice (Qslice.exe) |
Systemprocess |
现在我们来看一看系统线程,以及每一个真正运行映像文件的系统进程的用途。
中断和DPC
标 记为“Interrupts(中断)”和“DPCs”的这两行代表了用于中断和延迟过程调用的时间。第3章将会介绍这两种机制。请注意,虽然在 Process Explorer中,它们被显示在进程列表中,但它们并不是进程。它们之所以被显示在这里,是因为它们所消耗的CPU时间并没有计算在任何一个进程中(例 如,若一个应用系统有大量的中断任务,则它不会被显示成一个很消耗CPU时间的进程)。注意,任务管理器将中断和DPC时间包含在系统空闲时间中。因此, 如果使用任务管理器的话,那么像这种具有大量中断任务的应用系统看起来似乎是空闲的。
System进程和系统线程
System进程(在Windows 2000,为进程ID 8;在Windows XP和Windows Server 2003中则是进程ID 4)是一种特殊线程的母体,这种特殊线程只能在内核模式下运行,称为内核模式系统线程(kernel-mode system thread)。 系统线程具备普通用户模式线程的所有属性和环境(比如硬件环境、优先级,等等),但是不同的地方在于,它们只在内核模式下运行系统空间中加载的代码,无论 这些代码是在Ntoskrnl.exe中,还是在任何其他加载进来的设备驱动程序中。而且,系统线程没有一个用户进程地址空间,因此,任何的动态存储空 间,它们都必须从操作系统的内存堆中分配,比如从一个换页的或者非换页的池中分配。
系统线程是通过PsCreateSystemThread函 数(DDK中有文档说明)来创建的,该函数只能从内核模式中调用。Windows以及各种设备驱动程序在系统初始化阶段创建系统线程,以便执行各种要求线 程环境的操作,比如发出和等待I/O或其他对象,或者选举(poll)一个设备。例如,内存管理器使用系统线程来实现诸如“将脏页面写到页面文件或者映射 文件中”、“将进程在内存中换进换出”之类的功能。
内核会创建一个称为平衡集管理器(balance set manager) 的系统线程,它每秒钟被唤醒一次,从而有可能发出各种与调度和内存管理相关的事件。高速缓存管理器也使用系统线程来实现“预读(read-ahead)” 和“滞后写(write-behind)”I/O。文件服务器设备驱动程序(Srv.sys)利用系统线程来响应那些与网络共享(即磁盘分区上被共享到网 络上的文件数据)有关的网络I/O请求。甚至软盘驱动程序也有一个系统线程来选举软盘设备(在这种情况下,选举更加有效,因为一个靠中断驱动的软盘驱动程 序要消耗大量的系统资源)。有关特定系统线程的进一步信息,包含在讲述相应组件的章节中。
在 默认情况下,系统线程是属于System进程的,但是,一个设备驱动程序可以在任何的进程中创建系统线程。例如,Windows子系统设备驱动程序 (Win32k.sys)在Windows子系统进程(Csrss.exe)中创建系统线程,因而这些线程可以很容易地访问该进程用户模式地址空间中的数 据。
当 你在诊断或者检查一个系统的时候,若能够将单独的系统线程的执行过程映射到驱动程序中,甚至映射到包含该代码的子例程中,则一定非常有用。例如,在一个负 载很重的文件服务器上,System进程很有可能会消耗掉相当可观的CPU时间。但是,仅仅知道“当System进程在运行的时候‘某一个系统线程’正在 运行”,是不足以确定哪个设备驱动程序或者操作系统组件正在运行的。
所 以,如果System进程中的线程正在运行的话,首先要确定哪些线程正在运行(例如,通过性能工具)。一旦你找到了当前正在运行的一个或者多个线程,就可 以查看一下该系统线程是在哪个驱动程序中开始执行的(至少可以告诉你可能是哪个驱动程序创建了这个线程),或者检查一下所涉及的线程的调用栈(call stack),通过调用栈可以知道该线程当前正在哪里执行。
下面的两个实验演示了这两项技巧。
实验:标识出System进程中的系统线程
你 可以看到,System进程内部的线程一定是内核模式的系统线程,因为每个线程的启动地址都大于系统空间的开始地址(默认从0x80000000开始,除 非系统引导时在Boot.ini中用了/3GB开关)。而且,如果你看一看这些线程的CPU时间,你将会看到,这些线程累积起来的CPU时间都是在内核模 式下运行的。
为了找出是哪个驱动程序创建了某个系统线程,请检查一下该线程的启动地址(你可以用Pviewer.exe来显示此信息)。然后查找哪个驱动程序的基地址离该线程的启动地址最接近(但是位于该地址的前面)。Pstat实用工具(其输出的末尾)和内核调试器命令!drivers都可以列出每个已被加载的设备驱动程序的基地址。
为了很快地找到一个线程的当前地址,请在内核调试器中使用!stacks 0命令。下面是从一个实际的系统中输出的样例(使用了LiveKd):
kd> !stacks 0
Proc.Thread Thread ThreadState Blocker
[System]
8.000004 8146edb0 BLOCKED ntoskrnl!MmZeroPageThread+0x5f
8.00000c 8146e730 BLOCKED ?? Kernel stack not resident ??
8.000010 8146e4b0 BLOCKED ntoskrnl!ExpWorkerThread+0x73
8.000014 8146d030 BLOCKED ?? Kernel stack not resident ??
8.000018 8146ddb0 BLOCKED ntoskrnl!ExpWorkerThread+0x73
8.00001c 8146db30 BLOCKED ntoskrnl!ExpWorkerThread+0x73
8.000020 8146d8b0 BLOCKED ntoskrnl!ExpWorkerThread+0x73
8.000024 8146d630 BLOCKED ntoskrnl!ExpWorkerThread+0x73
8.000028 8146d3b0 BLOCKED ntoskrnl!ExpWorkerThread+0x73
8.00002c 8146c030 BLOCKED ntoskrnl!ExpWorkerThread+0x73
8.000030 8146cdb0 BLOCKED ntoskrnl!ExpWorkerThreadBalanceManager+0x55
8.000034 8146b470 BLOCKED ntoskrnl!MiDereferenceSegmentThread+0x44
8.000038 8146b1f0 BLOCKED ntoskrnl!MiModifiedPageWriterWorker+0x31
8.00003c 8146a030 BLOCKED ntoskrnl!KeBalanceSetManager+0x7e
8.000040 8146adb0 BLOCKED ntoskrnl!KeSwapProcessOrStack+0x24
8.000044 8146a5b0 BLOCKED ntoskrnl!FsRtlWorkerThread+0x33
8.000048 8146a330 BLOCKED ntoskrnl!FsRtlWorkerThread+0x33
8.00004c 81461030 BLOCKED ACPI!ACPIWorker+0x46
8.000050 8143a770 BLOCKED ntoskrnl!MiMappedPageWriter+0x4d
8.000054 81439730 BLOCKED dmio!voliod_loop+0x399
8.000058 81436c90 BLOCKED NDIS!ndisWorkerThread+0x22
8.00005c 813d9170 BLOCKED ltmdmntt!WakeupTimerThread+0x27
8.000060 813d8030 BLOCKED ltmdmntt!WriteRegistryThread+0x1c
8.000070 8139c850 BLOCKED raspptp!MainPassiveLevelThread+0x78
8.000074 8139c5d0 BLOCKED raspptp!PacketWorkingThread+0xc0
8.00006c 81384030 BLOCKED rasacd!AcdNotificationRequestThread+0xd8
8.000080 81333330 BLOCKED rdbss!RxpWorkerThreadDispatcher+0x6f
8.000084 813330b0 BLOCKED rdbss!RxSpinUpRequestsDispatcher+0x58
8.00008c 81321db0 BLOCKED ?? Kernel stack not resident ??
8.00015c 81205570 BLOCKED INO_FLTR+0x68bd
8.000160 81204570 BLOCKED INO_FLTR+0x80e9
8.000178 811fcdb0 BLOCKED irda!RxThread+0xfa
8.0002d0 811694f0 BLOCKED ?? Kernel stack not resident ??
8.0002d4 81168030 BLOCKED ?? Kernel stack not resident ??
8.000404 811002b0 BLOCKED rdbss!RxpWorkerThreadDispatcher+0x6f
8.000430 810f4990 READY parallel!ParallelThread+0x3e
8.00069c 80993030 READY rdbss!RxpWorkerThreadDispatcher+0x6f
第 一列是进程ID和线程ID(按照格式“进程ID.线程ID”显示)。第二列是线程的当前地址。第三列表明了该线程是处于等待(wait)状态、就绪 (ready)状态,还是运行(running)状态(请参见第6章关于线程状态的讲述)。最后一列是该线程的栈中最顶上的地址。这最后一列的信息可以使 得我们很容易看出,每个线程是在哪个驱动程序中被启动起来的。对于Ntoskrnl中的线程,函数的名字进一步指明了该线程正在做什么。
然而,如果正在运行的线程是一个系统辅助线程(ExpWorkerThread),那么你仍然不知道该线程在做什么,因为任何一个设备驱动程序都可以将工作提交给一个系统辅助线程。因此,追溯辅助线程行为的惟一方法是,在ExQueueWorkItem中设置一个断点。
当你到达该断点的时候,输入!dso work_queue_item esp+4。该命令将会转储第一个实参,它指向ExQueueWorkItem(一个工作队列结构),其中包含了在辅助线程环境中被调用的辅助例程的地址。此外,你在内核调试器中利用 k 命令(该命令显示当前的调用栈)可以看到调用者。当前的调用栈将会显示出,是哪个驱动程序将自己的工作排入队列中,并得到了此辅助线程(相对于从辅助线程中被调用的例程而言)。
实验:将系统线程映射到一个设备驱动程序上
在 这个实验中,我们会看到,如何将System进程中的CPU活动映射到产生该活动的相应的系统线程上(以及它所在的驱动程序)。这很重要,因为当 System进程正在运行的时候,你必须在线程粒度上才能真正理解当前正在发生的事情。在这个实验中,我们将在你的机器上产生文件服务器的活动,以此来生 成系统线程的活动。(文件服务器驱动程序,Srv.sys,创建一些系统线程来处理它所接到的针对文件I/O的请求。有关该组件的更多信息,请参考第13 章)。
1. 打开一个命令提示窗口。
2. 通过一个网络路径来访问你的C驱动器,列出你的整个C驱动器的所有目录。例如,如果你的计算机名称是COMPUTER1,则输入“dir \\computer1\c$ /s”(/s开关列出所有的子目录)。
3. 运行Process Explorer,用鼠标在System进程上双击。
4. 点击Threads标签。
5. 按照“CSwitch Delta(context switch delta,环境切换差量)”列排序。你应该看到,在Srv.sys中有一个或者多个线程正在运行,如下图所示:
如果你看到一个系统线程正在运行,但你不确定对应的驱动程序是哪个,那么,请按下Module按钮,这将会弹出文件属性对话框。如上图那样,当Srv.sys中的线程被选中的时候按下Module按钮,就会得到下面的显示结果。
会话管理器
会话管理器(\Windows\System32\Smss.exe)是系统中第一个创建的用户模式进程。负责完成执行体和内核初始化工作的内核模式系统线程在最后阶段创建了实际的Smss进程。
在启动Windows的过程中,会话管理器负责许多重要的步骤,比如打开额外的页面文件、执行延迟的文件改名和删除操作、创建系统环境变量。它也将子系统进程(通常只有Csrss.exe)和Winlogon进程启动起来,Winlogon进程依次会创建其他的系统进程。
在注册表的HKLM\SYSTEM\CurrentControlSet\Control\Session Manager下面,你可以找到许多配置信息,它们驱动了Smss的初始化步骤。其中有些信息在第5章介绍Smss的时候将会进一步解释(有关这些注册表键和值的详细描述,请参考Windows 2000资源工具箱中的注册表条目帮助文件Regentry.chm)。
Smss中的主线程在执行了这些初始化步骤以后, 一直在Csrss和Winlogon的进程句柄上等待。如果这两个进程中的任何一个非正常终止了,则Smss让系统崩溃掉(崩溃的代码是 STATUS_SYSTEM_PROCESS_TERMINATED或0xC000021A),因为Windows要依赖于这两个进程的存在才能运行得下 去。
同时,Smss等待加载子系统的请求、调试事件,以及创建新的终端服务器会话(terminal server sessions)的请求(关于终端服务的介绍,请参见第1章的“终端服务及多个会话”一节)。
终端服务会话(Terminal Services session)的创建是由Smss来完成的。当Smss接到一个创建会话的请求时,它首先调用NtSetSystemInformation,请求建立内核模式的会话数据结构。这又依次调用内部的内存管理器函数MmSessionCreate, 该函数建立起会话虚拟地址空间,该地址空间中包含会话中的换页内存池,以及由Win32子系统的内核模式部分(Win32k.sys)和其他的会话空间设 备驱动程序所分配的、属于每个会话的数据结构(更多的细节请参考第7章)。然后,Smss为该会话创建Winlogon和Csrss的实例。
Winlogon、LSASS和Userinit
Windows 登录进程(\Windows\System32\Winlogon.exe)处理交互式用户的登录和注销。当安全注意序列(SAS,Secure Attention Sequence)组合键被按下的时候,Winlogon就会接到一个用户登录请求。在Windows上默认的SAS是组合键Ctrl+Alt+Del。 使用SAS的原因是为了保护用户避免那些模拟登录过程的口令窃听程序,因为用户模式应用程序不可能截取这一键盘序列。
登 录过程的身份识别和认证是在一个名为GINA(Graphical Identification and Authentication,图形识别和认证)的可替换的DLL中实现的。标准的Windows GINA,即Msgina.dll,实现了默认的Windows登录界面。然而,开发人员可以提供他们自己的GINA DLL来实现其他的身份识别和认证机制——比如基于声波纹(voice print)的方法——以替换Windows标准的用户名/口令方法。而且,Winlogon可以加载那些需要执行二级认证的附加网络提供者DLL。这种 能力使得多个网络提供者可以在正常的登录过程中一次采集到所有的身份识别和认证信息。
一 旦用户名和口令已经捕捉到了,就可以将它们送到本地安全认证服务器进程(\Windows\System32\Lsass.exe,第8章中讲述)进行认 证。LSASS调用适当的认证包(实现为DLL的形式)以执行实际的验证操作,比如检查该口令是否符合存储在活动目录或者SAM(属于注册表的一部分,其 中包含了关于用户和组的定义)中的口令信息。
在成功完成了认证以后,LSASS调用安全引用监视器中的一个函数(例如,NtCreateToken), 以生成一个访问令牌对象(access token object),该对象包含了当前用户的安全轮廓。然后,Winlogon利用此访问令牌来创建该用户会话中的初始进程。这(或这些)初始进程被存储在注 册表键HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon下的注册表值Userinit中(默认是Useinit.exe,但是,在该注册表值中可以列出多个映像)。
Userinit执行该用户环境中的一些初始化工作(比如运行登录脚本、应用组策略),然后在注册表中查找Shell值(在前面提到过的同样的Winlogon键的下面),并且创建一个进程来运行系统定义的外壳程序(默认是Explorer.exe)。
然 后,Useinit退出。这正是为什么Explorer.exe在进程树中没有父进程的原因——它的父进程已经退出,而且正如前面所解释的,tlist将 所有父进程已不在运行的进程调整到最左边(看待这一现象的另一种方法是:Explorer是Winlogon的孙子进程)。
Winlogon 不仅当用户登录和注销的时候是活动的,而且无论何时,当它截取到键盘的SAS时也是活动的。例如,当你登录进来后,若按下了Ctrl+Alt+Del,则 Windows安全对话框就会出现,提示“注销”、“启动任务管理器”、“锁住工作站”、“关闭系统”等选项。Winlogon是负责处理这些交互工作的 进程。
关于登录过程中各个步骤的完整描述,请参考第5章的“Smss、Csrss和Winlogon”一节。有关安全认证的更多细节,请参考第8章。关于在跟LSASS交互时可调用函数(以Lsa开头的函数)的细节,请参考Platform SDK中的文档。
服务控制管理器
回 顾本章前面提到过的,Windows中的“服务(services)”既可以指一个服务器进程,也可以指一个设备驱动程序。这一节讲的服务是指用户模式的 进程。服务就像UNIX的“守护进程(daemon process)”或者VMS的“分派进程(detached process)”,这些进程可以被配置成能在系统引导时候自动启动起来,而无需交互式的登录过程。它们也可以被配置成手工启动(比如,通过运行 Services管理工具,或者调用Windows的StartService函数)。在通常情况下,这些服务并不与登录用户进行交互,尽管在有些特殊的条件下这是可能的(参考第4章)。
服 务控制管理器是一个特殊的系统进程,它运行的映像文件是\Windows\System32\ Services.exe,它负责启动、停止服务进程,也负责与这些服务进程进行交互。所谓服务程序,实际上只是调用了一些特殊Windows函数的 Windows映像。它们通过这些特殊的Windows函数,与服务控制管理器进行交互,以便执行诸如此类的动作:注册一个服务的成功启动、响应那些对状 态的请求,以及暂停或停止一个服务。在注册表的HKLM\SYSTEM\CurrentControlSet \Services下定义了一个系统的服务。资源工具箱的Registry Entries帮助文件(Regentry.chm)记载了针对系统服务的子键和值。
记 住,服务有三种名称:你在系统中看到的正在运行的进程名、注册表中的内部名称,以及在Services管理工具中给出的显示名(并不是所有的服务都有显示 名——如果一个服务没有显示名的话,则显示内部名称)。服务还可以有一个描述域(description field),以进一步详细解释一个服务的功能。
要想从一个服务进程映射到该进程所包含的服务,可以使用 tlist /s 命令。请注意,在服务进程和所运行的服务之间并不总是一一对应的,因为有的服务与其他的服务共享一个进程。在注册表中,一个服务的类型代码指明了该服务是运行它自己的进程,还是与同一映像中的其他服务共享一个进程。
有许多Windows组件是以服务的方式来实现的,比如Spooler(假脱机)、事件日志、任务调度器,以及各种网络组件。
实验:列出当前安装的服务
为了列出当前系统中所安装的服务,请从控制面板中选择“管理工具(Administrative Tools)”,然后选择“服务(Services)”。你应该会看到如下所示的输出:
要想看到有关一个服务的详细属性,请在这个服务上右键点击,然后选择“属性(Properties)”。例如,下面是打印假脱机(Print Spooler)服务(在上一个图中已被加亮显示)的属性:
注意,“可执行文件的路径(Path To Executable)”域指明了包含这个服务的程序。记住,有的服务与其他的服务共享同一个进程——从服务到进程之间的映射关系并不总是一一对应的。
实验:检查服务进程内部的有关服务的细节
Process Explorer可以加亮显示那些包含了一个或者多个服务的进程(你只需在Options菜单中选择“Configure Highlighting”中的项目就可以配置成这样)。如果你在一个包含了服务的进程上双击鼠标,你将会看到有一个Services视图,其中列出了该 进程内部的服务:定义了该服务的注册表键的名称、管理员可以看得见的显示名,以及描述该服务的文本信息(如果有的话)。例如,在Windows XP上,一个运行在System账号下的Svchost.exe进程中所包含的服务如下:
有关服务的更多细节,请参见第4章。