Windows Internals 学习笔记(一)

来源:互联网 发布:合并两首歌用的软件 编辑:程序博客网 时间:2024/04/27 22:08
 

         Windows Internals 4th Edition》是著名Windows内核专家Mark E. RussionovichDavid Solomon Expert Seminars的主席David A. Solomon合作的一本介绍Windows架构和工作细节的著作。中文版由微软亚洲研究院研究员潘爱民老师翻译,这本书倍受各权威人员和机构所推荐,堪称经典。我第一次遇见本书就对它钟爱得不得了,于是决心不仅要自己认真学完本书,更想分享下自己每一章的学习成果,给各位既想学好Windows内部原理,但又对厚厚一本书望而生畏或者没有时间去仔细的朋友,我尽量把自己认为理解可能有难度的部分详细分享一下,并尽可能讲得精简、有趣些,希望大家喜欢!这一系列文章,是视我阅读本书的进度而定的,有时看时间久些,有时快些。本系列文章针对的阅读对象是有一定(不用很多,我自己也是菜鸟一个)Windows知识与开发经验以及一定硬件常识的读者。最后还是忠告一点,如果有时间还是请大家亲自读一读本书,有许多东西还是要靠自己去领悟以及体会。如果文章中对书中的内容有所曲解的话,请大牛们指出!那么前言就讲到这里,开始第一章啦!

(一)基础概念和术语

本章前面一小半部分介绍Windows NT发展历史和版本号的就不写出了,如果你很感兴趣,请自己去查阅,反正不知道也不碍什么事。

首先讲到的一个基本概念是Windows API,完整写法是Windows Application Programming Interface,即Windows应用编程接口。对于开发人员,如果您想要去学习WinAPI,既可以去查阅SDK文档,也可以去看MSDNMSDN那个好啊!微软Technet讲师们都讲,MSDNKnowledge Base,有你需要的任何知识,走遍天下都不怕,哼哼!Windows API有许多的类别,而我们这本书要涉及的仅仅是关键基本服务的部分(比如进程线程、内存管理、IO、安全等),其他的么就忽略了。

此时,书上还提到了.NET,我觉得也是很有必要了解一下的,毕竟这是未来的趋势。简单的讲,.NET的应用程序是建立在.NET框架那些程序集上的(System.dll,System.Windows.dll等等,对.net我是白痴,只能举出这两个比较傻的例子),这两部分的程序代码都是运行在用户模式下,并且是托管代码(支持动态编译、对象周期管理等等特性)。而这两部分又是建立在非托管代码的CLRCommon Language Runtime,通用语言运行时,所谓通用,就是不管对C#,VB,J#都适用)上,CLR么自然就运行在经典的WinAPI上,所以呢CLR也是运行在用户模式下。故而我们可以得到这个结论:.NET是完全运行在用户模式的框架。

回到API来,API只是一个概念,通常大家都会和API函数混在一起,而API函数真正严格的定义是Windows API中已经被文档化的可调用子例程,就是说,一定是要M$(小注:微软比较有钱,所以我不写MS,而是写M$,也省得打完M,还要放掉Shift键。。。咔咔)一定要M$正式向世界公布的,如CreateProcessCreateWindow等,而像ntdll.dll里面的一些系统内部调用的函数ntreadfile就不能算,因为他们没名份,是小白脸。

第二个概念是原生的系统服务,这个就是和API函数相对的,所谓的未文档化的但可以在用户状态下调用的子例程。如果是只能在内核模式下调用的内部函数,就被叫做内核支持函数,比如ExAllocatePool用来给驱动分配内存的。

说到这,我突然想起来,也许有不少人对什么叫用户模式和内核模式不是很清楚,可能会妨碍理解。用户模式(对应ring3)与内核模式(对应ring0),形象的说,就是政府与人民的关系,内核模式可以执行一些特权级别的代码,可以访问特权级别的数据,因而一个内核错误就可以造成系统挂掉。。。而用户模式则在内存存取、代码执行等方面有一定的限制,对计算机系统影响较小,所以不用担心.NET框架会拖垮OS。具体准确的区别我们将在本文的后续部分看到,此就不赘述了。

第三个概念是Windows服务,它特指由Windows服务控制管理器启动的进程,信息均存在注册表中。

还有一个是DLLDynamic Link Library),动态链接库,是Windows中非常常见的一种文件类型。它是将一组可调用的子例程合起来链接成的单一二进制文件,可供应用程序动态加载。For examplemsvcrt.dllC语言运行时),用MinGW编译的C程序都将引用它。值得注意的是,操作系统会非常聪明地只在内存中维护一份DLL代码,以使其被多个应用程序共享。

第五个概念就是所谓的进程。通常的理解是,运行中的程序就是进程,其实这样的说法并不完全。进程不仅包含程序在内存中的代码拷贝,还含有该进程所有的各种资源。故而更准确的理解是,进程是一个容器。在Windows中,一个进程通常拥有以下一些元素:

进程私有虚拟地址空间,在32Windows下,通常是4GB,其中后2GB是共享的系统资源,前2GB可供应用程序使用。内存管理器通过虚拟地址描述符(Virtual address descriptor, VAD)来记录该进程地址空间的信息

程序的初始代码和数据,它们被映射到了上述的地址空间中。

打开的句柄列表。包括该进程打开的文件持有的句柄,端口的句柄以及信号量等等。

一个访问令牌(Token)它提供了Windows下基本的进程安全信息,比如与之相关的用户、安全组、运行级别等等。

至少一条线程。进程最初创建的线程叫做主线程,它是程序执行的第一线索。

说到进程,我们就不能不继而提及线程。线程是Windows执行这个进程时,进行调度的实体,或者说是基本单位。Windows是一个抢占式多任务的操作系统,所谓的抢占就是指操作系统不再像DOS时代那样对一些霸王进程束手无策,而是具有对所有线程执行权的抢夺权,所以也可以理解为操作系统有强占权。

一个典型的线程通常包含以下元素:

一组代表CPU状态的寄存器内容

两个栈,分别用于在内核模式下执行以及在用户模式下执行

线程局部存储区(TLS, thread-local storage),是该线程私有的

一个线程ID,它一定是唯一的

有时,线程也有自己的安全环境,可以模仿客户安全环境来测试多线程服务器应用程序

上面提到了一个线程的环境(context, 其实更习惯的是叫上下文)。通俗的讲,就是使得线程在被调度后仍能得以连贯执行而不得不保存的状态信息,包括易失的寄存器、栈、私有存储区。举个例子,你看书看了一半被调度去擦玻璃窗,那么你需要记下当前看到的页码,等擦完玻璃窗,可以连贯地继续上次看到的地方看下去。需要注意的是,线程的上下文数据结构(就是在内存中保存的数据的组织形式),是随着Windows与计算机体系架构而变化的,可以通过GetThreadContext API函数来得到相关信息(CONTEXT块),具体怎么搞,我也没试过,不知道呵。

书上接着提到了纤程(fiber),我反正从来没听到过,但确实是一个在一些场合很有用的东西。一般线程的执行,是受操作系统调度的,对进程来说,是透明、不可见、也感觉不到的,那么如果应用程序想自己调度自己的线程而不让操作系统插一脚呢?这就需要把当前的线程转变为一个纤程,然后调用特定API函数来手动启动它或调度它,相关API函数参见Platform SDK文档,这不是我们现在要关心的问题。

回到线程。前面说过,进程是线程的容器,含有线程所需要的各种资源,并且这些资源是共享的,对所有线程可见。比如说一个进程的所有线程共享同一个虚拟地址空间。

再回到进程,现在32位系统的内存管理模式是保护模式,也就是系统会保证所有进程的地址空间互相独立,不能随便互相访问,除非使一部分私有空间成为共享内存区,或者使用ReadProcessMemory/WriteProcessMemory等可以侵入别的进程地址空间的内存函数。

关于Windows进程模型,还有最后一个推广了的概念——作业(job),它指的是维护与管理一组相关进程(可理解为整体管理)来完成一个特定的工作(job),这组进程中的进程可以互相关联、牵制,而作业对象也会记录下该组进程的信息。这个模型主要是弥补了Windows下缺乏结构化进程树的不足,并且在一定程度上还超越了UNIX下的进程树。

我们将会在第六章非常详细地再讨论作业、进程和线程相关结构与算法,这里就先告一段落。

第六个概念非常的重要,也就是Windows的虚拟内存,它是Windows实施高效32位内存管理的基本模式,原理也似乎有些复杂,不过没关系,我们来逐步了解。我们在讲解进程时,提到每个进程都有自己独立的地址空间,大小为4GB,不是所有进程共享4GB,而是每个进程都有各自的4GB!而目前PC上大多标配2G,顶级配置也不过4GB,自然不可能满足所有进程的空间需求,于是我们就把4GB的虚拟地址空间分成许许多多的虚拟内存页,每一个页面都被映射到物理内存的某处,不存在一一对应的关系,正在被使用的数据所在的页面当然就处于内存中的某处,当前未被使用的页面有可能在内存中的某处睡觉,也有可能因为要腾出空间给其他需要被使用的页面而被系统赶到硬盘上去,这个过程称为换页(Paging),而在硬盘中用来存储暂时不用的页面的文件,就是所谓的“页面文件”,在Windows下就是Pagefile.sys(顺带提一句,Pagefile.pif是机器狗病毒,别被它迷惑啊!!!)。举个例子,如果程序需要访问某一内存地址的数据,则系统会首先检查,这个地址所在的页面是否在内存中,如果在,就直接读取,如果不在,那么说明这个页面被交换到硬盘上去了,CPU就会引发一个中断告诉OS,使得该页面被从硬盘加载到内存中去。虚拟内存机制是得到硬件级别支持的,而操作系统要干的事只是负责页面文件管理、虚拟地址到物理地址的转换以及中断的处理。

可能你会想到,2GB的进程私有空间对于占内存的应用程序是否会太少,比如数据库的映射。确实,Windows也想到了,所以提供了一个/3GB/USERVA选项,通过编译器与操作系统共同支持,将允许进程访问超过2GB但不超过3GB的空间。那么如果更大呢?Windows还提供一个称为地址窗口扩展的机制(AWE, Address Windowing Extension),将允许程序在32位环境下最多访问到64GB内存,但有一个前提,就是地址的转换翻译工作得由程序员自己负责,Windows是管不着的。

第七个概念是我们前面已经遇到过的用户模式与内核模式,现在我们将较详细地解析下这两种模式。比较正式的定义是,内核模式指这样一种处理器执行模式:它允许访问所有的系统内存和所有的CPU指令;用户模式则是有限制的访问。一般来说,操作系统的代码,包括系统服务,设备驱动等,都运行在内核模式,而一般的应用程序代码都运行在用户模式下,但注意,并不是全部!比如说,当你打开画图程序,用鼠标在上面画了一条线时,系统既要从鼠标获得输入,也要向视频系统输出,这两件事都必须在内核模式下完成。所以准确地说,一个应用程序(或者说是它的某一个线程)总可能是一会儿运行在用户模式(尽管通常用户模式时间比例较大),一会儿运行在内核模式。通常的情况是,当一个用户应用程序调用内核模式的函数代码时,就会通过专门的CPU指令,从用户模式切换到内核模式,执行完后,在把控制权交还给用户线程前,会把内核模式切换回用户模式,不然么。。。这样,操作系统就可以达到保护自己的目的。

为了支持这样一种模式的划分,硬件提供了不同的访问模式,对于最常见的Intel X86处理器体系,定义了四种特权级,可以把它们形象地画成四个同心圆,半径由内到外变大,处于最核心的那个圆对应的就是ring0ring就是圆环的意思)模式,具有最高权利,接着是ring1,ring2,ring3,特权级别递减。Windows只使用,ring0作为内核模式,ring3作为用户模式,原因是Win过去支持的硬件体系只支持2个特权级。总之,这样就从硬件上提供了特权级的支持。

对于操作系统来说,前面已经讲过,每一个进程的4GB地址空间,前2GB是进程私有的,后2GB是操作系统和设备驱动共享的同一个虚拟地址空间,只不过在每个进程的地址空间里都被映射了一次。它们都被分成了无数的虚拟内存页面。其实这每个页面都被标记了处理器只能在什么特权级下才能访问,比如这后2GB所含的所有页面只能在内核模式下才可以访问,其他的普通进程若敢访问,就会出现Access Violation(非法访问)的错误而被系统干掉。而普通用户地址空间中所有页面在用户模式下都可以随意访问。但如果页面是只读的,比如包含可执行代码的页面,无论什么模式下都是不可写的。

前面也讲,设备驱动也是内核模式下运行的,Windows就无法对它们访问内存、执行代码进行保护管理,这就存在一个安全隐患,因为一个Driver很可能无意或有意地绕过系统安全机制修改操作系统的内核代码。这也就是要引入驱动签名机制的原因,一旦一个驱动未经过签名而妄图加载到操作系统中时,Windows会警告用户。另外,操作系统还提供一种驱动效验器的机制(Driver Verifier),可以帮助开发人员找到驱动编写中的问题,比如缓冲区溢出或者内存泄露问题。

第八个概念关于终端服务与多个会话。所谓的终端服务,指的是Windows为了在单个系统中支持多个可交互的用户会话而提供的能力。也就是支持一个远程的用户在另一台机器上登录,创建一个会话,并在这台服务器上运行应用程序。客户机把用户输入传给服务器,而由服务器把图形界面传送给客户机。一个会话(Session),我认为可以理解为一个独立的桌面,机器首个登录会话叫做控制台会话,或零会话。其他的会话可以通过Windows的远程桌面连接创建,若在XP系统中,还可以通过快速用户切换(也就是Fast user switch这个服务)来建立。快速用户切换的原理是,如果一个远程用户暂时想断开连接,但并不想注销它,则Windows可以把这个远程用户创建的会话中运行的进程以及描述会话的所有数据结构保存下来,以便日后该用户恢复连接,然后再允许其他用户登录。这有点类似于线程调度的感觉。但是永远记得:Windows XP中同一个时刻只能有一个人在使用系统,要么是一个远程用户,要么是一个本地用户。

下面是第九个概念,也是很核心的一个,就是对象与句柄。对象是指某一个静态定义的对象类型的单个运行时的实例。对象类型的定义包含了一个系统定义的数据类型(即C中的结构)、可以在该数据类型的实例上进行操作的一些函数、对象的属性。正如你想的那样,Windows系统所说的对象与编程语言中的对象概念基本一样,只不过书上把数据类型与对象属性分开来,暂时也没发现有什么意义,也许随着我们深入下去,会有所明了。Windows是以对象的方式来管理资源的。任何一个进程都是进程对象类型的一个实例,文件是文件对象类型的一个实例,驱动有驱动对象,设备有设备对象。

对象属性是对象中的数据域,每一个属性都定义了对象的一部分状态。如进程对象的属性通常有进程ID、一个基本调度优先级和一个指向访问令牌对象的指针。对象方法是操作对象的手段,通常读取或改变对象的属性。与面向对象(OO)相仿,对象的内部结构是被封闭的,你不可以直接访问或改变对象的属性等内部数据,而必须通过对象提供的方法来操作。这样的好处就是,对象的结构与实现对对象的使用者透明,也无需可见,只需要暴露一部分固定的方法(可以叫做接口),这样一旦对象的创建者需要更改对象的实现,就无需通知使用者更改代码以适应新的对象实现版本。

另外一点,Windows的对象同样具有继承的特性。Windows上许多的共享对象是建立在Windows原生对象上的,比如桌面对象、窗口对象、菜单对象、文件、进程等对象。

对象技术提供了便捷的途径来实现操作系统任务:

第一.   为系统资源提供了可读的名字

第二.   方便进程间共享资源与数据

第三.   保护资源,避免未授权访问

第四.   引用跟踪,当一个对象不再被使用时,系统可以自动释放其使用的资源,也就是一直说的对象生命周期管理

当然也不是Windows中所有的数据结构都是对象,只有确实需要被共享、保护、命名或者让用户模式的程序看得到(通过系统服务)的数据才被放到对象中去。如果知识系统某个组件内部函数实现的数据结构,自然就没必要弄成对象。

句柄就是指向对象实例的引用,用来“抓住”对象。

第十个概念就是Windows的安全性。Windows核心安全功能有:

一.可共享对象的安全。自主保护(need-to-know,知道什么?我也不是很理解,请大侠指教)。安全审计,会对用户发起的动作进行记录。登录口令认证。当一个用户释放了某一资源,后来的用户无法通过访问未初始化资源来看到前一个用户留下的痕迹。

二.系统内部对象的安全。Windows有两种访问控制形式。

第一种是自主访问控制,由对象的所有者授权或者阻止别人访问对象。当用户登陆到系统时,都会得到一组安全凭证或者一个安全环境(就是前面说线程的安全上下文),之后系统拿它们和想访问的对象的访问控制列表进行比较,来确定它们是否允许执行所请求的操作。

第二种是特权访问控制,有时候,对象的所有者不知道死到哪里去了,而领导管理者又要急着使用该对象,怎么办呢?Windows允许管理员具有特权可以访问被保护的对象,并管理这些对象的权限。

可以说,Windows安全性遍及了API接口的各个方面。因为Win的安全性是基于对象的,它在每一个对象身上设置了一个Windows安全描述符,当应用程序第一次尝试去访问时,Windows会来验证,成功则继续执行,否则拒绝该程序访问对象。值得一提的是,Windows在许多共享对象上实现了对象的安全性。

第十一个概念,地球人都知道,就是注册表。正如书上所说,想在脱离注册表的情况下谈论Windows的内部机理,是不可能的。因为注册表是Windows的数据库,引导与配置信息、软件设置信息、安全数据库以及每个用户的配置信息等等都保存在其中。

同时,注册表还提供了一些反映内存即时数据的信息,这些信息本身并不在注册表之中!只是临时挂接到注册表上的。这些数据包括比如CPU主频、缓存大小、加载了哪些驱动以及用到哪些资源、性能计数器等等。可以直接通过注册表函数来访问。

通常我们会通过一些第三方工具来设置系统注册表的内容,比如各类优化工具、配置工具等,而并不直接regedit来自己改,在干这类事时,要谨慎谨慎再谨慎,任何的改变轻则导致性能下降,重则系统直接下地狱。当然也不用太害怕,恰当的设置也可以给系统带来些惊喜哦!

终于到最后一个概念了,就是Unicode我们的常识是,一个英文字母总是占一个字节而一个汉字占两个字节。这种差别给开发人员带来了很大的困难,因为如果他们想拥有多语言支持的话,就不得不为其开发几套不同的函数,分别ANSI接受窄字版本(字母占1b,汉字占2b)字符串和Unicode宽字版本字符串(统一都占两个字节),显然窄字版本有时会起到意想不到的后果。对于Windows NT,只需记住,它的内部全都是以Unicode来存储和处理字符串的,哪些ANSI版本的API函数,其实质是在调用开始与结束时做一个转换处理,中间的过程仍然调用Unicode版本API处理。

这章其实还是比较无趣的,都是成段的概念,但是请你务必全部弄懂,否则看下去会有较大困难,如果文章中的内容有看不懂的,可以在回复中随便提问,或是自己在搜索引擎上查找相关内容。

再次建议大家一定有时间再去看遍原书,即便不看,也要把书上附带的所有实验还有实验工具都熟悉起来,这无疑对你认知Windows与提高实践能力有巨大帮助。下一章我们会看到Windows系统的结构,可以对Win有一个轮廓式的了解。

原创粉丝点击