当前位置:   article > 正文

Windows驱动开发WDM_wdm驱动工作原理

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

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/583341
推荐阅读
相关标签
  

闽ICP备14008679号