Windows驱动程序分为两类:一类是不支持即插即用功能的NT式驱动程序;另一类是支持即插即用功能的WDM驱动程序。

NT式驱动的基本结构:

1)驱动加载过程与驱动入口函数DriverEntry:

驱动程序入口点函数通常命名为DriverEntry,也可以指定另外的名字,但最好遵循这个名字:

DRIVER_INITIALIZE DriverEntry;

NTSTATUS DriverEntry(

__in  struct _DRIVER_OBJECT *DriverObject, //指向驱动对象的指针

__in  PUNICODE_STRING RegistryPath //指向设备服务键的键名字符串的指针

)

{ ... }

DriverEntry主要是对驱动程序进行初始化工作,它是由系统进程所调用的。在Windows中有个特殊的进程叫做系统进程,它是名为System的进程。系统进程在系统启动时就已经被创建了。

驱动加载时,系统进程启动新的线程,调用执行体组件中的对象管理器,创建一个驱动对象,这个驱动对象就是一个DRIVER_OBJECT的结构体。此外,系统进程调用执行体组件中的配置管理程序,查询此驱动程序对应的注册表中的项。

在DriverEntry中,主要功能是对系统进程创建的驱动对象进行初始化。另外,设备服务键的键名有时候需要保存下来,因为这个字符串不是长期存在的(函数返回后可能消失)。这个字符串内容一般是/REGISTRY/MACHINE/SYSTEM/ControlSet/Services/[服务名]

DriverEntry返回值是NTSTATUS的数据,NTSTATUS是被定义为32位的无符号长整型。在驱动开发中,习惯用NTSTATUS返回状态,其中0~0x7FFFFFFF是正确的状态;0x80000000~0xFFFFFFFF是错误的状态。可以使用宏NT_SUCCESS来检测状态的正确性。

注意:DriverEntry参数的修饰“IN(__in)”、“OUT(__out)”、“INOUT(__inout)”在DDK中被定义为空串,它们的功能类似与程序注释:“IN(__in)”表示该参数纯粹用于输入目的;“OUT(__out)”表示该参数仅用于函数的输出参数;“INOUT(__inout)”用于既可以输入又可以输出的参数。

2)创建设备对象:

在NT式驱动中,创建设备对象由IoCreateDevice内核函数完成:

NTSTATUS IoCreateDevice(

__in      PDRIVER_OBJECT DriverObject, //指向驱动对象的指针

__in      ULONG DeviceExtensionSize, //指定设备扩展的大小(I/O管理器根据这个大小在内存中

//创建设备扩展,并与驱动对象关联)

__in_opt  PUNICODE_STRING DeviceName, //设置设备对象的名字

__in      DEVICE_TYPE DeviceType, //设置设备对象的类型

__in      ULONG DeviceCharacteristics, //设置设备对象的特征

__in      BOOLEAN Exclusive, //设置设备对象是否在内核模式下使用,一般设置为TRUE

__out     PDEVICE_OBJECT *DeviceObject //I/O管理器负责创建这个设备对象,

//并返回设备对象的地址

);

设备名称用UNICODE字符串指定,并且字符串必须是“/Device/[设备名]”的形式,在Windows下所有设备都是以类似名字命名的,例如磁盘分区的C盘、D盘分别是“/Device/HarddiskVolume1”、“/Device/HarddiskVolume2”。当然也可以不指定设备名字,这时I/O管理器会自动分配一个数字作为设备的设备名:如“/Device/00000001”。

指定的设备名只能被内核模式下的其他驱动所识别,在用户模式下的应用程序是无法识别这个设备的,对此,有两种解决方法:一是通过符号链接找到设备;一是通过设备接口找到设备,这种在NT驱动中很少使用。

符号链接可以理解成为设备对象起一个“别名”。设备对象的名字只能被内核模式驱动识别,而别名可以被用户模式下的应用程序识别。所谓的C盘,指的是名为“C:”的符号链接,其真正的设备对象是“/Device/HarddiskVolume1”。创建符号链接的函数是IoCreateSymbolicLink:

NTSTATUS IoCreateSymbolicLink(

__in  PUNICODE_STRING SymbolicLinkName, //符号链接的字符串

__in  PUNICODE_STRING DeviceName //设备对象名的字符串

);

在内核模式下,符号链接是以“/??/”开头的(或者是“/DosDevices/”开头),如C盘就是“/??/C:”。而用户模式下则是以//./开头的,如C盘就是//./C:

3)DriverUnload例程

在驱动对象中会设置DriverUnload例程,此例程在驱动被卸载时被调用。在NT驱动中,DriverUnload一般负责删除在DriverEntry中创建的设备对象,并且将设备对象所关联的符号链接删除,另外,DriverUnload还负责对一些资源进行回收。

下面代码是一个简单的NT式驱动程序,先看头文件HelloDDK.h:

#pragma once

/**

* 这里采用C++语言编写,如果直接包含ntddk.h,函数的符号表

* 会导入错误,所以需要加入extern "C",以保证符号表的正确导入

*/

#ifdef __cplusplus

extern "C"

{

#endif

#include <NTDDK.h> //所有NT式驱动程序都要包含该头文件

#ifdef __cplusplus

}

#endif

/**

* 定义分页标记、非分页标记和初始化内存块

*/

#define PAGEDCODE code_seg("PAGE")

#define LOCKEDCODE code_seg()

#define INITCODE code_seg("INIT")

#define PAGEDDATA data_seg("PAGE")

#define LOCKEDDATA data_seg()

#define INITDATA data_seg("INIT")

#define ARRAYSIZE(p) (sizeof(p)/sizeof((p)[0]))

/**

* 指定设备扩展结构体,这种结构体广泛应用与驱动程序中

* 根据不同驱动程序的需要,它负责补充定义设备的相关信息

*/

typedef struct _DEVICE_EXTENSION

{

PDEVICE_OBJECT pDevice;

UNICODE_STRING ustrDeviceName; //设备名称

UNICODE_STRING ustrSymLinkName; //符号链接名

}DEVICE_EXTENSION, *PDEVICE_EXTENSION;

/**

* 函数声明

*/

NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject);

VOID HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject);

NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,

IN PIRP pIrp);

和普通的应用程序不同,Windows驱动程序的入口函数不是main函数,而是一个叫做DriverEntry的函数,DriverEntry函数由内核中的I/O管理器负责调用:

DRIVER_INITIALIZE DriverEntry; (最新版,下面用到的有点不同)

NTSTATUS DriverEntry(

__in  struct _DRIVER_OBJECT *DriverObject, //I/O管理器传递进来的驱动对象

__in  PUNICODE_STRING RegistryPath //Unicode字符串,指向此驱动负责的注册表

)

{ ... }

代码如下:

#include "HelloDDK.h"

/**

* 函数名称:DriverEntry

* 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象

* 参数列表:

*    pDriverObject:从I/O管理器中传过来的驱动对象

*    pRegistryPath:驱动程序在注册表中的路径

* 返回值:返回初始化驱动状态

*/

#pragma INITCODE      //指明此函数是加载到INIT内存区域中,即成功加载后,可以退出内存

extern "C" NTSTATUS DriverEntry(//extern "C"指明按C方式编译成_DriverEntry@8的符号,而非C++方式

IN PDRIVER_OBJECT pDriverObject,

IN PUNICODE_STRING pRegistryPath)

{

NTSTATUS status;

KdPrint(("Enter DriverEntry/n")); //宏,调试版本会用DbgPrint代替,发行版本不执行任何操作

//注册其他驱动调用函数入口

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;

}

CreateDevice函数是一个帮助函数,辅助DriverEntry创建一个设备对象。其完全可以展开在DriverEntry中,此处是为了代码的简洁好看:

/**

* 函数名称: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//ASCEDDKDevice");

//创建设备对象

status = IoCreateDevice(pDriverObject,

sizeof(DEVICE_EXTENSION),

&(UNICODE_STRING)devName,

FILE_DEVICE_UNKNOWN,

0, TRUE, &pDevObj);

if(!NT_SUCCESS(status))

return status;

//设备对内存的操作分为两种:DO_BUFFERED_IO和DO_DIRECT_IO

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;

}

卸载驱动例程用于设备被卸载的情况,由I/O管理器负责调用此回调函数。此例程遍历系统中所有此类设备对象。第一个设备对象的地址存在于驱动对象的DeviceObject域中,每个设备对象的NextDevice域记录着下一个设备对象的地址,这样就形成一个链表。卸载驱动例程的主要目的就是遍历系统中所有此类设备对象,然后删除设备对象以及符号链接:

/**

* 函数名称: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);

}

}

对设备对象的创建、关闭和读写操作都被指定到以下这个默认的派遣例程中:

/**

* 函数名称:HelloDDKDispatchRoutine

* 功能描述:对读IRP进行处理

* 参数列表:

*    pDevObj:功能设备对象

*    pIrp:从I/O请求包

* 返回值:返回状态

*/

#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;

IoCompleteRequest(pIrp, IO_NO_INCREMENT);

KdPrint(("Leave HelloDDKDispatchRoutine/n"));

return status;

}

Windows NT驱动程序的基本结构和实例的更多相关文章

  1. Windows NT 驱动程序开发人员提示 -- 应注意避免的事项

    下面是开发人员在使用 Windows NT 设备驱动程序时应当避免的事项列表: 1.  一定不要在没有标注 I/O 请求数据包 (IRP) 挂起 (IoMarkIrpPending) 的情况下通过调度 ...

  2. Windows内核 WDM驱动程序的基本结构和实例

    WDM驱动的基本结构: WDM驱动模型是建立在NT式驱动程序模型基础之上的.对于WDM驱动程序来说,一般都是基于分层的,即完成一个设备的操作,至少要由两个驱动设备共同完成. 1)物理设备对象和功能设备 ...

  3. 理解和使用NT驱动程序的执行上下文

    理解Windows NT驱动程序最重要的概念之一就是驱动程序运行时所处的“执行上下文”.理解并小心地应用这个概念可以帮助你构建更快.更高效的驱动程序. NT标准内核模式驱动程序编程的一个重要观念是某个 ...

  4. [转帖]windows7/windows NT介绍

    windows7/windows NT介绍 原文应该是IT168发布的 但是一直没找到 感觉看了之后 明白了很多 技术都是互相融合的 没有严格意义上的对立直说.   Windows 7/Windows ...

  5. 为不同版本的 Windows 编写驱动程序

    MSDN原文:https://msdn.microsoft.com/zh-cn/library/windows/hardware/ff554887(v=vs.85).aspx 创建驱动程序项目时,指定 ...

  6. 第一章 Windows NT System Components

    Page 3. The focus(焦点) of this book is Windows NT file system and the interaction(交互) of the file sys ...

  7. [原创]使用GCC创建 Windows NT 下的内核DLL

    原文链接:使用GCC创建 Windows NT 下的内核DLL 在温习<<Windows 2000 Driving>>分层驱动程序一章的时候,看到了关于紧耦合驱动连接方式,这种 ...

  8. 超具体Windows版本号编译执行React Native官方实例UIExplorer项目(多图慎入)

    ),React Native技术交流4群(458982758).请不要反复加群! 欢迎各位大牛,React Native技术爱好者加入交流!同一时候博客右側欢迎微信扫描关注订阅号,移动技术干货,精彩文 ...

  9. Windows server 2008 布署FTP服务器实例(适用于阿里云)!

    Windows server 2008 布署FTP服务器实例(适用于阿里云). 1.打开管理.配置-用户-新建用户,如:ftp_user,并设置password.选择永只是期和password不能更改 ...

随机推荐

  1. TestNg依赖高级用法之强制依赖与顺序依赖------TestNg依赖详解(二)

    TestNg使用dependsOnGroups属性来进行依赖测试, 测试方法依赖于某个或某些方法,这个/这些方法作为前置依赖条件 强制依赖:如果被依赖的某一个方法发生了异常,那么之后的方法都不会被执行 ...

  2. sed 字符串替换

    1. sed替换的基本语法为: sed 's/原字符串/替换字符串/' 单引号里面,s表示替换,三根斜线中间是替换的样式,特殊字符需要使用反斜线”\”进行转义. 2. 单引号” ‘ ’”是没有办法用反 ...

  3. MFC 打开文件夹选择框并获取文件夹路径

    CString FicowGetDirectory() { BROWSEINFO bi; char name[MAX_PATH]; ZeroMemory(&bi, sizeof(BROWSEI ...

  4. js库写法

    前言: 现在javascript库特别多,其写法各式各样,总结几种我们经常见到的,作为自己知识的积累.而目前版本的 JavaScript 并未提供一种原生的.语言级别的模块化组织模式,而是将模块化的方 ...

  5. iOS学习16之OC集合遍历和数组排序

    1.集合遍历 1> 遍历 集合(Collection):OC中提供的容器类:数组,字典,集合. 遍历:对集合中元素依次取出的过称叫做遍历. 三种方式:① for循环遍历: ② NSEnumera ...

  6. BZOJ4112 : [Wf2015]Pipe Stream

    枚举答案,考虑将速度区间等长地划分成若干个小区间. 设$n_i$表示$i$次敲击能得到的区间数,$v_i$表示$i$次敲击之后答案落在$[v1,v_i]$之间,则$n_0=1,v_0=v2$. 因为对 ...

  7. BZOJ 2882 & 后缀数组的傻逼实现

    题意: 一个字符环,求一个开头使字典序最小. SOL: 后缀数组打起来...然后居然卡过...10sec的实现我10936ms...居然卡过??? rank倒三...啦啦啦啦啦.... 改个离散化会不 ...

  8. BZOJ 1051 & 强联通分量

    题意: 怎么说呢...这种题目有点概括不来....还是到原题面上看好了... SOL: 求出强联通分量然后根据分量重构图,如果只有一个点没有出边那么就输出这个点中点的数目. 对就是这样. 哦还有论边双 ...

  9. 来自于2016.2.24的flag

    今天又做了一套xj模拟题-------打比赛这种事情变得越来越无聊了------既影响自己的计划(虽然看起来很难完成的样子),又扰乱心情.而且题目大都是学习算法之类的,与计划不接轨就非常没有兴趣. 然 ...

  10. JS中toFixed()方法的问题及解决方案

    最近发现JS当中toFixed()方法存在一些问题,采用原生的Number对象的原型对象上的toFixed()方法时,规则并不是所谓的“四舍五入”或者是“四舍六入五成双”,所谓“四舍六入五成双”,在百 ...