Windows驱动开发WDM

Windows驱动

这次重新阅读《windows驱动开发技术详解》(张帆,史彩成等编著),写博客记录一下,用以加深自己对驱动的理解。

驱动对象(DRIVER_OBJECT)

每个驱动程序会有唯一的驱动对象与之对应,并且这个驱动对象是在驱动加载的时候,被内核中的对象管理程序所创建的。

驱动对象用DRIVER_OBJECT来表示,内核对一个驱动只加载一个实例。对于DRIVER_OBJECT的说明,详见:http://msdn.microsoft.com/en-us/library/windows/hardware/ff544174(v=vs.85).aspx

说明一下其中的一个成员:DeviceObject。

MSDN上面是这么定义:

PDEVICE_OBJECT DeviceObject
Pointer to the device objects created by the driver. This member is automatically updated when the driver callsIoCreateDevice successfully. A driver can use this member and theNextDevice member of DEVICE_OBJECT to step through a list of all the device objects that the driver created.

DeviceObject指向驱动对象的第一个设备对象。DeviceObject->NextDevice指向下一个设备对象,最后一个设备对象的NextDevice指向空。比如第一次调用IoCreateDevice的时候,DriverObject->DeviceObject指向第一个设备对象(DeviceObject->NextDevice=NULL),再调用一次IoCreateDevice创建第二个设备成功后,DriverObject->DeviceObject->NextDevice会指向第二个设备对象。

设备对象都是由驱动程序创建的,而非操作系统创建,当驱动被卸载的时候,需要遍历每个设备对象,并将其删除。

设备对象(DEVICE_OBJECT)

每个驱动会创建一个或者多个设备对象,用DEVICE_OBJECT表示。每个设备对象都有一个指针指向下一个设备对象,因此形成一个设备链。设备链的第一个设备是由DRIVER_OBJECT的DeviceObject指定。

参考:http://msdn.microsoft.com/en-us/library/windows/hardware/ff543147(v=vs.85).aspx

需要说明的是:

1. NextDevice,指向下一个设备对象,注意这里指的下一个对象是同属于一个驱动对象的设备。也就是说这个设备链是指同一个驱动对象的设备链。

2. AttachedDevice,这里指的是更高一层驱动的设备对象。比如另外一个驱动附加到本驱动,那么AttachedDevice指的是那个驱动里面的第一个设备对象。

设备扩展 (DEVICE_EXTENSION)

DEVICE_OBJECT里面记录一些通用信息,但是大多数情况下,驱动程序需要额外记录一些信息,这个时候就需要设备扩展了。

设备扩展是由程序员指定内容和大小,由I/O管理器创建的,并且保存在非分页内存中。

在驱动程序中,尽量不要使用全局变量,数据可以保存在设备扩展里面。

WDM驱动的基本结构

从Windows2000以后,微软引入了新的驱动模型:WDM。WDM是建立在NT驱动模型之上的。

WDM驱动一般分为2种设备对象:

1. 物理设备对象(Physical Device Object,PDO)

2. 功能设备对象(Function Device Object,FDO)

比如:当用户插入USB盘的时候,总线驱动会创建PDO,然后提示用户加载FDO,如果操作系统已经提供了相应的驱动,那么操作系统会自动加载这个驱动。如果没有,则需要用户去安装相应的驱动。

在FDO和PDO之间,可以创建过滤驱动。

1. 位于FDO下面的,称之为下层过滤驱动;

2. 位于FDO上面的,称之为上层过滤驱动。

简单画了一个示意图:

过滤驱动不是必须的,在WDM中,基本上可以说PDO和FDO是必须的。AttachedDevice指向上层驱动的设备对象。

入口程序DriverEntry

跟NT驱动一样,WDM驱动的入口函数也是DriverEntry。WDM驱动需要额外设置2个派遣函数:

1. AddDevice

2. IRP_MJ_PNP

跟NT驱动不同,NT驱动一旦加载就创建设备,而WDM驱动是操作系统加载PDO以后,调用驱动的AddDevice例程,然后在AddDevice例程里面创建FDO,并且附加到PDO之上。

驱动程序的垂直层次结构

DeviceObject->AttachedDevice指向上层设备,如上面的图示。但是设备对象里面不能记录下一层驱动的设备对象(NextDevice指的是同一个驱动里面的下一个设备对象),这时就可以通过设备扩展来记录。

通过AttachedDevice和设备扩展,我们可以遍历整个设备对象堆栈,包括从上到下和从下到上。

驱动程序的水平层次结构

《windows驱动技术开发详解》里面将同一个驱动创建出来的设备对象的关系称之为水平层次,我觉得还是蛮形象的。

水平层次的第一个设备对象是由它的驱动对象所指定(DRIVER_OBJECT::DeviceObject)。每一个设备对象可以通过NextDevice找到水平层次(同一个驱动对象)的下一个设备对象。

比如插入2块同样型号的网卡。插入第一个网卡的时候,系统会创建一个PDO,并且加载相应的FDO,插入第二个网卡的时候,系统创建另外一个PDO,并且加载相应的FDO。那么这两个PDO之间就是同一个水平层次,这两个FDO也处于同一个水平层次。

Windows驱动开发WDM (2)- 一个简单的WDM驱动程序

入口函数DriverEntry

#pragma INITCODE 
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
                                IN PUNICODE_STRING pRegistryPath)
{
    KdPrint(("Enter DriverEntry\n"));
 
    pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
    pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = 
    pDriverObject->MajorFunction[IRP_MJ_READ] = 
    pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
    pDriverObject->DriverUnload = HelloWDMUnload;
 
    KdPrint(("Leave DriverEntry\n"));
    return STATUS_SUCCESS;
}
提供4个派遣函数:

HelloWDMAddDevice

HelloWDMPnp

HelloWDMDispatchRoutine

HelloWDMUnload

在DriverEntry里面简单的设置一下。
AddDevice函数

AddDevice函数是WDM驱动特有的,NT驱动没有,这也是主要的区别之一。先给出代码:

#pragma PAGEDCODE
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
                           IN PDEVICE_OBJECT PhysicalDeviceObject)
//DriverObject就是指向本驱动程序的一个对象,是由PNP管理器传递进来的。
//PhysicalDeviceObject是PNP管理器传递进来的底层驱动设备对象,这个东西在NT驱动中是没有的。通常称之为PDO,确切的说是由总线驱动创建的。

    PAGED_CODE();
    KdPrint(("Enter HelloWDMAddDevice\n"));
 
    NTSTATUS status;
    PDEVICE_OBJECT fdo;
    UNICODE_STRING devName;
    RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");//设备名称,设备名称只能被内核模式下的其他驱动所识别。
 
    //创建FDO(Function Device Object)
    status = IoCreateDevice(
        DriverObject,
        sizeof(DEVICE_EXTENSION),
        &(UNICODE_STRING)devName,
        FILE_DEVICE_UNKNOWN,
        0,
        FALSE,
        &fdo);
    if( !NT_SUCCESS(status))
        return status;
    PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
    pdx->fdo = fdo;
    //将FDO附加在PDO上面,并且将Extension中的NextStackDevice指向FDO的下层设备。如果PDO上面有过滤驱动的话,NextStackDevice就是过滤驱动,如果没有就是PDO。
    pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
    UNICODE_STRING symLinkName;
    //创建链接符号,这样用户模式的应用就可以访问此设备。内核模式下,符号链接是以\??\开头的(或者\DosDevices\)。用户模式下则是\\.\开头。
    //这里就可以在用户模式下用\\.\HelloWDM来访问本设备。
    RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWDM");
 
    pdx->ustrDeviceName = devName;
    pdx->ustrSymLinkName = symLinkName;
    status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName);
 
    if( !NT_SUCCESS(status))
    {
        IoDeleteSymbolicLink(&pdx->ustrSymLinkName);
        status = IoCreateSymbolicLink(&symLinkName,&devName);
        if( !NT_SUCCESS(status))
        {
            return status;
        }
    }
 
    fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;//DO_BUFFERED_IO,定义为“缓冲内存设备”
    fdo->Flags &= ~DO_DEVICE_INITIALIZING;//将Flag上的DO_DEVICE_INITIALIZING位清零,保证设备初始化完毕,必须的。
 
    KdPrint(("Leave HelloWDMAddDevice\n"));
    return STATUS_SUCCESS;
}
IoCreateDevice有个设备类型参数,这里使用FILE_DEVICE_UNKNOWN。Windows已经预先定义了一些常见的设备类型,如果驱动设备并不在这些类型里面,有两种办法:

1. 使用FILE_DEVICE_UNKNOWN;

2. 使用>=0x8000(32768 - 65535)的值

详见:http://msdn.microsoft.com/en-us/library/windows/hardware/ff563821(v=vs.85).aspx

在DriverEntry函数里面通过pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;设置AddDevice的例程函数。我画了一个简单的图来描述AddDevice基本流程:


 AddDevice函数有2个参数:DriverObject和PDO。

DriverObject就是当前驱动程序的一个实例,PDO是物理设备对象(由总线驱动创建)。

AddDevice函数主要工作就是创建一个功能设备对象FDO,然后附加在传递进来的PDO上面。

PNP IRP处理函数

WDM支持PNP(即插即用),这也是不同于NT驱动的一个主要特征。所有WDM驱动都需要设置PNP IRP的派遣函数。如:

pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
这个是在入口函数DriverEntry里面设置的。

看具体代码:

#pragma PAGEDCODE
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
                        IN PIRP Irp)
{
    PAGED_CODE();
 
    KdPrint(("Enter HelloWDMPnp\n"));
    NTSTATUS status = STATUS_SUCCESS;
    PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;//DEVICE_EXTENSION是在AddDevice里面创建的。
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);//获取irp信息
    switch (stack->MinorFunction)//根据PNP irp的minor function code来调用相应的处理函数。
    {
    case IRP_MN_REMOVE_DEVICE:
        status = HandleRemoveDevice(pdx, Irp);
        break;
    default:
        status = DefaultPnpHandler(pdx, Irp);
        break;
    }
    
    KdPrint(("Leave HelloWDMPnp\n"));
    return status;
}

Pnp派遣函数有2个参数:fdo和Irp。

fdo就是由AddDevice函数创建的那个功能设备对象,Irp是I/O管理器传进来的一个包(I/O Request Packet)。

这个例子里面简单处理了IRP_MN_REMOVE_DEVICE这个minor function code,其他所有的minor function code统统用一个函数来处理DefaultPnpHandler。

先看一下IRP_MN_REMOVE_DEVICE的处理函数:

#pragma PAGEDCODE
NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp)
{
    PAGED_CODE();
    KdPrint(("Enter HandleRemoveDevice\n"));
 
    //设置IRP的完成状态。
    Irp->IoStatus.Status = STATUS_SUCCESS;
    //将IRP请求向底层驱动转发。
    NTSTATUS status = DefaultPnpHandler(pdx, Irp);
 
    //删除符号链接
    IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName);
 
    //调用IoDetachDevice()把fdo从设备栈中脱开:
    if (pdx->NextStackDevice)
        IoDetachDevice(pdx->NextStackDevice);
    
    //删除fdo:
    IoDeleteDevice(pdx->fdo);
    KdPrint(("Leave HandleRemoveDevice\n"));
    return status;
}
里面做了一些简单的删除工作: 设置IRP完成状态 -> 将IRP请求向下一层驱动转发 -> 删除符号链接 -> 删除功能设备对象(FDO)。

再看一些DefaultPnpHandler函数:

#pragma PAGEDCODE
NTSTATUS DefaultPnpHandler(PDEVICE_EXTENSION pdx, PIRP Irp)
{
    PAGED_CODE();
    KdPrint(("Enter DefaultPnpHandler\n"));
    IoSkipCurrentIrpStackLocation(Irp);
    KdPrint(("Leave DefaultPnpHandler\n"));
    return IoCallDriver(pdx->NextStackDevice, Irp);//将irp传递给下层驱动
}
啥也没做,就是跳过本层堆栈,直接将irp传递到下层驱动。
IoSkipCurrentIrpStackLocation:修改IRP的IO_STACK_LOCATION,使下层驱动可以获得跟本层驱动一样的IRP。

MSDN解释:

The IoSkipCurrentIrpStackLocation macro modifies the system'sIO_STACK_LOCATION array pointer, so that when the current driver calls the next-lower driver, that driver receives the sameIO_STACK_LOCATION structure that the current driver received.

 IoCallDriver: 将irp传递给下层驱动设备对象。

现在可以看到,这个驱动程序例子只是处理了Remove Device,对于其他所有PNP请求,只是简单的传递给下层驱动处理。

DriverUnload函数

因为IRP_MN_REMOVE_DEVICE的函数里面已经处理了设备删除,DriverUnload函数里面不需要处理什么事情了,只是简单打印一些log。

DriverEntry函数里面的代码,简单设置一下派遣函数,如:

pDriverObject->DriverUnload = HelloWDMUnload;
HelloWDMUnload的实现:

#pragma PAGEDCODE
void HelloWDMUnload(IN PDRIVER_OBJECT DriverObject)
{
    PAGED_CODE();
    KdPrint(("Enter HelloWDMUnload\n"));
    KdPrint(("Leave HelloWDMUnload\n"));
}
啥都没做,就是打印log。

最后剩下一个函数是一个派遣函数,处理CREATE,READ, WRITE等irp。

DispatchRoutine

看DriverEntry里面的几行代码:

    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = 
    pDriverObject->MajorFunction[IRP_MJ_READ] = 
    pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
这个驱动例子里面,将DEVICE_CONTROL, CREATE, READ 和WRITE的IRP用同一个派遣函数来处理。

#pragma PAGEDCODE
NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT fdo,
                                 IN PIRP Irp)
{
    PAGED_CODE();
    KdPrint(("Enter HelloWDMDispatchRoutine\n"));
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
    //打印IRP的major function code和minor function code。
    ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
    KdPrint(("DispatchRoutine, Major function code: %x, Minor function code: %x, control code: %x\n", 
        stack->MajorFunction, stack->MinorFunction, code));
 
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;    // no bytes xfered
    IoCompleteRequest( Irp, IO_NO_INCREMENT );
    KdPrint(("Leave HelloWDMDispatchRoutine\n"));
    return STATUS_SUCCESS;
}
HelloWDMDispatchRoutine是一个派遣函数,它有2个参数:fdo和irp(跟PNP处理函数一样)

这里只做了2个事情:

1. 打印一些log

2. 设置irp完成状态,调用IoCompleteRequest函数来完成这个irp。不需要往下层驱动传irp了,因为这个irp已经完成了。
 

好了,代码基本介绍完毕,看一下完整代码:

/************************************************************************
* 文件名称:HelloWDM.cpp                                                 
* 作    者:张帆
* 完成日期:2007-11-1
*************************************************************************/
#include "HelloWDM.h"
 
/************************************************************************
* 函数名称:DriverEntry
* 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
      pRegistryPath:驱动程序在注册表的中的路径
* 返回 值:返回初始化驱动状态
*************************************************************************/
#pragma INITCODE 
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
                                IN PUNICODE_STRING pRegistryPath)
{
    KdPrint(("Enter DriverEntry\n"));
 
    pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
    pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = 
    pDriverObject->MajorFunction[IRP_MJ_READ] = 
    pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;
    pDriverObject->DriverUnload = HelloWDMUnload;
 
    KdPrint(("Leave DriverEntry\n"));
    return STATUS_SUCCESS;
}
 
/************************************************************************
* 函数名称:HelloWDMAddDevice
* 功能描述:添加新设备
* 参数列表:
      DriverObject:从I/O管理器中传进来的驱动对象
      PhysicalDeviceObject:从I/O管理器中传进来的物理设备对象
* 返回 值:返回添加新设备状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
                           IN PDEVICE_OBJECT PhysicalDeviceObject)
//DriverObject就是指向本驱动程序的一个对象,是由PNP管理器传递进来的。
//PhysicalDeviceObject是PNP管理器传递进来的底层驱动设备对象,这个东西在NT驱动中是没有的。通常称之为PDO,确切的说是由总线驱动创建的。

    PAGED_CODE();
    KdPrint(("Enter HelloWDMAddDevice\n"));
 
    NTSTATUS status;
    PDEVICE_OBJECT fdo;
    UNICODE_STRING devName;
    RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");//设备名称,设备名称只能被内核模式下的其他驱动所识别。
 
    //创建FDO(Function Device Object)
    status = IoCreateDevice(
        DriverObject,
        sizeof(DEVICE_EXTENSION),
        &(UNICODE_STRING)devName,
        FILE_DEVICE_UNKNOWN,
        0,
        FALSE,
        &fdo);
    if( !NT_SUCCESS(status))
        return status;
    PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
    pdx->fdo = fdo;
    //将FDO附加在PDO上面,并且将Extension中的NextStackDevice指向FDO的下层设备。如果PDO上面有过滤驱动的话,NextStackDevice就是过滤驱动,如果没有就是PDO。
    pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
    UNICODE_STRING symLinkName;
    //创建链接符号,这样用户模式的应用就可以访问此设备。内核模式下,符号链接是以\??\开头的(或者\DosDevices\)。用户模式下则是\\.\开头。
    //这里就可以在用户模式下用\\.\HelloWDM来访问本设备。
    RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWDM");
 
    pdx->ustrDeviceName = devName;
    pdx->ustrSymLinkName = symLinkName;
    status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName);
 
    if( !NT_SUCCESS(status))
    {
        IoDeleteSymbolicLink(&pdx->ustrSymLinkName);
        status = IoCreateSymbolicLink(&symLinkName,&devName);
        if( !NT_SUCCESS(status))
        {
            return status;
        }
    }
 
    fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;//DO_BUFFERED_IO,定义为“缓冲内存设备”
    fdo->Flags &= ~DO_DEVICE_INITIALIZING;//将Flag上的DO_DEVICE_INITIALIZING位清零,保证设备初始化完毕,必须的。
 
    KdPrint(("Leave HelloWDMAddDevice\n"));
    return STATUS_SUCCESS;
}
 
/************************************************************************
* 函数名称:DefaultPnpHandler
* 功能描述:对PNP IRP进行缺省处理
* 参数列表:
      pdx:设备对象的扩展
      Irp:从IO请求包
* 返回 值:返回状态
*************************************************************************/ 
#pragma PAGEDCODE
NTSTATUS DefaultPnpHandler(PDEVICE_EXTENSION pdx, PIRP Irp)
{
    PAGED_CODE();
    KdPrint(("Enter DefaultPnpHandler\n"));
    IoSkipCurrentIrpStackLocation(Irp);
    KdPrint(("Leave DefaultPnpHandler\n"));
    return IoCallDriver(pdx->NextStackDevice, Irp);//将irp传递给下层驱动
}
 
/************************************************************************
* 函数名称:HandleRemoveDevice
* 功能描述:对IRP_MN_REMOVE_DEVICE IRP进行处理
* 参数列表:
      fdo:功能设备对象
      Irp:从IO请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp)
{
    PAGED_CODE();
    KdPrint(("Enter HandleRemoveDevice\n"));
 
    //设置IRP的完成状态。
    Irp->IoStatus.Status = STATUS_SUCCESS;
    //将IRP请求向底层驱动转发。
    NTSTATUS status = DefaultPnpHandler(pdx, Irp);
 
    //删除符号链接
    IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName);
 
    //调用IoDetachDevice()把fdo从设备栈中脱开:
    if (pdx->NextStackDevice)
        IoDetachDevice(pdx->NextStackDevice);
    
    //删除fdo:
    IoDeleteDevice(pdx->fdo);
    KdPrint(("Leave HandleRemoveDevice\n"));
    return status;
}
 
/************************************************************************
* 函数名称:HelloWDMPnp
* 功能描述:对即插即用IRP进行处理
* 参数列表:
      fdo:功能设备对象
      Irp:从IO请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
                        IN PIRP Irp)
{
    PAGED_CODE();
 
    KdPrint(("Enter HelloWDMPnp\n"));
    NTSTATUS status = STATUS_SUCCESS;
    PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;//DEVICE_EXTENSION是在AddDevice里面创建的。
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);//获取irp信息
    switch (stack->MinorFunction)//根据PNP irp的minor function code来调用相应的处理函数。
    {
    case IRP_MN_REMOVE_DEVICE:
        status = HandleRemoveDevice(pdx, Irp);
        break;
    default:
        status = DefaultPnpHandler(pdx, Irp);
        break;
    }
    
    KdPrint(("Leave HelloWDMPnp\n"));
    return status;
}
 
/************************************************************************
* 函数名称:HelloWDMDispatchRoutine
* 功能描述:对缺省IRP进行处理
* 参数列表:
      fdo:功能设备对象
      Irp:从IO请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT fdo,
                                 IN PIRP Irp)
{
    PAGED_CODE();
    KdPrint(("Enter HelloWDMDispatchRoutine\n"));
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
    //打印IRP的major function code和minor function code。
    ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
    KdPrint(("DispatchRoutine, Major function code: %x, Minor function code: %x, control code: %x\n", 
        stack->MajorFunction, stack->MinorFunction, code));
 
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;    // no bytes xfered
    IoCompleteRequest( Irp, IO_NO_INCREMENT );
    KdPrint(("Leave HelloWDMDispatchRoutine\n"));
    return STATUS_SUCCESS;
}
 
/************************************************************************
* 函数名称:HelloWDMUnload
* 功能描述:负责驱动程序的卸载操作
* 参数列表:
      DriverObject:驱动对象
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
void HelloWDMUnload(IN PDRIVER_OBJECT DriverObject)
{
    PAGED_CODE();
    KdPrint(("Enter HelloWDMUnload\n"));
    KdPrint(("Leave HelloWDMUnload\n"));
}

 接下来就是如何安装了。

WDM驱动安装

WDM驱动安装需要一个inf文件,这里就直接引用《windows驱动开发技术详解》里面的,没有做任何改动,直接贴出来:

;; The Win2K DDK documentation contains an excellent INF reference.

;--------- Version Section ---------------------------------------------------

[Version]
Signature="$CHICAGO$"
Provider=Zhangfan_Device
DriverVer=11/1/2007,3.0.0.3

; If device fits one of the standard classes, use the name and GUID here,
; otherwise create your own device class and GUID as this example shows.

Class=ZhangfanDevice
ClassGUID={EF2962F0-0D55-4bff-B8AA-2221EE8A79B0}


;--------- SourceDiskNames and SourceDiskFiles Section -----------------------

; These sections identify source disks and files for installation. They are
; shown here as an example, but commented out.

[SourceDisksNames]
1 = "HelloWDM",Disk1,,

[SourceDisksFiles]
HelloWDM.sys = 1,MyDriver_Check,

;--------- ClassInstall/ClassInstall32 Section -------------------------------

; Not necessary if using a standard class

; 9X Style
[ClassInstall]
Addreg=Class_AddReg

; NT Style
[ClassInstall32]
Addreg=Class_AddReg

[Class_AddReg]
HKR,,,,%DeviceClassName%
HKR,,Icon,,"-5"

;--------- DestinationDirs Section -------------------------------------------

[DestinationDirs]
YouMark_Files_Driver = 10,System32\Drivers

;--------- Manufacturer and Models Sections ----------------------------------

[Manufacturer]
%MfgName%=Mfg0

[Mfg0]

; PCI hardware Ids use the form
; PCI\VEN_aaaa&DEV_bbbb&SUBSYS_cccccccc&REV_dd
;改成你自己的ID
%DeviceDesc%=YouMark_DDI, PCI\VEN_9999&DEV_9999

;---------- DDInstall Sections -----------------------------------------------
; --------- Windows 9X -----------------

; Experimentation has shown that DDInstall root names greater than 19 characters
; cause problems in Windows 98

[YouMark_DDI]
CopyFiles=YouMark_Files_Driver
AddReg=YouMark_9X_AddReg

[YouMark_9X_AddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,HelloWDM.sys
HKR, "Parameters", "BreakOnEntry", 0x00010001, 0

; --------- Windows NT -----------------

[YouMark_DDI.NT]
CopyFiles=YouMark_Files_Driver
AddReg=YouMark_NT_AddReg

[YouMark_DDI.NT.Services]
Addservice = HelloWDM, 0x00000002, YouMark_AddService

[YouMark_AddService]
DisplayName = %SvcDesc%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %10%\System32\Drivers\HelloWDM.sys

[YouMark_NT_AddReg]
HKLM, "System\CurrentControlSet\Services\HelloWDM\Parameters",\
"BreakOnEntry", 0x00010001, 0


; --------- Files (common) -------------

[YouMark_Files_Driver]
HelloWDM.sys

;--------- Strings Section ---------------------------------------------------

[Strings]
ProviderName="Zhangfan."
MfgName="Zhangfan Soft"
DeviceDesc="Hello World WDM!"
DeviceClassName="Zhangfan_Device"
SvcDesc="Zhangfan"
有关里面的说明,以后再讲。

有了inf后,就可以通过控制面板->安装硬件来安装这个驱动。给出最后一个截图,其他略。


安装成功后,可以在设备管理里面看到:

这样就安装成功了。

搞了半天,这个驱动有啥用呢?当然我们可以写一个用户模式的测试程序。

调用驱动(用户模式和内核模式通信)

写个很简单的测试例子:

// TestWDMDriver.cpp : Defines the entry point for the console application.
//
 
#include "stdafx.h"
#include <windows.h>
 
#define DEVICE_NAME L"\\\\.\\HelloWDM"
 
int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
 
    if (hDevice != INVALID_HANDLE_VALUE)
    {
        for(int i = 0; i < 10; i++)
        {
            DWORD RetBytes = 0;
            BOOL b = DeviceIoControl(hDevice, 0xAB, NULL, 0, NULL, 0, &RetBytes, NULL);
            printf("Test number %d, DeviceIoControl result: %d, byte: %d\n", i + 1, b, RetBytes);
 
            Sleep(1000);
        }
 
        CloseHandle(hDevice);
    }
    else
        printf("CreateFile failed, err: %x\n", GetLastError());
 
    return 0;
}
 
相当的简单,通过CreateFile函数来打开HelloWDM驱动,通过设备名字\\.\HelloWDM。这个名字对应驱动的AddDevice函数里面设置的符号链接\\DosDevices\\HelloWDM。

这里有必要讲一下驱动的几个名字概念:

1. 首先是设备名称,就是IoCreateDevice里面用到的那个名字。这个名字只能被内核模式的程序(如其他驱动)所识别。用户模式的程序是不知道这个名字的。

2. 符号链接,驱动程序里面可以为某个设备设置符号链接,以\??\开头(或者\DosDevices\),这样用户模式的程序就可以通过这个符号链接来访问这个设备。

3. 用户模式程序里面的设备名称,以\\.\开头
比如这个例子里面内核模式的设备名称是"\Device\MyWDMDevice",符号链接是"\DosDevices\HelloWDM",然后用户模式程序通过"\\.\HelloWDM"访问这个设备。

当CreateFile成功打开这个设备后,就可以操作这个设备了。测试例子里面简单往这个设备调用了10次DeviceIoControl函数(也就是发送一个IRP_MJ_DEVICE_CONTROL类型的IRP)。

运行测试程序,在debugview里面可以看到:

 哈哈,驱动程序成功地收到了来自测试程序(用户模式)的信息。

这里只是简单打印一下信息,

Major function code 是e(16进制),看一下WDM.H

#define IRP_MJ_CREATE                   0x00
#define IRP_MJ_CREATE_NAMED_PIPE        0x01
#define IRP_MJ_CLOSE                    0x02
#define IRP_MJ_READ                     0x03
#define IRP_MJ_WRITE                    0x04
#define IRP_MJ_QUERY_INFORMATION        0x05
#define IRP_MJ_SET_INFORMATION          0x06
#define IRP_MJ_QUERY_EA                 0x07
#define IRP_MJ_SET_EA                   0x08
#define IRP_MJ_FLUSH_BUFFERS            0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION   0x0b
#define IRP_MJ_DIRECTORY_CONTROL        0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL      0x0d
#define IRP_MJ_DEVICE_CONTROL           0x0e
确实0x0e对应的是IRP_MJ_DEVICE_CONTROL。

然后测试例子里面发送了一个0xAB的控制码给驱动,那么驱动里面通过stack->Parameters.DeviceIoControl.IoControlCode得到的控制码也是0xab,看上面的debugview信息。

这样就大功告成了。首先我们写了一个简单的WDM驱动,然后安装,之后在用户模式里面访问这个驱动。这也就是驱动的一般流程。

当然真正的驱动程序远没有这个例子简单。我个人对于驱动大概也就是小学生的水平,需要继续学习,研究。

我将完整代码打包上传了,有兴趣可以下载:http://download.csdn.net/detail/zj510/4794275
(用DDK编译驱动,简单调用build命令即可。用VS2008编译用户模式的测试程序)
————————————————
版权声明:本文为CSDN博主「zj510」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zj510/article/details/8208732

第一章概述...........................................................................................................................6 1.1 本教程的规划:...............................................................................................................6 第二章WDM驱动程序的运行.................................................................................................7 2.1 WDM驱动程序的基本调用流程:.....................................................................................7 2.1.1驱动程序何时从何处开始执行?........................................................................7 2.1.1.1第一次安装好驱动程序:................................................................7 2.1.1.2驱动程序正常运行:........................................................................7 2.1.2 DriverEntry()大约做些什么?.........................................................................7 2.1.2.1 IRP主功能码(Major Function Code).......................................9 2.1.2.2 IRP_MJ_PNP次功能码(Minor Function Code).......................10 2.1.2.3 IRP_MJ_POWER次功能码(Minor Function Code)...................10 2.1.3驱动程序与应用程序相关的功能码如何调用?..............................................10 2.1.3.1 DriverEntry()中您必须要注册回调函数...................................10 2.1.3.2 在您的应用程序中正确调用CreateFile().............................11 2.1.3.3 应用层调用驱动的消息参照:.....................................................11 2.1.3.4 IoControl调用:...........................................................................11 第三章开始编写WDM驱动程序...........................................................................................13 3.1 得到一个Demo工程:.....................................................................................................13 3.2 在VC下配置DDK的开发环境...........................................................................................15 3.2.1 我的目录.............................................................................................15 3.2.1.1我们应该在系统环境变量里设置..................................................15 3.2.2安装VC6................................................................................................17 3.2.3 打开wdm1\sys\Wdm1.dsp工作区文件...............................................18 3.2.4 修改H:\driverDev\MakeDrvr.bat文件...........................................18 3.2.5 设置VC的环境.....................................................................................19 3.2.5.1前面的内容编译时出了错误(配置'MakeDrvr')......................19 3.2.5.1.1 在project -> settings中设置成如下:.......................19 3.2.5.1.2 还可以在Tools-> Options-〉directories中选择“Executable files”并添加MakeDrvr.bat的目录即可.................20 3.2.5.1.3 再按F7编译 有编译提示..................................................20 3.2.5.1.4 搞清楚 MakeDrvr.BAT文件的功能...................................21 3.2.5.2前面的内容编译时出了错误,让我们看看是什么原因..............22 第四章 安装DebugPrintMonitor驱动程序.................................................................................24 4.1 用控制面板安装DebugPrintMonitor...........................................................................24 4.2 检查DebugPrint driver的安装情况...........................................................................29 第五章 安装wdm1驱动程序..........................................................................................................30 5.1 INF 文件.........................................................................................................................30 5.1 全新安装驱动.................................................................................................................30 5.1.1 安装驱动WDM1.SYS.............................................................................................30 5.2 测试DebugPrintMonitor...............................................................................................30 第六章 执行应用程序...................................................................................................................32 6.1 打开Wdm1Test.dsp.........................................................................................................32 6.2 编译Wdm1Test 工程.......................................................................................................32 6.3 修改Wdm1Test .CPP文件的setupapi.h的路径...........................................................33 6.3 重新指定Wdm1Test 工程的setupapi.lib的路径.......................................................35 6.4 类型DWORD_PTR和ULONG_PTR没定义的错误.................................................................36 6.4 调试WdmTest工程...........................................................................................................37 6.4.1 设置断点.............................................................................................................37 6.4.2 单步执行.............................................................................................................38 6.4.3 SYS目录下驱动程序代码对照:.......................................................................40 6.4.4 EXE中继续往下执行ReadFile/WriteFile.......................................................40 6.4.4.1 执行ReadFile的情况.............................................................................40 6.4.4.2 执行WriteFile的情况...........................................................................42 6.4.5其他的请自己执行..............................................................................................43 第七章 启用wdm1驱动程序..........................................................................................................44 第八章 停用wdm1驱动程序..........................................................................................................45 8.1 点击“我的电脑” –〉“属性” –〉“硬件”.........................................................45 8.2 点击 “设备管理器”并展开其他设备.......................................................................45 第九章还有更好的DebugView.exe.............................................................................................47 9.1得到DebugView.exe.........................................................................................................47 9.2 原理..................................................................................................................................48 9.2.1 DBG.......................................................................................................................48 9.2.2 DbgPrint().........................................................................................................48 9.2.3 如何使用DbgPrint()........................................................................................48 9.2.4 修改wdm1工程的例子........................................................................................48 9.3 如何使用DebugView.......................................................................................................48
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值