驱动前言

 

 前言
  说起来有点搞笑,关于驱动程序,我也是无意在网络上看到有关它的介绍。当然凡事介绍此程序,大都关于计算机病毒或者是杀毒软件厂商宣称自己的软件进入驱动级,如何如何的牛X。也就是从那时候起,我才开始关注驱动程序,从而走上了学习的道路。
  不管什么技术,不管你想要学驱动程序的动机如何,不管你是想造病毒还是搞安全,不管你是因为爱好还是因为MONEY。在笔者眼里,只要肯学并且能够坚持下来,那就是好样的!
  一名合格的黑客或者安全人士肯定得学会编写驱动程序。因为对于病毒而言,运行在RING3级别(环3)到头来肯定被杀软干掉。对于杀软来说,如果还停留在RING3,那么肯定没有前途。不过现在的杀软肯定都已经是驱动级。
  驱动程序(内核程序)可以干任何你想要干的事情。这是驱动程序令大家兴奋的特点。
  那么什么是驱动程序呢?我的理解就是:符合微软驱动程序编写规范从而能够进入系统内核级的程序。说的直白点,所谓的驱动程序规范和你的C语言规范一个道理。比如说C必须以main函数作为入口点,而驱动是以DriverEntry作为入口点。很显然,我这里只是举例,他们之间差别还是非常大的。
  既然编写规范有本质的区别,那么你的任务就是从零开始学,前提是你必须具有C的基础,并且非常熟悉C的指针,要充分理解指针!
  或许我的文章应该算是读后感吧或者算是笔记,不管算什么,我将努力的把我的文章写得尽可能的通俗易懂。当然,既然我也算是初学者,肯定有些地方写的不合理,或者理解有错误,还请读者斧正。
  我所参考的书籍是:《寒江独钓--windows内核编程》《天书夜读---从汇编语言到windows内核编程》《rootkits---windows内核的安全防护》《windowsNT内核程序指南》
  在此感谢编写以上书籍的作者,因为他们,你我才能够有机会融入内核这奇幻般的世界,用自己的所学去创造成就感!
   接下来我介绍我写文章的思路和顺序:
  1,我尽可能把我认为有必要写的知识点讲解出来

  2,在讲解方面我可能的详细

  3,顺序肯定是一步一步的,循序渐进

  4,从大家都能接受的思考来安排先后

  5,文章编号就是1,2,3,4,5.。。。。。。一直下去,文章名字格式基本相同   

 

  另外:笔者一直以为,计算机方面的知识最终还是靠自己去理解。很多时候笔者发现自己能够理解的东东,不见得能够表达清楚讲给别人。还有,由于每个人的知识积累不同,很多时候一个知识点会涉及到另一个知识点,那么就产生了扩展。这是笔者最怕的地方。 

 此博客里面的文章全部是笔者的原创。笔者有个愿望就是打造一个比较全面的驱动学习库。在这里,你可以找到你想要的任何知识点。当然,笔者也还没得道,也还在不停的学习。 通过自学不停的扩充这个知识库。我相信我的愿望终究能够实现!

  理论上讲,笔者会天天关注此博客,因此如有任何问题,可以提出来一起研究。


C语言指针
 

  C语言难吗?笔者认为一般,只要认真去理解,还不是很难。但是我所了解的,大学里还是有比较多的学生栽在指针上,总是对指针不了解。现在好了,今天笔者让你理解什么是指针。希望能助你跨过这个槛,为以后学习驱动程序的编写打好良好的基础,我要严正申明。学习驱动程序之前必须理解指针,不要指望模棱两口就可以跑到驱动那边混日子,如是这样,笔者送你3个字和一个符号:没前途!
   既然说到指针,那么指针有什么用呢?指针有什么特点呢?
   为什么要学习C呢,因为WINDOWS内核是用C编写的。学内核的好处就是你真的没有必要去学C++(这确实是个好消息),因此你绝对不会被类的重载,派生等等东西让自己心烦。不过话又说回来,学内核的朋友绝大多数都是先开发应用程序,所以C++都应该很熟悉。
   不过我要申明:C++是可以开发内核的。

    以上几乎都废话,转入正题,先来几个概念~~~~:
   1:指针具有指向某个内存单元的功能(其实是一组内存单元,也可以称为内存块,也可以称为区域)
   2,指针具有“跨越数”或者说是“跳跃数”也可以说是“管辖能力”
   3,以上2点组合起来就是:指针变量具有指向和“管辖”一个内存区域的能力(是区域,不是单个内存单元)
   如何理解?大家先别着急,慢慢来,我都不着急,你着急什么~~~我们来一个一个讲解相关的知识点!
    1, int*P;这是申明一个指针变量P,注意这里面的*号,此*号的作用是告诉编译器这个P是个指针类型的变量。很多同学在看书的时候总是不理解什么时候写*号什么时候不写
   2,补充说下:申明就是申明,定义是申明加赋值。比如说“ int a;”就是申明;而“inta=88;”就是定义。很多同学一直很模糊,这里已经澄清了这2个概念。
   3,什么是整型变量?因为这个变量里面可以存放整型数据。那么什么是指针变量?因为这个变量里面将来要存放地址。
   4,或许你还不太清楚指针有什么用途,我举个例子:在内核中,指针的用途比较广泛,操作系统里很多的数据都是组合成一个结构,之后存放在内存中。如果说我要使用某个结构中的某个数据,那么我可以使用指针,先让指针指向这个结构的首地址,之后再进一步取得想要的数据
   5,指针不仅仅只是具有指向某个地址的功能,其实它真正的能力是“管辖”,也就是说,一旦他指向某个地址之后,从这个地址开始的一定范围的内存都被此指针“管辖”了。具体管辖范围是多大,在申明中已经指出来了。“int*P”中的int就是管辖范围(4个字节)


   继续,如果这里有个结构:
       structpoint    --------申明一个point结构
  {
    intx;       ---x占用4个字节
      inty;       ---y占用4个字节
  };                  ---那么这个point结构在内存中占用8个字节(在申明的时候暂时不占用内存)
      
      pointa;     -----定义一个具有point结构的变量a
         pointb;     -----定义一个具有point结构的变量b

     如果上面代码运行了,对于a来说,它会有个初始地址并且会占用8个字节的连续内存。此时你脑子里会构建一副图画:一个大的内存空间里,突然出现了个内存块,这个内存块就是a。
   如果说我要管辖这个内存区域,很显然要说明我要管辖的区域的大小和我要管辖的区域的开始位置。
      这里我申明一个指针变量  point* P;
                                   P=&a;     -----用P指向a
       关于point*P;  *号表示P这个变量是指针变量,前面的point说明此指针变量的管辖范围是point结构的范围,也就是8字节。
      关于P=&a; 让P这个指针变量指向a结构的首地址。
     现在你应该恍然大悟了。此时此刻的指针变量P,它真正具有管辖这块内存区域的能力。对!指针的作用就是指向某个内存区域(是一组连续的内存单元)。因为只有指向某个区域才有意义。反过来讲,单单指向某个内存单元意义很有限,几乎没意义!
     
     总结:
  1,整型变量是什么?整型变量里存放整型数据的;那么什么是指针变量?指针变量里存放的是地址数据!(比较起来理解往往就简单很多)
  2,关于指针变量而言,单单具有存放地址数据的功能还是远远不够的。他还具有管辖一块内存区域的能力。
  3,以上2点很重要,理解了之后,再去理解二维数组的指针,指向函数的指针就有了理论性基础。
  4,一开始我谈到“跳跃数”,你学到数组指针的时候会经常遇到P+1,P+2,什么的,这P+1就是跳跃一次,P+2就是跳跃2次。“跳多远”呢?这就是跳跃数。“int*P”中的int就是说明跳跃数为4字节
  5,可以看到啊,不管是“管辖范围”还是“跳跃数”其实概念都一样,只是场合不同翻译出来的名字不同而已。
   *******************************8
    笔者就只讲到这里,因为以后的东西需要靠自己,靠自己去理解。可能有同学说int不是2个字节嘛,怎么4个啊,这个问题没有必要去研究,至少在内核中的确4个字节,而内核中的int一般写成Uint类型


  Windows内核结构

从现在开始,我们探讨驱动程序。此时此刻作为新手,肯定有很多问题要问。至少我刚开始学的时候问了自己很多问题。(截止到发稿日起,其实我也就学了40天左右,嘿嘿)

问题具体有这么几个:

1,为什么驱动程序就运行在内核级?(这个问题有点傻乎乎)

2,内核是什么?

3,驱动程序运行之后在内核的哪个部位

4,我们以前用的Win32API函数的工作原理是什么,这些函数如何与内核打交道的

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)对象管理器是什么?它都干些什么?对象又是什么?在系统中,一个窗口,一个进程,一个线程,或者说一个按钮(按钮也是个窗口),你每次开机都能看到的桌面等等在计算机内部都算是一个一个的对象。编写过应用层软件的朋友都知道句柄这个东西,句柄可以理解成对象的编号。很多的WIN32API函数使用的时候都要提供句柄值。举个生动的例子:熊猫烧香病毒有个功能就是使用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个组件都是内核级的。他们各自“肩负”着自己的使命。他们对外界表现出中立,也不提供编程接口,服务调用也不是公开的。所以很多书上都没有具体深入的介绍,笔者当然也没有办法去深入研究。

接下来我们继续这枯燥的理论基础,关于这方面的内容你可以参阅《WindowsNT设备驱动程序设计指南》这本书。笔者感觉这本书读起来有点费劲!


                                  Windows 内核结构

 我们继续探讨内核结构。上半节大家已经隐隐约约模模糊糊似懂非懂的了解一些内核的组件。但是笔者并没有介绍这些内核组件之间的前后(上下)关系,或者说是层次关系。别着急,现在我就来阐述:

 这3大内核模块,Exective模块中的大多数组件比内核级高,少数组件和内核级同级别,HAL最底层(书上画的图就是这样),之后就是硬件了。按道理讲硬件不属于WINDOWS系统了。关于层次高低你没有必要太清楚。但是有个概念你必须得知道:驱动程序虽然运行在RING0(有人叫“零级环”也有人叫“环0”),已经属于内核模块(很多书上把驱动程序就叫成内核模块,首先操作系统把整个内核当一个整体,既然整个内核算是一个整体,那么驱动程序充其量就只能称为模块了。)但是他不见得是最底层的程序。我所理解的是:驱动程序算是I/O管理器的一个“插件”,因为驱动程序被I/O管理和使用。所以我就大胆的告诉你,驱动程序和I/O管理器可能同一层。那么很显然内核层比驱动程序更加的底层。

  (注明:以上关于层次的高低,只是笔者的猜测,因为书上没有明确告知。但是即使猜错了也不会影响到以后的学习。笔者还以为:学习任何东西没有必要一开始就必须得对(特别像这样的顶尖技术并且可以参考的资料实在是太少)而是抱着猜测的态度继续下去,当你学到一定程度的时候你突然感觉自己的猜测行不通了,那么真理离你越来越近了,因为至少你否决了一个错误答案!如果你不这样做,那么一个问题想不通,两个问题想不通,越来越多,以后的知识就没有办法去学了。)

  以上是对上半节的遗留问题作出了解决并且写了下自己的想法。废话不多说,我们继续~~

  Windows系统内核这个大家族中除了有我介绍的3大内核模块以外,还有一些扩展组件,这些组件统称为保护子系统。或许你很早就听说过这些家伙的名字,或许你还不清楚他们,OK,我来慢慢介绍:

  可以说保护子系统真的离我们很近很近,你打开电脑看到操作系统的画面,窗口的样式,外观,鼠标,还有作为程序员经常使用的API函数都是保护子系统提供给我们的。我上文还说过,我们用户和普通程序员是不能直接和Exective打交道的,而是和这些子系统打交道,通过这些子系统间接的和Exective“交流”。需要注意的是我们接下来要介绍的保护子系统也是内核级(个别不是)。顺便说一下,一开始微软公司并不打算把这些子系统加入内核级。

  保护子系统是个总称,它是由几个子系统组成的。这些子系统可以分两大类,一类是:整体子系统;另一类是:环境子系统。环境子系统包含Win32子系统,DOS虚拟机(VDM)子系统,WindowsonWindows(W0W)子系统,POSIX子系统,OS/2子系统。其中Win32子系统是我们研究的重点。接下来我们分别介绍

 1,整体子系统。说实在我不太理解这方面的内容,我只知道它里面的服务控制管理器可以装载,管理和卸载出信任的系统组件,比如服务和驱动程序。不过我感觉知道这点够了,至少我知道我们的驱动程序是由服务控制管理器这个家伙来“维护”的。

 2,环境子系统之Win32子系统:

   (1)它实现了用户和程序员所看到的GUI(图形用户界面),作为WINDOWSNT的屏幕和窗口管理器,他定义了整个系统的GUI策略和风格。

    (2)它还显露了应用程序用于与Executive交互的Win32API

    (3)它和其他子系统稍微有所不同,它一部分运行在内核级,一部分运行在用户级。运行在用户级的那部分名字叫:Win32API DLL。运行在内核级的是个驱动程序,名字叫:WIN32K.sys

   具体讲解:因为Win32子系统显露了大家所熟悉的Win32 API,并且大家已经知道我们跟Executive交互也是通过Win32API。那么理解起来应该从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子系统 谢谢


WindowsIRQL(中断请求级)

 

   北京的天气真是奇怪,今天是学生们开学的日子,如果在我们老家(江苏泰州市海陵区)的话,现在估计还是比较热的,但是今天清晨我穿着短袖感觉还是有点凉。或许是我这几天感冒发烧的缘故吧,感觉特别的不爽。

   我本打算好好休息休息,想想自己上班并不是很累,所以就坚持去了公司。前面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章开始,我们就会涉及驱动程序的编写。前面的章节比较难懂,多想想,思考思考,务必搞懂

下面是个例子,代码作者是张帆,我来详细解释,从中遇到知识点,我延伸出来分析。

忘记说了,前些日子,公司搬家,好了之后有累出病了,之后休息年假回老家,就这样折腾了好久好久,博客也忘记写了。实在不好意思~~~OK,继续来吧

**************************

Driver.h

*****************************
#pragma once

#ifdef __cplusplus
extern "C"
{
#endif
#include <NTDDK.h>
#ifdef __cplusplus
}
#endif

#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")

#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")

#define arraysize(p) (sizeof(p)/sizeof((p)[0]))

typedef struct _DEVICE_EXTENSION {
 PDEVICE_OBJECT pDevice;
 UNICODE_STRINGustrDeviceName; //设备名称
 UNICODE_STRINGustrSymLinkName; //符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

// 函数声明

NTSTATUS CreateDevice (IN PDRIVER_OBJECT pDriverObject);
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject);
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
        IN PIRP pIrp);

 

 

******************************

Driver.cpp

**************************** 

#include "Driver.h"


#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
   INPDRIVER_OBJECT pDriverObject,
   INPUNICODE_STRING pRegistryPath )
{
 NTSTATUS status;
 KdPrint(("Enter DriverEntry\n"));

 //注册其他驱动调用函数入口
 pDriverObject->DriverUnload =HelloDDKUnload;
 pDriverObject->MajorFunction[IRP_MJ_CREATE]= HelloDDKDispatchRoutine;
 pDriverObject->MajorFunction[IRP_MJ_CLOSE]= HelloDDKDispatchRoutine;
 pDriverObject->MajorFunction[IRP_MJ_WRITE]= HelloDDKDispatchRoutine;
 pDriverObject->MajorFunction[IRP_MJ_READ]= HelloDDKDispatchRoutine;
 
 //创建驱动设备对象
 status = CreateDevice(pDriverObject);

 KdPrint(("DriverEntry end\n"));
 return status;
}


#pragma INITCODE
NTSTATUS CreateDevice (
  INPDRIVER_OBJECT pDriverObject)
{
 NTSTATUS status;
 PDEVICE_OBJECT pDevObj;
 PDEVICE_EXTENSION pDevExt;
 
 //创建设备名称
 UNICODE_STRING devName;
 RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
 
 //创建设备
 status = IoCreateDevice( pDriverObject,
      sizeof(DEVICE_EXTENSION),
      &(UNICODE_STRING)devName,
      FILE_DEVICE_UNKNOWN,
      0,TRUE,
      &pDevObj);
 if (!NT_SUCCESS(status))
  return status;

 pDevObj->Flags |=DO_BUFFERED_IO;
 pDevExt =(PDEVICE_EXTENSION)pDevObj->DeviceExtension;
 pDevExt->pDevice = pDevObj;
 pDevExt->ustrDeviceName =devName;
 //创建符号链接
 UNICODE_STRING symLinkName;
 RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
 pDevExt->ustrSymLinkName =symLinkName;
 status = IoCreateSymbolicLink(&symLinkName,&devName );
 if (!NT_SUCCESS(status))
 {
  IoDeleteDevice( pDevObj);
  return status;
 }
 return STATUS_SUCCESS;
}


#pragma PAGEDCODE
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
{
 PDEVICE_OBJECT pNextObj;
 KdPrint(("Enter DriverUnload\n"));
 pNextObj =pDriverObject->DeviceObject;
 while (pNextObj != NULL)
 {
  PDEVICE_EXTENSION pDevExt =(PDEVICE_EXTENSION)
   pNextObj->DeviceExtension;

  //删除符号链接
  UNICODE_STRING pLinkName =pDevExt->ustrSymLinkName;
  IoDeleteSymbolicLink(&pLinkName);
  pNextObj =pNextObj->NextDevice;
  IoDeleteDevice(pDevExt->pDevice );
 }
}


#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
        IN PIRP pIrp)
{
 KdPrint(("EnterHelloDDKDispatchRoutine\n"));
 NTSTATUS status = STATUS_SUCCESS;
 // 完成IRP
 pIrp->IoStatus.Status =status;
 pIrp->IoStatus.Information =0; // bytes xfered
 IoCompleteRequest( pIrp, IO_NO_INCREMENT );
 KdPrint(("LeaveHelloDDKDispatchRoutine\n"));
 return status;
}

 

 

开始分段来解释:**************************此段是头文件*******************************

#pragmaonce           //这个比较常用,其意思就是此头文件只能被编译一次

#ifdef__cplusplus      //如果将来包含此头文件的代码是C++代码,那么
extern"C"                //用C语言方式来编译<NTDDK.h>
{
#endif                 
#include<NTDDK.h>    
#ifdef __cplusplus
                     //结束这个如果(下面详细解释为什么要用C语言编译方式)
#endif

#define PAGEDCODEcode_seg("PAGE")  //code_seg("PAGE")说明其后的函数作为可以放入分页内存
#define LOCKEDCODEcode_seg()       //code_seg()说明其后的函数必须常驻内存
#define INITCODEcode_seg("INIT")   //code_seg("INIT")说明其后的函数执行好了之后回收其占用的内存空间

#define PAGEDDATAdata_seg("PAGE")   //和上面差不多,现在不是针对代码,而是针对数据
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")

#define arraysize(p) (sizeof(p)/sizeof((p)[0]))//这里也是个宏,仔细看下里面的p应该是一维数组的指针,arraysize的作用就是算出数组里有多少个元素

 

typedef struct _DEVICE_EXTENSION{          //定义一个设备扩展结构,这个很重要,下面我详细解释    
 PDEVICE_OBJECT pDevice;
 UNICODE_STRINGustrDeviceName; //设备名称
 UNICODE_STRINGustrSymLinkName; //符号链接名
} DEVICE_EXTENSION,*PDEVICE_EXTENSION;   //设备扩展结构定义结束

// 函数声明

NTSTATUS CreateDevice (IN PDRIVER_OBJECTpDriverObject);  //定义一个私有函数CreateDevice
VOID HelloDDKUnload (IN PDRIVER_OBJECTpDriverObject);    //定义一个驱动程序卸载函数
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECTpDevObj,  //定义一个分发函数
        IN PIRP pIrp);

****************************************头文件分析结束*******************************

看到这肯定大家一头雾水,不要紧,继续分析CPP文件,最后会让大家理解清楚。继续

******************************

Driver.cpp

**************************** 

#include"Driver.h"      //包含头文件


#pragmaINITCODE      //刚才头文件里已经分析过了,这个的意思是让DriverEntry函数执行后之后就离开内存,别让它占用宝贵的内存资源
extern "C" NTSTATUS DriverEntry(   //DriverEntry函数是驱动程序的入口函数,和WinMain一样,微软就这样规定的。不需要解释
   INPDRIVER_OBJECTpDriverObject,   //系统会传入一个Driver结构的指针,相信解释下面会讲到
   INPUNICODE_STRING pRegistryPath )
{
 NTSTATUSstatus;        //申明一个状态变量,NTSTATUS就是状态型数据
 KdPrint(("EnterDriverEntry\n")); //输出一段字符,用WINDBG工具可以看到,具体说明下面详解

 //注册其他驱动调用函数入口
 pDriverObject->DriverUnload =HelloDDKUnload;  //刚才系统传入一个Driver结构的指针,那么下面这些语句就是给此结构里面的成员进行“赋值”。让这个本来空荡荡的结构“饱满”起来
 pDriverObject->MajorFunction[IRP_MJ_CREATE]= HelloDDKDispatchRoutine;
 pDriverObject->MajorFunction[IRP_MJ_CLOSE]= HelloDDKDispatchRoutine;
 pDriverObject->MajorFunction[IRP_MJ_WRITE]= HelloDDKDispatchRoutine;
 pDriverObject->MajorFunction[IRP_MJ_READ]= HelloDDKDispatchRoutine;
 
 //创建驱动设备对象
 status =CreateDevice(pDriverObject); //调用私有函数,请大家转到下面这个函数部分

 KdPrint(("DriverEntry end\n"));
 return status;
}


#pragmaINITCODE    //说明下面CreateDevice函数和DriverEntry函数一样,执行好了立刻“消失”
NTSTATUS CreateDevice (
  INPDRIVER_OBJECT pDriverObject) //把Driver结构的指针传入,为什么要传入进来呢?因为要用到。这样的解释很爽快吧 哈哈
{
 NTSTATUSstatus;       //上面解释过了
 PDEVICE_OBJECTpDevObj;  //定义一个设备对象结构的指针,很重要,下面详解
 PDEVICE_EXTENSIONpDevExt;   //定义一个设备扩展对象 设备扩展结构在头文件里已经定义
 
 //创建设备名称
 UNICODE_STRINGdevName;   //这个大家记住,驱动程序使用的是DDK。MFC编程中我们用CString类来定义一个字符串,而在驱动程序中,字符串是UNICODE_STRING结构,下面详细解释,目前你只要知道以后定义字符串就用它,还是它是个结构就OK
 RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice"); //初始化这个字符串,注意这个函数以及这个函数的用法。第一个参数是UNICODE_STRING结构的指针,第2个参数就是具体的字符串了。
 
 //创建设备
 status = IoCreateDevice(pDriverObject,    //此函数是DDK函数,它的作用是创建一个设备,第一个参数说明此设备属于哪个驱动程序。这句话千万记得,下面详解
      sizeof(DEVICE_EXTENSION),         //设备扩展的大小
      &(UNICODE_STRING)devName,          //设备名字
      FILE_DEVICE_UNKNOWN,             //是个什么设备呢,USB,还是串口?不知道,那么就写“不知道”
      0,TRUE,      //暂时不解释
      &pDevObj);    //刚才定义了一个设备的指针,再次取地址作为参数。哈哈,很多人理解出现僵局了,为什么已经是指针了,还再取地址呢?OK,下面详细解释。指针这关必须吃透
 if(!NT_SUCCESS(status))     //如果刚才创建设备不成功
  returnstatus;             //那么返回状态,一般都是成功的

 pDevObj->Flags |=DO_BUFFERED_IO;   //此设备的输入输出方式为 DO_BUFFERED_IO,下面详解
 pDevExt =(PDEVICE_EXTENSION)pDevObj->DeviceExtension;  //设备对象结构里有个成员叫做设备扩展,此成员也是个结构,用pDevExt指向这个设备扩展结构
 pDevExt->pDevice =pDevObj;   //你到头文件里看看设备扩展结构的定义,这个地方很容易理解
 pDevExt->ustrDeviceName =devName; 
 //创建符号链接
 UNICODE_STRINGsymLinkName;     //定义一个字符串变量
 RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");  //初始化
 pDevExt->ustrSymLinkName =symLinkName;   //继续“填补”设备扩展结构,也就是为其成员变量赋值
 status = IoCreateSymbolicLink(&symLinkName,&devName);  //DDK函数,创建一个符号链接,下面详解
 if(!NT_SUCCESS(status))    //如果不成功
 {
  IoDeleteDevice( pDevObj);  //把刚才建立的设备删除
  return status;
 }
 returnSTATUS_SUCCESS;    //返回成功
}


#pragmaPAGEDCODE    //HelloDDKUnload函数可以在分页到硬盘,此函数不必常驻内存
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
{
 PDEVICE_OBJECT pNextObj;  //定义一个设备
 KdPrint(("Enter DriverUnload\n"));//输出一段字符串
 pNextObj =pDriverObject->DeviceObject;  //Driver结构中有个Device设备结构,用pNextObj指向它
 while (pNextObj != NULL)
 {
  PDEVICE_EXTENSION pDevExt =(PDEVICE_EXTENSION)
   pNextObj->DeviceExtension;       //得到设备扩展,为什么?因为只有得到设备扩展,才能得到设备扩展里的符号链接,因为符号链接一会要删除。

  //删除符号链接
  UNICODE_STRING pLinkName =pDevExt->ustrSymLinkName; //得到符号链接
  IoDeleteSymbolicLink(&pLinkName);    //删除符号链接
  pNextObj =pNextObj->NextDevice;     //得到设备指针
  IoDeleteDevice(pDevExt->pDevice);    //删除设备
 }
}


#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
        IN PIRPpIrp)                    //此函数为驱动的分发函数
{
 KdPrint(("EnterHelloDDKDispatchRoutine\n"));
 NTSTATUS status = STATUS_SUCCESS;
 // 完成IRP
 pIrp->IoStatus.Status =status;      //IRP很重要,一会详解
 pIrp->IoStatus.Information =0; // bytes xfered
 IoCompleteRequest( pIrp, IO_NO_INCREMENT );
 KdPrint(("LeaveHelloDDKDispatchRoutine\n"));
 return status;
}

 

*****************************************

结束。哈哈哈 ,迷茫了吧,肯定N多问题要问吧。别急,下面章节开始细嚼慢咽,不过现在屡下流程还是必要的。

1,我们知道驱动程序的入口函数为DriverEntry()

2,驱动程序是由服务控制管理器加载的,关于服务控制管理器,我前面的章节已经介绍过。

3,之后系统在内存中创建一个Drvier结构,此时此刻内核中就多了一个Driver结构体,但是这个结构体只是一个空架子,里面的成员并没有赋值

4,I/O管理器调用DriverEntry()这个入口函数,并且把Drvier结构结构传进来

5,你看看上面的代码,你会发现,入口函数里很多代码就是在那为Drvier结构里的成员“赋值”

6,入口函数里还创建了一个设备对象,并且这个设备对象还带有设备扩展,并且还创建这个设备的符号链接

7,前面章节我已经提到过IRP,并且还说过IRP就像个短信,要注意的是只有设备才能接收IRP。你编写驱动程序的目的是什么?不管是硬盘驱动还是显卡驱动,还是键盘驱动,他们有一点是一样的,就是为应用程序服务,因此这些驱动也是会创建设备的。你的驱动程序如果也服务于应用程序,那么就必须创建设备。服务于应用程序的驱动很多很多,比如说杀毒软件,你所能看到的是它的应用程序部分,它的驱动程序部分运行在内核,应用程序可以发送控制信息来控制驱动程序。当然啦,如果你的驱动程序只是挂钩系统函数或者干些其他事情,那么可以不创建设备。设备扩展的用途比较广泛,设备扩展结构可以自己来定义,为什么要有这个东西呢?在内核中最好不要用全局变量,那么如果你实在打算要全局变量,那么就定义到设备扩展结构里,你仔细观察上面的代码,你会发现,驱动对象结构里面有个成员指向此驱动程序创建的设备对象结构,并且设备对象结构里有个成员指向设备扩展结构。你还会发现,上面所有的函数的参数要么是驱动对象指针,要么是设备对象指针。因此不管是哪个,我们都可以直接或间接的得到设备扩展结构,从而可以得到里面的成员变量。如果我们把一个变量放入设备扩展中,那么这个变量和全局变量没有任何区别,任何例程都可以直接或间接的得到它,修改它。这就是设备扩展的用途,说白了就这么简单。

8,设备链接的作用就是提供一个接口给应用程序,应用程序可以通过这个链接名来控制此设备。下面详解

 

此节到此结束,下面开始详细介绍各个细节。谢谢


上一节笔者提供了一段源代码,目的就是让大伙稍微熟悉下。因此笔者并没有详细解释其中各各语句的作用和意思。细心的读者其实也发现了,上一节我并没有打算把它作为一个教程来看待,因此我没有注明节号。OK,今天是第4节。我先送上楚狂人的一个关于键盘过滤的源代码,让大家熟悉熟悉一个真正有实际作用的驱动程序的框架。之后笔者将会暂时放开驱动程序代码的分析,转而继续探讨更进一步的“基础”知识。其中包括内存,指针,IRP,结构,等等。

  想必大家都听说过ROOTKIT技术,难吗? 难!********真的难道无法入手并且到了无法理解的地步吗?不见得!********张无忌为什么学乾坤大挪移只用了区区几小时?乾坤大挪移难吗?难!那么为什么张无忌很快就练成了?因为他任督二脉已经打通,他有了一定的基础了! OK,下面笔者的任务就是尽力协助大家打通任督二脉,助大家跨进驱动大门。享受不受约束的快感。

   下面代码来源于寒江独钓,再次感谢楚大哥~~~~

///
///@file     ctrl2cap.c
/// @author wowocock
/// @date  2009-1-27
///

#include <wdm.h>

// Kbdclass驱动的名字
#define KBD_DRIVER_NAME  L"\\Driver\\Kbdclass"

typedef struct _C2P_DEV_EXT
{
    //这个结构的大小
    ULONGNodeSize;
    //过滤设备对象
   PDEVICE_OBJECT pFilterDeviceObject;
    //同时调用时的保护锁
    KSPIN_LOCKIoRequestsSpinLock;
    //进程间同步处理 
    KEVENTIoInProgressEvent;
    //绑定的设备对象
   PDEVICE_OBJECT TargetDeviceObject;
    //绑定前底层设备对象
   PDEVICE_OBJECT LowerDeviceObject;
} C2P_DEV_EXT, *PC2P_DEV_EXT;

NTSTATUS
c2pDevExtInit(
    INPC2P_DEV_EXT devExt,
    INPDEVICE_OBJECT pFilterDeviceObject,
    INPDEVICE_OBJECT pTargetDeviceObject,
    INPDEVICE_OBJECT pLowerDeviceObject )
{
   memset(devExt, 0, sizeof(C2P_DEV_EXT));
   devExt->NodeSize = sizeof(C2P_DEV_EXT);
   devExt->pFilterDeviceObject =pFilterDeviceObject;
   KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));
   KeInitializeEvent(&(devExt->IoInProgressEvent),NotificationEvent, FALSE);
   devExt->TargetDeviceObject =pTargetDeviceObject;
   devExt->LowerDeviceObject =pLowerDeviceObject;
    return(STATUS_SUCCESS );
}

// 这个函数是事实存在的,只是文档中没有公开。声明一下
// 就可以直接使用了。
NTSTATUS
ObReferenceObjectByName(
                       PUNICODE_STRING ObjectName,
                       ULONG Attributes,
                       PACCESS_STATE AccessState,
                       ACCESS_MASK DesiredAccess,
                       POBJECT_TYPE ObjectType,
                       KPROCESSOR_MODE AccessMode,
                       PVOID ParseContext,
                       PVOID *Object
                       );

extern POBJECT_TYPE IoDriverObjectType;
ULONG gC2pKeyCount = 0;
PDRIVER_OBJECT gDriverObject = NULL;

// 这个函数经过改造。能打开驱动对象Kbdclass,然后绑定
// 它下面的所有的设备:
NTSTATUS
c2pAttachDevices(
                 IN PDRIVER_OBJECT DriverObject,
                 IN PUNICODE_STRING RegistryPath
                 )
{
    NTSTATUSstatus = 0;
   UNICODE_STRING uniNtNameString;
    PC2P_DEV_EXTdevExt;
   PDEVICE_OBJECT pFilterDeviceObject = NULL;
   PDEVICE_OBJECT pTargetDeviceObject = NULL;
   PDEVICE_OBJECT pLowerDeviceObject = NULL;

   PDRIVER_OBJECT KbdDriverObject = NULL;

   KdPrint(("MyAttach\n"));

    //初始化一个字符串,就是Kdbclass驱动的名字。
   RtlInitUnicodeString(&uniNtNameString,KBD_DRIVER_NAME);
    //请参照前面打开设备对象的例子。只是这里打开的是驱动对象。
    status =ObReferenceObjectByName (
       &uniNtNameString,
       OBJ_CASE_INSENSITIVE,
       NULL,
       0,
       IoDriverObjectType,
       KernelMode,
       NULL,
       &KbdDriverObject
       );
    //如果失败了就直接返回
   if(!NT_SUCCESS(status))
    {
       KdPrint(("MyAttach: Couldn't get the MyTest DeviceObject\n"));
       return( status );
    }
    else
    {
       // 这个打开需要解应用。早点解除了免得之后忘记。
       ObDereferenceObject(DriverObject);
    }

    //这是设备链中的第一个设备 
   pTargetDeviceObject =KbdDriverObject->DeviceObject;
    //现在开始遍历这个设备链
    while(pTargetDeviceObject)
    {
       // 生成一个过滤设备,这是前面读者学习过的。这里的IN宏和OUT宏都是
       // 空宏,只有标志性意义,表明这个参数是一个输入或者输出参数。
       status = IoCreateDevice(
           IN DriverObject,
           IN sizeof(C2P_DEV_EXT),
           IN NULL,
           IN pTargetDeviceObject->DeviceType,
           IN pTargetDeviceObject->Characteristics,
           IN FALSE,
           OUT &pFilterDeviceObject
           );

       // 如果失败了就直接退出。
       if (!NT_SUCCESS(status))
       {
           KdPrint(("MyAttach: Couldn't create the MyFilter Filter DeviceObject\n"));
           return (status);
       }

       // 绑定。pLowerDeviceObject是绑定之后得到的下一个设备。也就是
       // 前面常常说的所谓真实设备。
       pLowerDeviceObject =
           IoAttachDeviceToDeviceStack(pFilterDeviceObject,pTargetDeviceObject);
       // 如果绑定失败了,放弃之前的操作,退出。
       if(!pLowerDeviceObject)
       {
           KdPrint(("MyAttach: Couldn't attach to MyTest DeviceObject\n"));
           IoDeleteDevice(pFilterDeviceObject);
           pFilterDeviceObject = NULL;
           return( status );
       }

       // 设备扩展!下面要详细讲述设备扩展的应用。
       devExt =(PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension);
       c2pDevExtInit(
           devExt,
           pFilterDeviceObject,
           pTargetDeviceObject,
           pLowerDeviceObject );

       // 下面的操作和前面过滤串口的操作基本一致。这里不再解释了。
       pFilterDeviceObject->DeviceType=pLowerDeviceObject->DeviceType;
       pFilterDeviceObject->Characteristics=pLowerDeviceObject->Characteristics;
       pFilterDeviceObject->StackSize=pLowerDeviceObject->StackSize+1;
       pFilterDeviceObject->Flags |=pLowerDeviceObject->Flags &(DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE) ;
       //next device
       pTargetDeviceObject =pTargetDeviceObject->NextDevice;
    }
    returnstatus;
}

VOID
c2pDetach(IN PDEVICE_OBJECT pDeviceObject)
{
 PC2P_DEV_EXT devExt;
 BOOLEAN NoRequestsOutstanding = FALSE;
 devExt =(PC2P_DEV_EXT)pDeviceObject->DeviceExtension;
 __try
 {
  __try
  {
   IoDetachDevice(devExt->TargetDeviceObject);
   devExt->TargetDeviceObject= NULL;
   IoDeleteDevice(pDeviceObject);
   devExt->pFilterDeviceObject= NULL;
   DbgPrint(("DetachFinished\n"));
  }
  __except(EXCEPTION_EXECUTE_HANDLER){}
 }
 __finally{}
 return;
}


#define  DELAY_ONE_MICROSECOND (-10)
#define  DELAY_ONE_MILLISECOND(DELAY_ONE_MICROSECOND*1000)
#define  DELAY_ONE_SECOND(DELAY_ONE_MILLISECOND*1000)

VOID
c2pUnload(IN PDRIVER_OBJECT DriverObject)
{
   PDEVICE_OBJECT DeviceObject;
   PDEVICE_OBJECT OldDeviceObject;
    PC2P_DEV_EXTdevExt;

   LARGE_INTEGER lDelay;
    PRKTHREADCurrentThread;
    //delay sometime
    lDelay =RtlConvertLongToLargeInteger(100 * DELAY_ONE_MILLISECOND);
   CurrentThread = KeGetCurrentThread();
    //把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。
   KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY);

   UNREFERENCED_PARAMETER(DriverObject);
   KdPrint(("DriverEntry unLoading...\n"));

    //遍历所有设备并一律解除绑定
    DeviceObject= DriverObject->DeviceObject;
    while(DeviceObject)
    {
       // 解除绑定并删除所有的设备
       c2pDetach(DeviceObject);
       DeviceObject = DeviceObject->NextDevice;
    }
    ASSERT(NULL== DriverObject->DeviceObject);

    while(gC2pKeyCount)
    {
       KeDelayExecutionThread(KernelMode, FALSE,&lDelay);
    }
   KdPrint(("DriverEntry unLoad OK!\n"));
   return;
}

NTSTATUS c2pDispatchGeneral(
                                IN PDEVICE_OBJECT DeviceObject,
                                IN PIRP Irp
                                )
{
    //其他的分发函数,直接skip然后用IoCallDriver把IRP发送到真实设备
    //的设备对象。
   KdPrint(("Other Diapatch!"));
   IoSkipCurrentIrpStackLocation(Irp);
    returnIoCallDriver(((PC2P_DEV_EXT)
       DeviceObject->DeviceExtension)->LowerDeviceObject,Irp);
}

NTSTATUS c2pPower(
                      IN PDEVICE_OBJECT DeviceObject,
                      IN PIRP Irp
                      )
{
    PC2P_DEV_EXTdevExt;
    devExt=
       (PC2P_DEV_EXT)DeviceObject->DeviceExtension;

   PoStartNextPowerIrp( Irp );
   IoSkipCurrentIrpStackLocation( Irp );
    returnPoCallDriver(devExt->LowerDeviceObject, Irp );
}

NTSTATUS c2pPnP(
                    IN PDEVICE_OBJECT DeviceObject,
                    IN PIRP Irp
                    )
{
    PC2P_DEV_EXTdevExt;
   PIO_STACK_LOCATION irpStack;
    NTSTATUSstatus = STATUS_SUCCESS;
    KIRQLoldIrql;
    KEVENTevent;

    //获得真实设备。
    devExt =(PC2P_DEV_EXT)(DeviceObject->DeviceExtension);
    irpStack =IoGetCurrentIrpStackLocation(Irp);

    switch(irpStack->MinorFunction)
    {
    caseIRP_MN_REMOVE_DEVICE:
       KdPrint(("IRP_MN_REMOVE_DEVICE\n"));

       // 首先把请求发下去
       IoSkipCurrentIrpStackLocation(Irp);
       IoCallDriver(devExt->LowerDeviceObject, Irp);
       // 然后解除绑定。
       IoDetachDevice(devExt->LowerDeviceObject);
       // 删除我们自己生成的虚拟设备。
       IoDeleteDevice(DeviceObject);
       status = STATUS_SUCCESS;
       break;

   default:
       // 对于其他类型的IRP,全部都直接下发即可。
       IoSkipCurrentIrpStackLocation(Irp);
       status = IoCallDriver(devExt->LowerDeviceObject,Irp);
    }
    returnstatus;
}

// 这是一个IRP完成回调函数的原型
NTSTATUS c2pReadComplete(
                             IN PDEVICE_OBJECT DeviceObject,
                             IN PIRP Irp,
                             IN PVOID Context
                             )
{
    PIO_STACK_LOCATION IrpSp;
    ULONG buf_len = 0;
    PUCHAR buf = NULL;
    size_t i;

    IrpSp = IoGetCurrentIrpStackLocation( Irp );

    //  如果这个请求是成功的。很显然,如果请求失败了,这么获取
    //   进一步的信息是没意义的。
    if( NT_SUCCESS( Irp->IoStatus.Status ) )
    {
       // 获得读请求完成后输出的缓冲区
       buf = Irp->AssociatedIrp.SystemBuffer;
       // 获得这个缓冲区的长度。一般的说返回值有多长都保存在
       // Information中。
       buf_len = Irp->IoStatus.Information;

       //… 这里可以做进一步的处理。我这里很简单的打印出所有的扫
       // 描码。
       for(i=0;i<buf_len;++i)
       {
           DbgPrint("ctrl2cap: %2x\r\n", buf[i]);
       }
    }
   gC2pKeyCount--;

 if( Irp->PendingReturned)
 {
  IoMarkIrpPending( Irp );
 }
    returnIrp->IoStatus.Status;
}


NTSTATUS c2pDispatchRead(
                             IN PDEVICE_OBJECT DeviceObject,
                             IN PIRP Irp )
{
    NTSTATUSstatus = STATUS_SUCCESS;
    PC2P_DEV_EXTdevExt;
   PIO_STACK_LOCATION currentIrpStack;
    KEVENTwaitEvent;
   KeInitializeEvent( &waitEvent, NotificationEvent,FALSE );

 if (Irp->CurrentLocation ==1)
 {
  ULONG ReturnedInformation =0;
  KdPrint(("Dispatch encounteredbogus current location\n"));
  status =STATUS_INVALID_DEVICE_REQUEST;
  Irp->IoStatus.Status= status;
  Irp->IoStatus.Information= ReturnedInformation;
  IoCompleteRequest(Irp,IO_NO_INCREMENT);
  return(status);
 }

    //全局变量键计数器加1
   gC2pKeyCount++;

    //得到设备扩展。目的是之后为了获得下一个设备的指针。
    devExt=
       (PC2P_DEV_EXT)DeviceObject->DeviceExtension;

    //设置回调函数并把IRP传递下去。 之后读的处理也就结束了。
    //剩下的任务是要等待读请求完成。
   currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
   IoCopyCurrentIrpStackLocationToNext(Irp);
   IoSetCompletionRoutine( Irp, c2pReadComplete,
       DeviceObject, TRUE, TRUE, TRUE );
   return  IoCallDriver(devExt->LowerDeviceObject, Irp ); 
}

NTSTATUS DriverEntry(
                    IN PDRIVER_OBJECT DriverObject,
                    IN PUNICODE_STRING RegistryPath
                    )
{
    ULONGi;
    NTSTATUSstatus;
    KdPrint(("c2p.SYS: entering DriverEntry\n"));

    //填写所有的分发函数的指针
    for (i = 0;i < IRP_MJ_MAXIMUM_FUNCTION; i++)
    {
       DriverObject->MajorFunction[i] =c2pDispatchGeneral;
    }

    //单独的填写一个Read分发函数。因为要的过滤就是读取来的按键信息
    //其他的都不重要。这个分发函数单独写。
   DriverObject->MajorFunction[IRP_MJ_READ] =c2pDispatchRead;

    //单独的填写一个IRP_MJ_POWER函数。这是因为这类请求中间要调用
    //一个PoCallDriver和一个PoStartNextPowerIrp,比较特殊。
   DriverObject->MajorFunction [IRP_MJ_POWER] =c2pPower;

    //我们想知道什么时候一个我们绑定过的设备被卸载了(比如从机器上
    //被拔掉了?)所以专门写一个PNP(即插即用)分发函数
   DriverObject->MajorFunction [IRP_MJ_PNP] =c2pPnP;

    //卸载函数。
   DriverObject->DriverUnload = c2pUnload;
   gDriverObject = DriverObject;
    //绑定所有键盘设备
    status=c2pAttachDevices(DriverObject, RegistryPath);

    returnstatus;
}

代码全部结束。我开始分段来解释************************************************

**********************按照流程,先用入口函数谈起******************************

NTSTATUS DriverEntry(
                    IN PDRIVER_OBJECT DriverObject,
                    IN PUNICODE_STRING RegistryPath
                    )
{
    ULONGi;
    NTSTATUSstatus;
    KdPrint(("c2p.SYS: entering DriverEntry\n"));

    //填写所有的分发函数的指针
    for (i = 0;i < IRP_MJ_MAXIMUM_FUNCTION; i++)
    {
       DriverObject->MajorFunction[i] =c2pDispatchGeneral;
    }

    //单独的填写一个Read分发函数。因为要的过滤就是读取来的按键信息
    //其他的都不重要。这个分发函数单独写。
   DriverObject->MajorFunction[IRP_MJ_READ] =c2pDispatchRead;

    //单独的填写一个IRP_MJ_POWER函数。这是因为这类请求中间要调用
    //一个PoCallDriver和一个PoStartNextPowerIrp,比较特殊。
   DriverObject->MajorFunction [IRP_MJ_POWER] =c2pPower;

    //我们想知道什么时候一个我们绑定过的设备被卸载了(比如从机器上
    //被拔掉了?)所以专门写一个PNP(即插即用)分发函数
   DriverObject->MajorFunction [IRP_MJ_PNP] =c2pPnP;

    //卸载函数。
   DriverObject->DriverUnload = c2pUnload;
   gDriverObject = DriverObject;
    //绑定所有键盘设备
    status=c2pAttachDevices(DriverObject, RegistryPath);

    returnstatus;
}

看到上面这一小段的代码,很多朋友感觉和上节的代码很相似,但是又有点不同。多了几个函数:c2pDispatchRead,c2pPower,c2pPnP。其他都差不多。之后跳转到c2pAttachDevices函数,我们跟到这个函数

**********************************c2pAttachDevices函数*************************************

NTSTATUS
c2pAttachDevices(
                 IN PDRIVER_OBJECT DriverObject,
                 IN PUNICODE_STRING RegistryPath
                 )
{
    NTSTATUSstatus = 0;
   UNICODE_STRING uniNtNameString;
    PC2P_DEV_EXTdevExt;
   PDEVICE_OBJECT pFilterDeviceObject = NULL;
   PDEVICE_OBJECT pTargetDeviceObject = NULL;
   PDEVICE_OBJECT pLowerDeviceObject = NULL;

   PDRIVER_OBJECT KbdDriverObject = NULL;

   KdPrint(("MyAttach\n"));

    //初始化一个字符串,就是Kdbclass驱动的名字。
   RtlInitUnicodeString(&uniNtNameString, KBD_DRIVER_NAME); //代码开头部分已经定义这个宏
    //请参照前面打开设备对象的例子。只是这里打开的是驱动对象。
    status =ObReferenceObjectByName(      //这个函数非常重要,通过对象名得到其指针
       &uniNtNameString,    //Kbdclass是键盘类驱动,这个参数传入此驱动的路径
       OBJ_CASE_INSENSITIVE,
       NULL,
       0,
       IoDriverObjectType,
       KernelMode,
       NULL,
       &KbdDriverObject     //得到指向这个DRIVER结构的指针。
       );
    //如果失败了就直接返回
   if(!NT_SUCCESS(status))  //这段代码比较简单,因此不是我们重点关注对象
   {
       KdPrint(("MyAttach: Couldn't get the MyTest DeviceObject\n"));
       return( status );
    }
    else
    {
       // 这个打开需要解应用。早点解除了免得之后忘记。
       ObDereferenceObject(DriverObject);  
    }

    //这是设备链中的第一个设备 
   pTargetDeviceObject =KbdDriverObject->DeviceObject;//DeviceObject是Driver结构中的一个成员
   // 现在开始遍历这个设备链
    while (pTargetDeviceObject)
    {
       // 生成一个过滤设备,这是前面读者学习过的。这里的IN宏和OUT宏都是
       // 空宏,只有标志性意义,表明这个参数是一个输入或者输出参数。
       status = IoCreateDevice(
           IN DriverObject,
           IN sizeof(C2P_DEV_EXT),
           IN NULL,
           IN pTargetDeviceObject->DeviceType,
           IN pTargetDeviceObject->Characteristics,
           IN FALSE,
           OUT &pFilterDeviceObject
           );

       // 如果失败了就直接退出。
      
 if (!NT_SUCCESS(status))
       {
           KdPrint(("MyAttach: Couldn't create the MyFilter Filter DeviceObject\n"));
           return (status);
       }

       // 绑定。pLowerDeviceObject是绑定之后得到的下一个设备。也就是
       // 前面常常说的所谓真实设备。
       pLowerDeviceObject =
           IoAttachDeviceToDeviceStack(pFilterDeviceObject,pTargetDeviceObject); 

       //注意上面这个函数,第一个参数是过滤设备,第二个参数是设备栈上任意一个设备,返回的是过滤设备的下一层设备。设备对象结构中还有个成员,记录的是他上一层设备的指针。目前不深入,下面讨论到设备结构的时候详细研究。
       // 如果绑定失败了,放弃之前的操作,退出。
       if(!pLowerDeviceObject)
       {
           KdPrint(("MyAttach: Couldn't attach to MyTest DeviceObject\n"));
           IoDeleteDevice(pFilterDeviceObject);  //如果绑定失败,那么删除过滤设备,很好理解
           pFilterDeviceObject =NULL;      //指针赋NULL值
           return( status );
       }

       // 设备扩展!下面要详细讲述设备扩展的应用。
       devExt =(PC2P_DEV_EXT)(pFilterDeviceObject->DeviceExtension);//pFilterDeviceObject->DeviceExtension这段话是得到设备扩展的起始地址值,再加上PC2P_DEV_EXT之后强制把这个地址值“武装”成一个指针变量,并且此指针变量的跳跃数为C2P_DEV_EXT结构的大小。或许你可能不会理解为什么可以这样转变,因为在以前学习C语言,C++语言的时候从来没有人用过,但是至少目前你要足够的重视。因此内核编程中这样的技巧经常被使用。我会在下面几节的教程中详细探讨指针方面的问题。
       c2pDevExtInit(
           devExt,
           pFilterDeviceObject,
           pTargetDeviceObject,
           pLowerDeviceObject );

       // 下面的操作和前面过滤串口的操作基本一致。这里不再解释了。
       pFilterDeviceObject->DeviceType=pLowerDeviceObject->DeviceType;
       pFilterDeviceObject->Characteristics=pLowerDeviceObject->Characteristics;
       pFilterDeviceObject->StackSize=pLowerDeviceObject->StackSize+1;
       pFilterDeviceObject->Flags |=pLowerDeviceObject->Flags &(DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE) ;
       //next device
       pTargetDeviceObject =pTargetDeviceObject->NextDevice;
   }
   return status;
}


***********************c2pDispatchRead跟读操作有关的分发函数****************************

 NTSTATUS c2pDispatchRead(
                             IN PDEVICE_OBJECT DeviceObject,
                             IN PIRP Irp )
{
    NTSTATUSstatus = STATUS_SUCCESS;
    PC2P_DEV_EXTdevExt;
   PIO_STACK_LOCATIONcurrentIrpStack;   //当前IRP栈空间的结构,具体下面章节会谈到
   KEVENTwaitEvent;                     //定义一个事件对象,事件对象多用于进程间同步下面章节系统讨论
   KeInitializeEvent( &waitEvent, NotificationEvent,FALSE ); //初始化这个事件对象为通知型,并且初始状态为未激活

 if (Irp->CurrentLocation ==1)     //CruuentLocation是IRP结构的一个成员变量。他是栈空间的索引,等讨论到IRP结构的时候详解。这里的意思是如果为设备栈的最后一个设备,那么
 {
  ULONG ReturnedInformation =0;
  KdPrint(("Dispatch encounteredbogus current location\n"));
  status =STATUS_INVALID_DEVICE_REQUEST;
  Irp->IoStatus.Status =status; 
  Irp->IoStatus.Information= ReturnedInformation;
  IoCompleteRequest(Irp,IO_NO_INCREMENT);
//以上3句话就是完成一个IRP得固定用法。暂时死记
  return(status);
    

    //全局变量键计数器加1
   gC2pKeyCount++;

    //得到设备扩展。目的是之后为了获得下一个设备的指针。
    devExt=
       (PC2P_DEV_EXT)DeviceObject->DeviceExtension;

    //设置回调函数并把IRP传递下去。 之后读的处理也就结束了。
    //剩下的任务是要等待读请求完成。
   currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
   IoCopyCurrentIrpStackLocationToNext(Irp);
   IoSetCompletionRoutine( Irp, c2pReadComplete,
       DeviceObject, TRUE, TRUE, TRUE ); // c2pReadComplete是个完成函数,当IRP完成之后会调用它,需要注意的是完成函数运行在DISPATCH中断级,因此此函数里面的代码尽量不要太多,还有必须使用DISPATCH中断级的函数。
   return  IoCallDriver(devExt->LowerDeviceObject, Irp ); //让I/O管理器把此IRP传到下层设备
}

*****************************************进入c2pReadComplete完成例程看看*******************

 NTSTATUS c2pReadComplete(
                             IN PDEVICE_OBJECT DeviceObject,
                             IN PIRP Irp,
                             IN PVOID Context
                             )
{
    PIO_STACK_LOCATIONIrpSp;   //IRP栈空间的结构
    ULONG buf_len = 0;
    PUCHAR buf = NULL;
    size_t i;

    IrpSp = IoGetCurrentIrpStackLocation( Irp );//它是个宏,作用就是得到当前的IRP栈空间

    //  如果这个请求是成功的。很显然,如果请求失败了,这么获取
    //  进一步的信息是没意义的。
    if( NT_SUCCESS( Irp->IoStatus.Status ))  //如果IRP被完成。
    {
       // 获得读请求完成后输出的缓冲区
       buf =Irp->AssociatedIrp.SystemBuffer; //很重要,到时候讲到IRP结构的时候详细讨论
       // 获得这个缓冲区的长度。一般的说返回值有多长都保存在
       // Information中。
       buf_len =Irp->IoStatus.Information;//很重要,到时候讲到IRP结构的时候详细讨论

       //… 这里可以做进一步的处理。我这里很简单的打印出所有的扫
       // 描码。
       for(i=0;i<buf_len;++i)
       {
           DbgPrint("ctrl2cap: %2x\r\n", buf[i]);
       }
   }
   gC2pKeyCount--;

 if(Irp->PendingReturned )
 {
  IoMarkIrpPending(Irp );
          //如果一个设备设置了完成例程,这段代码必须存在这个完成例程中,不然IRP不会向上层设备回卷。这里很重要。具体问题下面讲到结构的时候会详细探讨。

   return Irp->IoStatus.Status;
}

 

***************************************

第4节就到这里结束了,相信大家会感觉无比的迷茫,好像一下子什么都不懂,什么都那么的抽象。别急,先别慌,这节还是让大家先熟悉熟悉,一回生二回熟嘛。可以发现,这段代码并不是很长,但是框架已经比较完成,涉及到得知识点比较的丰富。现在就开始一个一个知识点开始突破。OK。第5节开始详解内存。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值