在windows操作系统的发展历程中,Windows7是一个具有特殊意义的版本。它是目前最为复杂的单机操作系统,无论从代码规模、代码复杂度,到系统适应场景的复杂程度,都超过了以前所有的版本。从某种意义上,Windows7代表了软件工程的一个顶峰--人类可以构造出如此复杂且能稳定工作的软件系统!
1.Windows操作系统的版本
表1.1Windows操作系统的历次发布
产品名称 | 内部版本号 | 发布日期 |
Windows NT 3.1 | 3.1 | 1993年7月 |
Windows NT 3.5 | 3.5 | 1994年9月 |
Windows NT 3.51 | 3.51 | 1995年5月 |
Windows NT 4.0 | 4.0 | 1996年7月 |
Windows 2000 | 5.0 | 1999年12月 |
Windows XP | 5.1 | 2001年8月 |
Windows Server 2003 | 5.2 | 2003年3月 |
Windows Vista | 6.0(编译版本6000) | 2007年1月 |
Windows Server 2008 | 6.1(编译版本6001) | 2008年3月 |
Windows 7 | 6.1(编译版本7600) | 2009年10月 |
Windows Server 2008 R2 | 6.1(编译版本7600) | 2009年10月 |
注:"Windows 7"这一产品名称中的"7"并非指内部版本号,而是Windows家族的世代编号。实际上,为了使应用兼容性问题尽可能小,Windows 7的版本号其实是6.1,如表1.1所示。这使得那些检查大版本号的应用程序在Windows 7上可以像在Windows Vista上那样继续执行。事实上,Windows 7和Server 2008 R2有同样的版本号和编译版本,因为它们是从同样的Windows代码基编译而来。
基础概念和术语
Windows API
Windows应用编程接口(API)是针对Windows操作系统家族的用户模式系统编程接口。在64位版本Windows推广以前,32位版本Windows操作系统的编程接口被称为Win32 API,以区别原来的16位版本Windows的编程接口,即16位Windows API。Windows API包括数千个可调用的函数,它们可以被分成以下一些大类:
l 基本服务
l 组件服务
l 用户界面服务
l 图形和多媒体服务
l 消息和协作
l 网络
l Web服务
关于.NET
Micrsoft .NET框架是由一个被称为框架类库(FCL,Framework Class Library)的类库和一个提供了托管代码执行环境的公共语言运行库(CLR,Common Language Runtime)组成的,后者提供的托管代码执行环境包含以下一些特性:即时编译、类型检验、垃圾回收和代码访问安全性等。由于CLR具有这些特性,因此它所提供的开发环境能够提高开发人员的生产效率,减少常见的编程错误。
CLR的具体实现形式是一个典型的COM服务器,它的代码位于一个标准的用户模式Windows DLL中。实际上,.NET框架中所有组件的实现形式都是标准的用户模式Windows DLL,它们建立在非托管的Windows API函数之上(.NET框架中没有一个组件运行在内核模式下)。
Win32 API的历史
有意思的是,Win32并不是Windows NT最初预定的编程接口。因为Windows NT项目在启动之初,目标是替代OS/2第2版,所以,它的主要编程接口是32位OS/2 Presentation Manager API。然而,项目进行了一年以后,Micrsoft Windows 3.0进入市场,并且呈现出很好的发展势头。于是,Micrsoft转变了方向,使Windows NT成为未来Windows产品家族的替代品,而不是用来替代OS/2。也正是这个时候,才真正有必要制定Windows API--在此之前,在Windows3.0中,只有16位接口的API。
尽管当时在Windows API中将会引入很多在Windows 3.1上还无法使用的新函数,但是,Micrsoft还是决定让新的API与16位Windows API在函数名称、语义和数据类型用法上尽可能地兼容,以便减轻将已有的16位Windows应用程序移植到Windows NT上的负担。这也正是许多函数名称和接口看起来并不一致的原因:为了确保当时新的Windows API与老的16位Windows API保持兼容,这是必需的。
服务、函数和例程
在Windows的用户文档和程序设计文档中,有几个术语在不同的上下文环境中有着不同的含义,例如,服务可以指操作系统中可被调用的例程、设备驱动程序或者服务器进程。下面的列表描述了一些特定术语的含义:
Windows API函数 指Windows API中已被文档化的、可被调用的子例程。
原生的系统服务(或者系统调用) 指操作系统中未文档化的、可在用户模式下调用的底层服务。
内核支持函数(或例程) 指位于Windows操作系统内部且只能在内核模式下调用的子例程。
Windows服务 指由Windows服务控制管理器启动的进程。
DLL(动态链接库) 指一组可调用的子例程,合起来被链接成一个二进制文件,使用这些子例程的应用程序可以动态地加载此二进制文件。Windows的用户模式组件和应用程序大量使用了DLL。DLL比静态库的优势在于,应用程序可以共享DLL,Windows保证在内存中只有一份DLL代码,供所有引用该DLL的应用程序共享。注意,非可执行的.NET程序集也被编译成DLL,但是,它们没有导出任何子例程,而是由CLR解析出编译的元数据,以便访问对应的数据类型和成员。
进程、线程和作业
尽管表面上看起来程序和进程非常类似,但体质上它们却是截然不同的。程序是一个静态的指令序列,而进程是一个容器,其中包含了执行程序的特定实例时所用到的各种资源。从最高层次的抽象来看,Windows进程是由以下元素构成的:
l 私有的虚拟地址空间,这是指该进程可以使用的一组虚拟内存地址。
l 可执行的程序,它定义了初始的代码和数据,并且被映射到该进程的虚拟地址空间中。
l 已打开句柄的列表,这些句柄指向各种系统资源,比如信号量、通信端口和文件,该进程内所有的线程都可以访问这些系统资源。
l 被称为访问令牌的安全环境,它标识了与该进程关联的用户、安全组、特权、UAC(User Account Control,用户账户控制)虚拟化状态、会话,以及有限的用户账户状态。
l 被称为进程ID的唯一标识符(在内部,进程ID还是标识符客户ID的一部分)。
l 至少一个执行线程(尽管“空”进程也是有可能的,但没有用处)。
每个进程也指向它的父进程或者创建者进程。如果父进程不再存在,子进程中的这一信息并不会被更新。因此,一个进程有可能指向一个已不复存在的父进程。这并不是一个问题,因为任何一个进程都不必依赖于父进程信息的有效性。
线程是一个进程内部的实体,也是Windows执行此进程时的调度实体。如果没有线程,进程的程序将不可能运行。线程包括以下一些最基本的部件:
l 一组代表处理器状态的CPU寄存器中的内容。
l 两个栈--一个用于线程在内核模式下执行时,另一个用于在用户模式下执行时。
l 一个被称为线程局部存储的私有存储区域(TLS,thread-local storage),各个子系统、运行库和DLL都会用到该存储区域。
l 一个被称为线程ID的唯一标识符(它也是内部结构客户ID(client ID)的一部分--进程ID和线程ID是从同一个名字空间中产生的,所以它们永远不会重叠)。
l 有时候线程也有这它们自己的安全环境,或者令牌,多线程服务器应用程序要模仿其客户的安全环境时,常常会使用线程自己的安全环境。
易失的寄存器、栈和私有存储区域合起来被称为线程的环境。因为这些信息随着Windows所在机器架构的不同而有所不同,所以,此结构必须是与底层架构相关的。
注:对于在64位Windows版本上运行的32位应用程序,其线程将同时包含32位和64位环境,Wow64位会在必要的时候利用此环境,将应用程序从32位切换到64位模式下。这些线程将有两个用户栈和两个CONTEXT块,常规的Windows API函数将返回64位环境。
纤程与用户模式调度线程
因为将CPU的执行从一个线程切换到另一个线程,将不可避免地涉及内核调试器,所以,这可能是一个开销昂贵的操作,如果两个线程经常频繁地来回切换则尤其如此。Windows实现了两种机制来降低这一开销:纤程(fiber)和用户模式调度(UMS,user-mode scheduling)。
纤程使得一个应用程序可以调试它自己的“线程”的执行过程,而不必依赖于Windows内置的基于优先级的调度机制。纤程也常被称为“轻量”线程;从调度的角度来看,它们对于内核是不可见的,因为它们是在用户模式下在Kernel32.dll中实现的。为了使用纤程,首先要调用Windows的ConvertThreadToFiber函数,又可以创建额外的纤程。然而,与线程不同的是,纤程不会自动执行,它必须由SwitchToFiber函数手工选中,然后才能执行。新的纤程会一直运行,直到退出,或者调用SwitchToFiber再次选择运行另一个纤程。
UMS线程仅在64位Windows上可用,它基本上提供了与纤程同样的好处,没有更多的坏处。UMS线程有它们自己的内核线程状态,因此对于内核是可见的,这使得多个UMS线程都可以发出阻塞的系统调用、对资源进行共享或竞争,并且有每个线程特有的状态。然而,只要两个或多个UMS线程需要在用户模式下执行工作,它们可以周期性地切换执行环境而无须涉及内核调度器:环境切换在用户模式下完成。从内核的角度来看,同样的内核线程仍然在运行,一切都没有发生改变。当UMS线程执行需要进入内核的操作时,它切换到它的专属内核模式线程。
虽然线程有自己的执行环境,但是,同一个进程内部的所有线程都可以完全地读或者写该进程的虚拟地址空间。然而,一个进程中的线程不可能无意地引用另一个进程的地址空间,除非两种情况:第二个进程将它的一部分私有地址空间变成共享内存区;或者,第一个进程有权打开第二个进程,从而可以使用诸如ReadProcessMemory和Write ProcessMemory等跨进程的内存函数。
除了私有地址空间和一个或多个线程以外,每个进程还有一个安全环境和一个已打开句柄的列表,这些句柄指向诸如文件、共享内存区,或者像互斥体、事件或信号量等某个同步对象。
每个进程都有一个安全环境,存储在一个称为访问令牌的对象中。进程的访问令牌包含了该进程的安全标识和凭证。在默认情况下,线程没有自己的访问令牌,但是它们也可以获得一个访问令牌,因此单独的线程可以模仿另一个进程的安全环境--包括在远程Windows系统上运行的进程--而不会影响当前进程中的其他线程。
虚拟地址描述符(VAD,virtual address descriptor)是指一些数据结构,内存管理器利用这些数据结构来记录进程正在使用的虚拟地址。
Windows在进程模型上提供了一个扩展,称为作业。作业对象的主要功能是,使一组进程被当作一个整体来管理和维护。通过作业对象,可以对特定的属性进行控制,也可以对一个进程,或者所有与作业相关联的进程进行限制。作业对象也为所有与该作业相关联的进程记录下基本的审计信息,其中也包括曾经与该作业关联但是已经终止了的进程的审计信息。在某种程度上,作业对象弥补了Windows平台上缺乏结构化的进程树的不足,而且,它的功能在许多方面比UNIX风格的进程树更加强大。