操作系统提供了一些基本机制供内核模式组件(如执行程序、内核和设备驱动程序)使用。
本章介绍以下系统机制并说明如何使用它们:
第三章 系统机制 3.1 陷阱分布
中断和异常是两种操作系统情况,它们会导致处理器重定向到正常控制流之外的代码。这两种情况都可以由硬件或软件检测到。
术语“陷阱”是指当发生异常或中断时,处理器捕获执行线程并将控制权转移到操作系统中的固定地址的机制。在 中,处理器将控制权转移到陷阱处理程序(陷阱)。
陷阱处理程序是与特定中断或异常相关的函数。
图 3.1 显示了一些可以激活陷阱处理程序的条件。
内核通过以下方式区分中断和异常。
中断是异步事件(可随时发生),与处理器当前正在执行的任务无关。中断主要由 I/O 设备、处理器时钟或定时器产生,可以启用(打开)或禁用(关闭)。
相反,异常是一种同步条件,通常是执行特定指令的结果。(中止,例如机器检查,是与指令执行无关的典型处理器异常。)在相同条件下使用相同数据第二次运行程序可以重现原始异常。
例外情况包括:
内核还将系统服务调用视为异常(尽管从技术上讲,它们是系统陷阱)。
硬件和软件都可能产生异常和中断。例如,总线错误异常是由硬件问题引起的,而除以零异常则是软件错误的结果。同样,I/O 设备可以产生中断,内核本身也可以发出软中断(例如 APC 或 DPC,本章后面将讨论)。
当发生硬件异常或中断时,处理器会在被中断线程的内核堆栈中记录足够的机器状态信息,以便它可以返回到控制流中的该点并继续执行,就像什么都没有发生过一样。如果线程在用户模式下执行,则切换到线程的内核模式堆栈。然后,在被中断线程的内核堆栈上创建一个陷阱帧,并将线程的执行状态保存在陷阱帧中。
陷阱帧是线程完整执行环境的一个子集。您可以在内核调试器中键入 dt nt! 来查看陷阱帧的定义(第 5 章“进程和线程”介绍了线程环境)。当内核处理软中断时,它要么将软中断视为硬中断处理的一部分,要么在线程调用与软中断关联的内核函数时同步处理它。
在大多数情况下,内核会安装前端陷阱处理程序,这些处理程序在内核将控制权移交给与特定陷阱关联的处理程序之前或之后执行一些常规陷阱处理任务。例如,如果陷阱条件是设备中断,则内核的硬件中断陷阱处理程序会将控制权移交给中断设备的设备驱动程序提供的中断服务例程 (ISR)。如果陷阱条件是由对系统服务的调用引起的,则常规系统服务陷阱处理程序会将控制权移交给可执行文件中指定的系统服务函数。内核还会为它不希望看到或根本不处理的陷阱安装陷阱处理程序。这些陷阱处理程序通常执行系统函数,当内核检测到可能导致数据损坏的问题或不正确的行为时,这些函数会停止计算机(有关错误检查的更多信息,请参阅第二册第 14 章“崩溃转储分析”)。
以下章节更详细地描述了中断、异常和系统服务调度。
实验:将系统调用号映射到函数和参数
实验:查看系统服务的行为
通过观察“”对象中的“Calls/Sec”性能计数器,可以监视系统服务的行为。
运行性能监视器,在“监视工具”下单击“性能监视器”,然后单击“添加”按钮将计数器包含在图表中。
选择“”对象,然后选择“Calls/sec”计数器,然后单击“添加”按钮将计数器添加到图表中。 3.2 对象管理器
正如第 2 章“系统架构”中提到的,实现了一个对象模型,为执行体中实现的各种内部服务提供一致、安全的访问机制。
本节介绍对象管理器(),它是可执行文件中负责创建、删除、保护和跟踪对象的组件。
对象管理器将资源控制操作集中起来,否则这些操作可能会分散在整个操作系统中,并且旨在满足后面列出的许多目标。
实验:调查对象管理器
对象管理器的设计目标如下:
从内部来看,有三种类型的对象:
所谓执行体对象,是指执行体的各个组件(比如进程管理器、内存管理器、IO子系统等等)所实现的对象。
内核对象是指内核实现的一组比较基本的对象。
这些对象对于用户模式代码是不可见的;它们仅由可执行文件内部创建和使用。
内核对象提供最基本的能力,例如同步等,执行对象都是建立在它们之上的。
因此,许多执行体对象包含(封装)一个或多个内核对象,如图3.18所示。
本章后面将介绍内核对象数据结构的详细信息以及如何使用内核对象实现同步。
在本节的剩余部分,我们将重点介绍对象管理器的工作方式以及可执行对象、句柄和句柄表的数据结构。
这里我们还将简单描述对象如何参与安全访问检查机制。我们将在第 6 章详细讨论这个主题。
可执行对象
每个环境子系统都向其应用程序呈现了操作系统的不同面貌。执行对象和对象服务是环境子系统构建其自己的对象版本和其他资源的基础。
可执行对象通常由环境子系统代表用户应用程序创建,或由操作系统的各个组件作为其正常操作的一部分创建。例如,要创建文件,应用程序会调用子系统 DLL .dlI 中实现的函数。经过一些验证和初始化工作后,它又调用本机服务来创建可执行文件对象。
环境子系统为其应用程序提供的对象集可能比执行程序提供的对象集大或小。子系统使用执行程序对象来派生自己的对象集,其中许多对象直接对应于执行程序对象。例如,互斥和信号量直接构建在执行程序对象上,而执行程序对象又构建在相应的内核对象上。此外,子系统还提供命名管道和 (),以及 (),它们都构建在执行程序文件对象之上。
有些子系统,例如 UNIX 应用程序子系统,甚至不提供面向对象的支持。UNIX 应用程序子系统使用执行对象和服务作为向其应用程序表达 UNIX 样式进程、管道和其他资源的基础。
表 3.8 列出了可执行文件提供的基本对象,并简要描述了它们代表什么。您可以在本书后面专门介绍可执行组件的章节中找到有关可执行对象的更多详细信息(如果可执行对象直接导出到子系统,则可以在 API 参考文档中找到详细信息。)您还可以以提升的权限运行并更改目录以查看完整的对象类型列表。
笔记:
执行器总共实现了 42 种对象类型。其中许多对象仅由定义它们的执行器组件使用,无法直接通过 API 访问。此类对象的示例包括 ()、() 和 ()。
笔记:
由于 NT 最初设计为支持 OS/2,因此互斥对象必须与 OS/2 互斥对象的现有设计保持兼容,这要求线程能够放弃互斥对象,使其无法访问。由于这种行为对于互斥对象来说不寻常,因此创建了另一个内核对象 ()。最终,对 OS/2 的支持被放弃,该对象被 32 子系统用于互斥,但在内部它仍被称为 。
如图3.19所示,每个对象都有对象头和对象体,对象管理器控制对象头,而执行组件控制其创建的对象类型的对象体。
每个对象头还包含对一个称为类型 () 的特殊对象的引用,该对象包含其每个实例所共有的信息。
此外,还有最多 5 个可选子标题:
对象头和对象体
对象管理器使用存储在对象头中的数据来管理这些对象,而不必知道它们的类型。
表 3.9 简要描述了对象头中的各个字段,表 3.10 描述了可选对象子头中的各个字段。
对象头包含适用于任何类型对象的信息。此外,子头还包含仅与对象某些特定方面相关的可选信息。
请注意,这些结构的位置是从对象头的开头加上一个可变偏移量,该偏移量取决于与主对象头关联的子头的数量(除了创建者信息,如上所述)。对于对象头中存在的每个子头,该字段都会相应更新以反映子头在对象头中的存在。对象管理器通过首先检查表中的相应位是否已设置,然后使用剩余的位从表中选择正确的偏移量来找到从对象头开头的偏移量。
所有可能的子标头组合都有偏移量,但由于子标头(如果存在)总是按固定顺序分配,因此给定标头可以出现在与它之前的子标头数量一样多的位置。例如,由于名称信息子标头总是首先分配,因此它只有一个可能的偏移量。另一方面,句柄信息子标头(分配在第三个)有三个可能的位置,因为它可能在配额子标头之后分配,也可能不在配额子标头之后分配;它也可能在名称信息子标头之后分配。
表 3.10 描述了所有可选的对象子头及其位置。对于创建者信息,对象头标志中的值表示子头是否存在。(有关这些标志的信息,请参阅表 3.12。)这些子头中的每一个都是可选的,并且仅在某些条件下出现,无论是在系统启动期间还是在对象创建时。表 3.11 描述了这些条件。
最后,许多属性和/或标志决定了对象在创建时或执行某些操作时的行为。每当创建新对象时,对象管理器都会在名为对象属性 () 的结构中接收这些标志。此对象属性结构定义对象名称、将插入对象的根对象目录、对象的安全描述符和对象属性标志。
表 3.12 列出了可与对象关联的各种标志。
笔记:
当通过子系统的 API 函数(例如 或 )创建对象时,调用者不指定任何对象属性 - 子系统 DLL 在后台执行此操作。因此,通过 Win32 创建的所有命名对象都会进入目录(无论是全局实例还是每个会话实例),因为这是 .dll 在对象属性结构中指定的根对象目录。有关此内容以及它与会话命名空间的关系的更多信息,请参阅本章后面的内容。
除了对象头之外,每个对象都有一个对象主体,其格式和内容取决于对象类型:所有相同类型的对象都共享相同的对象主体格式。执行组件通过创建对象类型并为其提供必要的服务来控制给定类型的所有对象主体的数据。由于对象头具有静态的固定大小,因此对象管理器可以通过从对象指针中减去头的大小来轻松找到对象的对象头。如前所述,要访问对象的子头,对象管理器只需从指向对象头的指针中减去另一个固定值。
由于对象头和子头结构已经标准化,对象管理器可以提供少量的公共服务来操作存储在对象头中的属性,这些服务可以操作任何类型的对象(但有些公共服务对某些特定对象没有意义)。
表 3.13 列出了这些公共服务,子系统允许应用程序直接使用其中一些服务。虽然所有对象类型都支持这些公共对象服务,但每个对象都有自己的创建、打开和查询服务。例如,I/O 系统为其文件对象实现创建文件服务,而进程管理器为其进程对象实现创建进程服务。虽然可以实现公共创建对象服务,但这样的例程会非常复杂,因为例如初始化文件对象和进程对象所需的参数集会非常不同。此外,每次线程调用对象服务来确定句柄引用的对象类型并调用服务的正确版本时,对象管理器都会产生额外的处理开销。
对象头中包含的数据对所有对象来说都是通用的,但每个对象实例可以采用不同的值。
类型对象
例如,每个对象都有唯一的名称,也可能有唯一的安全描述符。但是,对象也可能包含一些对于特定类型的所有对象都是恒定的数据。例如,当您打开某个类型的对象的句柄时,您可以从一组特定于该对象类型的访问权限中进行选择。执行程序为线程对象提供终止和暂停(和其他)访问操作,为文件对象提供读取、写入、添加和删除(和其他)访问操作。特定于对象类型的属性的另一个示例是同步,稍后将对其进行介绍。
为了节省内存,对象管理器仅在创建新对象类型时才存储这些静态的、特定于对象类型的属性。它使用自己的一个对象(类型对象)来记录这些数据。
如图 3.20 所示,如果设置了对象跟踪调试标志(参见本章后面的“全局标志”部分),则类型对象还将链接所有相同类型(图中的进程类型)的对象,以便对象管理器可以找到这些对象并在必要时枚举它们。此功能利用了前面讨论的创建者信息子标头。
实验:查看对象标题和类型对象
类型对象不能在用户模式下进行操作,因为对象管理器不提供任何与类型对象相关的服务。
但是,类型对象定义的一些属性可以通过特定的本机服务或例程看到。表 3.14
列出类型初始化结构中存储的信息。
同步是应用程序可见的属性,指的是线程通过等待对象从一种状态变为另一种状态来同步其执行的能力。线程可以与可执行作业、进程、线程、文件、事件、信号量、互斥体和计时器对象同步。其他可执行对象不支持同步。
对象基于三种可能性来支持同步功能: