Chinaunix首页 | 论坛 | 博客
  • 博客访问: 19882134
  • 博文数量: 679
  • 博客积分: 10495
  • 博客等级: 上将
  • 技术积分: 9308
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-18 10:51
文章分类

全部博文(679)

文章存档

2012年(5)

2011年(38)

2010年(86)

2009年(145)

2008年(170)

2007年(165)

2006年(89)

分类: WINDOWS

2008-01-12 16:57:46

2.4  关键的系统组件

我们已经 看过了Windows的高层体系结构,现在我们来深入地研究Windows的内部结构,以及每个关键的操作系统组件所扮演的角色。图2.3是一个关于 Windows系统核心结构和组件的图,它比本章前面给出的结构图(见图2.1)更加详细和完整。请注意,它仍然没有显示所有的组件,特别是没有显示网络 组件(第13章将会介绍网络组件)。

这一节将详细阐述图2.3中的每一个主要元素。第 3章解释Windows使用的主要控制机制(比如对象管理器、中断,等等)。第5章讲述启动和关闭Windows的过程,第4章详细介绍各种管理机制,比 如注册表、服务进程,以及WMI(Windows Management Instrumentation,Windows管理规范)。然后,剩下的章节更加细节地讨论各个关键区域的内部结构和操作,比如进程和线程、内存管理、 安全性、I/O管理器、存储管理、高速缓存管理器、Windows文件系统(NTFS)和网络。

图2.3  Windows系统核心结构和组件

环境子系统和子系统DLL

正 如图2.3所示,Windows最初有三个环境子系统:OS/2、POSIX和Windows。如前所述,OS/2子系统最后一次是随Windows 2000发布。到了Windows XP以后,原来随Windows一起发行的基本POSIX子系统已经不再随系统一起发行了,尽管如此,一个增强的POSIX子系统版本作为免费提供给 UNIX产品的服务的一部分,可供用户使用。

正 如稍后将要解释的那样,三个子系统中的Windows子系统是非常特殊的,Windows非它不能运行(它拥有键盘、鼠标和显示器,而且即使在没有交互用 户登录的服务器系统上,它也是必须存在的)。实际上,其他两个子系统被配置成按需启动,而Windows子系统则必须总是在运行。

子系统启动信息被保存在注册表键HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems的下面。图2.4显示了该键下面的值。

图2.4  注册表编辑器显示了Windows子系统启动信息

Required 值列出了当系统引导时加载的子系统。该值有两个字符串:Windows和Debug。Windows值包含了Windows子系统的文件规范, Csrss.exe,它代表了客户/服务器运行时子系统(Client/Server Run-Time Subsystem)(参见本节后面的注)。 Debug值是空的(因为它被用于内部测试),因此什么也不做。Optional值表明了OS/2和POSIX子系统将被按需启动。注册表值Kmode包 含了Windows子系统的内核模式部分的文件名Win32k.sys(本章后面将会进一步解释)。

环 境子系统的角色是,将Windows基本系统服务的部分子集暴露给应用程序。每个子系统都提供了对于Windows原生服务不同部分的访问能力。这意味 着,建立在某一个子系统上的应用程序可以做到的事情,是另一个建立在不同子系统上的应用所无法做到的。例如,一个Windows应用程序不能够使用 POSIX的fork函数。

每一个可执行的映像(.exe)被绑定到一个(且是惟一的)子系统上。当一个映像文件被运行时,创建进程的代码会检查映像头部的子系统类型代码,所以它可以通知正确的子系统,有新的进程被创建了。在Visual C++中,link命令的/SUBSYSTEM修饰符可以指定此类型代码,利用Windows资源工具箱中的Exetype工具可以查看此类型代码。

注    这 是一个有关Windows历史的注释,Windows子系统进程之所以被称为Csrss.exe,是因为在Windows NT的原始设计中,所有的子系统都位于全局惟一的环境子系统进程中,作为该进程中的线程来执行的。在POSIX和OS/2子系统被移出来并放到它们自己的 进程中以后,Windows子系统进程的文件名没有改变。

不 同子系统之间,函数调用不能混合。换句话说,一个POSIX应用只能调用POSIX子系统导出的服务,Windows应用只能调用Windows子系统导 出的服务。正如稍后你将要看到的那样,这一限制正是为什么“最初的POSIX子系统(它实现了一个非常有限的函数集合,仅仅POSIX 1003.1)并非是一个移植UNIX应用程序的有用环境”的原因之一。

正如前面所提及的,用户应用程序并不直接调用 Windows的系统服务。相反,它们通过一个或者多个子系统DLL来进行。这些库导出的接口都有很好的文档说明,凡是链接到这些子系统的程序都可以调用 这些接口。例如,Windows子系统DLL(比如Kernel32.dll、Advapi32.dll、User32.dll和Gdi32.dll)实 现了Windows API函数。POSIX子系统DLL(Psxdll.dll)实现了POSIX API函数。

实验:查看可执行映像的子系统类型

你可以利 用Windows资源工具箱中的Exetype工具,或者Windows支持工具箱和Platform SDK中的相依性查找工具(Depends.exe),来看到映像的子系统类型,例如两个不同的Windows映像:Notepad.exe(简单的文本 编辑器,即记事本)和Cmd.exe(Windows命令提示环境),其子系统类型如下:

C:\>exetype \Windows\system32\notepad.exe
File "\Windows\system32\notepad.exe" is of the following type:
    Windows NT
    32 bit machine
    Built for the Intel 80386 processor
    Runs under the Windows GUI subsystem
 
C:\>exetype \Windows\system32\cmd.exe
File "\Windows\system32\cmd.exe" is of the following type:
    Windows NT
    32 bit machine
    Built for the Intel 80386 processor
    Runs under the Windows character-based subsystem

这 表明Notepad是GUI程序,而Cmd则是控制台或者基于字符的程序。而且,Exetype工具的输出暗示了:虽然针对GUI和基于字符的程序有两个 不同的子系统,但是,Windows子系统却只有一个。而且,在Intel 386处理器(就此而言,或者486)上是不支持Windows的——Exetype程序的文本输出尚未被更新。

当一个应用程序调用子系统DLL中的某个函数的时候,可能会发生下述三件事情之一。

n  该函数完全是在该子系统DLL中实现的,在用户模式下运行。换句话说,该函数并没有给环境子系统进程发送消息,而且也没有调用Windows执行体的系统服务。该函数是在用户模式下完成的,运行的结果被返回给调用者。此类函数的例子有GetCurrentProcess(它总是返回-1,在所有与进程相关的函数中,利用该值可以引用当前进程)和GetCurrentProcessId(对于一个正在运行的进程,进程ID不会改变,所以进程ID可以从某个缓存的地方获取到,从而无须每次调用至内核中)。

n  该函数要求调用Windows执行体一次或者多次。例如,Windows的ReadFileWriteFile函数分别要调用底层的(且无文档的)内部Windows I/O系统服务NtReadFileNtWriteFile

n  该函数要求在环境子系统进程中完成某些工作(环境子系统进程运行在用户模式下,它负责维护那些在其控制下运行的客户应用程序的状态)。在这种情况下,该函 数以消息的形式给环境子系统发送一个客户/服务器请求,从而让环境子系统执行某个操作。然后子系统DLL等待应答,收到应答之后再返回给调用者。

有些函数可能是以上列出的第2和第3项的组合,比如Windows的CreateProcessCreateThread函数。

尽 管Windows在设计的时候,要求支持多个独立的环境子系统,但是从实践的角度来看,让每个子系统都实现所有的代码来处理窗口和显示I/O,显然会导致 大量重复的系统函数,无论是对系统的空间大小,还是对系统的性能,最终必然带来负面影响。因为Windows是原生的子系统,所以Windows的设计者 决定将这些基本的函数放在Windows子系统中,并且让其他的子系统调用Windows子系统来完成显示I/O。因此,POSIX和OS/2子系统调用 Windows子系统中的服务来完成显示I/O(实际上,如果你检查这些映像的子系统类型,你将会看到,它们都是Windows可执行文件)。

下面我们来仔细地看一看每一个环境子系统。

Windows子系统

Windows子系统是由以下几个主要的组件构成的。

n  环境子系统进程(Csrss.exe)包含下列支持:

  ● 控制台(文本)窗口;

  ● 创建或删除进程和线程;

  ● 对16位虚拟DOS机(VDM)进程的一部分支持;

  ● 其他一些函数,比如GetTempFileDefineDosDeviceExitWindowsEx,以及几个自然语言支持函数。

n  内核模式设备驱动程序(Win32k.sys)包含:

  ● 窗口管理器(window manager),它控制窗口显示,管理屏幕输出,采集来自键盘、鼠标和其他设备的输入,同时也负责将用户的消息传递给应用程序;

  ● 图形设备接口(GDI,Graphics Device Interface),它是专门针对图形输出设备的函数库,其中包括线、文本和图形的绘制函数,以及绘图控制函数。

n  子系统DLL(比如Kernel32.dll、Advapi32.dll、User32.dll和Gdi32.dll),它们将已经文档化的 Windows API函数,翻译成Ntoskrnl.exe和Win32k.sys中恰当的且绝大多数未文档化的内核模式系统服务调用。

n  图形设备驱动程序是指与硬件相关的图形显示器驱动程序、打印机驱动程序和视频微端口驱动程序。

应用程序 调用标准的USER函数以便在显示器上创建用户界面控件,比如窗口和按钮。窗口管理器将这些请求发送给GDI,而GDI又将它们传递给图形设备驱动程序, 在设备驱动程序内部,这些请求被经过格式化以适合特定的显示器设备。显示器驱动程序与视频微端口驱动程序是成对的,以实现对视频显示的支持。

GDI提 供了一组标准的二维函数,使得应用程序无须知道任何有关图形设备的信息就可以与设备进行通信。GDI函数位于应用程序(比如显示器驱动程序和打印机驱动程 序)和图形设备的中间。GDI负责解释应用程序的图形输出请求,并且将这些请求发送给图形显示器驱动程序。它也为应用程序提供了标准的接口,以便这些应用 程序可以使用各种不同的图形输出设备。此接口使得应用程序代码可以独立于硬件设备和它们的驱动程序。GDI对它的消息进行剪裁,以符合设备的能力,它通常 要将应用程序的请求分解成多个易于处理的部分。例如,有些设备可以理解画椭圆的命令,而其他的设备则要求GDI将该命令解释成“将一系列像素放置在特定的 坐标处”。有关图形和视频驱动程序结构的更多信息,请参考Windows DDK中Graphics Drivers一书的“Design Guide”一节。

在Windows NT 4以前,窗口管理器和图形服务是用户模式Windows子系统进程的一部分。在Windows NT 4中,这些原本运行在Windows子系统进程环境中的窗口和图形代码被移到了一组可调用的系统服务中,直接运行在内核模式下(在文件 Win32k.sys中)。

之所以做这样的迁移,主要的理由是为了提高总体系统性能。虽然原始的设计已经被高度优化了,但是,把Windows图形子系统放在一个分开的服务器进程中,必将导致多次线程和进程环境的切换,从而消耗掉相当数量的CPU周期和内存资源。

例如,对于客户方的每个线程,在Windows子系统进程中都有一个专门的、配对的服务器线程在等待客户线程的请求。一个被称为fast LPC的 特殊的跨进程通信设施,可用来在这些线程之间发送消息。与普通的线程环境切换不同的是,在这些配对的线程之间通过fast LPC来传递消息并不会使内核中产生一个重新调度(rescheduling)的事件,这样,服务器线程在等待内核的抢先式线程调度器将执行权交给它以 前,可以利用客户线程尚未用完的时间片来运行自己的代码。利用共享的内存缓冲区,可以传递一些大的数据结构,比如位图;客户可以直接(但只读)访问关键的 服务器数据结构,以便使客户和Windows子系统服务器之间的线程/进程转换尽可能地减少到最少。并且,GDI操作可以分批处理(现在也是如此)。所谓 批处理(batching),意味着Windows应用程序发出的一系列图形调用先被放到一个GDI批处理队列中,直到一个队列填满了才被“推(pushed)”到服务器并且在输出设备上绘制出来。你可以使用Windows的GdiSetBatchLimit函数来设置队列的大小,而且在任何时候都可以通过GdiFlush函数刷空队列。相反地,GDI的只读属性和数据结构,一旦从Windows子系统进程中获得,就被缓存在客户端,以便后续的访问更加快速。

然而,尽管有了这些优化,对于图形密集的应用程 序,总体的系统性能仍然是不够的。显而易见的解决方案是,通过将窗口和图形系统转移到内核模式中,就可以去除那些附加的线程,以及由此带来的环境切换。而 且,一旦应用程序调用进入了窗口管理器和GDI中,这些子系统就可以直接访问其他的Windows执行体组件,而无须付出用户模式或者内核模式转换的代 价。若一个进程通过GDI调用视频驱动程序,并且以很高的频率和带宽与视频硬件进行交互,则在此情况下,这样的直接访问尤为重要。

那么,有哪些还保留在Windows子系统的用户 模式进程部分呢?控制台或者文本窗口的所有绘制和更新操作都是在这一部分中处理的,因为控制台应用程序并没有重绘一个窗口的概念。下面这样的行为很容易看 得到:打开一个命令提示窗口,然后拽着另一个窗口在它上面拖动,这时你将会看到,当Windows子系统重绘控制台窗口的时候它正在消耗CPU时间。但 是,除了对控制台窗口的支持以外,其他只有少数几个Windows函数会导致给Windows子系统进程发送消息:进程和线程的创建和终止、网络驱动器字 母映射,以及临时文件的创建。一般而言,一个正在运行的Windows应用程序不会引起很多(如果有的话)至Windows子系统进程的环境切换。

将USER和GDI放在内核模式中,Windows会更加不稳定吗

有 些人可能想知道,将这些代码放到内核模式中是否会严重影响系统的稳定性。其实,对系统稳定性的影响已经减到了最低限度,理由是,在Windows NT 4之前(这在今天仍然是正确的),用户模式Windows子系统进程(Csrss.exe)中的一个错误(比如访问违例,access violation)会导致系统崩溃,因为Windows子系统进程对于系统的运行来说,是(现在仍然是)一个至关重要的进程。因为那些描述显示器上当前 窗口信息的数据结构正是位于此进程之中,所以,一旦该进程死了,则整个用户界面就被毁了。然而,即使是一个作为服务器运行的Windows操作系统,它没 有任何交互进程,若没有这个进程,则系统也就无法运行,因为服务器进程可能要利用窗口消息机制来驱动应用程序的内部状态。对于Windows来说,同样这 部分代码现在运行在内核模式中,代码中的访问违例(access violation)只会使系统崩溃得更快一些而已,因为内核模式中的异常会导致系统崩溃。

然 而,有个额外的理论危险则是在窗口和图形系统被移到内核模式以前并不存在的。由于这部分代码现在运行在内核模式下,所以,任何错误(bug,比如使用了坏 的指针)都可能会破坏掉内核模式保护的数据结构。在Windows NT 4以前,这样的错误指针引用会引发一个访问违例,因为内核模式页面在用户模式下是不能写的。然后导致系统崩溃,如前所述。现在这部分代码运行在内核模式 下,若一个坏指针引用导致了对某个内核模式页面的一次写操作,那么,它有可能并不立刻使系统崩溃;相反,如果它破坏了某个数据结构,则有可能在不久之后导 致系统崩溃。然而,这里也存在一种小的可能性,也就是,这样的坏指针引用将破坏内存缓冲区(而不是数据结构),可能的结果是,把破坏了的数据返回给用户程 序,或者把坏数据写到了磁盘上。

将 图形驱动程序移到了内核模式中以后,还有另外一种可能的影响。先前,图形驱动程序的一部分代码在Csrss中运行,其他部分在内核模式下运行。现在,整个 驱动程序都运行在内核模式下。尽管Windows中支持的图形设备驱动程序并不全部是Microsoft开发的,但是Microsoft直接与硬件制造商 合作,以努力确保这些厂商能够制造出可靠和有效的驱动程序。随系统一起发行的所有驱动程序如同其他的执行体组件一样,都要经过同一套严格的测试过程。

最 后,理解下面的一点也是很重要的:从根本上讲,这样的设计(指在内核模式下运行窗口和图形子系统)并不冒险。它等同于许多其他设备驱动程序所使用的方法 (例如,网卡驱动程序和硬盘驱动程序)。从Windows有了高度可靠性的需求之初,所有这些驱动程序就一直在内核模式下运行。

有些人曾经推测,将窗口管理器和GDI移到内核模式中,必将伤害到Windows的抢先式多任务调度能力。他们的理论是,由于所有附加的Windows处理时间都是花在内核模式中的,所以,其他线程被抢先运行的机会就更少了。

这 种观点是建立在一种对Windows体系结构的误解的基础之上的。在其他许多有名无实的抢先式操作系统中,这确实是真的,在内核模式中的执行过程永远不会 被操作系统调度器抢占掉——或者说,只能被少量预定义的内核重入点(points of kernel reentrancy)抢占掉。然而,在Windows中,在执行体(executive)任何地方运行的线程,都与用户模式下运行的线程一起被抢占和调 度,执行体内部的所有代码都是完全可重入的(reentrant)。还有其他的理由,为了在SMP硬件上达到高度的系统扩展性,这种能力是必要的。

另一种推 测是,SMP的扩展性将会因为这一改变而受到影响。其理论大致如下:原先的时候,应用程序和窗口管理器或者GDI之间的交互会涉及两个线程,一个在应用程 序中,另一个在Csrss.exe中,因此,在SMP系统上,两个线程可能会并行运行,从而提高系统的吞吐量。这种分析说明,他们误解了Windows NT技术在Windows NT 4以前的版本中是如何工作的。在大多数情况下,从客户应用程序到Windows子系统进程的调用是同步运行的,也就是说,客户线程在等待服务器线程的时候 是完全阻塞的,只有当服务器线程完成了该调用时客户线程才开始继续运行。因此,在SMP硬件上根本做不到并行性。这种现象很容易观察到,你只需在一个 SMP系统上用性能工具来查看一个繁忙的图形应用程序即可。你将会发现,在一个双处理器的系统上,每个处理器都是将近50%的负担,而且,很容易可以发 现,一个Csrss线程与繁忙的应用线程结成了一对呼应的线程。实际上,因为这两个线程相互之间关系很紧密,而且共享了状态,所以,处理器的高速缓存必须 要经常被刷新,以维护两者的一致性。正是因为这种经常性的刷新动作,所以在Windows NT 3.51系统中,单线程图形应用程序在SMP机器上运行起来比在单处理器系统上往往要略慢一些。

因此, Windows NT 4中的这一变化使那些大量使用窗口管理器和GDI的应用程序在SMP机器上的吞吐量大大提高,特别是当多个应用线程在忙的时候。当两个应用线程在一台具有 双处理器的Windows NT 3.51机器上忙于运行的时候,总共四个线程(应用程序中有两个,再加上Csrss中两个)在竞争这两个处理器的时间。尽管在任何时候往往只有两个线程准 备运行,但是由于缺乏一致的模式(即这些线程的运行模式),从而导致丢失了内存引用的局部性,以及高速缓存的一致性。之所以会丢失这种局部性和一致性,是 因为繁忙的线程更有可能从一个处理器转移到另一个处理器上。在Windows NT 4的设计中,这两个应用线程的每一个都有它自己的处理器,Windows的自动线程亲和性总是倾向于在同样的处理器上运行同一个线程,因而使内存引用的局 部性尽可能地最大化,并且尽可能地同步减少每个处理器私有高速缓存的需求。

所以,简而言之,将窗口管理器和GDI从用户模式移到内核模式,不仅提高了性能,而且在系统稳定性和可靠性方面没有任何显著的退化,即使是在一个配置了终端服务的系统中创建了多个会话的情况下仍是如此。

POSIX子系统

POSIX差不多可以看成“一个基于UNIX的可 移植的操作系统接口(a Portable Operating System Interface based on UNIX)”的首字母缩写,它指的是针对UNIX风格的操作系统接口的一组国际标准。POSIX标准鼓励厂商们实现UNIX风格的接口,从而使它们保持兼 容,这样程序员们就可以很容易地将他们的应用程序从一个系统转移到另一个系统上。

Windows仅仅实现了众多POSIX标准中的 一个标准,即POSIX.1,正式的名称是ISO/IEC 9945—1:1990或者IEEE POSIX standard 1003.1—1990。该标准之所以被包含进来,主要是为了满足美国政府在20世纪80年代中后期制定的政府采购要求,即POSIX.1兼容性是强制性 的,这是在美国标准和技术委员会(National Institute of Standards and Technology)开发的FIPS(Federal Information Processing Standard,联邦信息处理标准)151—2中规定的。Windows NT 3.5、3.51和4已经被正式测试过,并且通过了FIPS 151—2的鉴定。

因为POSIX.1兼容性是Windows的一个强制性目标,所以,在设计Windows操作系统时,必须要保证,所设计的基本系统能够实现POSIX.1子系统——比如fork函 数是在Windows执行体中实现的,而针对文件硬链接(hard file links)的支持则是在Windows文件系统中的。然而,因为POSIX.1定义了很有限的一组服务(比如进程控制、跨进程通信、简单的字符单元 I/O即simple character cell I/O,等等),所以,随Windows 2000带的POSIX子系统并不是一个完整的编程环境。而且,因为应用程序不能在不同的Windows子系统之间进行混合调用,所以默认情况下, POSIX应用程序严格受限于POSIX.1中定义的各种服务。这一限制意味着,Windows上的POSIX可执行程序无法创建线程,也无法创建窗口, 或者使用远过程调用(RPC)或套接字(sockets)。

为了解决这一限制带来的不方便, Microsoft提供了一个称为“Windows Services for Unix”的产品,它(从3.5版本开始)包含了一个增强的POSIX子系统环境,该环境提供了将近2000个UNIX函数,以及差不多300个UNIX 类的实用工具(有关更多关于Windows Services for Unix的信息,请参考)。

这一增强的POSIX子系统有助于将UNIX应用 程序移植到Windows平台上。然而,因为这些程序仍然被链接为POSIX可执行程序,所以它们不能调用Windows函数。为了将UNIX应用移植到 Windows中,并且允许使用Windows函数,你可以购买UNIX-to-Windows移植软件包,比如来自Mortice Kern System Inc公司()的MKS工具箱产品。通过这种方法,UNIX应用程序可以被重新编译和链接成一个Windows可执行程序,然后再慢慢地加入一些对Windows原生函数的调用。

实验:观察POSIX子系统的启动

在 默认情况下,POSIX子系统被配置成:当第一次有一个POSIX可执行程序被运行的时候,该子系统被启动起来。所以你只需运行一个POSIX程序就可以 观察到它被启动起来,比如说,你可以运行某一个随“Windows Services for Unix”带的实用工具(在Windows 2000资源工具箱的介质上,你可以在\Apps\POSIX文件夹中找到一小部分POSIX实用工具。注意,这些工具并不会被作为资源工具箱的一部分而 安装到系统中)。按照下面的步骤可以观察到POSIX子系统被启动起来。

1.   启动一个命令提示窗口。

2.   运行 Process Explorer(进程管理器),检查一下POSIX子系统当前并没有在运行(也就是说,在系统中并没有Psxss.exe进程)。确保Process Explorer以树状视图的形式来显示进程列表(按下Ctrl+T即可)。

3.   运行一个POSIX程序,比如随“Windows Services for Unix”带的C Shell或者Korn Shell(或者Windows 2000资源工具箱中的POSIX工具,比如\Apps\POSIX\Ls.exe)。

4.   回到Process Explorer中,注意到新的Psxss.exe进程是Smss.exe的一个子进程(根据你的机器上高亮显示间隔的长短不同,该进程可能作为一个新进程而仍然是高亮显示的)。

要 想在Windows中编译和链接一个POSIX应用程序,要求使用Platform SDK中的POSIX头文件和库文件。POSIX可执行文件需要链接POSIX子系统库Psxdll.dll。因为在默认情况下,Windows被配置成 按需启动POSIX子系统,所以当你第一次运行一个POSIX应用程序的时候,POSIX子系统进程(即Psxss.exe)必须被启动起来。在系统重新 引导以前,它会一直保持运行(如果你杀掉了POSIX子系统进程,那么你就不能再运行其他的POSIX应用程序了,除非你重新引导系统)。POSIX映像 文件本身并不直接运行。相反,一个特殊的称为Posix.exe的支持映像文件被启动起来,然后它再创建一个子进程来运行POSIX应用程序。

OS/2子系统

如 同内置的POSIX子系统一样,OS/2环境子系统在有用性方面也非常有限,它仅支持OS/2 1.2的16位基于字符的或者视频I/O(VIO)的应用程序。虽然Microsoft也确实出售过一个替代OS/2 1.2的Windows NT 4展示管理器子系统,但它并不支持OS/2 2.x(或更以后版本的)应用程序(而且在Windows 2000及以后,它也不再可用了)。

另外,因为Windows不允许用户应用程序直接访问硬件,所以,如果OS/2程序中包含了企图执行IN/OUT指令(以便访问某些硬件设备)和高级视频I/O(AVIO)的I/O特权代码,则这样的程序在Windows平台上无法支持。

而那些使用了CLI/STI指令的应用程序则是支持的,但是系统中所有其他的OS/2应用程序,以及发出了CLI指令的进程中所有其他的线程,都被挂起,直到执行了一条STI指令为止。

在原生的OS/2 1.2上有16MB的内存限制,但这种限制并不适用于Windows —— OS/2子系统利用Windows的32位虚拟地址空间,可以向OS/2 1.2应用程序提供多达512MB内存,如图2.5所示。

图2.5  OS/2子系统虚拟内存布局结构

平 铺的区域(tiled area)是指虚拟地址空间中预先被保留,然后当16位应用程序需要段的时候被提交或解除提交的512MB内存。OS/2子系统为每个进程维护了一个局部 描述符表(LDT,Local Descriptor Table),而共享的内存段则是在所有OS/2进程的同样LDT条目中。

正 如我们将要在第6章中讨论的那样,线程(thread)是一个程序的执行元素,所以它们必须能被调度,以获得处理器时间。虽然Windows优先级的范围 从0到31,但是64个OS/2优先级(0到63)仅仅被映射到Windows动态优先级1至15。OS/2线程永远也不会接收Windows实时优先级 16至31。

如同POSIX子系统一样,OS/2子系统也是在你第一次激活一个兼容的OS/2映像文件时才自动启动起来。它会一直保持运行状态,直到系统被重新引导为止。

有关Windows如何处理正在运行的POSIX和OS/2应用程序的更多信息,请参见第6章的“CreateProcess的流程”一节。

Ntdll.dll

Ntdll.dll是一个特殊的系统支持库,主要用于子系统DLL。它包含两种类型的函数:

n  系统服务分发存根(stubs),它们会调用Windows执行体系统服务;

n  内部支持函数,供子系统、子系统DLL以及其他的原生映像文件使用。

第一组函数为Windows执行体系统服务提供了接口,在用户模式下可以通过这些接口函数调用Windows执行体的系统服务。这样的函数超过了200个之多,比如NtCreateFileNtSetEvent等等。正如前面所说明的,这些函数的大多数功能可以通过Windows API来访问得到(然而,有些函数则不然,它们被用于操作系统内部)。

对 于每一个这样的函数,Ntdll包含了一个同名的入口点。函数内部的代码包含了与处理器体系结构相关的模式切换指令,通过该指令可转换到内核模式下,从而 调用系统服务分发器(system service dispatcher,第3章将会详细地介绍)。分发器在检验了某些参数以后,再调用真正的内核模式系统服务,其中包括Ntoskrnl.exe内部的实 际代码。

Ntdll也包含许多支持函数,比如映像加载器(以Ldr开头的函数)、堆管理器、Windows子系统进程通信函数(以Csr开头的函数),以及一般的运行库过程(以Rtl开头的函数)。它也包含了用户模式下的异步过程调用(APC,Asynchronous Procedure Call)分发器和异常分发器(关于APC和异常,将在第3章中解释)。

执行体

Windows执行体是Ntoskrnl.exe中的上层(内核是其下层)。执行体包含以下类型的函数。

n  可在用户模式下调用的导出函数。这些函数称为系统服务(system services),并且通过Ntdll被导出。这些服务绝大多数可通过Windows API来访问,或者通过另一个环境子系统的API来访问。然而对于有些服务,通过任何一个文档化的子系统函数都无法访问它们(这样的例子有:LPC、诸如NtQueryInformationProcess这样的查询函数,以及诸如NtCreatePagingFile这样的专用函数,等等)。

n  可通过DeviceIoControl函数来调用的设备驱动器函数。这为从用户模式到内核模式提供了一个通用的接口,因而在用户模式下可以调用设备驱动程序中并不与读或者写操作关联的函数。

n  只能在内核模式下调用的导出函数,并且这些函数在Windows DDK或者Windows IFS Kit (Installable File System Kit, 可安装文件系统的开发工具箱)中已经文档化(有关Windows IFS Kit的更多信息,请浏览)。

n  在内核模式下调用,但未在Windows DDK或者IFS Kit中文档化的导出函数(比如以Inbv开头的、在引导视频驱动程序中调用的函数)。

n  定义为全局符号但是未被导出的函数。它们包括在Ntoskrnl内调用的内部支持函数,比如以Iop或者Mi开头的那些函数(分别是内部I/O管理器支持函数和内部内存管理支持函数)。

n  未定义为全局符号,而是在一个模块内部的函数。

执行体包含了以下的主要组件(在本书后续的章节中将会详细地介绍这些组件)。

n  配置管理器(configuration manager,在第4章中介绍)负责系统注册表的实现和管理。

n  进程和线程管理器(process and thread manager,在第6章中介绍)创建或终止进程和线程。针对进程和线程的底层支持是在Windows的内核中实现的;而执行体则在这些底层对象的基础上又加上了附加的语义和功能。

n  安全引用监视器(security reference manager或称SRM,在第8章中讲述)强制在本地计算机上实行安全策略。它守护着操作系统的资源,执行对运行时对象的保护和审计。

n  I/O管理器(I/O manager,在第9章中解释)实现了与设备无关的I/O操作,负责将这些操作分派到恰当的设备驱动程序以供进一步处理。

n  即插即用(PnP)管理器(Plug and Play manager,在第9章中 解释)的任务是,为了支持一个特定的设备,确定哪些驱动程序是必需的,同时它也负责加载这些驱动程序。它在设备列举过程中,获取到每个设备的硬件资源需 求。PnP管理器根据每个设备的资源需求,分配适当的硬件资源,比如I/O端口、IRQ、DMA通道和内存位置。当系统中的设备变化(增加或者移除设备) 时,它也负责发送恰当的事件通知。

n  电源管理器(power manager,在第9章中解释)负责协调电源事件,并且向设备驱动程序产生电源管理I/O通知。电源管理器可以配置成:当系统空闲的时候,通过将CPU置于睡眠状态从而降低电源消耗。单独设备的电源消耗变化可以由设备驱动程序来处理,但是需要电源管理器来协调。

n  WDM Windows管理规范例程(WDM Windows Management Instrumentation routines,在第4章中解释)允许设备驱动程序可以发布有关性能和配置的信息,以及接收来自用户模式WMI服务的命令。WMI信息的消费客户可以在本地机器上,也可以在跨越网络的远程机器上。

n  高速缓存管理器(cache manager,在第11章中解释)提高了以文件为基础的I/O操作的性能,其做法是,让最近引用过的磁盘数据驻留在主内存中以便快速访问(并且延迟磁盘写操作,在将更新数据发送到磁盘之前先在内存中停留一小段时间)。你将会看到,它利用内存管理器的映射文件的支持来做到这一点。

n  内存管理器(memory manager,在第7章中解释)实现了虚拟内存。这是一种内存管理方案,为每个进程提供了一个巨大的私有地址空间,其数量可以大大超过当前可用的物理内存。内存管理器也为高速缓存管理器提供最基本的底层支持。

n  逻辑预取器(logical prefetcher,在第7章中解释)加速系统和进程的启动过程。对于要在系统或进程启动过程中引用的数据,它优化了这些数据的加载过程。

另外,Windows执行体还包含四组主要的支持函数,以上这些执行体组件也会用到这些支持函数。在这些支持函数中,差不多有三分之一可以在DDK中找到相应的文档,因为设备驱动程序也要用到它们。以下是四大类支持函数。

n  对象管理器(object manager),它创建、管理和删除Windows执行体对象和抽象数据类型,这些对象和数据类型往往代表了操作系统的资源,比如进程、线程和各种同步对象。本书第3章介绍了对象管理器。

n  LPC设施(在第3章中解释),它在同一台机器上的客户进程和服务器进程之间传递消息。LPC是RPC(Remote Procedure Call,远过程调用)的一个更加灵活的优化版本。这里的RPC是指跨网络的客户进程和服务器进程之间的通信设施工业标准。

n  一组涉及范围广泛的公共运行库函数,比如字符串处理、算术操作、数据类型转换,以及安全结构处理等。

n  执行体支持例程,比如系统内存分配(换页的和非换页的内存池)、互锁的内存访问,以及两种特殊类型的同步对象:资源和快速互斥体(fast mutexes)。

内核

内核是由Ntoskrnl.exe中的一组函数以 及对于硬件体系结构的低层支持(比如中断和异常分发)构成的。Ntoskrnl.exe中的这组函数提供了一些最为基本的机制,比如线程调度和同步服务, 供执行体组件使用;而对硬件的低层支持则随着每个处理器结构的不同而有所区别。内核代码主要是用C编写的,对于那些要用到特殊的处理器指令和寄存器,并且 不容易在C代码中访问的任务,则保留使用汇编代码的形式。

如同上一部分中提到的各种执行体支持函数一样,内核中的许多函数也在DDK中有详细的文档描述(通过搜索以Ke打头的函数,可以找到它们)。因为在实现设备驱动程序的时候也需要用到它们。

内核对象

内 核提供了一组定义明确的、可预知的操作系统低层原语和机制,从而使得执行体中的高层组件可以做它们需要做的事情。内核实现了操作系统的基本机制,并且避免 了各种策略决定,从而将自己与执行体的其余部分分离开。它几乎将所有的策略决定都留给了执行体,惟一的例外是线程调度和分发,这是由内核自己来实现的。

从 内核外部来看,执行体将线程和其他可共享的资源都表示为对象。这些对象要求一些策略开销,比如用以维护它们的对象句柄(object handle),以及用以保护它们的各种安全检查,还有相应的资源配额(当它们被创建的时候资源配额就会被扣除)。这些开销在内核中是不存在的,内核实现 了一组更为简单的对象,称为内核对象(kernel objects),它们帮助内核控制好中心处理过程,并且支持执行体对象的创建工作。执行体层的绝大多数对象包装了一个或者多个内核对象,把它们的内核属性合并起来。

一组称为控制对象(control objects)的内核对象建立了有关控制各种操作系统功能的语义。这包括APC对象、DPC(Deferred Procedure Call,延迟过程调用)对象,以及I/O管理器使用的一些对象,比如中断对象等。

另一组称为分发器对象(dispatcher objects)的内核对象融合了同步的能力,它们改变或者影响了线程的调度。分发器对象包括内核线程、互斥体(内部称为突变体即mutant)、 事件、内核事件对(pair)、信号量(semaphore)、定时器,以及可等待的定时器(waitable timer)。执行体利用内核函数来创建内核对象的实例,并维护这些对象实例,同时构建更加复杂的、提供给用户模式的对象。第3章中将更加详细地介绍对 象,第6章讲述进程和线程。

硬件支持

内核的另一个主要任务是将执行体和设备驱动程序从Windows所支持的各种硬件体系结构中抽象出来,或者隔离出这些变种之间的差异。这项任务包括要处理各种功能(比如中断处理、异常分发和多处理器同步)方面的变化情况。

即便是对这些与硬件相关的功能,在设计内核的时候也企图使公共代码尽可能地最大化。内核支持一组可移植的接口,这组接口的语义在不同的体系结构上是等同的。而且,实现这组可移植接口的大部分代码在不同的体系结构上也是等同的。

然而,在这组接口中,有些在不同的体系结构上有不同的实现;或是在有些接口的实现中,部分代码与体系结构相关。

这些与体 系结构独立的接口可以在任何一台机器上被调用,而且无论实现代码是否随体系结构的不同而不同,接口的语义总是相同的。有些内核接口(比如自旋锁例程,将在 第3章中讲述)实际上是在HAL(在下一部分讲述)中实现的,因为它们的实现即使在同一体系结构族的系统中也可能有所不同。

内核中有 一小部分代码涉及与x86有关的接口,之所以需要这些代码是为了支持老的MS-DOS程序。这些x86接口并不是可移植的,因为在任何其他体系结构的机器 上它们是不可能被调用的;而且它们也不会出现在这样的机器上。例如,与x86相关的代码提供了相应的功能来维护全局描述符表(GDT)和LDT,这些正是 x86的硬件特性。

在内核中与体系结构相关的代码的另一个例子,是提供“翻译缓冲区(translation buffer)”和“CPU高速缓存”支持的接口。为了提供这样的支持,不同的体系结构需要不同的代码,因为高速缓存的实现方式各有不同。

另一个例 子是环境切换(context switching)。尽管从高层来看,线程选择和环境切换可以使用同样的算法(上一个线程的执行环境被保存起来,新线程的环境被加载进来),但是在不同 处理器上的实现之间,还是存在一些体系结构方面的差异。因为执行环境是由处理器的状态(寄存器等)来描述的,所以哪些信息应该被保存或加载,则是随着体系 结构的不同而有所不同的。

阅读(6472) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2008-01-12 16:58:24

原文地址:http://book.csdn.net/bookfiles/296/10029612591.shtml