posts - 18,comments - 11,trackbacks - 0

我们学习程序设计,都是从“Hello World”开始的,驱动程序也不例外,今天我就写一个驱动版的“Hello World”来热热身,目的希望大家能对驱动程序的基本框架有所了解。

驱动程序分为2类,一个是Kernel模式驱动,另一个是Windows模式驱动,2种模式本质是相同,但细节不同,本文介绍的是内核模式驱动和驱动程序的安装、使用。

驱动程序同普通的EXE,DLL一样,都属于PE文件,而且都有一个入口函数。但EXE中,入口函数是main()/WinMain()和Unicode的wmain()/wWinmain(),DLL的入口函数则可有可无,它是DllMain()。驱动程序也有入口函数,而且是必须的,它是DriverEntry(),再次提示,它是必须的,因为I/O管理器会首先调用驱动程序的DriverEntry(),它的作用就像DllMain()--完成一些初始化工作。DriverEntry()一共有2个参数:1PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针,我们操作驱动程序,全靠它,它是由I/O管理器传递进来的;2)PUNICODE_STRING RegistryPath,驱动程序的服务主键,这个参数的使用并不多,但要注意,在DriverEntry()返回后,它可能会消失,所以如果需要使用,记住先要保存下来。DriverEntry()的返回一个NTSTATUS值,它是一个ULONG值,具体的定义,请参见DDK中的NTSTATUS.H头文件,里边有详细的定义。

既然要写驱动版的“Hello World”,就需要确定如何来与驱动程序通信,常用的共享内存,共享事件,IOCTL宏,或者直接用ReadFile()或WriteFile()进行读写,在本文里我就采用一种简单的、但又很常用的IOCTL宏,它依赖的IRP派遣例程是IRP_MJ_DEVICE_CONTROL,Win32程序使用DeviceIoControl()与驱动进行通信,根据不同的IOCTL宏,输出不同的调试信息。为了简便,我并没有使用ReadFile()将信息读出来,而是直接用DbgPrint()输出,所以需要使用DbgView查看,其他调试工具也可以。PS:偷懒!

驱动程序与I/O管理器通信,使用的是IRP,即I/O请求包。IRP分为2部分:1)IRP首部;2)IRP堆栈。IRP首部信息如下:

IRP首部:

IO_STATUS_BLOCK IoStatus         包含I/O请求的状态
PVOID AssociatedIrp.SystemBuffer 如果执行缓冲区I/O,这个指针指向系统缓冲区
PMDL MdlAddress        如果直接I/O,这个指针指向用户缓冲区的存储器描述符表
PVOID UserBuffer        I/O缓冲区的用户空间地址

IRP堆栈:

UCHAR MajorFunction               指示IRP_MJ_XXX派遣例程
UCHAR MinorFunction               同上,一般文件系统和SCSI驱动程序使用它
 
union Parameters               MajorFunction的联合类型
{
struct Read                       IRP_MJ_READ的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
 
struct Write                      IRP_MJ_WRITE的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
 
struct DeviceIoControl            IRP_MJ_DEVICE_CONTROL参数
ULONG OutputBufferLength
ULONG InputBufferLength
ULONG IoControlCode
PVOID Type3InputBuffer

PDEVICE_OBJECT DeviceObject       请求的目标设备对象的指针
PFILE_OBJECT FileObject                请求的目标文件对象的指针,如果有的话

操作IRP。对于不同的IRP函数,操作也是不同的:有的只操作IRP首部;有的只操作IRP堆栈;还有操作IRP整体,下面是一些常用的函数:

IRP整体:

    名称                            描述                                   调用者
IoStartPacket             发送IRP到Start I/O例程              Dispatch
IoCompleteRequest     表示所有的处理完成                  DpcForIsr
IoStartNextPacket      发送下一个IRP到Start I/O例程     DpcForIsr
IoCallDriver                发送IRP请求                              Dispatch
IoAllocateIrp             请求另外的IRP                            Dispatch
IoFreeIrp                 释放驱动程序分配的IRP                I/O Completion

IRP堆栈:

    名称                                             描述                         调用者
IoGetCurrentIrpStackLocation   得到调用者堆栈的指针             Dispatch
IoMarkIrpPending              为进一步的处理标记调用者I/O堆栈  Dispatch
IoGetNextIrpStackLocation 得到下一个驱动程序的I/O堆栈的指针 Dispatch
IoSetNextIrpStackLocation      将I/O堆栈指针压入堆栈            Dispatc

在驱动程序,IRP派遣例程起着很重要的作用,每个IRP派遣例程,几乎都有对应的Win32函数,下面是几个常用的:

IRP派遣例程:

    名称                                      描述                           调用者
IRP_MJ_CREATE                   请求一个句柄                    CreateFile
IRP_MJ_CLEANUP            在关闭句柄时取消悬挂的IRP      CloseHandle
IRP_MJ_CLOSE                     关闭句柄                         CloseHandle
IRP_MJ_READ                      从设备得到数据                 ReadFile
IRP_MJ_WRITE                    传送数据到设备                 WriteFile
IRP_MJ_DEVICE_CONTROL    控制操作(利用IOCTL宏)   DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL  控制操作(只能被内核调用)       N/A
IRP_MJ_QUERY_INFORMATION    得到文件的长度             GetFileSize
IRP_MJ_SET_INFORMATION         设置文件的长度             SetFileSize
IRP_MJ_FLUSH_BUFFERS             写输出缓冲区或者丢弃输入缓冲区  FlushFileBuffers FlushConsoleInputBuffer PurgeComm
IRP_MJ_SHUTDOWN             系统关闭                     InitiateSystemShutdown

====================================================================================================

下面开始写我们的驱动版的“Hello World”,程序很简单,先介绍一下流程:

1,调用IoCreateDevice()创建一个设备,并返回一个设备对象。
2,调用IoCreateSynbolicLink()创建一个符号连接,使Win32程序可以使用驱动程序
3,设置IRP_MJ_DEVICE_CONTROL派遣例程HelloWorldDispatch()和卸载例程HelloWorldUnLoad()。

如果Win32程序使用DeviceIoControl(),则执行HelloWorldDispatch()函数
4,调用IoGetCurrentIrpStackLocation()得到当前调用者的IRP指针
5,取得IO控制代码,完成后使用IoCompleteRequest()完成IRP操作

如果使用ControlService()停止驱动程序,则执行HelloWorldUnLoad()函数
4,调用IoDeleteSymbolicLink()删除符号连接
5,调用IoDeleteDevice()删除已建立的设备

驱动入口DriverEntry()

//创建设备
IoCreateDevice(DriverObject,        //驱动程序对象
               0,                   //扩展设备的大小,由于不需要,所以置0
               &DeviceNameString,   //设备名称
               FILE_DEVICE_UNKNOWN, //设备类型
               0,                   //指示设备允许的操作
               FALSE,               //如果为TRUE,表示只能有一个线程使用该设备,为FALSE,则没有限制
               &lpDeviceObject);    //返回的设备对象

//创建符号连接
IoCreateSymbolicLink(&DeviceLinkString,   //存放符号连接的UNICODE_STRING
                     &DeviceNameString);  //设备名称

//派遣例程和卸载例程
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;吧
DriverObject->DriverUnload=HelloWorldUnLoad;

IRP派遣例程HelloWorldDispatch()

IrpStack=IoGetCurrentIrpStackLocation(pIrp);   //得到当前调用者的IRP堆栈

//获取IO控制代码,并执行指定操作,这里只是DbgPrint()
IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes)  {
......

IoCompleteRequest(pIrp,IO_NO_INCREMENT);   //完成IRP操作

卸载例程HelloWorldUnLoad()

//删除符号连接和设备
IoDeleteSymbolicLink(&DeviceLinkString);
IoDeleteDevice(DriverObject->DeviceObject);

====================================================================================================完整代码:

#ifndef __HELLOWORLD_C__
#define __HELLOWORLD_C__

#define DEBUGMSG

#include "HelloWorld.h"

//驱动入口
NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
{
    NTSTATUS ntStatus=STATUS_SUCCESS;
    PDEVICE_OBJECT lpDeviceObject=NULL;   //指向设备对象的指针
    UNICODE_STRING DeviceNameString;      //设备名称
    UNICODE_STRING DeviceLinkString;      //符号连接

    //调试信息
    #ifdef DEBUGMSG
           DbgPrint("Starting DriverEntry()\n");
    #endif

    RtlInitUnicodeString(&DeviceNameString,NT_DEVICE_NAME);  //初始化Unicode字符串

    //创建设备
    ntStatus=IoCreateDevice(DriverObject,0,&DeviceNameString,FILE_DEVICE_UNKNOWN,
                            0,FALSE,&lpDeviceObject);

    //使用NT_SUCCESS宏检测函数调用是否成功
    if (!NT_SUCCESS(ntStatus))
    {
        #ifdef DEBUGMSG
               DbgPrint("Error IoCreateDevice()\n");
        #endif
        goto Error;
    }

    RtlInitUnicodeString(&DeviceLinkString,DOS_DEVICE_NAME);

    //创建符号连接
    ntStatus=IoCreateSymbolicLink(&DeviceLinkString,&DeviceNameString);

    if (!NT_SUCCESS(ntStatus))
    {
        #ifdef DEBUGMSG
               DbgPrint("Error IoCreateSymbolicLink()\n");
        #endif
        goto Error;
    }

    //设置IRP派遣例程和卸载例程
    DriverObject->MajorFunction[IRP_MJ_CREATE]=//HelloWorldDispatch;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]=//HelloWorldDispatch;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=HelloWorldDispatch;
    DriverObject->DriverUnload=HelloWorldUnLoad;

    return ntStatus;

    Error:
          #ifdef DEBUGMSG
                 DbgPrint("Error DriverEntry()\n");
          #endif

          return ntStatus;
}

NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp)
{
    NTSTATUS ntStatus=STATUS_SUCCESS;
    ULONG IoControlCodes=0;             //I/O控制代码
    PIO_STACK_LOCATION IrpStack=NULL;   //IRP堆栈

    //设置IRP状态
    pIrp->IoStatus.Status=STATUS_SUCCESS;
    pIrp->IoStatus.Information=0;

    #ifdef DEBUGMSG
           DbgPrint("Starting HelloWorldDispatch()\n");
    #endif

    IrpStack=IoGetCurrentIrpStackLocation(pIrp);    //得到当前调用者的IRP

    switch (IrpStack->MajorFunction)
    {
            case IRP_MJ_CREATE:
                 #ifdef DEBUGMSG
                        DbgPrint("IRP_MJ_CREATE\n");
                 #endif
                 break;

            case IRP_MJ_CLOSE:
                 #ifdef DEBUGMSG
                        DbgPrint("IRP_MJ_CLOSE\n");
                 #endif
                 break;

            case IRP_MJ_DEVICE_CONTROL:

                 #ifdef DEBUGMSG
                        DbgPrint("IRP_MJ_DEVICE_CONTROL\n");
                 #endif

                 //取得I/O控制代码
                 IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;

                 switch (IoControlCodes)
                 {
                         //启动
                         case START_HELLPWORLD:
                              DbgPrint("Starting \"Hello World\"\n");
                              break;

                         //停止
                         case STOP_HELLPWORLD:
                              DbgPrint("Stoping \"Hello World\"\n");
  %

posted on 2009-03-10 11:04 冰火 阅读(262) 评论(0)  编辑 收藏 引用 所属分类: 驱动开发