本章将介绍 Windows 的基本概念,这些概念是方便接下来对 Windows 内部理解从而提前了解关于 Windows 基本概念和部分术语。
Windows API
Windows 应用程序编程接口(application programming interface,API)是Windows操作系统家族的用户模式系统编程接口。64 位 Windows 发布前,32 位 Windows 的编程接口被称为 Win32 API。这主要是为了将其与早期的 16 位 Windows API 区分开来。现在大部分称呼 Windows API 为 Win32 API 同时指代 32 位和 64 位 Windows 的编程接口。
Windows API 的风格
Windows API 最初只包含 C 语言风格的函数。目前,已有数千种此类函数可供开发者使用。在 Windows 诞生之日,C 语言是最自然的选择,因为它可以说是众多语言的最小公分母(也就是说,也可以通过其他语言来访问 C 语言),并且 C 语言足够底层,足以用来暴露操作系统服务。但 C 语言的不足之处是函数的绝对数量少以及缺少命名一致性和逻辑分组(例如 C++ 的命名空间)。这些难题造成的后果之一便是一些较新的 API 使用了不同的 API 机制:组件对象模型(component object model,COM)。
COM 最初是为了让 Microsoft office 应用程序能够在文档之间通信并交换数据(例如将 Excel 表格嵌入 Word 文档或者 PowerPoint 演示文稿)。这种能力也叫做对象链接和嵌入(object Linking and embedding,OLE)。OLE 最初使用一种古老的 Windows 消息传递机制 —— 动态数据交换(dynamic data exchange,DDE)实现。DDE 有一些固有局限,因此人们开发了新的通信方式:COM。实际上 COM 最初被称为 OLE2,大概在 1993 年正式发布。
COM 基于两个基本原则:
- 第一个原则:客户端可以通过接口与对象(有时也称为 COM 服务器对象)通信。接口是一种明确定义的“合约”,由一系列在逻辑上互相关联的方法组成,并按照虚拟表调度机制分组。这也是一种 C++ 编译器实现虚拟表调度的通用方法。借此可以实现二进制兼容性,避免编译器名称重整(mangling)问题,并可以通过很多语言(和编译器)调研这些方法,例如 C、C++、visual basic、.net、delphi 等。
- 第二个原则:组件的实现可以动态加载,无须静态链接到客户端。
COM 服务器这个称呼通常代表用于实现 COM 类的动态链接库(dynamic link library,DLL)或可执行文件(executable,EXE)。COM 还提供了与安全性、跨进程排列(marshalling)、线程模型等有关的重要功能。对 COM 的详细介绍这里就不详细说明了,大家感兴趣可以参考 Don Box 撰写的 Essential COM 一书。
Windows 运行时
Windows 8 新增了一种名为 Windows 运行时的全新 API 和支持运行时(有时也简称为 WinRT,但是和基于 ARM 架构的 Windows 操作系统版本—— Windows RT 是两回事)。Windows 运行时由平台服务组成,主要面向 Windows 应用(曾被称为 metro 应用、modern 应用、immersive 应用以及 Windows 商店 APP)的应用开发者。Windows 应用可运行在不同类型和规格的设备上,从小型的物联网设备到手机、平板电脑、笔记本电脑、台式机,甚至 Xbox one 和 HoloLens 等设备均可支持。
从 API 的角度来看,WinRT 是在 COM 的基础上构建出来的,并对基本的 COM 基础架构增加了各种扩展。例如,WinRT 可使用完整的类型元数据(存储在 WINMD 文件中,基于 .NET 元数据格式)在 COM 中扩展出一种名为类型库的类似概念。从 API 设计的角度来看,它比经典 Windows API 函数更内敛,可提供命名空间、层次结构、一致的命名以及编程模式。
Windows 应用沿袭了一系列新规则,这一点和普通的 Windows 应用程序(现在可称为 Windows 桌面应用程序或者经典 Windows 应用程序)截然不同。
各种 API 与应用程序间的关系并不那么直白。桌面应用可以使用 WinRT API 的子集,而 Windows 应用也可以使用 Win32 和 COM API 的子集。这部分的具体细节可以参考 MSDN 文档,WinRT API 并不是新增的“原生” API,而有点类似使用传统的 Windows API 的 .NET。
.NET Framework
.NET Framework 是 Windows 的一部分。下表列出了 Windows 默认安装的 .NET Framework 版本。新版 .NET Framework 也可以安装在旧版本的 Windows 操作系统中。
Windows 版本 | .NET Framework 版本 |
---|---|
Windows 8 | 4.5 |
Windows 8.1 | 4.5.1 |
Windows 10 | 4.6 |
Windows 10 版本 1511 | 4.6.1 |
Windows 10 版本 1607 | 4.6.2 |
.NET Framework 包含两个主要组件:
- 公共语言运行时(common language runtime,CLR)。这是 .NET 的运行时引擎,其中包含的即时(just lm time, JIT)编译器可以将公共中间语言(common intermediate language, CIL)指令转换为底层硬件 CPU 机器语音、垃圾回收器、类型验证、代码访问安全性等内容。它是作为一种 COM 进程内服务器(DLL)实现的,可使用 Windows API 提供的各类设施。
- .NET Framework 类库(framework class library, FCL)。这是一个庞大的类型集合,用于实现客户端和服务器应用程序通常可能需要的功能,例如用户界面服务、网络、数据库访问等。
通过提供上述功能以及新的高级编程语言(C#、visual basic、F#)和支持工具,.NET Framework 可以帮助开发者提升目标应用程序的开发销量并提高安全性和可靠性。.NET Framework 和 Windows 操作系统直接的关系如下图所示:
服务、函数和例程
在 Windows 用户文档和编程文档中,很多术语在不同语境下有着不同的含义。例如,“服务” 这个词可以代表操作系统中可调用的例程、设备驱动程序,也可以代表某个服务器进程。下面列出了不同术语的含义:
- Windows API 函数。Windows API 中已公开并且可以调用的子例程。如 CreateProcess、CreateFile、GetMessage。
- 原生系统服务(或系统调用)。操作系统中未公开,但可以从用户模式调用的底层服务。例如:Windows 的 CreateProcess 函数调用 NtCreateUserProcess 这个内部系统服务可新建一个进程。
- 内核支持函数(或例程)。在 Windows 操作系统内部,只能从内核模式调用的子例程。例如:驱动程序可以调用 ExAllocatePoolWithTag 例程从 Windows 系统堆中分配内存。
- Windows 服务。由 Windows 服务控制管理器启动的进程。例如,运行在用户模式中的 Task Scheduler 服务也可以支持 schtasks 命令
- 动态链接库(DLL)。可调用的子例程互相链接成的二进制文件,使用该子例程的应用程序可以动态加载这样的文件。例如,Msvcrt.dll 和 Kernel32.dll(Windows API 子系统库之一)。Windows 用户模式组件和应用程序大量使用了 DLL。相比静态库,DLL 的优势在于应用程序可以共享 DLL,Windows 可确保多个应用程序使用的同一个 DLL 只在内存中存在一个副本。
进程
虽然表面上程序和进程看起来很像,但是却存在本质差异。程序是一种静态指令序列,而进程是一种容器,其中包含了执行程序实例时会用到的一系列资源。从最高层的抽象来看,一个 Windows 进程可包含下列元素:
- 一块私有的虚拟地址空间。可供该进程使用的一系列虚拟内存地址。
- 一个可执行的程序。定义了初始代码和数据,会映射到进程的虚拟地址空间。
- 一个已打开句柄的列表。句柄会映射到各种系统 资源。例如,信号量、同步对象以及可被进程中所有线程访问的文件。
- 一个安全上下文。用于确定与进程相关的用户、安全组、特权、属性、声明、能力、用户账户控制(user account control,UAC)虚拟化状态、会话、受限用户账户状态身份的访问令牌,此外还包含 appcontainer 标识符和相关沙箱信息。
- 一个进程 ID。唯一标识符,从内部来说属于客户端 ID 标识符的一部分。
- 至少一个执行线程。虽然可以创建“空的”进程。
很多工具能帮我们查看(甚至修改)进程和进程信息。下面我们会来尝试演示一下。
使用任务管理器查看进程信息
在 Windows 下可以使用快捷键 Ctrl+shift+esc 打开任务管理器,或者通过在工具栏右键鼠标选择打开任务管理器。
在进程选项卡中默认会显示出四列信息:CPU、memory、disk 和 network。右键点击表头后可以选择显示更多了列信息或者隐藏部分显示的信息。可显示的信息包括进程名称、进程 ID、类型、状态、发布者以及命令行。一些进程可以展开,展开后显示该进程创建的顶级可见窗口。
可以进入详细信息选项卡,在这里也会显示出进程信息,但会以更紧凑的方式显示。这里不会显示进程创建的窗口,但是会提供更多不同类型的信息列。
同样,在详细信息里也可以右键点击表头,选择设置列信息从而能够显示出更多关于进程的信息。
这里需要重点说明几个选项值的作用:
- “线程”: 显示出每个进程所拥有的线程数量。
- “句柄”:显示出该进程内部锁打开的内核对象句柄数量。
- “状态”:显示该进程当前的运行状态,running、suspended 等情况。
父进程
每个进程还会指向自己的父进程(父进程可以是创建者进程,但也并非总是如此)。如果父进程已经不存在,这些信息将不再更新。因此进程有可能指向不存在的父进程。但这并不会造成问题,因为任何进程的运行都不依赖父进程信息的有效与否。process explorer 会考虑父进程的启动时间,以避免子进程附加到重用的进程 ID 上。
大部分工具都不会显示进程的父进程或创建者进程的 ID 这个属性。我们可以使用性能监视器查询 creating process ID 来获取这些信息。通过使用 Windows 调试工具中的 Tlist.exe 工具并配合 /t 开关来显示进程树。(在新的 Windows 10 中 Tlist.exe 已经更改为 TaskList (Tasklist.exe),并且也不再支持 .t 选项了)
这里我们使用 MS 提供的 sysinternals 包中的 process explorer 来显示更详细的进程信息。(这个工具的说明及下载链接:https://learn.microsoft.com/zh-cn/sysinternals/downloads/)
sysinternals 的 process explorer 可以显示比其他类似工具更详细的进程和线程信息,此外还能显示或实现一些独特的功能:
- 进程安全令牌,例如租和特权列表以及虚拟化状态。
- 通过高亮强调显示进程、线程、DLL 和句柄列表中的变化。
- 服务承载进程内的服务列表,包括服务的显示名和描述。
- 其他进程属性列表,例如缓解策略和进程保护级别。
- 包含在作业中的进程以及作业细节。
- 承载 .NET 应用程序的进程以及与 .NET 相关的细节,如 appdomain 列表,加载的程序集,以及 CLR 性能计数器。
- 承载 Windows 运行时的进程(沉浸式进程)。
- 进程和线程的启动时间。
- 内存映射文件的完整列表(不仅仅是 DLL)。
- 挂起进程或线程的能力。
- 终止特定线程的能力。
- 轻松辨别一段时间内 CPU 资源消耗量最大的进程。
注意:性能监视器可以显示一组指定进程的 CPU 利用率,但是无法自动显示性能监视器会话启动后创建的进程信息,只有通过手动方式创建的二进制输出格式才能包含这样的信息。
Process Explorer 还可以帮助用户在一个位置轻松访问下列信息:
- 进程树,并能将树的部分内容折叠。
- 进程中打开的句柄,包括未命名的句柄。
- 进程中的 DLL 列表。
- 进程中的线程活动。
- 用户模式和内核模式的线程栈,包括使用 Windows 调试工具提供的 Dbghelp.dll 将地址映射到的名称。
- 内存管理器详细信息,例如内存提交量峰值、内核内存换页限制,以及非换页内存池限制。
线程
线程是位于进程中、供 Windows 调度执行的一种实体。如果没有线程,进程的程序将无法运行。
线程包含以下基本要素:
- 代表进程状态的一系列 CPU 寄存器内容。
- 两个栈:一个供线程在内核模式下执行使用;另一个供线程在用户模式下执行使用。
- 一个供子系统、运行时库以及 DLL 使用,名为线程本地存储的私有存储区域。(thread-local storage,TLS)
- 一个名为线程 ID 的唯一标识符。
此外,线程有时也会有自己的安全上下文,也成为令牌,主要被多线程服务器应用程序用于模仿所服务的客户端的安全上下文。
易失和非易失的寄存器以及私有存储区域组合在一起形成了线程的上下文。由于这些信息在不同架构的计算机上运行的 Windows 中都是不同的。因此从本质上来说,这种结构和特定架构相关的。Windows 的 GetThreadContext 函数可供我们访问这种与架构有关的信息(也称为 CONTEXT 块)。此外,每个线程还有自己的栈(由线程上下文中的栈寄存器部分指向)。
将执行过程从一个线程切换到另一个线程需要内核调度器的参与,这可能是一个高开销的操作,尤其是在两个线程需要频繁互相切换时,为了减少开销,Windows 实现了两种机制:
- 纤程(fiber)
- 用户模式调度(user-mode scheduling,UMS)线程
纤程
纤程可以让应用程序不借助 Windows 内置的基于优先级的调度机制直接安排自己的线程的执行。纤程通常也可以叫做轻量级线程(lightweight thread)。在调度方面,纤程对内核是不可见的,因为纤程是通过 Kernel32.dll 在用户模式下实现的。若要使用纤程,首先需要调用 Windows 的 ConvertThreadToFiber 函数,该函数会将线程转换为运行中的纤程。随后新转换的纤程可通过 CreateFiber 函数创建更多纤程。(每个纤程可以有自己的一组纤程)但是与线程不同,纤程只有在调用 SwitchToFiber 函数并手动选择之后才能开始执行。新建的纤程将持续运行,直到退出或再次调用 SwitchToFiber 函数并选择运行其他纤程。有关纤程函数的详情可参阅 Windows SDK 文档。
用户模式调度线程
用户模式调度(UMS)线程仅适用于 64 位 Windows,提供了与纤程类似的基本用途,但避免了纤程的大部分不足之处。UMS 线程有自己的内核线程状态,因此对内核可见, 借此多个 UMS 线程即可发出阻塞的系统调用,并可共享或竞争资源。或者,当两个或更多 UMS 线程需要在用户模式下执行操作时,还可以定期切换执行上下文(由一个线程将执行权让给另一个线程),这个过程可在用户模式下进行,无须调度器参与。从内核的角度来看,此时依然运行了相同的内核线程,没有任何变化。当 UMS 线程执行的操作需要进入内核(例如系统调用)时,可以切换到它自己的专属内核模式线程(这一过程叫做定向上下文切换,directed context Switch)。虽然并发的 UMS 线程依然无法通过多个进程运行,但它们符合一种预抢占(pre-emptible)模式,所以并不是完全合作的关系。
虽然线程有自己的执行上下文,甚至一个进程中的每个线程都共享了该进程的虚拟地址空间,但一个进程中的所有线程都可以完全读写访问该进程的虚拟地址空间。不过线程无法无意中引用到其他进程的地址空间,除非其他进程将自己的部分私有地址空间编程共享内存区(在 Windows API 中称为文件映射对象),或者除非一个进程有权打开另一个进程以使用跨进程内存函数,例如 ReadProcessMemory 和 WriteProcessMemory 函数(此时进程必须运行在同一用户账户下,没有位于 AppContainer 或其他类型的沙箱中,并且除非目标进程有某种保护机制,否则默认即可访问)。
除了私有地址空间和一个或多个线程,每个进程还有自己的安全上下文,以及到文件、共享内存区、互斥体、事件、信号量等同步对象等内核对象的打开的句柄列表。
图中,VAD 指的是虚拟地描述符,是一种数据结构,内存管理器用它来追踪进程使用的虚拟地址。
每个进程的安全上下文存储在一个名为访问令牌的对象中。进程访问令牌包含 了进程的安全标识和凭据。默认情况下,线程没有自己的访问令牌,但可以获取令牌以便让自己模拟另一个进程(包括远程 Windows 系统中的进程)的安全上下文,这一过程不会影响进程中的其他线程(进程和线程安全性的相关内容后面还会详细说明)。
作业
Windows 为进程模型提供了一种名为“作业”的扩展。作业对象的主要功能是将一组资源作为整体进行管理和操作。作业对象可用于控制某些属性,并对作业所关联的一个或多个进程加以限制。此外还可以为作业关联的所有进程,以及作业所关联但关联之后已经终止的所有进程记录基本的账户信息。在一定程度上,作业对象弥补了 Windows 在结构化进程树上的不足,同时很多时候它比 Unix 那样的进程树更强大。
虚拟内存
Windows 实现了一种基于平面(线性)地址空间的虚拟内存系统,让每个进程可以“觉得”自己能够获得一个极大地私有地址空间。虚拟内存为内存提供的逻辑视图肯呢个为物理布局并不一致。在运行时,内存管理器可以(在硬件的协助下)对虚拟地址进行转换。即时映射到实际存储了数据的物理地址,通过对保护和映射过程加以控制,操作系统即可确保进程之间不会互相影响,也不会覆写操作系统的数据。
由于大部分 OS 的物理内存数远少于进程运行时所需的虚拟内存总数,内存管理器需要对一些内存内容进行转换,即分页到磁盘上。将数据分页到磁盘上可以将物理内存释放给其他进程或操作系统自身使用。当线程需要访问被分页到磁盘上的虚拟地址时,虚拟内存管理器会将相关信息从磁盘重新加载到物理内存中。
应用程序无须专门调整即可利用分页功能所提供的好处,因为硬件的支持使得内存管理器可以在进程或线程不知道或者无须协助的情况下分页。在两个进程所使用的虚拟内存中,部分依然映射在物理内存(physical memory RAM)中,另一部分则已经被分页到磁盘上。注意,连续的虚拟内存块可能被映射到不连续的物理内存块。这些块也叫做页,每个页的默认大小为 4KB。
每种硬件平台的虚拟内存地址空间大小各异。在 32 位 X86 系统中,虚拟内存空间总数的理论最大值为 4GB。默认情况下,Windows 会将这一地址空间中较低的一半(从 0x00000000 到 0x7FFFFFFF)分配给进程,作为进程独有的私有存储,并将较高的一半(从 0x80000000 到 0xFFFFFFFF)分配给自己作为受保护的操作系统内存使用。较低一半的映射会通过变化体现当前执行进程的虚拟地址空间,但较高一半的(大部分)映射总是由操作系统的虚拟内存组成的。Windows 支持的启动选项,例如引导配置数据库中的 increaseuserva 修饰符可以让运行带有特殊标记程序的进程最多使用 3GB 的私有地址空间,仅为操作系统保留 1GB,该方式可以将数据库服务器等应用程序将大部分内容保留在进程的地址空间中,减少将数据库视图的子集映射到磁盘的需求,进而改善整体运行性能。
32 位 Windows 支持的两种典型的虚拟地址空间布局如下图所示。
虽然 3GB 的虚拟地址空间好过 2GB,但依然不足以映射非常大的数据库。为了在 32 位系统上解决这一问题,Windows 提供了一种名为地址窗口化扩展(Address Windowing Extension,AWE)的机制,可以让 32 位应用程序最多分配 64GB 的物理内存,随后将视图或窗口映射到自己的 2GB 虚拟地址空间中。虽然 AWE 将虚拟内存到物理内存的映射关系的管理负担转移到了开发者身上,但是确实满足了直接访问更多物理内存的需求,具体数量甚至超过了 32 位进程地址空间一次可以容纳的上限。
64 位 Windows 为进程提供了更大的地址空间,因为 64 位的地址长度最多可以访问 2 的 64 次方(16 EB,1EB = 1024 PB,1PB = 1024 TB,1TB = 1024 GB)。
内核模式和用户模式
为了防止用户应用程序访问或修改操作系统的重要数据,Windows 使用了两种处理器访问模式(其实运行 Windows 的处理器可能支持更多模式)。两种模式分别是用户模式和内核模式:
- 用户模式:应用程序代码运行在用户模式下。
- 内核模式:操作系统代码运行在内核模式下。内核模式下处理器执行模式允许访问所有的系统内存和 CPU 指令。
一些处理器会使用代码段特权级(code privilege level)或 Ring 级这样的术语区分不同的模式。但也有处理器使用类似监管模式(supervisor mode)和应用程序模式来进行区分。
虽然每个 Windows 进程都有自己的私有内存空间,但是内核模式的操作系统和设备驱动程序代码共享了同一个虚拟地址空间。虚拟内存中的每个页都会通过标签指明处理器必须用哪种访问模式读取或写入页。系统空间中的内存也只能从内核模式访问,而用户地址空间中的所有页都可以从用户模式或者内核模式访问。
只读页(静态数据的页)在任何模式下都不可写入。此外,对于支持不可执行内存保护功能的处理器,Windows 会将页包含的数据标记为不可执行,这样防止疏忽或者恶意代码在数据区域中执行(需要开启数据执行保护功能(data execution prevention,DEP))。
对于在内核模式下运行的组件,Windows 对它们使用的私有读/写系统内存不提供任何保护。换句话说,一旦处于内核模式,操作系统和设备驱动代码都可以访问整个系统空间内存,并可以绕过 Windows 安全机制访问各种对象。因为 Windows 操作系统有大量代码运行在内核模式下,所以运行于内核模式的组件必须仔细设计和测试,以确保不会违反系统安全机制或导致系统不稳定。
这种缺乏保护的特性也使得在加载第三方设备驱动时需要更加慎重,尤其是第三方设备驱动程序不包含数字签名,一旦进入内核模式,驱动程序就可以完整访问操作系统的所有数据。这也是从 Windows 2000 开始进行驱动签名机制的原因之一。(除了签名机制,Windows 提供了驱动程序验证程序进行驱动程序测试和查找 bug,后面会说明,链接:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/devtest/devcon-examples#example-8-list-all-driver-files)
通过 Windows 10 自带的性能监控器可以观察到当前的内核模式和用户模式之间切换以及占用的耗时对比。
虚拟机监控程序
近些年,应用程序和软件开发模式方面有了很大的变化,例如云服务的出现和无处不在的物联网设备。这些新趋势推动着操作系统和硬件供应商必须设法以更高效的方式通过宿主机硬件实现来宾操作系统的虚拟化,例如可能需要通过服务器托管多个租户,用一台服务器运行 100 个互相隔离的网站,甚至让开发者无须购买专属硬件即可测试几十种不同的操作系统。用户对虚拟化技术的速度、效率和安全性提出了更高的要求,进而催生了新的计算模式和软件理论。实际上,当今的一些软件,例如 Docker,本身就得到了 Windows 10 和 Windows server 2016 的支持,可以运行在容器中,进而获得全面隔离的虚拟机环境,借此运行同一个应用程序栈或框架来实现来宾/宿主机模式的革新。
为了提供此类虚拟化服务,几乎所有现代化的解决方案都会用到虚拟机监控程序(hypervisor),这是一种特殊的高特权组件,可对计算机上的所有资源,从虚拟内存和物理内存到设备中断,甚至到 PCI 和 USB 设备实现虚拟化和隔离。Hyper-V 就是这样的一种虚拟机监控程序,Windows 8.1 以及后续版本中的 Hyper-V 客户端功能就是通过这项技术实现的。
在 Windows 10 中,微软使用 Hyper-V 虚拟机监控程序提供了一系列基于虚拟化的安全性(Virtualization Based Security,VBS)的新服务:
- Device Guard(设备防护)。相比仅使用 KMCS,通过虚拟机监控程序代码完整性(HVCI)可提供更强大的代码签名保证,并可对 Windows 操作系统的用户模式和内核模式代码提供定制的签名策略。
- Hyper Guard(超防护)。可保护与内核及虚拟机监控程序有关的重要数据结构和代码。
- Credential Guard(凭据防护)。可防止未经授权访问域账户的凭据和密文,并可与生物验证机制配合使用。
- Application Guard(应用程序防护)。可为 Microsoft Edge 浏览器提供更强大的沙箱机制。
- Host Guardian(主机保护者)和 Shielded Fabric(受防护的构造)。可借助虚拟 TPM(v-TPM)保护虚拟机,防范来自基础设施的威胁。
固件
Windows 组件对操作系统和系统内核的安全程度的依赖性与日俱增,而系统内核的安全性取决于虚拟机监控程序所提供的保护。
那么这就产生一个问题:然后确保虚拟机监控程序组件可以安全地加载并验证其内容。通常这是启动加载程序(boot loader)的职责,但启动加载程序本身也需要获得同等程度的验证检查,导致不同组件间的信任关系日趋复杂。
那么又该如何通过根信任链保证启动过程是可靠且不受影响的?在现代化的 Windows 8 和后续系统中,这是通过系统固件实现的,但前提是必须使用基于 UEFI 且获得了认证的系统。
作为 Windows 的一项规定,同时也是 UEFI 标准的一部分,必须通过安全启动对于启动有关的软件的签名质量提供强保证和要求。通过这样的验证过程,即可确保从启动过程的一开始,Windows 组件就能以安全地方式加载。此外注入受信任平台模块(Trtusted Platform Module,TPM)等技术也可以度量整个启动过程,并提供相应的证明(本地或远程证明)。
终端服务和多会话
终端服务指的是 Windows 通过一个系统为多个交互式用户会话提供支持的能力。远程用户可以借助 Windows 终端服务在其他计算机上建立会话,登录服务器并运行应用程序。随后服务器会将图形用户界面(Graphical User Interface,GUI)传输给客户端,客户端会将用户输入回传给服务器。(类似于 X Window 系统,Windows 允许咋服务器系统上云霄特定应用程序,并将显示画面回传给远程客户端,但不需要将整个桌面传输给远端)
第一个会话通常是服务会话,即会话 0,其中包含系统服务承载进程。在计算机上通过控制台物理登录建立的第一个会话是会话 1,随后通过远程桌面连接程序(Mstsc.exe)或快速用户切换功能连接更多的会话。
Windows 客户端版本只允许一个远程用户连接到计算机,如果连接时已有人登录控制台,则工作站被锁定。也就是说,远程连接使用计算机不可以支持多人同时使用。
Windows 服务器系统支持两个并发远程连接。这是为了方便进行远程管理,例如所用的管理工具肯呢个要求用户登录到被管理的计算机。如果具备必要的许可并且配置为终端服务器,则可支持更多远程会话。
对象和句柄
在 Windows 操作系统中,内核对象是指某个静态定义的对象类型的单个运行时实例。对象类型由一个系统定义的数据类型、针对该数据类型执行操作的函数,以及一组对象属性构成。
如果需要开发 Windows 应用程序,可能会遇到很多概念,例如进程、线程、文件、事件对象等。这些对象都是基于 Windows 创建和管理的底层对象。在 Windows 中,进程实际上是进程对象类型的实例,文件是文件对象类型的实例。
对象属性是对象中的数据字段,这些字段定义了对象的部分状态。例如,进程类型的对象会通过属性包含进程 ID、基本调度优先级、访问令牌对象的指针等。对象方法是指操作系统对象的手段,通常可用于读取或更改对象属性。例如,某个进程的 Open 方法可接受进程标识符作为输入,并将返回到对象的指针作为输出。
对象和普通数据之间最本质的区别是对象的内部结构是不透明的。必须调用对象服务才能获得对象中存储的数据,或将外部数据放入对象,而不能直接读取或更改对象内部的数据。这个差异将对象的底层实现与单纯只能使用这些对象的代码有效地区分开了,借此可以随时轻松访问和更改对象的具体实现。
借助对象管理器这个内核组件,对象拥有可以方便完成下列四大重要操作系统任务的能力:
- 为系统资源提供易于理解的名称
- 跨越进程共享资源和数据
- 保护资源免遭未经授权的访问
- 引用跟踪,借此系统可以识别某个对象什么时候不再使用,以便自动释放。
Windows 操作系统中并不是所有数据结构都是对象,只有需要共享、保护、命名或者对用户模式程序可见的数据才有必要放在对象中。
安全性
Windows 从设计之初就充分考虑了安全性,可满足政府与业界各类正式的安全评级需求,例如 common criteria for information technology security evaluate(CCITSE)规范。达到政府认可的安全评级,可以让操作系统在相关领域内更有竞争力。当然,这其中的很多功能能为任何多用户系统带来好处:
Windows 的核心安全功能包括以下几个方面:
- 为文件、目录、进程、线程等所有可共享的系统对象提供酌情决定,并且强调应用的保护。
- 针对主体或用户,以及他们发起的操作执行安全审核与问责。
- 登录时的用户身份验证。
- 防止用户未经授权访问其他用户已经撤销分配的资源,例如空闲内存或磁盘。
Windows 针对对象提供了三种形式的访问控制:
- 酌情决定的访问控制。大多数人在想到操作系统安全性时,首先会想到这种保护机制。通过这种方法,对象(例如文件或打印机)的所有者可以运行或拒绝他人访问。用户登录时可以获得一系列的安全凭据,也叫做安全上下文。当用户试图访问某个对象时,系统会将他们的安全上下文与所要访问控制列表进行对比,进而判读该用户是否有权执行所请求的操作。在 Windows server 2012 和 Windows 8 中,这种酌情决定的控制机制还通过基于属性的访问控制(动态访问控制)进一步加强。不过资源的访问控制列表并不一定要识别个别用户和组,还可以识别允许访问资源所需具备的属性或声明,例如“许可级别:顶级机密”或“资历:10 年”。通过借助 Active Directory 解析 SQL 数据库和架构自动获得这样的属性,这种更优雅,灵活的安全模型可以帮助组织摆脱手动管理组以及组层次结构的繁琐工作。
- 特权访问控制。在酌情决定的访问控制无法完全满足需求时,这也是种必要机制。这种方法可以确保在所有者不可用时,他人依然可以访问受保护的对象。例如,某位员工离职,管理员需要通过某种方式访问以前只能被该员工访问的文件,此时管理员可以在 Windows 中获取文件的所有权,随后即可按需管理文件的访问权。
- 强制完整性机制。如果需要为同一个用户账户访问的受保护对象提供额外的安全控制,此时就需要使用这种机制。很多几首都用到了这一机制,例如为 Windows 应用提供的沙箱机制,通过用户配置为受保护模式的 Internet explorer 提供隔离,以及保护提权后的管理员账户创建的对象不被未经提权的管理员账户访问等。
从 Windows 8 开始,系统会使用一个名为 appcontainer 的沙箱承载 Windows 应用,这种技术可以在不同的 appcontainer 之间,以及 appcontainer 与非 Windows 应用进程之间实现隔离。appcontainer 中的代码可以通过 broker 通信,有时候还可以与其他 appcontainer 或进程通过 Windows 运行时所提供的完善定义的协定通信。
Windows API 接口全面融入了各种安全机制。Windows 子系统通过与操作系统类似的做法实现了基于对象的安全模型:
- 为共享的 Windows 对象设置 Windows 安全描述符,防止未经授权访问。
- 当应用程序首次试图访问一个共享对象时,Windows 子系统会验证应用程序的权限。
- 如果安全检查通过,Windows 子系统将允许应用程序继续访问。
注册表
只要用过 Windows 操作系统,那么肯定听说过甚至使用过注册表。谈到 Windows 内部原理免不了提到注册表,因为注册表这个系统数据库中包含了
- 自动和配置系统必须的信息
- 控制 Windows 运行的系统级软件设置
- 安全数据库
- 使用的屏幕保护程序等用户配置信息
注册表还为内存中的易失数据提供了访问接口,例如系统当前的硬件状态(加载了哪些设备驱动程序、驱动程序使用了哪些资源)。此外还有 Windows 性能计数器。性能计数器实际上并不真正位于注册表中,但可以通过注册表访问性能计数器的详细信息。
虽然很多 Windows 用户和管理员永远不需要直接面对注册表(因为大部分配置选项都是通过标准的管理工具查看和更改),但注册表依然是一个很实用的 Windows 内部信息来源,其中包含了很多可以影响系统性能和行为的设置。(注册表的系统级配置根键 HKEY_LOCAL_MACHINE,简称 HKLM)
Unicode
Windows 与大部分其他操作系统有一个巨大的不同:Windows 中大部分文本字符串都是以 16 位宽的 Unicode 字符串(从技术上来看其实使用了 UTF-16LE)存储和处理。Unicode 是一种国际化的字符集标准,为全世界大部分已知字符集定义了唯一值,可为每种字符提供 8 位、16 位,甚至 32 位的编码。
由于很多应用程序处理的是 8 位(单字节)的 ANSI 字符串,因此很多 Windows 函数可以通过两个入口点接受字符串参数:
- 一个 Unicode(16 位宽字符)版本
- 一个 ANSI(8 位窄字符)版本
如果调用窄字符版本的 Windows 函数,则可能会对性能有微弱影响,因为输入的字符串参数需要先转换为 Unicode 字符才能被系统处理,并将输出参数从 Unicode 转换为 ANSI 输出给应用程序。因此,如果需要在 Windows 上运行老版本的服务或代码且相关代码都是使用 ANSI 字符串编写的,Windows 会将 ANSI 字符转换为 Unicode 供自己使用,然而 Windows 绝对不会转换文件内的数据,需要应用程序决定数据需要存储为 Unicode 还是 ANSI 的形式。
通过 Dependency Walker 工具打开 Kernel32.DLL 可以看到其中包含的函数接口,同时包含了 CreateFileA 和 CreateFileW 两种函数接口,这就是为不同的字符串类型提供的成对的函数接口。
(Dependency Walker 工具下载链接:http://dependencywalker.com/)文章来源:https://www.toymoban.com/news/detail-528886.html
总结
本文在这里仅仅将 Windows 操作系统中的主要的概念和术语进行了整理和记录,方便后面对于 Windows 内核及驱动的学习。通过本文的阅读能够对 Windows 操作系统的大致结构和部分专业名词有一定了了解和熟悉,具体的细节还需要深入学习和了解。文章来源地址https://www.toymoban.com/news/detail-528886.html
到了这里,关于Windows 基本概念和术语的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!