0.driverbase-WDM和NT驱动

来源:互联网 发布:mac os sierra beta4 编辑:程序博客网 时间:2024/04/25 11:42

WDM要导入的的头文件是WDM.h

和NT式驱动程序一样,入口函数同样是DriverEntry,且在C++编译的时候需要用extern"C"修饰

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;

和NT驱动不同在于WDM多了一个叫个AddDevice的回调

这个回调来自DriverExtension->AddDevice中,此回调的作用是创建设备对象并由PNP(即插即用)管理器调用.

同时,通过MajorFuntion[IRP_MJ_PNP]设置了PNP的IRP处理,也是和NT驱动不同点之一

所以,在WDM的驱动程序中,创建设备对象的任务不再由DriverEntry来承担,

而是需要驱动程序向系统注册一个称为AddDevice的函数,此函数由PNP管理器负责调用,主要就是创建设备对象,函数原型如下:

NTSTATUSDRIVER_ADD_DEVICE (    __in struct _DRIVER_OBJECT *DriverObject,    __in struct _DEVICE_OBJECT *PhysicalDeviceObject    );

第一个是driverentry传过来的驱动对象,

第二个是PNP管理器传进来的底层驱动设备对象,这个概念在NT驱动是没有的

下面来看下这个函数:

#define PAGED_CODE() {                                                       \    if (KeGetCurrentIrql() > APC_LEVEL) {                                    \        KdPrint(("EX: Pageable code called at IRQL %d\n", KeGetCurrentIrql())); \        PAGED_ASSERT(FALSE);                                                    \

也就是IRQL超过了APC_LEVEL时,在check版中会产生一个断言,断言会使程序终止,

下面解释下IRQL:

IRQL是Interrupt ReQuest Level 中断请求级别。一个由windows虚拟出来的概念,划分在windows下中断的优先级

IRP是I/O Request Packet I/O请求包

IRQL是分0-31级的,中断的意思就是正常运行的程序被打断,而转向做其他事,这个有个level,就是说程序如果level大于或等于中断源的level,它就可以无视中断源(官大一级压死人,平起平坐也不用鸟你)

引入一些小概念:

 引起中断的事件称为中断源

中断源向CPU提出处理的请求称为中断请求

发生中断时被打断程序的暂停点称为断点

CPU暂停现行程序而转为响应中断请求的过程称为中断响应

处理中断源的程序称为中断处理程序

一般level如下:

PASSIVE_LEVEL

线程即运行在该中断级别上,它对所有中断都作出响应。用户模式代码都是运行在该中断级别上。

APC_LEVEL

当I/o 操作完成时,系统会产生这个中断。为了响应这个中断,应用程序向I/O 完成中断处理例程队列中插入一个APC回调函数,当I/O完成时,该APC函数被调用。如果你不想对这个中断做出响应,你就可以将IRQL提升至APC_LEVEL。这时当I/O完成时,应用程序将不会受到该中断信号。可以调用KeEnterCriticalRegion或KeEnterGuardedRegion将IRQL提升至该级别。APC中断通常是由处理器引发,可以想自己发出也可是像其他处理器发出。一般情况下,是不应该的使用的APC_LEVEL的,除非你想使用Fast Mutexes之类的东西。

DISPATCH_LEVEL 

为了能够执行多任务,系统必须允许线程调度。而线程调度的根本就是靠时钟中断来保证的,该级别的中断即调度中断。当你的代码运行的IRQL被提升为DISPATH_LEVEL时,就意味着你的代码不在受线程中断影响力。你的代码会一直运行直到你将IRQL设置为低于DISPATH_LEVEL为止。这中间如果发生缺页错误之类的IRQL级别在DISPATH_LEVEL之下的严重中断时,这些中断均不会被处理。这时,代码将无法正常运行。所以,DISPATH_LEVEL的使用绝对要慎之又慎。只有在是自旋锁时,你才应该考虑选择该IRQL。

相对于NT驱动,扩展结构中多了一个参数:

typedef struct _DEVICE_EXTENSION{    PDEVICE_OBJECT fdo;    PDEVICE_OBJECT NextStackDevice;UNICODE_STRING ustrDeviceName;// 设备名UNICODE_STRING ustrSymLinkName;// 符号链接名} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

NextStackDevice!
pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);

IoAttachDeviceToDeviceStack函数简介:

如果一个设备被其他设备绑定,它们在一起的一组设备,被称为设备栈,这个函数返回了最终被绑定的设备指针,也就是最顶层设备

MSDN:The IoAttachDeviceToDeviceStack routine attaches the caller's device object to the highest device object in the chain and returns a pointer to the previously highest device object.(把调用者设备对象加到设备对象链的最上层,同时返回原来最上层的设备对象)

比如原来有一个Device A,在这个Device的上层有一个过滤驱动程序的设备Device B, 
可以用这个图表示: 
+---+ 
| B | (最上层)
+---+  
| A | 
+---+ 
  
然后我们有一个新的Device C,用IoAttachDeviceToDeviceStack(C, A)之后,就把C放到了A所在的链的末端,如下: 
+---+ 
| C | (最上层)
+---+ 
| B | (次上层,原来的最上层)
+---+ 
| A | 
+---+ 
同时返回B(原来的最上层)。

下面看下HelloWDM处理PNP的回调函数

其中,IRP_MJ_PNP会细分为若干个字类,如,IRP_MN_START_DEVICE,IRP_MN_REMOVE_DEVICE,IRP_MN_STOP_DEVICE

首先用:

PAGED_CODE();

保证运行在低于APC_LEVEL的中断优先级的级别上

PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

得到设备的扩展结构

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

得到当前IRP的堆栈

 IRP可以看成是窗口程序中的消息(Message),DEVICE_OBJECT可以看成是窗口程序中的窗口(Window)

任何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的 IO_STACK_LOCATION 结构数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序,另外还有一个堆栈单元供IRP的创建者使用,如下图:


io_stack_location的结构如下图:


继续上面的代码:

ULONG fcn = stack->MinorFunction;

fcn指定了IRP_MN_START_DEVICE,IRP_MN_QUERY_REMOVE_DEVICE,IRP_MN_REMOVE_DEVICE

static NTSTATUS (*fcntab[])(PDEVICE_EXTENSION pdx, PIRP Irp) = {DefaultPnpHandler,// IRP_MN_START_DEVICEDefaultPnpHandler,// IRP_MN_QUERY_REMOVE_DEVICEHandleRemoveDevice,// IRP_MN_REMOVE_DEVICEDefaultPnpHandler,// IRP_MN_CANCEL_REMOVE_DEVICE

不在上述列表中的fcn,默认调DefaultPnpHandler函数:

if (fcn >= arraysize(fcntab)){// 未知的子功能代码status = DefaultPnpHandler(pdx, Irp); // some function we don't know aboutreturn status;}

否则调对应列表中的函数:

status = (*fcntab[fcn])(pdx, Irp);

可以看下MajorFunction的类型:

UCHAR MajorFunction;typedef unsigned char UCHAR;
最多也就255种

下面看下卸载:

HandleRemoveDevice,// IRP_MN_REMOVE_DEVICE
而原来的外部卸载函数是没有东西的:

pDriverObject->DriverUnload = HelloWDMUnload;


#pragma PAGEDCODEvoid HelloWDMUnload(IN PDRIVER_OBJECT DriverObject){PAGED_CODE();KdPrint(("Enter HelloWDMUnload\n"));KdPrint(("Leave HelloWDMUnload\n"));}

所以直接卸载函数是HandleRemoveDevice:

#pragma PAGEDCODENTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp){PAGED_CODE();KdPrint(("Enter HandleRemoveDevice\n"));Irp->IoStatus.Status = STATUS_SUCCESS;//设置虎IRP状态为顺利完成NTSTATUS status = DefaultPnpHandler(pdx, Irp);//调用默认的PNP的IRP的处理函数IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName);//删除此设备对象的符号链接    //调用IoDetachDevice()把fdo从设备栈中脱开:    if (pdx->NextStackDevice)        IoDetachDevice(pdx->NextStackDevice);    //删除fdo:    IoDeleteDevice(pdx->fdo);//删除设备对象.KdPrint(("Leave HandleRemoveDevice\n"));return status;}







原创粉丝点击