首页
社区
课程
招聘
[原创]真正的驱动入门(二)[附2个源代码]
发表于: 2007-12-10 11:16 26088

[原创]真正的驱动入门(二)[附2个源代码]

2007-12-10 11:16
26088

【文章标题】: 真正的驱动入门(二)
【下载地址】: 自己搜索下载
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
      (-)出后,感觉如果要想学驱动还看一下《驱动模型设计》等等一堆大牛写的,我的只能提供一种更便捷途径,比如配置
  方面的问题,一些大牛书写的比较模糊的概念。注:概念是针对偶比较模糊...:-)。
  
      这次先完成上次的基于MFC框架测试程序的编写,接着分析WDM模型模糊的概念。最后一个就是我们的多功能程序,把所
  学融合到一起...今天和明天和后天一口气完成...
  
       接着上次的.我们现在来做一个MFC框架程序.选择对话框模式,并去掉配置过程中的“关于对话框”和“ActiveX控件”,
  使程序看起来更简洁和苗条..H0 ho....
  
      在TestCCDeathMfc.dsp工程添加OpenByInterface.cpp.编译一下。说无法找到 <devintf.h>        //
  
      接着修改TestCCDeathMfc.dsp,在Source=\OpenByInterface.cpp.添加跟以前一样...
  
      修改OpenByInterface.cpp头文件改为如下:
  
  #include "StdAfx.h"
  #include <winioctl.h>   //XP需要 这个....
  #include <devintf.h>        // DriverWorks
  
      在主框架添加头文件:
  
  //添加头文件:
  #include "..\\CCDeathInterface.h"
  #include "..\\CCDeathIoctl.h"
  #include <winioctl.h>   //XP需要 这个....
  //函数声明:
  HANDLE OpenByInterface(GUID* pClassGuid, DWORD instance, PDWORD pError);
  //变量声明:
  HANDLE        hDevice = INVALID_HANDLE_VALUE;
  GUID ClassGuid = CCDeathDevice_CLASS_GUID;
  
  加入到驱动程序工程。接着设为活动工程,开始进行界面设置
  OK 程序运行效果一样.....
  
  MFC测试框架效果图:
  
  一、第一步:请结合<<驱动模型设计>>Walter Oney 来看....写的太好了,第一次看不懂,看了第二次,还是不懂,看了第三遍,
  懂了一点点。
  
  a.分页与非分页 && 任意线程上下文与非任意上下文
  
  分页:不妨理解内存与硬盘的交互
  非分页:不妨理解内存
  任意线程上下文:任意,可以理解不固定,不确定的线称。上下文(环境):描述表,就是一个结构了,包含线程信息。
                  就象女朋友可以有很多个。
  非任意线程上下文:就是已经确定的线程。就象老婆只有一个。
  
  
  b.IRQL与自旋锁:主要解决共享资源的问题;  而内核同步对象主要控制线程流程。
  
  IRQL是解决单CPU抢先问题,一个1cpu
  自旋锁是解决多CPU抢先(并发)问题,多个CPU。
  
  但是 我们明白我们程序设计要符合顶层的需要,必须得让我们的驱动程序支持多CPU,所以采用IRPL是不够的。自旋锁,是
  最明智的选择。
  
  其实IRQL很好理解的...还有自旋锁也很好理解的....沉住气...多看几遍。注:把这几函数给背起来,怀恋小时侯,小学背
  课文....
  
  背函数:
  IRP:
    KIRQL Irp;
    KeRaiseIrql(XXX_LEVEL,&Irp);
    KeLowerIrql(Irp);
  
  SpinLock:
    KSPIN_LOCK  spinlock;
    KIRQL Irp;
    KeAcquireSpinLock(&spinlock,&Irp);
    KeReleaseSpinLock(&spinlock,Irp);
  
  所谓的自旋是指序将在一个小的循环内重复这个“测试并设置(test-and-set)”操作。其实自旋是一种很棒的机制,我
  爱原子......HoHo....
  
  对于DISPATCH_LEVEL都封装了更快捷的操作,前提是你必须知道你这个是在DISPATCH_LEVEL,不然其余就免谈了..
  
  IRP:
    KeRaiseIrqlToDpcLevel();
  
  SpinLock:
  
    KeAcquireSpinLockAtDpcLevel();
    KeRealseSpinLockFromDpcLevel();
  
  
  对于界限DISPATCH_LEVEL
  
    IRQL:执行在高于或等于DISPATCH_LEVEL级上的代码绝对不能造成页故障。
    SpinLock你仅能在低于或等于DISPATCH_LEVEL级上请求自旋锁,在你拥有自旋锁期间,内核将把你的代码提升到
    DISPATCH_LEVEL级上运行。
  c.还是想说一下数组指针与指针数组...
    static NTSTATUS (*fcntab[])(PDEVICE_OBJECT, PIRP) = <--3
    {
      HandleStartDevice, // IRP_MN_START_DEVICE
      HandleQueryRemove, // IRP_MN_QUERY_REMOVE_DEVICE
      <etc.>,
    };
  
  数组指针是指向数组的,
  如: int a[3][3],(*p)[3];
      p=a;
   
  指针数组是这样一种特殊的数组:它的每一个数组元素都是一个指针。 可以这么理解:指针数组,数组一个就行了,不被
  束缚(没有括号)
  如:int *p[3];
          |   |
         指针+ 数组 = 指针数组 好记吧....
  *p[0],*p[1],*p[2]都是一个指针。
  
  
  
  二、请求包的问题。只要看懂这个这节,其余相对比较简单:-)....
  
  
  IRP的结构是用来与驱动程序通信的,记得上次的I.information()=sizeof(ULONG)吗?
  常用的东东:I/O管理器、Pnp管理器、线程调度器、 OS、驱动程序.明白各自的职责。
  
  a.IRP结构较难理解的地方:
  
  IRP结构中的一个函数.
  PendingReturned(BOOLEAN):这个函数偶一开始都不知道是怎么一回事...天呀....
  
  if ( Irp->PendingReturned(true) )//true时候返回STATUS_PENDING.如果返回这个值必须调用这个值;
  {
    IoMarkIrpPending( Irp );//pengding中文意思的是等候的意思...
  }
  一个IRP用IoMarkIrpPending()把它转为未处理完成状态
  STATUS_PENDING以通知调用者我们没有完成这个IRP。
  
  b.I/O堆栈单元跟IRP有什么关系:
  
  我们可以从数据结构的角度来分析她:下面是IRP的结构中的CurrentLocation (CHAR)成员函数和Tail成员变量,可以看出。
  CurrentLocation (CHAR)和Tail.Overlay.CurrentStackLocation(PIO_STACK_LOCATION)没有公开为驱动程序使用,因为你完
  全可以使用象IoGetCurrentIrpStackLocation这样的函数获取这些信息。但意识到CurrentLocation就是当前I/O堆栈单元的索
  引以及CurrentStackLocation就是指向它的指针,会对驱动程序调试有一些帮助。
  
  c.I/O堆栈单元很歧义的一句话
  
   “同时还创建了一个与之关联的IO_STACK_LOCATION结构数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序,
  另外还有一个堆栈单元供IRP的创建者使用”
  怎么跑出来两个堆栈单元??崩溃ing....
  
  
  
  驱动程序通常做的是分配一个带有额外堆栈单元的IRP,在第一个单元中设置DeviceObject指针,在调用
  IoSetCompletionRoutine和IoCallDriver前用IoSetNextIrpStackLocation函数跳过那个额外堆栈单元。如果你这样做,
  那么在完成例程中调用IoMarkIrpPending将不会出现问题,并且完成例程也能得到了一个有效的设备对象。
  用哪个函数创建这个额外的堆栈单元呢?
  
  2K源代码来自驱网
  
  #define IopInitializeIrp( Irp, PacketSize, StackSize ) { \
  RtlZeroMemory( (Irp), (PacketSize) ); \
  (Irp)->Type = (CSHORT) IO_TYPE_IRP; \
  (Irp)->Size = (USHORT) ((PacketSize)); \
  (Irp)->StackCount = (CCHAR) ((StackSize)); \
  (Irp)->CurrentLocation = (CCHAR) ((StackSize) + 1); \
  (Irp)->ApcEnvironment = KeGetCurrentApcEnvironment(); \
  InitializeListHead (&(Irp)->ThreadListEntry); \
  (Irp)->Tail.Overlay.CurrentStackLocation = \
  ((PIO_STACK_LOCATION) ((UCHAR *) (Irp) + \
  sizeof( IRP ) + \
  ( (StackSize) * sizeof( IO_STACK_LOCATION )))); }
  
  NTSTATUS
  FASTCALL
  IopfCallDriver(
  IN PDEVICE_OBJECT DeviceObject,
  IN OUT PIRP Irp
  )
  
  /*++
  
  Routine Description:
  
  This routine is invoked to pass an I/O Request Packet (IRP) to another
  driver at its dispatch routine.
  
  Arguments:
  
  DeviceObject - Pointer to device object to which the IRP should be passed.
  
  Irp - Pointer to IRP for request.
  
  Return Value:
  
  Return status from driver's dispatch routine.
  
  --*/
  
  {
  PIO_STACK_LOCATION irpSp;
  PDRIVER_OBJECT driverObject;
  NTSTATUS status;
  
  //
  // Ensure that this is really an I/O Request Packet.
  //
  
  ASSERT( Irp->Type == IO_TYPE_IRP );
  
  //
  // Update the IRP stack to point to the next location.
  //
  Irp->CurrentLocation--;
  
  if (Irp->CurrentLocation <= 0) {
  KeBugCheckEx( NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR) Irp, 0, 0, 0 );
  }
  
  irpSp = IoGetNextIrpStackLocation( Irp );
  Irp->Tail.Overlay.CurrentStackLocation = irpSp;
  
  //
  // Save a pointer to the device object for this request so that it can
  // be used later in completion.
  //
  
  irpSp->DeviceObject = DeviceObject;
  
  //
  // Invoke the driver at its dispatch routine entry point.
  //
  
  driverObject = DeviceObject->DriverObject;
  
  PERFINFO_DRIVER_MAJORFUNCTION_CALL(Irp, irpSp, driverObject);
  
  status = driverObject->MajorFunction[irpSp->MajorFunction]( DeviceObject,
  Irp );
  
  PERFINFO_DRIVER_MAJORFUNCTION_RETURN(Irp, irpSp, driverObject);
  
  return status;
  }
  
  太微妙了,IO请求包个人感觉概念抽象程度较高...至少在这边要看2遍以上....想想整个流程真的很清晰....
                   NTSTATUS status=
  
  NTSTATUS IoCallDriver(PDEVICE_OBJECT device, PIRP Irp)
  IoCallDriver()
  {...
  return (*driver->MajorFunction[fcn])(device, Irp);
  
  }
                                       
  三.即插即用 ;下面只是提了一下,重点在综合程序里头。
  术语:I/O资源
  WDM包含四种标准I/O资源类型:I/O端口、内存寄存器、DMA通道、中断请求。
  因为I/O总线与CPU在寻址物理硬件的方式上不同,所以存在着两种资源列表。
  raw资源包含总线相关的数值,而translated资源包含系统相关的数值。
  //===综合程序解说
  3种通知方式:
  窗口通知(SDK,可参考ccdeath中的《实用软件-详细代码注释》) :响应WM_DEVICECHANGE消息
  服务通知 :调用RegisterServiceCtrlHanderEx寄存一个扩展的控制处理函数后
  内核模式通知:调用IoRegisterPlugPlayNotification.
  自制通知:IoReportTargetDeviceChange| IoReportTargetDeviceChageAsynchronous
  控制命令:
  //采用METHOD_BUFFERD模式
  #ifndef __CCDeathPnPIoctl__h_
  #define __CCDeathPnPIoctl__h_
  
  #define PNPEVENT_IOCTL_800 CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
  #endif
  接口
  //接口文件包含窗口通知、定制通知、内核通知
  
  #define WindowsDevice_CLASS_GUID \
           { 0x7cbbad7c, 0x3873, 0x476b, { 0xa1, 0x22, 0x1e, 0x8e, 0x1a, 0x7e, 0xc6, 0x6a } }
  
  DEFINE_GUID(MakeDevice_CLASS_GUID, 0x28ce01a1, 0x9436, 0x11d2, 0x81, 0xb5, 0x0, 0xc0, 0x4f, 0xa3, 0x30, 0xa6);
  
  #define  KernerDevice_CLASS_GUID \
           { 0xc6287288, 0x81d8, 0x4c2b, { 0x92, 0x21, 0x95, 0xb1, 0xaa, 0xed, 0x29, 0x49 } }
  
  //在Device.cpp加入:头文件和变量名和一些API声明;
  
  //===========下面我们来看核心部分...
  
  
  
  
  四.读写数据
  
  五.电源管理
  
  ...AddDevice(....)
  {.....
  
          CCDeathDevice* pDevice = new (           //分配内存一个设备对象内存
              NULL,                    // no name
                          FILE_DEVICE_UNKNOWN,
              NULL,                    // no name
                          0,
                          DO_DIRECT_IO
                                  | DO_POWER_PAGABLE//状态位DO_POWER_PAGABLE
                          )
                  CCDeathDevice(Pdo, m_Unit);//初始化对象
  
          if (pDevice == NULL)                      //分配是否成功
          {
                  T<<"错误的创建设备对象"<<"\n";
                  status = STATUS_INSUFFICIENT_RESOURCES;
          }
          else
          {
                  status = pDevice->ConstructorStatus();//判断其构造函数有没有错误 ...
                  if (!NT_SUCCESS(status))
                  {
                          delete pDevice;//有错构造,则删除设备对象
                  }
                  else
                  {
                          m_Unit++;
                          pDevice->ReportNewDevicePowerState(PowerDeviceD1);//由开始状态0转入1
                  }
          }
  ....
  }
  //分配在OnDevicePowerUp()和OnDeviceSleep()例程中分别判断是系统电源还是设备电源就可以了....
  然后再写2个函数分别根据当前上升的电源状态标位得出具体的状态名...
  
  PCHAR DevicePowerStateName(DEVICE_POWER_STATE ps);
  PCHAR SystemPowerStateName(SYSTEM_POWER_STATE ps);
  
  比如:
  PCHAR SystemPowerStateName(SYSTEM_POWER_STATE ps)//PS是具体的数值
  {
          static PCHAR PowerStates[] = {//先根据DDK找出电源的状态,列出来
                                            //或者SYSTEM_POWER_STATE,光标定位在这,VC助手go一下,把
                                            //这个状态拷贝过来   
                  "PowerSystemUnspecified",
                  "PowerSystemWorking",
                  "PowerSystemSleeping1",
                  "PowerSystemSleeping2",
                  "PowerSystemSleeping3",
                  "PowerSystemHibernate",
                  "PowerSystemShutdown"
          };
  
          if (ps > PowerSystemShutdown)  //最大值是6,如果超过这6种,被定位没有声明的状态。
                  return "<undefined power state>";
          else
                  return PowerStates[ps];//根据位置判断好
  }
  
  电源睡眠的时候不需要检测。
  电源上升的时候每隔30秒检测一下。
  
  
  六.windows管理诊断管理[省略]
  
  七.综合程序
  
  顺便提一下,生成对话框之后必须删除设备句柄。自己在写一个构造函数,或者重载OnDestory()都是可以的...
  CPnp::~CPnp()
  {
          if (m_hDevice!=INVALID_HANDLE_VALUE)
          {
                  delete m_hDevice;
          }
  }
  
  电源管理程序部分如上,运行效果如下:
  
  
  10.328          Default                    ccdeath说: CCDeathDevice::Create++.  IRP 8254B008
  10.328          Default                    ccdeath说: CCDeathDevice::Create--.  IRP 8254B008, STATUS 0
  10.328          Default                    ccdeath说: CCDeathDevice::OnDevicePowerUp++.  IRP 827A5C90
  10.328          Default                    ccdeath说: 已经进入了OnDevicePowerUp
  10.328          Default                    ccdeath说: 设备的电源状态PowerDeviceD0
  10.328          Default                    ccdeath说: CCDeathDevice::OnDevicePowerUp--.  IRP 827A5C90, STATUS 0
  12.719          Default                    ccdeath说: CCDeathDevice::DeviceControl++.  IRP 8269C610
  12.719          Default                    ccdeath说: 已经进入到电源处理了....
  12.719          Default                    ccdeath说: CCDeathDevice::DeviceControl--.  IRP 8269C610, STATUS 0
  41.469          Default                    ccdeath说: CCDeathDevice::OnDeviceSleep++.  IRP 82673338
  41.469          Default                    ccdeath说: 已经进入了OnDevicePowerSleep
  41.469          Default                    ccdeath说: 设备的电源状态PowerDeviceD1
  41.469          Default                    ccdeath说: CCDeathDevice::OnDeviceSleep--.  IRP 82673338, STATUS 0
             Monitor                     -- end --
  
  Demo程序效果图:
  电源管理效果截图:  
  
  
  
  
  
   
     
  
--------------------------------------------------------------------------------
【经验总结】
  
...
  
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2007年12月10日 11:12:17


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 7
支持
分享
最新回复 (18)
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
2
自旋锁是解决多CPU抢先(并发)问题,多个CPU.


对Spinlock的理解有误。不是同步多个CPU,而是同步多个进程

偶是把IRP的那些结构打印到一张纸上了,没事的时候看下,加深印象。呵呵。

写的很好啊。期待下文。。。

我12月11号生日耶。
2007-12-10 11:56
0
雪    币: 271
活跃值: (18)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
3
”Windows NT为解决一般的同步问题提供了两种方法,一个是中断请求优先级(IRQL)方案,另一个是在关键代码段周围声明和释放自旋锁。IRQL可以避免在单CPU上的破坏性抢先,而自旋锁可以防止多CPU间的干扰。 “

偶看这段话来的....其实自旋锁可以解决多cpu问题 也可以解决多线程问题....人家的出发点不同而已....
2007-12-10 12:10
0
雪    币: 321
活跃值: (271)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
4
感觉写驱动,还是不用类库的好。
2007-12-10 12:13
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
5
真好。你好有中文的参考资料可看。MS偶的资料都是英文的。杂整的????
以前对SpinLock琢磨的就不透彻。也许它也用于多CPU吧。呵呵。
2007-12-10 12:25
0
雪    币: 195
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
牛人
看不懂
加精吧!
2007-12-10 12:31
0
雪    币: 325
活跃值: (97)
能力值: ( LV13,RANK:530 )
在线值:
发帖
回帖
粉丝
7
迟到的生日快乐 呵呵。
D一下LZ。不过觉得VC 6 的程序在Vista上面看起来怎么这么怪  
我把Spinlock 的API 补充完整:

其实是可以在DIRQL Acquire Spinlock的
上传的附件:
2007-12-10 14:12
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
8
顶。晚上7点“大学美术”,考试回来看继续顶。。。

SpinLock啊~~~~~
2007-12-10 18:17
0
雪    币: 1844
活跃值: (35)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
9
happy birthday
2007-12-10 19:22
0
雪    币: 1919
活跃值: (901)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
10
期待下篇~~~
2007-12-11 12:48
0
雪    币: 486
活跃值: (13)
能力值: ( LV9,RANK:430 )
在线值:
发帖
回帖
粉丝
11
太强了,支持一个。
2007-12-11 19:03
0
雪    币: 1505
能力值: (RANK:210 )
在线值:
发帖
回帖
粉丝
12
安河写的那本书不适合入门......看了俩月硬是没看懂
2007-12-11 20:08
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
13
武安河 的那书看着是很不爽,用DS写驱动。。。
2007-12-12 09:00
0
雪    币: 145
活跃值: (85)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
14
这回我要按步骤来.
2007-12-14 21:03
0
雪    币: 2943
活跃值: (1788)
能力值: ( LV9,RANK:850 )
在线值:
发帖
回帖
粉丝
15
好好学习,天天向上
2007-12-15 01:54
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
很不错,哪位大牛可以推荐本好的驱动入门的书籍呢?
2008-4-24 09:28
0
雪    币: 8764
活跃值: (5240)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
17
CCDeath简直就是我们初学者的福音啊! 太感你了....我都激动的快哭了....555
2008-4-24 10:49
0
雪    币: 50
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
最近正在做这方面的东西,多谢楼主了
2009-5-19 20:33
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
请教高手,为什么我用DS3.2开发驱动程序时没有自动生成openbyintf.cpp文件?谢谢指点
2009-11-12 21:18
0
游客
登录 | 注册 方可回帖
返回
//