原本打算自己留着,因为写的太差,怕放出来让牛们看了笑话,不过一是最近刚开始学驱动,也遇到过不少问题,所以打算还是把学习笔记放出来,和大家交流下;也希望能给想学驱动的朋友点帮助; 


驱动开发学习笔记一
  Windows 驱动的基本功能是为上层服务提供一些调用例程,总的来说,驱动程序可以分为用户模式的驱动(如,打印机驱动程序,和VDD)和内核模式的驱动,而内核模式的驱动有如下三种类型:
a ::文件系统驱动 File System drviers    处理I/O请求
b :即插即用驱动 Plug and Play drivers  用于pnp和电源管理的硬件设备
c :非即插即用驱动 NO plug and play drivers   用于扩展系统功能

关于对驱动分类的介绍,还可以进一步划分,可查看,《windows internal Fourth edition》第八章,这里我们只需要知道windows中的驱动可分NT式驱动程序和WDM驱动程序即可;

NT驱动的加载过程之一  DriverEntry:
和所有的应用层面上编写的程序一样,windows驱动程序也有一个入口函数,其函数名一般为DriverEntry,在这个入口函数中主要是对驱动程序进行初始化工作,它由系统程序调用,即System进程,它在系统启动的时候就被创建;当驱动加载的时候,system进程启动一个新的线程,调用执行体组件中的对象管理器,创建一驱动对象;驱动对象是一个DRIVER_OBJECT的结构体,同时,system进程调用执行体组件的配置管理程序,查询此驱动程序对应的注册表项;下面我们来看一下Driver_Entry例程:

/************************************************************************
* 函数名称:DriverEntry
* 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
      pRegistryPath:驱动程序在注册表的中的路径
* 返回 值:返回初始化驱动状态
*************************************************************************/
#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
      IN PDRIVER_OBJECT pDriverObject,
      IN PUNICODE_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;
}

DriverEntry的第一个参数是一个指针,指向一个刚被初始化的驱动程序对象,该对象就代表你的驱动程序。WDM驱动程序的DriverEntry例程应完成对这个对象的初始化并返回。非WDM驱动程序需要做大量额外的工作,它们必须探测自己的硬件,为硬件创建设备对象(用于代表硬件),配置并初始化硬件使其正常工作。而对于WDM驱动程序,颇麻烦的硬件探测和配置工作由PnP管理器自动完成,如果你想知道非WDM驱动程序是如何初始化自身的,参见Art Baker的《The Windows NT Device Driver Book (Prentice Hall, 1997)》、Viscarola和Mason的《Windows NT Device Driver Development (Macmillan, 1998)》。
DriverEntry的第二个参数是设备服务键的键名。这个串不是长期存在的(函数返回后可能消失),如果以后想使用该串就必须先把它复制到安全的地方。
这里我们只需要知道,DriverEnter函数的功能只是为由System进程创建的驱动对象进行初始化,例如,为常用IRP注册派遣例程,或者是startI/O例程,以及设置卸载例程函数和创建设备对象;

最后需要说明的是DriverEnter的参数修饰,IN,OUT,INOUT,它们在驱动程序中都被定义为空串: #define  IN()  #define OUT() 它们的功能是对参数进行注释,当看到一个“IN”参数时,代表其后面的参数纯粹用于输入目的,如在DriverEnter中的第一个参数,你可以改变这个DRIVER_OBJECT指针指向的对象,但你不能改变这个指针本身,也就是不能传一个不是DRIVER_OBJECT类型的指针给它;OUT代表这个参数仅用于输出参数,INOUT代表这个参数即用于输入也用于输出;

由上我们知道,在DriverEnter函数中只是对由系统创建的驱动对象做些初始化工作,而我们在应用程序下用CreateFileAPI函数打开的是设备,所以,在DriverEnter中有一函数用来创建设备对象,由于一个驱动对象可以创建多个设备对象,因此,创建设备对象的过程单独写成一函数;函数原型如下:

/************************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice (
    IN PDRIVER_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;
}

其实,上面这段代码中,真正创建设备的是IoCreateDevice函数,此函数在DDk中声明如下;
NTKERNELAPI
NTSTATUS
IoCreateDevice(
    IN PDRIVER_OBJECT DriverObject,    //设备对象
    IN ULONG DeviceExtensionSize,      //扩展大小
    IN PUNICODE_STRING DeviceName OPTIONAL,  //设备对象名
    IN DEVICE_TYPE DeviceType,    //设备类型
    IN ULONG DeviceCharacteristics,   //设备对象特征
    IN BOOLEAN Exclusive,          //设置设备对象是否可在内核模式下使用
    OUT PDEVICE_OBJECT *DeviceObject  //输出参数,I/o管理器负责创建这个设备对象
);

CreateDevice这个函数时,有几点需要注意:
1: 设备名称用unicode 字符指定,并且字符串必须是 “\Device\设备名 的形式,如磁盘分区C盘,为\Device\harddisk\volume1,当然你也可以不用指定设备名,但是这样做,不是设备就不需要设备名了,而是I/O管理器为设备创建一设备名,如果指定了设备名,只能被内核模式下的其他驱动程序识别,但是在用户模式下的应用程序能识别它吗?答案是否定的; 为了让用户模式的应用程序能够识别或者是使用设备,有两种方法可以使用: 一是通过创建一符号链接给用户模式应用程序使用,二是使用设备接口,一般NT中很少使用设备接口,而WDM中使用的较多;
2:你可以将符号链接理解为是设备对象的一个别名,设备对象的名称只能被内核模式下的驱动识别,而别名也可以被用户模式下的应用程序识别.创建符号链接用如下函数:
NTKERNELAPI
NTSTATUS
IoCreateSymbolicLink(
    IN PUNICODE_STRING SymbolicLinkName,
    IN PUNICODE_STRING DeviceName
);

注意:在内核模式下使用字符串必须使用unicode字符串,关于字符串的定义和使用有与之相关的函数,后面会单独介绍;

我们可以再设备扩展中记录我们需要的使用的对象或者是变量,也可以是其它的一些数据结构;



卸载设备驱动程序:
/************************************************************************
* 函数名称:HelloDDKUnload
* 功能描述:负责驱动程序的卸载操作
* 参数列表:
      pDriverObject:驱动对象
* 返回 值:返回状态
*************************************************************************/
#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 );
  }
}

由于在DriverEnter函数中创建了设备对象和符号链接,有可能还申请了其它一些资源,所以,当退出时,需要删除设备对象和符号链接,以及回收DriverEnter函数中申请的资源;我们知道,驱动对象可能拥有不至一个的设备对象,并且通过链表来管理这些设备对象,所以,在DriverUnload函数中,我们遍历由DriverObject其中一个愈传进来的设备对象链表,并将它们一一删除,删除设备使用函数IoDeleteDevice函数:


派遣函数:
/************************************************************************
* 函数名称:HelloDDKDispatchRoutine
* 功能描述:对读IRP进行处理
* 参数列表:
      pDevObj:功能设备对象
      pIrp:从IO请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
                 IN PIRP pIrp) 
{
  KdPrint(("Enter HelloDDKDispatchRoutine\n"));
  NTSTATUS status = STATUS_SUCCESS;
  // 完成IRP
  pIrp->IoStatus.Status = status;
  pIrp->IoStatus.Information = 0;  // bytes xfered
  IoCompleteRequest( pIrp, IO_NO_INCREMENT );
  KdPrint(("Leave HelloDDKDispatchRoutine\n"));
  return status;
}

在win32中,我们都知道应用程序是消息驱动的,而在驱动模式中,可以把驱动理解为是IRP驱动的,驱动程序一般是分层结构,对设备的操作会转化为对HAL,硬件抽象层的操作,IRP由I/O管理器创建,并传递给对应的驱动程序处理,而驱动程序调用我们在DriverEnter中注册的pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;函数来处理与之对应的IRP; 

IRP是驱动开发的核心,关于IRP的数据结构和运行机制,学完后单独用一章来总结;