看《Windows驱动程序开发技术详解》的朋友们可能发现,按照书上的代码写了(抄了)HelloDDK后安装运行后正常,但卸载时发现Windows 7 蓝屏了,报错信息为PAGE_FAULT_IN_NON_PAGED_AREA (这可着实吓坏了我这个刚入门Windows驱动程序的萌新)
先上解决方案:
把所有#pragma INITCODE 替换成 #pragma PAGEDCODE
至于为什么,我在debug这段HelloDDK后发现是死在了卸载驱动例程的删除符号链接上,通过WinDbg的Watch发现这些无论是符号链接还是驱动设备都显示读取内存错误的提示……然后我试着把这段通过链表递归删除的代码去掉,虽然去掉后驱动卸载没有发生蓝屏,但再次安装时会发现无法安装,原因估计就是在于上次卸载驱动例程根本没卸载干净(包括这些符号链接),而重复创建时发生冲突。于是就有可能发生在创建设备时存储的设备的内存位置有问题。
网上查了一下,说INITCODE是在完成这段函数后会自动删除以节省内存。但是在这我的Windows 7 上却发现这会导致创建的所有信息(包括符号链接和设备对象)都变成无法读取的状态,自然卸载的时候就不会成功(至于为什么会变成无法读取的状态仍未知,如果有哪位知道请及时指出)。而报错信息本质上也确实是关于分页内存的错误信息。全部使用#pragma PAGEDCODE 就不用担心这种问题发生
下面附上改后的代码,并附上了一点注释方便驱动程序入门者理解
//Driver.h
#pragma once
#include <ntddk.h>
//定义分页标记、非分页标记、初始化内存块
#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);
//Driver.cpp
#include "Driver.h"
//驱动程序入口函数 DriverEntry
//#pragma INITCODE //Windows 7 下会导致蓝屏
#pragma PAGEDCODE
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath){ //参数:(IO设备对象,驱动程序在注册表中的路径)
NTSTATUS status;
KdPrint(("Enter DriverEntry. ID = HelloDDK\n"));
//注册其他驱动程序调用函数入口
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(("Finished DriverEntry. ID = HelloDDK\n"));
return status;
}
//初始化设备对象函数 CreateDevice
//#pragma INITCODE //Windows 7 下会导致蓝屏
#pragma PAGEDCODE
NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject){
NTSTATUS status;
PDEVICE_OBJECT pDevObj; //设备对象(系统的)
PDEVICE_EXTENSION pDevExt; //设备扩展(可以理解为我们自己的)
KdPrint(("Started creating device\n"));
#if DBG
_asm int 3
#endif
//创建设备名称
UNICODE_STRING deviceName;
RtlInitUnicodeString(&deviceName, L"\\Device\\MyDDKDeivce");
//创建设备
status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &(UNICODE_STRING)deviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
if(!NT_SUCCESS(status)) return status;
pDevObj->Flags |= DO_BUFFERED_IO; //设备对内存的操作有两种:BUFFERED_IO和DO_DIRECT_IO,此部分讲解请参考第三章。
//填写我们自己定义的设备信息结构体
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = deviceName;
//创建符号链接,使之对应用程序可见。该符号链接指向真正的设备名称
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, L"\\??\\HelloDDK");
pDevExt->ustrSymLinkName = symLinkName;
status = IoCreateSymbolicLink(&symLinkName, &deviceName);
if(!NT_SUCCESS(status)){
IoDeleteDevice(pDevObj); //删除设备
return status;
}
KdPrint(("Create device successfully\n"));
return STATUS_SUCCESS;
}
//驱动程序卸载操作函数 HelloDDKUnload
#pragma PAGEDCODE
void HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject){
#if DBG
_asm int 3
#endif
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); //这边在 Windows 7 下pLinkName会显示memory read error(估计就是NULL),造成蓝屏,由于INITCODE被移除或使用了不同的内存段
//链表递归,删除设备对象
pNextObj = pNextObj->NextDevice;
IoDeleteDevice(pDevExt->pDevice);
}
KdPrint(("Delete device successfully\n"));
}
//默认派遣例程,用于对设备对象的创建、关闭、读写操作。在此处将其成功返回 HelloDDKDispatchRoutine
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp){
KdPrint(("Enter HelloDDKDispatchRoutine\n"));
NTSTATUS status = STATUS_SUCCESS;
//完成IRP
pIrp->IoStatus.Status = status; //设置IRP状态为成功。IRP的讲解请参考第4章
pIrp->IoStatus.Information = 0; //设置操作的字节数为0,这里无实际意义
IoCompleteRequest(pIrp, IO_NO_INCREMENT); //指示完成此IRP
KdPrint(("Finish HelloDDKDispatchRoutine\n"));
return status;
}