预计阅读本页时间:-
11.3.4 子系统、DLL和用户态服务
回到图11-6,我们可以看到Windows Vista操作系统是由内核态中的组件和用户态的组件组成的。现在我们已经介绍完了我们的内核态组件,因此,我们接下来看看用户态组件。其中对于Windows有三种组件尤为重要:环境子系统、DLL和服务进程。
我们已介绍Windows子系统模型,所以这里不作更多详细介绍,而主要是关注原始设计的NT,子系统被视为一种利用内核态运行相同底层软件来支持多个操作系统个性化的方法。也许这是试图避免操作系统竞争相同的平台,例如在DEC的VAX上的VMS和Berkeley UNIX。或者也许在微软没有人知道OS/2是否会成为一个成功的编程接口,他们加上了他们的投注。结果,OS/2成为无关的后来者,而Win32 API设计为与Windows 95结合并成为主导。
Windows用户态设计的第二个重要方面是在动态链接库(DLL),即代码是在程序运行的时候完成的链接,而非编译时。共享的库不是一个新的概念,最现代化的操作系统使用它们。在Windows中几乎所有库都是DLL,从每一个进程都装载的系统库ntdll.dll到旨在允许应用程序开发人员进行代码通用的功用函数的高层程序库。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
DLL通过允许在进程之间共享通用代码来提高系统效率,保持常用代码在内存中,处理减少从程序磁盘到内存中的加载时间。并允许操作系统的库代码进行更新时无需重新编译或重新链接所有使用它的应用程序,从而提高系统的使用能力。
此外,共享的库介绍版本控制的问题,并增加系统的复杂性,因为为帮助某些特定的应用而引入的更改可能会给其他的一些特定的应用带来可能的错误,或者因为实现的改变而破坏了一些其他的应用——这是一个在Windows世界称为DLL黑洞的问题。
DLL的实现在概念上是简单的。并非直接调用相同的可执行映像中的子例程的代码,一定程度的间接性引用被编译器引入:IAT(导入地址表)。当可执行文件被加载时,它查找也必须加载的DLL的列表(这将是一个图结构,因为这些DLL本身会指定它们所需要的其他的DLL列表)。所需的DLL被加载并且填写好它们的IAT。
现实是更复杂的。另一个问题是代表DLL之间的关系图可以包含环,或具有不确定性行为,因此计算要加载的DLL列表可以导致不能运行的结果。此外,在Windows中DLL代码库有机会来运行代码,只要它们加载到了进程中或者创建一个新线程。通常,这是使它们可以执行初始化,或为每个线程分配存储空间,但许多DLL在这些附加例程中执行大量的计算。如果任何函数调用的一个附加例程需要检查加载的DLL列表,死锁可能会发生在这个过程。
DLL用于不仅仅共享常见的代码。它们还可以启用一种宿主的扩展应用程序模型。Internet Explorer可以下载并链接到DLL调用ActiveX控件。另一端互联网的Web服务器也加载动态代码,以为它们所显示的网页产生更好的Web体验。像Microsoft Office的应用程序允许链接并运行DLL,使得Office可以类似一个平台来构建新的应用程序。COM(组件对象模型)编程模式允许程序动态地查找和加载编写来提供特定发布接口的代码,这就导致几乎所有使用COM的应用程序都以in-process的方式来托管DLL。
所有这类动态加载的代码,为操作系统造成了更大的复杂性,因为程序库的版本管理不是只为可执行体匹配对应版本的DLL,而是有时把多个版本的同一个DLL加载到进程中——Microsoft称之为肩并肩(side-by-side)。单个的程序可以承载两个不同的DLL,每个可能要加载同一个Windows库——但对该库的版本有不同要求。
较好的解决方案是把代码放到独立的进程里。而在进程外承载的代码结果具有较低的性能,并在很多情况下会带来一个更复杂的编程模型。微软尚未提供在用户态下来处理这种复杂度的一个好的解决办法。但这让人对相对简单的内核态产生了希望。
该内核态具有较少的复杂性,是因为它相对于用户态提供了更少的对外部设备驱动模型的支持。在Windows中,系统功能的扩展是通过编写用户态服务来实现的。这对于子系统运行得很好,并且在只有很少更新的时候,而不是整个系统的个性化的情况下,能够取得更好的性能。在内核实现的服务和在用户态进程实现的服务之间只有很少的功能性差异。内核和过程都提供了专用地址空间,可以保护数据结构和服务请求可以被审议。
但是,可能会与服务的用户态处理内核中服务有重大的性能差异。通过现代的硬件从用户态进入内核是很慢的,但是也比不上要来回切换两次的更慢,因为还需要从内存切换出来进入另一个进程。而且跨进程通信带宽较低。
内核态代码(非常仔细地)可以把用户态处理的数据作为参数传递给其系统调用的方式来访问数据。通过用户态的服务,数据必须被复制到服务进程或由映射内存等提供的一些机制(Windows Vista中的ALPC功能在后台处理)。
将来跨地址空间的切换代价很可能会越来越小,保护模式将会减少,或甚至成为不相关。在Singularity中,微软研究院(Fandrich等人,2006年)使用运行时技术,类似C#和Java,用来做一个完全软件问题的保护。这就要求地址空间的切换或保护模式下没有硬件的切换代价。
Windows Vista利用用户态的服务进程极大地提升了系统的性能。其中一些服务是同内核的组件紧密相关的,例如lsass.exe这个本地安全身份验证服务,它管理了表示用户身份的令牌(token)对象,以及文件系统用来加密的密钥。用户态的即插即用管理器负责确定要使用新的硬件设备所需要的正确的驱动程序来安装它,并告诉内核加载它。系统的很多功能是由第三方提供的,如防病毒程序和数字版权管理,这些功能都是作为内核态驱动程序和用户态服务的组合方式实现的。
在Windows Vista中taskmgr.exe有一个选项卡,标识在系统上运行的服务。(早期版本的Windows将显示服务使用net start命令的列表)。很多服务是运行在同一进程(svchost.exe)中的。Windows也利用这种方式来处理自己启动时间的服务,以减少启动系统所需的时间。服务可以合并到相同的进程,只要它们能安全地使用相同的安全凭据。
在每个共享的服务进程内,个体服务是以DLL的形式加载的。它们通常利用Win32的线程池的功能来共享一个进程池,这样对于所有的服务,只需要运行最小数目的线程。
服务是系统中常见的安全漏洞的来源,因为它们是经常是可以远程访问的(取决于TCP/IP防火墙和IP安全设置),且不是所有程序员都是足够仔细的,他们很可能没有验证通过RPC传递的参数和缓冲区。