驱动学习1
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管理器负责调用,主要就是创建设备对象,函数原型如下:
NTSTATUS DRIVER_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,它就可以无视中断源(官大一级压死人,平起平坐也不用鸟你)
引入一些小概念:
引起中断的事件称为中断源。
发生中断时被打断程序的暂停点称为断点。
一般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_DEVICE DefaultPnpHandler, // IRP_MN_QUERY_REMOVE_DEVICE HandleRemoveDevice, // IRP_MN_REMOVE_DEVICE DefaultPnpHandler, // IRP_MN_CANCEL_REMOVE_DEVICE
不在上述列表中的fcn,默认调DefaultPnpHandler函数:
if (fcn >= arraysize(fcntab)) { // 未知的子功能代码 status = DefaultPnpHandler(pdx, Irp); // some function we don't know about return status; }
否则调对应列表中的函数:
status = (*fcntab[fcn])(pdx, Irp);
可以看下MajorFunction的类型:
UCHAR MajorFunction; typedef unsigned char UCHAR;最多也就255种
下面看下卸载:
HandleRemoveDevice, // IRP_MN_REMOVE_DEVICE而原来的外部卸载函数是没有东西的:
pDriverObject->DriverUnload = HelloWDMUnload;
#pragma PAGEDCODE void HelloWDMUnload(IN PDRIVER_OBJECT DriverObject) { PAGED_CODE(); KdPrint(("Enter HelloWDMUnload\n")); KdPrint(("Leave HelloWDMUnload\n")); }
所以直接卸载函数是HandleRemoveDevice:
#pragma PAGEDCODE NTSTATUS 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; }