实验:查看已安装的设备驱动程序
通过运行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列出了执行体组件通常会用到的大多数函数名称前缀。这些主要的执行体组件,其中每个也都会使用前缀的变化形式来标记内部函数一一前缀的第一个字母后面跟一个ⅰ(代表internal,即内部的),或者整个前缀后面跟一个p(代表private,即私有的)。例如,Ki代表内核的内部函数,而Psp指内部的进程支持函数。
如果你理解了Windows系统例程的命名习惯,就可以很容易地将这些导出函数的名称解析出来。
一般的格式如下:
<前缀><操作><对象>
在这一格式中,前缓部分是导出该例程的内部组件;操作部分说明了在对象或者资源上做什么事情;对象部分标明了是在什么上进行操作。
例如,ExAllocatePoolWithTag是一个负责从换页的或者非换页的内存池里进行内存分配的执行体支持例程。KelnitializeThread则是分配并建立内核线程对象的例程。
系统进程
以下系统进程会出现在每一个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可以看到哪些进程已经退出(用模糊的图标来指示)。
接下来的几部分将讲述图2.5中显示的关键系统进程。这些部分简要地说明了进程启动的顺序,本书下册第13章将详细地描述在Windows引导和启动过程中涉及的步骤。
系统空闲进程
图2.5中列出的第一个进程是系统空闲进程(Ide)。在第5章中会介绍,进程是由它们的映像文件名称来标识的。然而,这个进程(以及名为System的进程)并没有运行一个实际的用户模式映像文件(也就是说,在Windows目录下没有名为“System Idle Process.exe”的文件)。
而且,在不同的工具中,该进程的显示名称也不尽相同(由于实现细节的原因)。表2.6列出了空闲进程的一些名称(其进程D为0)。第5章中会详细地解释空闲进程。
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中运行,如下图所示。
如果你看到一个系统线程正在运行,但不确定对应的驱动程序是哪个,那么,请单击Module按钮,然后将会弹出文件属性对话框。如上图那样,当Srv2.sys中的线程被加亮显示时单击Module按钮,就会得到下面的显示结果。
会话管理器
会话管理器(%SystemRoot%\System32.Smss.exe)是系统中创建的第一个用户模式进程。这一进程由负责完成执行体和内核初始化工作最后阶段的内核模式系统线程创建。
Smss启动时,会检查自己是第一个实例(主Smss),还是主Smss为了创建会话而启动起来的一个实例。(如果存在命令行参数,则是后一种情形。)通过在引导过程中以及在终端服务会话的创建过程中创建多个Smss实例,Smss可以同时创建多个会话(最多4个并发会话,再为除去一个CPU外每个额外的CPU加上一个额外的会话)。这一能力增强了终端服务器系统的登录性能,在这种终端服务器系统上,同时会有许多个用户连接上来。一旦一个会话完成了初始化,该会话的Smss副本便终止。因而结果是,只有初始的Smss.exe进程仍然是活动的。(关于终端服务的描述,参见第1章的“终端服务及多个会话”一节。)
主Smss执行下面的一次性初始化步骤:
- 将该进程和初始线程标记为“关键的(critical)”。(如果一个被标记为“关键的”的
进程或线程退出,则Windows崩溃。更多信息参见第5章。) - 将进程的基本优先级提升到11。
- 如果系统支持动态增加热处理器,则允许自动更新处理器亲和性,这样,如果新的处
理器被加入进来,新的会话将可以利用这些新加入的处理器。(有关动态增加处理器的更多信息,参见第5章。) - 创建相应的命名管道和邮件槽,用于Smss、Csrss、Lsm(本章后文将介绍)之间的通
信。 - 创建ALPC端口接收命令。
- 根据HKLMISYSTEM\CurrentControlSet\ControllSession Manager\Environment中的定
义,创建系统范围的环境变量。 - 根据HKLM\SYSTEMI\CurrentControlSet\ControlSession Manager\DOS Devices中的定
义,在对象管理器名字空间的\Global??目录下为该注册表键中定义的设备创建符号链接。 - 在对象管理器名字空间中创建\Sessions根目录。
- 运行HKLMSYSTEMCurrentControlSet\Contro\Session Manager’BootExecute中的程
序。(默认是Autochk.exe,执行磁盘检查。) - 根据HKLM\SYSTEMCurrentControlSetlControlSession Manager\PendingFileRename
Operations中指定的信息,处理尚未完成的文件改名操作。 - 初始化页面文件(paging file)。
- 初始化注册表的其余部分(HKLM Software、SAM和Security储巢 〔hive))。
- 运行HKLMISYSTEM\CurrentControlSet\ControllSession ManagerlSetupExecute中的程
序。 - 打开已知DLL (HKLMISYSTEMICurrentControlSet\ControlSession ManagerlKnown
DLLs),将它们映射为永久内存区(映射文件)。 - 创建一个线程来响应会话创建请求。
- 创建Smss来初始化会话0(非交互会话)。17.创建Smss来初始化会话1(交互会话)。
一旦这些步骤完成,Smss将永远在会话0的Csrss…exe实例的句柄上等待。因为Csrss被标记为关键进程(参见第5章),所以,如果Csss退出,这一等待操作将永远不会完成,因为此时系统会崩溃。
会话启动的Smss实例将完成以下事项:
- 调用NtSetSystemInformation,请求建立起内核模式的会话数据结构。这又进而调用到内部的内存管理器函数MmSessionCreate,它建立起会话虚拟地址空间,其中包含该会话的换页内存池,以及由Windows-子系统的内核模式部分(Win32k.sys)和其他的会话空间设备驱动程序所分配的属于每个会话的数据结构(更多细节参见本书下册第10章)。
- 为该会话创建子系统进程(默认情况下,为子系统进程Csrss.exe)。
- 创建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)”小节将
简要介绍。 - 一直等待,直至系统停机。