Windows内核结构
从现在开始,我们探讨驱动程序。此时此刻作为新手,肯定有很多问题要问。至少我刚开始学的时候问了自己很多问题。(截止到发稿日起,其实我也就学了40天左右,嘿嘿)
问题具体有这么几个:
1,为什么驱动程序就运行在内核级?(这个问题有点傻乎乎)
2,内核是什么?
3,驱动程序运行之后在内核的哪个部位
4,我们以前用的Win32 API函数的工作原理是什么,这些函数如何与内核打交道的
5,如果内核有结构,那么结构是什么?
6,等等等等。OK,我一个一个的说明,并且有补充
**********************************
关于第一个问题,由于涉及比较深入,我打算在以后详细讲解,现在你只要知道,驱动程序编写规范本来是微软公司为那些硬件厂商的专业程序员编写硬件驱动程序而提供的一套解决方案。由于驱动程序一旦运行,就直接进入内核级,因此一开始的时候,微软本不打算把这个规范公开出来,但是随着时间的推移和一些因素的影响,微软也感觉到驱动程序规范有必要公开了,让更多的人掌握这门技术(比如杀毒软件公司),在这样的情形下,驱动程序被越来越多的人研究
什么是内核?内核由3个主要的内核模块组成,分别是Executive,内核,硬件抽象层(HAL)。因此我们一般所说的内核是广义上的说法~现在分别介绍这3个内核模块(如果您在阅读的时候遇到不理解的东西,可以不必过于深入,因为现在你所涉及的东西,将来还会涉及到,到那个时候,你理解起来会比较容易。)
这一节概念性的东西非常之多,笔者先一个一个介绍,在本章末尾,笔者会把这些东西连贯起来,到那时候或许你能恍然大悟或者有所感悟。你目前要做的事情就是:硬着头皮先看下去!
1,HAL:它的中文名字是硬件抽象层。它算是一个软件(薄层软件)。它显露出一组定义好的函数。这些函数与CPU以外的所有硬件打交道,使得内核和驱动程序调用这些函数更加方便的操作硬件。需要注意的是,不同的硬件,其操作它的HAL函数也会不同,一旦硬件有更换,那么待更换的硬件所对应的HAL函数就会消失,紧接着是新的硬件对应的HAL函数被系统加载。由此可以看出,HAL层算是操作系统的最底层,它直接跟硬件打交道。事实上也是如此,系统运行之后首先被加载的就是HAL。HAL层向上屏蔽了操作硬件的细节,一旦硬件有改动,只要更换相应的HAL就行了,好处就是可移植性强。
2,内核,内核是来描述CPU工作的。其管理的机制有:中断和异常处理,线程调度和同步,多处理机同步,定时控制。说白了,这些工作都是CPU来做的,那么如何控制CPU干这些事情,这是内核的事情。那么这些事情内核如何去做的呢?关于这个问题,我们暂时没有必要搞清楚,因为驱动程序编写不会涉及到这个方面。可以看出来,CPU干的事情都和调度有关,线程调度如是,同步如是,中断处理如是。这些很智能的事情都是由计算机的大脑--CPU来干,关于这一点应该没有人敢反对吧 ~~~注明:关于中断方面的知识,下一节我重点详解。
3,Executive。他的中文名字是“执行者”。它也是3大内核模块成员之一。它比较复杂,由7大组件组成,分别是:对象管理器,进程管理器,虚拟空间管理器,I/O管理器,配置管理器,安全监视器,本地进程调用。这7个组件之间彼此是独立的,又彼此之间可以通信(通过它们之间都熟悉的接口),彼此独立就意味着可以替换其中的一个或多个而不影响其他组件,为了之间可以通信,待替换的组件必须和以前的老组件在接口方面必须一样,不然其他组件不知道该如何“联系”你。就好像你手机换了,号得留着一样~~~其中I/O管理器和我们将来要编写的驱动程序有着密切的关系,所以它才是我们要关心的重点!不过其他组件我也介绍介绍:
(1)对象管理器是什么?它都干些什么?对象又是什么?在系统中,一个窗口,一个进程,一个线程,或者说一个按钮(按钮也是个窗口),你每次开机都能看到的桌面等等在计算机内部都算是一个一个的对象。编写过应用层软件的朋友都知道句柄这个东西,句柄可以理解成对象的编号。很多的WIN32 API函数使用的时候都要提供句柄值。举个生动的例子:熊猫烧香病毒有个功能就是使用FindWindow这个API函数+窗口名探测它想要探测的窗口的句柄。之后用PostMessage(刚才探测到的窗口的句柄值, WM_CLOSE, 0, 0)来关闭这个窗口。可以看的出来,在应用层编程中,我们的操作一般就是针对于对象。那么系统中如此之多的对象谁来管理呢?还有如果一个程序运行了,会产生一你所看到的窗口,那么窗口的句柄应该也随之生成,那么谁来负责干这些事情呢。OK,就是它----对象管理器!它负责对象的创建,删除,维护全局对象名字空间,记录对象有多少等待引用。
(2)配置管理器。暂时不做过多介绍。配置管理器里有一个名叫“登记库”(Registry)的数据库。将来驱动程会与此数据库相连并且使用此数据库做以下工作:1,把自己标识为受信任的系统组件2,查找和分配外围硬件3,建立错误记录消息文件4,启动驱动程序性能测量。
(3)进程管理器。进程管理器处理进程和线程的创建,管理和删除。它还为同步线程的活动提供一组标准的服务。进程管理器显露出来的大多数功能和内核比起来有点“业余” ,上文已经说了,内核才是同步线程管理进程的地方,因此内核给人的感觉那才算是“专业”。
(4)安全性引用监视器。我们知道WINDOWS系统有很多安全策略,比如一个进程不能访问别的进程的地址空间。但是策略有了还没有用,还要有人来实施。谁来呢?就它!安全性引用监视器提供一组原语,内核或者用户模式组件可以使用这些原语来验证对对象的访问。I/O管理器在调用驱动程序中的任何例程之前必须使用这些原语来测试自己有没有权限调用这些例程。但是驱动程序本身几乎不和监视器打交道,因此我们没有必要研究过深入。(原语:一段具有特定功能的代码,并且这段代码在运行期间不允许中断,必须一气呵成。例程:你可以理解成一个函数,一段代码,一个子程序等等)
(5)虚拟内存管理器。这方面的知识放到第4节来讲解。因为涉及到得东西比较多。就目前而言,你只要知道,内存在不够用的情况下为了让更多的程序运行就必须加大内存,虚拟内存技术就是把硬盘的一段空间拿出来当内存用。
(6)局部过程调用机制。局部过程调用(LPC)机制就是消息传递机制,开发过应用层程序的朋友都知道应用程序是消息驱动的。你可以发个消息给别的窗口。很显然驱动程序(也就是内核程序)可不是消息驱动。所以我们学习驱动程序编程没有必要研究这个东西。可以忽略
(7)I/O管理器。学习驱动程序理解这个很重要,I/O管理器是直接与驱动程序打交道并且它和驱动程序之间几乎每时每刻都在“交流”。在理解I/O管理器之前,我先解释下什么是I/O?为什么I/O管理器就和驱动程序关系很密切?I/O又叫输入输出,那么既然是输入输入很然涉及到硬件,反过来讲,我实在想不出有什么输入输出跟硬件没有关系!并且我们这里介绍的驱动是设备驱动,又是和硬件紧密联系的东西。因此这2个东西能够“走的非常的近”。我举个例子你就应该明白了:比如说你调用了API函数,这个API函数的功能是发送数据包。这个时候I/O管理器截获到这个API函数的调用,并且把这个函数调用翻译成IRP(你可以暂时理解成短信)发送给网卡驱动程序,网卡驱动程序接收到此IRP并且分析后看情况调用相关的例程实现发送数据包的操作。以上只是个简单的叙述,并且很不严谨!暂时你脑子有这个感觉就行。
以上这7个组件都是内核级的。他们各自“肩负”着自己的使命。他们对外界表现出中立,也不提供编程接口,服务调用也不是公开的。所以很多书上都没有具体深入的介绍,笔者当然也没有办法去深入研究。
接下来我们继续这枯燥的理论基础,关于这方面的内容你可以参阅《Windows NT设备驱动程序设计指南》这本书。笔者感觉这本书读起来有点费劲!
驱动程序----2(windows内核结构)下
(2009-08-26 23:47:53)转载▼ 标签:杂谈
Windows 内核结构
我们继续探讨内核结构。上半节大家已经隐隐约约模模糊糊似懂非懂的了解一些内核的组件。但是笔者并没有介绍这些内核组件之间的前后(上下)关系,或者说是层次关系。别着急,现在我就来阐述:
这3大内核模块,Exective模块中的大多数组件比内核级高,少数组件和内核级同级别,HAL最底层(书上画的图就是这样),之后就是硬件了。按道理讲硬件不属于WINDOWS系统了。关于层次高低你没有必要太清楚。但是有个概念你必须得知道:驱动程序虽然运行在RING0(有人叫“零级环”也有人叫“环0”),已经属于内核模块(很多书上把驱动程序就叫成内核模块,首先操作系统把整个内核当一个整体,既然整个内核算是一个整体,那么驱动程序充其量就只能称为模块了。)但是他不见得是最底层的程序。我所理解的是:驱动程序算是I/O管理器的一个“插件”,因为驱动程序被I/O管理和使用。所以我就大胆的告诉你,驱动程序和I/O管理器可能同一层。那么很显然内核层比驱动程序更加的底层。
(注明:以上关于层次的高低,只是笔者的猜测,因为书上没有明确告知。但是即使猜错了也不会影响到以后的学习。笔者还以为:学习任何东西没有必要一开始就必须得对(特别像这样的顶尖技术并且可以参考的资料实在是太少)而是抱着猜测的态度继续下去,当你学到一定程度的时候你突然感觉自己的猜测行不通了,那么真理离你越来越近了,因为至少你否决了一个错误答案!如果你不这样做,那么一个问题想不通,两个问题想不通,越来越多,以后的知识就没有办法去学了。)
以上是对上半节的遗留问题作出了解决并且写了下自己的想法。废话不多说,我们继续~~
Windows系统内核这个大家族中除了有我介绍的3大内核模块以外,还有一些扩展组件,这些组件统称为保护子系统。或许你很早就听说过这些家伙的名字,或许你还不清楚他们,OK,我来慢慢介绍:
可以说保护子系统真的离我们很近很近,你打开电脑看到操作系统的画面,窗口的样式,外观,鼠标,还有作为程序员经常使用的API函数都是保护子系统提供给我们的。我上文还说过,我们用户和普通程序员是不能直接和Exective打交道的,而是和这些子系统打交道,通过这些子系统间接的和Exective“交流”。需要注意的是我们接下来要介绍的保护子系统也是内核级(个别不是)。顺便说一下,一开始微软公司并不打算把这些子系统加入内核级。
保护子系统是个总称,它是由几个子系统组成的。这些子系统可以分两大类,一类是:整体子系统;另一类是:环境子系统。环境子系统包含Win32子系统,DOS虚拟机(VDM)子系统,Windows on Windows(W0W)子系统,POSIX子系统,OS/2子系统。其中Win32子系统是我们研究的重点。接下来我们分别介绍
1,整体子系统。说实在我不太理解这方面的内容,我只知道它里面的服务控制管理器可以装载,管理和卸载出信任的系统组件,比如服务和驱动程序。不过我感觉知道这点够了,至少我知道我们的驱动程序是由服务控制管理器这个家伙来“维护”的。
2,环境子系统之Win32子系统:
(1)它实现了用户和程序员所看到的GUI(图形用户界面),作为WINDOWS NT的屏幕和窗口管理器,他定义了整个系统的GUI策略和风格。
(2)它还显露了应用程序用于与Executive交互的Win32 API
(3)它和其他子系统稍微有所不同,它一部分运行在内核级,一部分运行在用户级。运行在用户级的那部分名字叫:Win32 API DLL。运行在内核级的是个驱动程序,名字叫:WIN32K.sys
具体讲解:因为Win32子系统显露了大家所熟悉的Win32 API,并且大家已经知道我们跟Executive交互也是通过Win32 API。那么理解起来应该从Win32 API着手!写过程序的朋友都知道API函数的强大,它可以分3类:
(1)USER函数管理GUI对象,如菜单和按钮
(2)GDI函数在图形设备上执行低级绘画操作,如显示器和打印机
(3)KERNEL函数管理进程,线程,同步对象,共享内存和文件等等
为了和Executive联系起来,我举例:
(1)如果你使用Postmessage,或者Sendmessage函数的时候,因为这些函数的功能是发送系统消息,联系上半节学过的知识,你会发现这些API函数的调用会间接的调用“局部过程调用机制”
(2)如果你调用Createwindow函数,大家都晓得此函数生成一个窗口,从上文可知此函数属于USER类,这个时候因为此函数的调用会间接的调用WIN32K.sys。只不过巧的是WIN32K.sys这个组件比较特殊,它不在Executive里面,而是属于Win32子系统(注意这仅仅是个特例)
(3)如果你使用Lineto函数来画一条线,很显然此函数应该属于GDI类,那么也是由WIN32K.sys处理。说到这,我要明确指出,凡是你调用的函数属于USER类型或者GDI类型(属于GDI类型的绘画函数都是低级绘画函数,比如游戏界面的绘画就不能使用这些低级函数),那么都会由WIN32K.sys来处理
(4)如果你调用CreateProcess这些操作进程的函数,由上面可知,最终会间接的调用“进程管理器”来处理
(5)如果你打算调用CreateFile这些操作文件的函数,并且文件都存放在硬盘上,而硬盘又属于输入输出设备,很显然对此函数的调用最终会由“I/O管理器”接手,I/O管理器再调用硬盘驱动程序来实现硬盘上的操作。(其实应该是文件系统驱动程序,为了新手好理解,暂时我写成硬盘驱动程序)
******
由上面的举例你应该可以看的出来,原来我们所调用的API函数在内核中都有对应的模块来处理,而我们将来所要学习的设备驱动程序只是“冰山的一角”,从而不得不感叹微软的强大,人家对细节的处理,有条不紊。
关于环境子系统的其他部分,我就不打算介绍下去,读者可以参考相关书籍。请读者重点理解WIN32子系统 谢谢
驱动程序----3(IRQL)
(2009-09-04 01:26:13)转载▼ 标签:驱动程序
Windows IRQL(中断请求级)
北京的天气真是奇怪,今天是学生们开学的日子,如果在我们老家(江苏泰州市海陵区)的话,现在估计还是比较热的,但是今天清晨我穿着短袖感觉还是有点凉。或许是我这几天感冒发烧的缘故吧,感觉特别的不爽。
我本打算好好休息休息,想想自己上班并不是很累,所以就坚持去了公司。前面2节我已经介绍了Windows的整体结构,不知道大家看了之后感觉如何,有没有发现些不合理的地方。如果有还请指正!今天我向大家介绍的是IRQL(中断请求级),这节是基础,很重要,请大家理解透!因为将来写驱动程序的时候要时刻面临这些问题~~~
中断分为硬件中断和软件中断。顾名思义,硬件中断就是由于硬件的某个问题而导致的终端。软件中断时软件问题而导致的中断。
关于中断,大家要知道CPU维护着一张中断表(IDT),这个表属于CPU表,就目前而言你只要知道有这个表就行了,此表的作用就是当中断发生之后根据不同中断类型运行不同的例程(此例程就是来处理中断的)
硬件中断类型有7个,软件中断有3个,我们这里只研究软件中断,因为我们这里以中间层驱动为研究对象,暂时不涉及设备驱动程序(中间层驱动和设备驱动程序之间的区别我会在第5节讲解)
现在具体来看软件中断,我们先思考思考,什么时候会产生中断?很显然是硬件或者软件出现某个问题(不一定是故障问题)才会发起一个中断给CPU,让CPU来处理。
继续.如果一个程序里有一个“共享的结构变量”(比如你在第1节看到的point结构变量,所谓“共享变量”你完全可以理解成“全局变量”),并且有2个或者多个线程可以使用或者说读写这个变量,那么有个问题来了,假如2个或者多个线程同时往这个结构变量里写数据怎么办?我来模拟具体的步骤让读者能看懂!
1,比方说我这个程序你有一个全局的POINT结构变量,这个结构里有X和Y这2个元素(X=9,Y=8)
2,Windows操作系统随时随刻进行着进程的调度(注意的是,进程是由线程组成的,那么进程调度的说法并不科学,其实应该是线程的调度),并且这个调度是强制性的,他不会因为你这个线程没有执行完或者其他原因而在那等你。比如说我这个程序有2个线程:A 和 B。
3,刚才说了,线程的调度是强制性的,因此可能会出现“巧合”的情况,那就是:A线程在运行,A本来的打算是把X赋值为4,Y赋值为5,但是不巧的是,当A把X值改为4之后由于线程调度的原因突然中断,中断之后由B线程运行,而B线程本来的目的就是读出X和Y的值。这个时候B读出的值是:X=4,Y=8。很明显这并不是程序员本来的意图。程序员的意图是A全部重新赋值之后,再让B取出。
4,这个问题的解决方案比较多,我们将来用的自旋锁和事件都是解决这个问题的方法,其原理就是只有A线程必须把“能引起巧合的那部分代码全部执行”之后才允许其他线程执行同样的能引起巧合的代码,哪怕其他线程具有使用CPU的权利(线程具有使用CPU的权利是系统调度所赋予的,但是相关码的执行权是可以人为控制的)
5,这样一来,线程之间的同步就形成了
既然巧合不可避免,那么我们就必须采取“限制”或者说“控制”措施。就上面的例子而言,我们只要“控制”好操作“共享结构变量”的代码就行了。我们已经知道A线程中有一段代码操作共享结构,B也有一段代码操作共享结构,如果我们规定,凡是与操作共享变量有关的代码都运行在“高的等级上”,那么一切问题都解决了。(为了以后叙述方便,我们把操作共享结构的代码称谓“敏感代码”)为了读者理解,我继续按步骤解释:
1,上面的例子很明显是软件,那么涉及的是软件中断,软件中断分为:DISPATCH,APC,PASSIVE。其中我们只研究DISPATCH和PASSIVE。那个APC不去研究。DISPATCH中断级高于PASSIVE
2,一开始线程A的代码运行在PASSIVE中断级上,当要执行敏感代码的时候先提高中断级(提高到DISPATCH级),再执行敏感代码,执行好了之后再降低中断级(降低到PASSIVE级).同理B线程也如此。
3,微软规定,当运行在DISPATCH级的代码没有执行完,运行在同等级或低等级上的代码都不允许执行。
4,这样一来,问题就解决了。你可以看到,只要A中的降低中断级的操作没有完成,B中的敏感代码永远不会执行,A中降低中断级的操作要想执行的前提就是A中的敏感代码必须全部执行完毕。同理B也一样。
5,在2中,提高中断级的任务就是由自旋锁等等来干
6,需要补充的是,如果某些函数必须运行在DISPATCH上的话,我们在调用此函数之前必须使用命令人为提高中断级,再运行此函数,运行好了之后再人为的降低。这点很重要。
说明:代码没有高低等级之分,我们之所以要引入等级其实就为了阻止“巧合”的产生。由于软件中断级就3个,因此普通代码一般都运行在PASSIVE级,因为这些代码不会因为线程的调度而产生问题。只有必要的时候才将一部分代码运行在DISPATCH上,并且代码一旦运行结束必须降低中断级,如果不这样做,线程中的其他代码(包括此程序里的其他线程的代码)就没有办法运行了。
这一节我们讲了中断级的概念和他存在的意义。下一节我们讲解有关内存的知识,从第5章开始,我们就会涉及驱动程序的编写。前面的章节比较难懂,多想想,思考思考,务必搞懂