Windows 运用程序编写插口 (API) 是对于 Windows 电脑操作系统大家族的客户方式系统软件程序编写插口. 在 32 位版本号的 Windows 营销推广之前, 31 位版本号 Windows 电脑操作系统的程序编写插口被称作 Win32 API, 以差别于原先的 18 位版本号 Windows 的程序编写插口, 即 18 位 Windows API. 在这书中, 专业术语 Windows API 兼指 Windows 的 31 位和 32 位程序编写插口.
深入解析 Windows 操作系统 下册 PDF 英文第 6 版 http://www.xz577.com/e/984.html
注 : Windows 软件开发工具 (SDK) 文本文档叙述了 Windows API. 那份文本文档能够在 WWW.msdn.microsoft.Com 免费在线查看. 有关怎样在 Windows 基本 API 上编写程序, 有这份很好的材料 ---Jeffrey Richter 和 Christophe Nasarre 所作的 Windows via C/C++ 一书.
Windows API 包括数千个可启用的涵数, 他们能够被分为下列某些类别:
这书关键关心重要基础服务项目 (例如系统进程和进程, 内存管理, I/O 和安全系数) 的內部原理.
有关. NET Microsoft.NET 架构是由 1 个被称作架构类库 (FCL,Framework Class Library) 的类库和 1 个出示了托管代码实行自然环境的公共性語言运行库 (CLR,Common Language Runtime) 构成的, 前者出示的代码执行自然环境包括下列某些特点:
因为 CLR 具备这种特点, 因而它所出示的开发工具可以提升开发者的生产率, 降低常用的程序编写不正确. 有关. NET 架构以及关键构架的精采叙述, 参加 Jeffrey Richter 的 CLR via C#
Win32 API 的历史时间 有趣的是, Win32 并非 Windows Nt 最开始预订的程序编写插口. 由于 Windows Nt 新项目在起动之初, 目地是替代 Os/2 第 2 版, 因此, 它的关键程序编写插口是 31 位 Os/2 Presentation Manager API. 显然, 新项目开展了两年后, Microsoft Windows 5.0 进到销售市场, 而且展现出非常好的发展潜力. 因此, Microsoft 变化了方位, 使 Windows Nt 变成将来 Windows 商品大家族的代替品, 而并不是用于取代 Os/2. 也更是这一那时候, 才真实必须订制 Windows API--- 在此之前, 在 Windows 5.0 中, 只能 18 位插口的 API. 虽然那时候在 Windows API 上将导入许多在 Windows 3.2 上还没法应用的新涵数, 可是, Microsoft 還是决策让新的 API 与 18 位 Windows API 在涵数名字, 词义和数据类型使用方法上尽量适配, 便于缓解将现有的的 18 位 Windows 手机应用程序移殖到 Windows Nt 上的承担. 这都是很多涵数名字和插口看上去并不是相同的缘故: 为了保证那时候新的 Windows API 与老的 18 位 Windows API 维持适配, 它是必需的.
在 Windows 的用户文档和编程设计文本文档中, 几个专业术语在不一样的语义自然环境中拥有不一样的含意. 比如, 服务项目能够指电脑操作系统中能够被启用的例程, 机器设备驱动安装或是网络服务器系统进程. 下边的目录叙述了某些特殊的专业术语在这书中的含意:
虽然表层上看上去程序流程和系统进程十分类似, 但实质上他们确是迥然不同的. 程序流程是 1 个静态数据的命令编码序列, 而系统进程是 1 个器皿, 至少包括了程序执行的特殊案例需要的各种各样資源. 从最大层级的抽象性看来, Windows 系统进程是由下列原素组成的:
每一系统进程也对准它的父系统进程或是创建者系统进程. 要是父系统进程不要存有, 子系统进程中的这种信息内容并不容易被升级.
有关系统进程和进程的信息内容, Sysinternals 荣誉出品的 Process Explorer 出示了比别的一切专用工具必须多的关键点, 正由于这般, 你将会在这书的很多试验中见到这一专用工具的应用.
线程是一个进程内部的实体, 也是 Windows 执行此进程时的调度实体. 如果没有线程, 进程的程序将不可能运行起来. 线程包括以下一些最基本的部件:
易失的寄存器, 栈和私有存储区域结合起来被称为线程的环境(context). 因为这些信息随着 Windows 所在机器架构的不同而有所不同, 所以, 此架构必须是与底层架构相关的. Windows 的 GetThreadContext 函数允许程序访问这一与架构相关的信息(称为 CONTEXT 块).
因为将 CPU 的执行从一个线程切换到另一个线程, 将不可避免地涉及内核调度器, 所以, 这可能是一个开销昂贵的操作, 如果两个线程经常频繁来回切换则尤为如此. Windows 实现了两种机制来降低这种开销: 纤程 (fiber) 和用户模式调度(UMS)
纤程使得一个应用程序可以调度它自己的 "线程" 的执行过程, 而不必依赖 Windows 内置的基于优先级的调度机制. 纤程也常被称为 "轻量" 线程: 从调度的角度来看, 它们对于内核是不可见的, 因为它们是在用户模式下在 Kernel32.dll 中实现的. 使用方法参考 Windows SDK 文档.
UMS 线程仅在 64 位 Windows 长可用, 它基本上提供了与纤程同样的好处, 没有更多的坏处. UMS 线程有它们自己的内核线程状态, 因此对于内核是可见的, 这使得多个 UMS 线程都可以发出阻塞的系统调用, 对资源进行共享或竞争, 并且有每个线程特有的状态(per-thread state)
虽然线程有自己的执行环境, 但是, 同一个进程内部的所有线程共享该进程的虚拟地址空间(以及同属于该进程的其他资源), 这意味着, 一个进程内的所有线程都可以完全地读或写该进程的虚拟地址空间. 然而, 一个进程中的线程不可能无意识地引用另一个进程的地址空间, 除非两种情况: 第二个进程将它的一部分私有地址空间变成共享内存区(shared memory section)(在 Windows API 中称为文件映射对象(file mapping object)); 或者, 第一个进程有权打开第二个进程, 从而可以使用诸如 ReadProcessMemory 和 WriteProcessMemory 等跨进程的内存函数.
除了私有地址空间和一个或者多个线程以外, 每个进程还有一个安全环境和一个已打开句柄的列表, 这些句柄指向诸如文件, 共享内存区, 或者像互斥体, 事件或者信号量等某个同步对象
每个进程都有一个安全环境, 存储在一个称为访问令牌 (access token) 的对象中. 进程的访问令牌包含了该进程的安全标识和凭证. 在默认情况下, 线程没有自己的访问令牌, 但是他们也可以获得一个访问令牌, 因此单独的线程可以模仿另一个进程的安全环境.
虚拟地址描述符 (AVD) 是指一些数据结构, 内存管理器利用这些数据结构来记录进程正在使用的虚拟地址.
Windows 在进程模型上提供了一个拓展, 称为作业(job). 作业对象的主要功能是, 使一组进程被当做一个整体来管理和维护. 通过作业对象, 可以对特定属性进行控制, 也可以对一个进程, 或者所有与作业相关联的进程进行限制. 作业对象也可以为所有与该作业相关联的进程记录下基本审计信息, 其中也包括曾经与改作业关联但是已经终止了的进程的审计信息. 在某种程度上, 作业对象弥补了 Windows 平台上缺乏结构化的进程树的不足, 而且, 它的功能.
Windows 实现了一个基于平面 (线性) 地址空间的虚拟内存系统, 使每个进程感觉自己独立拥有一个很大的私有地址空间. 虚拟内存提供了一个内存逻辑视图, 它可能并不对应于内存的物理布局. 在运行的时候, 内存管理器借助于硬件的支持, 将虚拟地址转译或者映射 (map) 成真正存放数据的物理地址. 操作系统通过控制这一保护和映射机制, 可以确保一个进程不会闯入到另一个进程中, 也不会改写操作系统的数据. 因为大多数系统所拥有的物理内存, 比当前正在运行的进程所用到的虚拟内存总量要少得多, 所以, 内存管理器会将内存中的有些内容转移或者翻 (page) 到磁盘上. 将数据翻译到磁盘上以后, 就可以释放这部分物理内存, 因此, 这些物理内存可以被别的进程所使用, 或者用于操作系统本身. 当线程访问一个已被翻到磁盘上的虚拟地址时, 虚拟内存管理器会将磁盘上的信息装回内存中. 应用程序无须任何改变就可以利用这种分页 (paging) 功能, 因为在硬件的支持下, 内存管理器无须任何关于进程和线程的知识, 也无须进程或线程的协助, 就可以实现分页. 虚拟地址空间的大小随着硬件平台而有所不同. 在 32 位 x86 系统中, 总的虚拟地址空间有一个理论上的最大值 4GB. 在默认情况下, Windows 将这一部分地址空间的一半 (4GB 虚拟地址空间中较低的一半, x00000000x7FFFFFFF) 分配给进程, 作为它们独有的私有存储空间, 而另一半 (地址空间中较高的一半, x80000000xFFFFFFFF) 则用于它自己的被保护的操作系统内存. 低一半地址空间的映射关系会发生变化, 以便总是反应出当前正在执行的进程的虚拟地址空间, 而高一半地址空间的映射关系总是由操作系统的虚拟内存构成. Windows 支持一些引导选项, 可以使那些运行带有特殊标记的程序 (在可执行映像文件头部设置了大地址空间感知标志) 的进程能够使用多达 3GB 的私有空间 (给操作系统留下 1GB). 这一选项使得像数据库服务器这样的应用程序的更多内容保留在进程的地址空间中, 从而减少映射该数据库的子集视图的需求. 虽然 3GB 比 2GB 更好, 但是, 对于映射非常大的数据库(许多个 GB) 而言, 虚拟地址空间任然不足. 针对在 32 位系统上的这种需求, Windows 提供了一个被称为地址窗口扩展 (AWE,Addres Windowing Extension) 的机制, 使得 32 位应用程序可以申请多达 64GB 物理内存, 然后将内存视图或者窗口映射到它的 2GB 虚拟地址空间中. 虽然 AWE 将管理 "虚拟内存 - 物理内存" 映射关系的负担放到了程序员的身上, 但是, 它确实解决了问题, 使得进程能够直接访问更多的物理内存. 超过了它的 32 位进程地址空间一次能够映射的数量. 64 位 Windows 为进程提供了更大的地址空间: 在 IA-64 系统上位 7152GB,x64 系统上为 8192GB. 注意, 这些大小值并不代表这些平台上的架构限制. 64 位地址空间的大小超过了 170 亿 GB, 但当前的 64 位硬件限制了地址空间比该值要小, 而在当前的 64 位 Windows 中, 由于 Windows 的实现机制, 此地址空间又被降低到 8192GB(8TB).
为了避免用户应用程序访问和 / 或修改关键的操作系统数据, Windows 使用了两种处理器访问模式 (即使运行 Windows 的底层处理器支持多余两种模式): 用户模式和内核模式. 用户程序运行在用户模式下, 而操作系统代码(比如系统服务和设备驱动程序) 运行在内核模式下. 内核模式是指这样一种处理器执行模式: 它允许访问所有的系统内存和所有的 CPU 指令. 通过让操作系统软件比应用软件有更高的特权级, 处理器位操作系统设计者提供了必要的保护, 可以确保行为不正常的应用程序不会破环系统整体的稳定性.
注: x86 和 x64 处理器架构定义了四种特权级, 或者称为四个环 (ring), 来保护系统代码和数据不会被低级别的代码恶意地或无意地改写. Windows 使用特权级 0(或称为 0 环) 作为内核模式, 特权级 3(或称 3 环)作为用户模式. Windows 之所以只使用两级, 是因为它过去支持的一些硬件架构 (比如 Compaq Alpha 和 Silicon Graphics MIPS) 只实现了两个特权级.
虽然每个 Windows 进程都有自己私有的内存空间, 但是内核模式的操作系统和设备驱动程序代码共享一个虚拟地址空间. 虚拟内存中的内一个页面都被标记了处理器必须在什么访问模式下才可以读和 / 或写该页面. 系统空间中的页面只有在内核模式下才可以访问, 而用户地址空间的所有页面都可以在用户模式下访问. 只读页面 (比如包含静态数据的页面) 在任何模式下都是不可写的. 此外, 在支持不可执行 (no-execute) 内存保护的处理器上, Windows 将包含数据的页面标记为不可执行, 从而防止数据区域被无意或恶意地当作代码来执行.
对于在内核模式下运行的组件, 32 位 Windows 对它们所使用的私有系统内存并不提供读写保护. 换句话说, 一旦进入内核模式, 操作系统和设备驱动程序的代码可以完全访问系统空间的内存, 也可以绕过 Windows 的安全机制直接访问对象. 因为有大量的 Windows 操作系统代码运行在内核模式下, 所以很关键的一点是, 对在内核模式下运行的组件必须要谨慎地设计和测试, 以确保它们不会破坏系统的安全性, 也不会造成系统不稳定.
由于缺乏保护, 因此, 再加载第三方设备驱动程序时更需要加倍小心, 因为一旦进入内核模式, 这些软件就可以完全访问所有的操作系统数据. 这一弱点也正是 Windows 要引入驱动程序签名机制的原因之一. 引入驱动程序签名机制以后, 当未签名的即插即用驱动程序企图加入系统中时, Windows 会警告用户, 并且阻止其加入系统 (如果做了这样的配置). 还有一种被称为驱动程序检验器(Driver Verifier) 的机制, 可以帮助设备驱动程序的编写者找到驱动程序中引发安全性或可靠性问题的缺陷(比如缓存区溢出或者内存泄漏).
在 64 位版本的 Windows 中, 内核模式代码签名 (KMCS,Kernel Mode Code Signing) 策略规定, 64 位设备驱动程序 (不仅仅是即插即用程序) 必须要经过某个主码认证权威机构发放的密钥来签名. 用户不能明确地强制安装未经签名的驱动程序, 即使管理员也不行, 但有一个一次性的例外, 这一限制可以在系统引导时手工禁止, 即, 在引导时按下 F8 键, 选择高级引导选项 "禁用驱动程序强制签名 (Disable Driver Signature Enforcement)". 这会导致在桌面壁纸上出现一个水印, 同时特定的数字版权保护(DRM) 特性被关闭.
在第 2 章 "系统架构" 中将看到, 用户应用程序将在进行系统服务调用时, 会从用户模式切换到内核模式下. 例如, Windows 的 ReadFile 函数最终要调用 Windows 内部的一个例程, 由该例程真正完成从一个文件中读取数据的任务. 由于该例程需要访问内部的系统数据结构, 所以, 它必须运行在内核模式下. 从用户模式切换到内核模式, 可以通过专门的处理器指令来完成, 该指令会将处理器切换到内核模式下, 并进入内核模式中的系统服务分发代码, 进一步调用 Ntostrnl.exe 或 Win32k.sys 中适当的内部函数. 在将控制权返回用户线程之前, 处理器的模式被切换回用户模式. 通过这种方法, 操作系统将自身和它的数据保护起来, 使它们不会被用户进程看到或者修改. 因此, 很自然地, 对于一个用户线程来说, 它的一部分时间运行在用户模式下, 另一部时间运行在内核模式下. 实际上, 因为图形和窗口系统的大部分时间也运行在内核模式下, 所以, 图形密集的应用程序花在内模式下的时间比在用户模式下的时间多得多. 这一点很容易测试, 一种简单的办法是, 运行一个图形密集的应用程序, 比如 "画图" 或者 Chess Tians, 然后使用性能监视器(具体方法百度)
终端服务指的是在单个系统中, Windows 对于多个可交互用户会话的支持. 利用 Windows 的终端服务, 一个远程用户可以在另一台机器上建立一个会话, 并且登陆进去, 在该服务器上运行应用程序. 服务器把图形用户界面 (以及其他可配置的资源, 比如音频和剪切板) 传送到客户机, 客户机把用户的输入传回服务器.(与 X 窗口系统类似, Windows 允许在一个服务器系统运行独立的应用程序, 其显示部分远程传送到客户机, 而非将整个桌面远程到客户机). 第一个会话被认为是服务会话, 或者零号会话, 它包含了宿纳系统服务的进程. 在机器的物理控制台上的第一个登陆会话位一号会话, 而其他会话可以通过远程桌面连接程序 (Mstsc.exe) 来建立, 或者通过使用快速用户切换来建立.
Windows 的客户机版本允许单个远程用户连接到及其上, 但如果有人已经在控制台上登陆了, 则工作站会被锁住(也就是说, 一个人可以利用本地或者远程方式使用 Windows 客户机系统, 但不能同时以这两种方式来使用系统).
Windows 服务器系统支持两个并发的远程连接(这是为了方便远程管理, 例如, 有些管理工具要求登录到被管理的机器中才可以使用), 以及两个以上的远程会话.
所有 Windows 客户机版本都支持通过使用一个被称为快速用户切换 (fast user switching) 的特性, 在本地创建多个会话. 当用户选择断开其会话, 而不是注销其登录时, 当前会话仍然保留在系统中, 而系统返回到主屏幕. 如果一个新的用户登陆进来, 则新建一个会话.
在 Windows 操作系统中, 内核对象是指某个静态定义的对象类型的单个运行时实例. 对象类型由系统定义的的数据类型, 在该数据类型的实例上进行操作的一组函数, 以及一组对象属性构成. 如果你编写 Windows 应用程序, 那么, 你可能会遇到进程, 线程, 文件和事件对象, 这只是对象的一些例子而已. 这些对象都是以 Windows 创建和管理的底层对象为基础的. 在 Windows 中, 任何进程都是进程对象类型的实例, 文件是文件对象类型的实例, 如此等等.
对象属性是对象中的数据域, 每个对象属性定义了对象的一部分状态. 例如, 类型为进程的对象, 其属性包括进程 ID, 基本调度优先级和一个指向访问令牌对象的指针. 对象方法, 即操纵对象的手段, 通常用于读取或者改变对象的属性. 例如, 进程的 open 方法接受一个进程标识符作为输入, 返回一个指向该进程对象的指针作为输出.
对象和普通数据结构之间的最根本区别是, 对象的内部结构是不透明的. 你必须调用一个对象服务才可以获得对象内部的数据, 或者把数据放入对象内部. 你不能直接读取或者改变一个对象内部的数据. 这一区别将对象的底层实现与那些仅仅使用该对象的代码隔离开来, 此技术使得对象的实现可以随着时间而容易地改变.
借助一个被称为对象管理器的内核组件, Windows 中的对象提供一种便捷的途径来实现下列四个重要的操作系统任务:
并非 Windows 操作系统中的所有数据结构都是对象. 只有确实需要被共享, 保护, 命名或者让用户模式程序看得到 (通过系统服务) 的数据才被放入到对象中. 仅仅被操作系统的某一个组件用来实现其内部函数的数据结构并不是对象.
Windows 从一开始就被要求是安全的, 能够满足政府和工业界各种正式的安全评级的需求
Windows 的核心安全功能包括: 针对所有可共享系统对象 (比如文件, 目录, 进程, 线程等等) 的自主保护 (need-to-know) 和强制完整性保护, 安全审计(针对主体或者用户和他们发起的动作的记录), 登陆时候的用户认证, 以及禁止用户通过访问未初始化资源的做法来访问其他用户已释放的资源(比如空闲内存或磁盘空间).
Windows 有三种对对象的访问控制形式. 第一种控制形式称为自主访问控制, 大多数人一想到操作系统安全性, 自然就会想到这种保护机制. 其做法是, 由对象 (比如文件或者打印机) 的所有者授权或者拒绝其他人访问这些对象. 当用户登陆系统中时, 他们会得到一组安全凭证, 或者一个安全环境. 当他们试图访问对象时, 系统会将他们的安全环境与他们要访问的对象的访问控制列表进行比较, 以确定他们是否可以执行所请求的操作.
当自主访问控制不能满足需要时, 应该使用特权访问控制. 这种方法可以确保: 即使对象的所有者当前无法联系到, 也有人可以访问被保护的对象. 例如, 如果一个员工离开了公司, 则管理员必须有办法访问那些本该只有该员工才可以访问的文件. 在这种情况下, 在 Windows 中, 管理员能够接管这些文件的所有权, 从而可以根据需要管理它们的权限.
最后, 当需要额外的一层安全控制以实现在同一个账户内部的对象访问保护时, 需要使用强制完整性控制. 它既被用于将 Internet Explorer 的保护模式与用户的配置隔离开, 也被用于保护那些由提升了特权的管理员所创建的对象, 避免被未被提升特权的管理员账户所访问.
如果你与 Windows 操作系统打过交道, 那么, 你可能听说过或者看到过注册表. 探讨 Windows 内部机理, 就不能不提到注册表, 因为注册表是系统数据库, 它包含了引导和配置系统所必需的消息, 全系统范围内控制 Windows 操作的软件设置, 安全数据库, 以及针对每个用户配置设置(比如使用哪个屏幕保护程序).
同时, 注册表也是一个反映内存中易失数据的窗口, 比如系统的当前硬件状态 (哪些设备驱动程序已经加载, 它们用到了哪些资源, 等等) 以及 Windows 的性能计数器.
虽然许多 Windows 用户和管理员从来不需要直接查看注册表 (因为你可以通过标准的管理工具来查看或者改变大多数配置设置), 但是, 注册表仍然是一个非常有用的 Windows 内部信息来源, 因为它包含了许多会影响系统性能和行为的设置. 在本书中你将会看到许多注册表键, 当介绍某些组件时, 将会提及相应的注册表键. 本书中提到的绝大多数注册表键位于全系统范围的配置(即 HKEY_LOCAL_MACHINE) 下面, 我们将其缩写位 HKLM.
Windows 区别于大多数其他操作系统的一个方面是, 它的大多数内部文本串是以 16 位宽度的 Unicode 字符来存储和处理的.
因为许多应用程序只处理 8 位 (单字节)ANS 字符串, 所以, 接受字符串参数的 Windows 函数有两个入口点: 一个 Unicode(宽字符, 16 位) 版本和一个 ANSI(窄字符, 8 位)版本.
Windows 应用编程接口 (API) 是针对 Windows 操作系统家族的用户模式系统编程接口. 在 64 位版本的 Windows 推广以前, 32 位版本 Windows 操作系统的编程接口被称为 Win32 API, 以区别于原来的 16 位版本 Windows 的编程接口, 即 16 位 Windows API. 在本书中, 术语 Windows API 兼指 Windows 的 32 位和 64 位编程接口.
注 : Windows 软件开发工具 (SDK) 文档描述了 Windows API. 这份文档可以在 www.msdn.microsoft.com 在线免费查阅. 关于如何在 Windows 基础 API 上编写程序, 有一份极好的资料 ---Jeffrey Richter 和 Christophe Nasarre 所写的 Windows via C/C++ 一书.
Windows API 包含数千个可调用的函数, 它们可以被分成以下一些大类:
本书主要关注关键基本服务 (比如进程和线程, 内存管理, I/O 和安全性) 的内部机理.
关于. NET Microsoft.NET 框架是由一个被称为框架类库 (FCL,Framework Class Library) 的类库和一个提供了托管代码执行环境的公共语言运行库 (CLR,Common Language Runtime) 组成的, 后者提供的代码执行环境包含以下一些特征:
由于 CLR 具有这些特性, 因此它所提供的开发环境能够提高开发人员的生产效率, 减少常见的编程错误. 关于. NET 框架及其核心架构的精彩描述, 参见 Jeffrey Richter 的 CLR via C#
Win32 API 的历史 有意思的是, Win32 并不是 Windows NT 最初预定的编程接口. 因为 Windows NT 项目在启动之初, 目的是代替 OS/2 第 2 版, 所以, 它的主要编程接口是 32 位 OS/2 Presentation Manager API. 然而, 项目进行了一年后, Microsoft Windows 3.0 进入市场, 并且呈现出很好的发展势头. 于是, Microsoft 转变了方向, 使 Windows NT 成为未来 Windows 产品家族的替代品, 而不是用来替代 OS/2. 也正是这个时候, 才真正有必要定制 Windows API--- 在此之前, 在 Windows 3.0 中, 只有 16 位接口的 API. 尽管当时在 Windows API 中将引入很多在 Windows 3.1 上还无法使用的新函数, 但是, Microsoft 还是决定让新的 API 与 16 位 Windows API 在函数名称, 语义和数据类型用法上尽可能兼容, 以便减轻将已有的的 16 位 Windows 应用程序移植到 Windows NT 上的负担. 这也是许多函数名称和接口看起来并不一致的原因: 为了确保当时新的 Windows API 与老的 16 位 Windows API 保持兼容, 这是必须的.
在 Windows 的用户文档和程序设计文档中, 有几个术语在不同的上下文环境中有着不同的含义. 例如, 服务可以指操作系统中可以被调用的例程, 设备驱动程序或者服务器进程. 下面的列表描述了一些特定的术语在本书中的含义:
尽管表面上看起来程序和进程非常相似, 但本质上它们却是截然不同的. 程序是一个静态的指令序列, 而进程是一个容器, 其中包含了执行程序的特定实例所需的各种资源. 从最高层次的抽象来看, Windows 进程是由以下元素构成的:
每个进程也指向它的父进程或者创建者进程. 如果父进程不再存在, 子进程中的这一信息并不会被更新.
关于进程和线程的信息, Sysinternals 出品的 Process Explorer 提供了比其他任何工具都要多的细节, 正因为如此, 你将会在本书的许多实验中看到这个工具的运用.
线程是一个进程内部的实体, 也是 Windows 执行此进程时的调度实体. 如果没有线程, 进程的程序将不可能运行起来. 线程包括以下一些最基本的部件:
易失的寄存器, 栈和私有存储区域结合起来被称为线程的环境(context). 因为这些信息随着 Windows 所在机器架构的不同而有所不同, 所以, 此架构必须是与底层架构相关的. Windows 的 GetThreadContext 函数允许程序访问这一与架构相关的信息(称为 CONTEXT 块).
因为将 CPU 的执行从一个线程切换到另一个线程, 将不可避免地涉及内核调度器, 所以, 这可能是一个开销昂贵的操作, 如果两个线程经常频繁来回切换则尤为如此. Windows 实现了两种机制来降低这种开销: 纤程 (fiber) 和用户模式调度(UMS)
纤程使得一个应用程序可以调度它自己的 "线程" 的执行过程, 而不必依赖 Windows 内置的基于优先级的调度机制. 纤程也常被称为 "轻量" 线程: 从调度的角度来看, 它们对于内核是不可见的, 因为它们是在用户模式下在 Kernel32.dll 中实现的. 使用方法参考 Windows SDK 文档.
UMS 线程仅在 64 位 Windows 长可用, 它基本上提供了与纤程同样的好处, 没有更多的坏处. UMS 线程有它们自己的内核线程状态, 因此对于内核是可见的, 这使得多个 UMS 线程都可以发出阻塞的系统调用, 对资源进行共享或竞争, 并且有每个线程特有的状态(per-thread state)
虽然线程有自己的执行环境, 但是, 同一个进程内部的所有线程共享该进程的虚拟地址空间(以及同属于该进程的其他资源), 这意味着, 一个进程内的所有线程都可以完全地读或写该进程的虚拟地址空间. 然而, 一个进程中的线程不可能无意识地引用另一个进程的地址空间, 除非两种情况: 第二个进程将它的一部分私有地址空间变成共享内存区(shared memory section)(在 Windows API 中称为文件映射对象(file mapping object)); 或者, 第一个进程有权打开第二个进程, 从而可以使用诸如 ReadProcessMemory 和 WriteProcessMemory 等跨进程的内存函数.
除了私有地址空间和一个或者多个线程以外, 每个进程还有一个安全环境和一个已打开句柄的列表, 这些句柄指向诸如文件, 共享内存区, 或者像互斥体, 事件或者信号量等某个同步对象
每个进程都有一个安全环境, 存储在一个称为访问令牌 (access token) 的对象中. 进程的访问令牌包含了该进程的安全标识和凭证. 在默认情况下, 线程没有自己的访问令牌, 但是他们也可以获得一个访问令牌, 因此单独的线程可以模仿另一个进程的安全环境.
虚拟地址描述符 (AVD) 是指一些数据结构, 内存管理器利用这些数据结构来记录进程正在使用的虚拟地址.
Windows 在进程模型上提供了一个拓展, 称为作业(job). 作业对象的主要功能是, 使一组进程被当做一个整体来管理和维护. 通过作业对象, 可以对特定属性进行控制, 也可以对一个进程, 或者所有与作业相关联的进程进行限制. 作业对象也可以为所有与该作业相关联的进程记录下基本审计信息, 其中也包括曾经与改作业关联但是已经终止了的进程的审计信息. 在某种程度上, 作业对象弥补了 Windows 平台上缺乏结构化的进程树的不足, 而且, 它的功能.
Windows 实现了一个基于平面 (线性) 地址空间的虚拟内存系统, 使每个进程感觉自己独立拥有一个很大的私有地址空间. 虚拟内存提供了一个内存逻辑视图, 它可能并不对应于内存的物理布局. 在运行的时候, 内存管理器借助于硬件的支持, 将虚拟地址转译或者映射 (map) 成真正存放数据的物理地址. 操作系统通过控制这一保护和映射机制, 可以确保一个进程不会闯入到另一个进程中, 也不会改写操作系统的数据. 因为大多数系统所拥有的物理内存, 比当前正在运行的进程所用到的虚拟内存总量要少得多, 所以, 内存管理器会将内存中的有些内容转移或者翻 (page) 到磁盘上. 将数据翻译到磁盘上以后, 就可以释放这部分物理内存, 因此, 这些物理内存可以被别的进程所使用, 或者用于操作系统本身. 当线程访问一个已被翻到磁盘上的虚拟地址时, 虚拟内存管理器会将磁盘上的信息装回内存中. 应用程序无须任何改变就可以利用这种分页 (paging) 功能, 因为在硬件的支持下, 内存管理器无须任何关于进程和线程的知识, 也无须进程或线程的协助, 就可以实现分页. 虚拟地址空间的大小随着硬件平台而有所不同. 在 32 位 x86 系统中, 总的虚拟地址空间有一个理论上的最大值 4GB. 在默认情况下, Windows 将这一部分地址空间的一半 (4GB 虚拟地址空间中较低的一半, x00000000x7FFFFFFF) 分配给进程, 作为它们独有的私有存储空间, 而另一半 (地址空间中较高的一半, x80000000xFFFFFFFF) 则用于它自己的被保护的操作系统内存. 低一半地址空间的映射关系会发生变化, 以便总是反应出当前正在执行的进程的虚拟地址空间, 而高一半地址空间的映射关系总是由操作系统的虚拟内存构成. Windows 支持一些引导选项, 可以使那些运行带有特殊标记的程序 (在可执行映像文件头部设置了大地址空间感知标志) 的进程能够使用多达 3GB 的私有空间 (给操作系统留下 1GB). 这一选项使得像数据库服务器这样的应用程序的更多内容保留在进程的地址空间中, 从而减少映射该数据库的子集视图的需求. 虽然 3GB 比 2GB 更好, 但是, 对于映射非常大的数据库(许多个 GB) 而言, 虚拟地址空间任然不足. 针对在 32 位系统上的这种需求, Windows 提供了一个被称为地址窗口扩展 (AWE,Addres Windowing Extension) 的机制, 使得 32 位应用程序可以申请多达 64GB 物理内存, 然后将内存视图或者窗口映射到它的 2GB 虚拟地址空间中. 虽然 AWE 将管理 "虚拟内存 - 物理内存" 映射关系的负担放到了程序员的身上, 但是, 它确实解决了问题, 使得进程能够直接访问更多的物理内存. 超过了它的 32 位进程地址空间一次能够映射的数量. 64 位 Windows 为进程提供了更大的地址空间: 在 IA-64 系统上位 7152GB,x64 系统上为 8192GB. 注意, 这些大小值并不代表这些平台上的架构限制. 64 位地址空间的大小超过了 170 亿 GB, 但当前的 64 位硬件限制了地址空间比该值要小, 而在当前的 64 位 Windows 中, 由于 Windows 的实现机制, 此地址空间又被降低到 8192GB(8TB).
为了避免用户应用程序访问和 / 或修改关键的操作系统数据, Windows 使用了两种处理器访问模式 (即使运行 Windows 的底层处理器支持多余两种模式): 用户模式和内核模式. 用户程序运行在用户模式下, 而操作系统代码(比如系统服务和设备驱动程序) 运行在内核模式下. 内核模式是指这样一种处理器执行模式: 它允许访问所有的系统内存和所有的 CPU 指令. 通过让操作系统软件比应用软件有更高的特权级, 处理器位操作系统设计者提供了必要的保护, 可以确保行为不正常的应用程序不会破环系统整体的稳定性.
注: x86 和 x64 处理器架构定义了四种特权级, 或者称为四个环 (ring), 来保护系统代码和数据不会被低级别的代码恶意地或无意地改写. Windows 使用特权级 0(或称为 0 环) 作为内核模式, 特权级 3(或称 3 环)作为用户模式. Windows 之所以只使用两级, 是因为它过去支持的一些硬件架构 (比如 Compaq Alpha 和 Silicon Graphics MIPS) 只实现了两个特权级.
虽然每个 Windows 进程都有自己私有的内存空间, 但是内核模式的操作系统和设备驱动程序代码共享一个虚拟地址空间. 虚拟内存中的内一个页面都被标记了处理器必须在什么访问模式下才可以读和 / 或写该页面. 系统空间中的页面只有在内核模式下才可以访问, 而用户地址空间的所有页面都可以在用户模式下访问. 只读页面 (比如包含静态数据的页面) 在任何模式下都是不可写的. 此外, 在支持不可执行 (no-execute) 内存保护的处理器上, Windows 将包含数据的页面标记为不可执行, 从而防止数据区域被无意或恶意地当作代码来执行.
对于在内核模式下运行的组件, 32 位 Windows 对它们所使用的私有系统内存并不提供读写保护. 换句话说, 一旦进入内核模式, 操作系统和设备驱动程序的代码可以完全访问系统空间的内存, 也可以绕过 Windows 的安全机制直接访问对象. 因为有大量的 Windows 操作系统代码运行在内核模式下, 所以很关键的一点是, 对在内核模式下运行的组件必须要谨慎地设计和测试, 以确保它们不会破坏系统的安全性, 也不会造成系统不稳定.
由于缺乏保护, 因此, 再加载第三方设备驱动程序时更需要加倍小心, 因为一旦进入内核模式, 这些软件就可以完全访问所有的操作系统数据. 这一弱点也正是 Windows 要引入驱动程序签名机制的原因之一. 引入驱动程序签名机制以后, 当未签名的即插即用驱动程序企图加入系统中时, Windows 会警告用户, 并且阻止其加入系统 (如果做了这样的配置). 还有一种被称为驱动程序检验器(Driver Verifier) 的机制, 可以帮助设备驱动程序的编写者找到驱动程序中引发安全性或可靠性问题的缺陷(比如缓存区溢出或者内存泄漏).
在 64 位版本的 Windows 中, 内核模式代码签名 (KMCS,Kernel Mode Code Signing) 策略规定, 64 位设备驱动程序 (不仅仅是即插即用程序) 必须要经过某个主码认证权威机构发放的密钥来签名. 用户不能明确地强制安装未经签名的驱动程序, 即使管理员也不行, 但有一个一次性的例外, 这一限制可以在系统引导时手工禁止, 即, 在引导时按下 F8 键, 选择高级引导选项 "禁用驱动程序强制签名 (Disable Driver Signature Enforcement)". 这会导致在桌面壁纸上出现一个水印, 同时特定的数字版权保护(DRM) 特性被关闭.
在第 2 章 "系统架构" 中将看到, 用户应用程序将在进行系统服务调用时, 会从用户模式切换到内核模式下. 例如, Windows 的 ReadFile 函数最终要调用 Windows 内部的一个例程, 由该例程真正完成从一个文件中读取数据的任务. 由于该例程需要访问内部的系统数据结构, 所以, 它必须运行在内核模式下. 从用户模式切换到内核模式, 可以通过专门的处理器指令来完成, 该指令会将处理器切换到内核模式下, 并进入内核模式中的系统服务分发代码, 进一步调用 Ntostrnl.exe 或 Win32k.sys 中适当的内部函数. 在将控制权返回用户线程之前, 处理器的模式被切换回用户模式. 通过这种方法, 操作系统将自身和它的数据保护起来, 使它们不会被用户进程看到或者修改. 因此, 很自然地, 对于一个用户线程来说, 它的一部分时间运行在用户模式下, 另一部时间运行在内核模式下. 实际上, 因为图形和窗口系统的大部分时间也运行在内核模式下, 所以, 图形密集的应用程序花在内模式下的时间比在用户模式下的时间多得多. 这一点很容易测试, 一种简单的办法是, 运行一个图形密集的应用程序, 比如 "画图" 或者 Chess Tians, 然后使用性能监视器(具体方法百度)
终端服务指的是在单个系统中, Windows 对于多个可交互用户会话的支持. 利用 Windows 的终端服务, 一个远程用户可以在另一台机器上建立一个会话, 并且登陆进去, 在该服务器上运行应用程序. 服务器把图形用户界面 (以及其他可配置的资源, 比如音频和剪切板) 传送到客户机, 客户机把用户的输入传回服务器.(与 X 窗口系统类似, Windows 允许在一个服务器系统运行独立的应用程序, 其显示部分远程传送到客户机, 而非将整个桌面远程到客户机). 第一个会话被认为是服务会话, 或者零号会话, 它包含了宿纳系统服务的进程. 在机器的物理控制台上的第一个登陆会话位一号会话, 而其他会话可以通过远程桌面连接程序 (Mstsc.exe) 来建立, 或者通过使用快速用户切换来建立.
Windows 的客户机版本允许单个远程用户连接到及其上, 但如果有人已经在控制台上登陆了, 则工作站会被锁住(也就是说, 一个人可以利用本地或者远程方式使用 Windows 客户机系统, 但不能同时以这两种方式来使用系统).
Windows 服务器系统支持两个并发的远程连接(这是为了方便远程管理, 例如, 有些管理工具要求登录到被管理的机器中才可以使用), 以及两个以上的远程会话.
所有 Windows 客户机版本都支持通过使用一个被称为快速用户切换 (fast user switching) 的特性, 在本地创建多个会话. 当用户选择断开其会话, 而不是注销其登录时, 当前会话仍然保留在系统中, 而系统返回到主屏幕. 如果一个新的用户登陆进来, 则新建一个会话.
在 Windows 操作系统中, 内核对象是指某个静态定义的对象类型的单个运行时实例. 对象类型由系统定义的的数据类型, 在该数据类型的实例上进行操作的一组函数, 以及一组对象属性构成. 如果你编写 Windows 应用程序, 那么, 你可能会遇到进程, 线程, 文件和事件对象, 这只是对象的一些例子而已. 这些对象都是以 Windows 创建和管理的底层对象为基础的. 在 Windows 中, 任何进程都是进程对象类型的实例, 文件是文件对象类型的实例, 如此等等.
对象属性是对象中的数据域, 每个对象属性定义了对象的一部分状态. 例如, 类型为进程的对象, 其属性包括进程 ID, 基本调度优先级和一个指向访问令牌对象的指针. 对象方法, 即操纵对象的手段, 通常用于读取或者改变对象的属性. 例如, 进程的 open 方法接受一个进程标识符作为输入, 返回一个指向该进程对象的指针作为输出.
对象和普通数据结构之间的最根本区别是, 对象的内部结构是不透明的. 你必须调用一个对象服务才可以获得对象内部的数据, 或者把数据放入对象内部. 你不能直接读取或者改变一个对象内部的数据. 这一区别将对象的底层实现与那些仅仅使用该对象的代码隔离开来, 此技术使得对象的实现可以随着时间而容易地改变.
借助一个被称为对象管理器的内核组件, Windows 中的对象提供一种便捷的途径来实现下列四个重要的操作系统任务:
并非 Windows 操作系统中的所有数据结构都是对象. 只有确实需要被共享, 保护, 命名或者让用户模式程序看得到 (通过系统服务) 的数据才被放入到对象中. 仅仅被操作系统的某一个组件用来实现其内部函数的数据结构并不是对象.
Windows 从一开始就被要求是安全的, 能够满足政府和工业界各种正式的安全评级的需求
Windows 的核心安全功能包括: 针对所有可共享系统对象 (比如文件, 目录, 进程, 线程等等) 的自主保护 (need-to-know) 和强制完整性保护, 安全审计(针对主体或者用户和他们发起的动作的记录), 登陆时候的用户认证, 以及禁止用户通过访问未初始化资源的做法来访问其他用户已释放的资源(比如空闲内存或磁盘空间).
Windows 有三种对对象的访问控制形式. 第一种控制形式称为自主访问控制, 大多数人一想到操作系统安全性, 自然就会想到这种保护机制. 其做法是, 由对象 (比如文件或者打印机) 的所有者授权或者拒绝其他人访问这些对象. 当用户登陆系统中时, 他们会得到一组安全凭证, 或者一个安全环境. 当他们试图访问对象时, 系统会将他们的安全环境与他们要访问的对象的访问控制列表进行比较, 以确定他们是否可以执行所请求的操作.
当自主访问控制不能满足需要时, 应该使用特权访问控制. 这种方法可以确保: 即使对象的所有者当前无法联系到, 也有人可以访问被保护的对象. 例如, 如果一个员工离开了公司, 则管理员必须有办法访问那些本该只有该员工才可以访问的文件. 在这种情况下, 在 Windows 中, 管理员能够接管这些文件的所有权, 从而可以根据需要管理它们的权限.
最后, 当需要额外的一层安全控制以实现在同一个账户内部的对象访问保护时, 需要使用强制完整性控制. 它既被用于将 Internet Explorer 的保护模式与用户的配置隔离开, 也被用于保护那些由提升了特权的管理员所创建的对象, 避免被未被提升特权的管理员账户所访问.
如果你与 Windows 操作系统打过交道, 那么, 你可能听说过或者看到过注册表. 探讨 Windows 内部机理, 就不能不提到注册表, 因为注册表是系统数据库, 它包含了引导和配置系统所必需的消息, 全系统范围内控制 Windows 操作的软件设置, 安全数据库, 以及针对每个用户配置设置(比如使用哪个屏幕保护程序).
同时, 注册表也是一个反映内存中易失数据的窗口, 比如系统的当前硬件状态 (哪些设备驱动程序已经加载, 它们用到了哪些资源, 等等) 以及 Windows 的性能计数器.
虽然许多 Windows 用户和管理员从来不需要直接查看注册表 (因为你可以通过标准的管理工具来查看或者改变大多数配置设置), 但是, 注册表仍然是一个非常有用的 Windows 内部信息来源, 因为它包含了许多会影响系统性能和行为的设置. 在本书中你将会看到许多注册表键, 当介绍某些组件时, 将会提及相应的注册表键. 本书中提到的绝大多数注册表键位于全系统范围的配置(即 HKEY_LOCAL_MACHINE) 下面, 我们将其缩写位 HKLM.
Windows 区别于大多数其他操作系统的一个方面是, 它的大多数内部文本串是以 16 位宽度的 Unicode 字符来存储和处理的.
因为许多应用程序只处理 8 位 (单字节)ANS 字符串, 所以, 接受字符串参数的 Windows 函数有两个入口点: 一个 Unicode(宽字符, 16 位) 版本和一个 ANSI(窄字符, 8 位)版本.
来源: http://www.bubuko.com/infodetail-3109871.html