http://blog.csdn.net/zuishikonghuan/article/details/49047055
在之前的一篇博文“驱动开发(2)第一个NT驱动和NT驱动的编译”(http://blog.csdn.net/zuishikonghuan/article/details/48805945)中,给出了一个空壳驱动,下面,将会以此源码为基础进行扩充,并给大家解说一下NT驱动的基本结构。其实WDM和KWDF驱动也是基于此的。
1。驱动对象 DRIVER_OBJECT
驱动的入口函数 DriverEntry 的第一个参数就是驱动加载时系统为当前驱动创建的驱动对象的指针。
这个结构微软没有完全公开,如果有兴趣可以可以到网上找找相关资料。其中一些重要的成员有:
PDEVICE_OBJECT DeviceObject:指向第一个设备对象的指针。一个驱动可以创建多个“设备”,这个成员是指向第一个设备对象的指针。驱动程序可以使用此成员和 DEVICE_OBJECT 的 NextDevice 成员来逐句通过驱动程序创建的所有设备对象的列表。
UNICODE_STRING DriverName:驱动名称,是 UNICODE_STRING 字符串。(见我的上上篇博文“内核中的字符串”)
PDRIVER_DISPATCH MajorFunction:派遣函数的指针。应将他视作一个数组,通过他注册派遣函数,如何注册我已经在之前的那一篇博文“驱动开发(2)第一个NT驱动和NT驱动的编译”中详细说了。
PDRIVER_UNLOAD DriverUnload:驱动卸载函数的指针。指向一个卸载函数,驱动卸载时负责清理工作,应在这个函数中关闭打开的句柄、释放申请的内存,防止内存泄露。如果驱动程序不注册卸载函数,那么驱动一旦加载就无法卸载。同样,如何注册我已经在之前的那一篇博文“驱动开发(2)第一个NT驱动和NT驱动的编译”中详细说了。
2。设备对象 DEVICE_OBJECT
结构原型:
typedef struct _DEVICE_OBJECT {
CSHORT Type;
USHORT Size;
LONG ReferenceCount;
struct _DRIVER_OBJECT *DriverObject;
struct _DEVICE_OBJECT *NextDevice;
struct _DEVICE_OBJECT *AttachedDevice;
struct _IRP *CurrentIrp;
PIO_TIMER Timer;
ULONG Flags;
ULONG Characteristics;
__volatile PVPB Vpb;
PVOID DeviceExtension;
DEVICE_TYPE DeviceType;
CCHAR StackSize;
union {
LIST_ENTRY ListEntry;
WAIT_CONTEXT_BLOCK Wcb;
} Queue;
ULONG AlignmentRequirement;
KDEVICE_QUEUE DeviceQueue;
KDPC Dpc;
ULONG ActiveThreadCount;
PSECURITY_DESCRIPTOR SecurityDescriptor;
KEVENT DeviceLock;
USHORT SectorSize;
USHORT Spare1;
struct _DEVOBJ_EXTENSION * DeviceObjectExtension;
PVOID Reserved;
} DEVICE_OBJECT, *PDEVICE_OBJECT;
说说几个重要的成员
DriverObject:指向创建此设备的驱动对象的指针
NextDevice:下一个设备对象的指针,创建过设备的驱动程序再创建设备时,创建之前最后一个设备的此成员是指向新创建的设备对象的指针。 DRIVER_OBJECT里的 DeviceObject 和此成员形成了一个类似于链表的结构,我们可以通过 DeviceObject 和 NextDevice 来枚举一个驱动创建的所有设备。
AttachedDevice:此设备附加到的设备对象的指针。关于这个,我将会在之后的过滤驱动的博文中说。
Flags:标志,比如DO_BUFFERED_IO,DO_DIRECT_IO指定设备的读写方式。
DO_BUFFERED_IO:读写此设备使用缓冲方式(系统复制缓冲区)。
DO_DIRECT_IO:读写此设备使用直接方式。
DO_EXCLUSIVE:一次只允许一个线程打开设备句柄。
DeviceExtension:设备扩展指针。创建设备时,我们需要传入设备扩展的长度,系统会分配内存,并通过这个成员让我们得到设备扩展。
DeviceType:设备类型,由IoCreateDevice等函数创建设备时指定的类型。
3。创建设备
使用 IoCreateDevice 函数创建设备,此函数的原型是:
NTSTATUS IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject,
_In_ ULONG DeviceExtensionSize,
_In_opt_ PUNICODE_STRING DeviceName,
_In_ DEVICE_TYPE DeviceType,
_In_ ULONG DeviceCharacteristics,
_In_ BOOLEAN Exclusive,
_Out_ PDEVICE_OBJECT *DeviceObject
);
DriverObject:调用方的驱动对象的指针。每个驱动程序在其驱动入口例程的参数接收指向其驱动程序对象的指针。WDM 功能和筛选器驱动程序也在他们的 AddDevice 例程接收驱动程序对象指针。
DeviceExtensionSize:指定驱动程序确定要为设备扩展的设备对象分配的字节数。设备扩展的内部结构是由驱动程序定义。
设备名称:驱动名称,是 UNICODE_STRING 字符串(见我的上上篇博文“内核中的字符串”)可空。
DeviceType:设备类型,比如:FILE_DEVICE_DISK、FILE_DEVICE_KEYBOARD等等。本例中采用FILE_DEVICE_UNKNOWN,未知设备。
设备类型有很多,详见MSDN。
DeviceCharacteristics: 指定一个或多个系统定义的常数,或在一起,提供有关驱动程序的设备的其他信息。大多数驱动程序为该参数指定了FILE_DEVICE_SECURE_OPEN。
比如说:
FILE_FLOPPY_DISKETTE:指示该设备是软盘设备。
FILE_READ_ONLY_DEVICE:指示不能写入该设备。
FILE_REMOVABLE_MEDIA:指示存储设备支持可移动媒体。
等等
Exclusive:设备是否是独占的,TRUE表示独占,大多数驱动程序把此参数设置为FALSE。
DeviceObject:指向一个 PDEVICE_OBJECT 的指针,用于接收设备对象的指针。即此参数是设备对象的指针变量的指针。
返回值:成功返回STATUS_SUCCESS,失败返回指定的错误码。
设备扩展:
在驱动程序的开发中,一般不建议用全局变量,而应该使用设备扩展。创建设备时,我们需要传入设备扩展的长度,系统会分配内存,并通过返回的设备对象中的相关成员返回设备扩展的指针。
这里我们定义一个设备扩展的结构,只有一个成员,符号连接名:
typedef struct _DEVICE_EXTENSION {
UNICODE_STRING SymLinkName; //我们定义的设备扩展里只有一个符号链接名成员
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
创建设备:
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//创建设备名称的字符串
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\MyDevice1");
//创建设备
status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
if (!NT_SUCCESS(status))
return status;
pDevObj->Flags |= DO_BUFFERED_IO;//将设备设置为缓冲I/O设备,关于缓冲I/O设备将会在以后的博文中讲
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展
4。创建符号连接
创建设备后,应用程序一般不能直接访问设备,因此我们需要为设备对象创建符号连接。关于符号连接的内核模式、用户模式命名的问题,请看我之前的博文“驱动开发(1)基础知识”。
创建符号连接用的是 IoCreateSymbolicLink 函数,原型为:
NTSTATUS IoCreateSymbolicLink(
_In_ PUNICODE_STRING SymbolicLinkName,
_In_ PUNICODE_STRING DeviceName
);
SymbolicLinkName:符号连接名称
DeviceName:要将符号连接绑定到的设备名称
这两个参数都是 UNICODE_STRING 字符串(见我的上上篇博文“内核中的字符串”)
返回值:成功返回STATUS_SUCCESS,失败返回指定的错误码。
创建符号连接:
//创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, L"\\??\\MyDevice1_link");
pDevExt->SymLinkName = symLinkName;
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDevObj);
return status;
}
5。释放资源
驱动程序卸载时,我们需要删除设备和符号连接。因此我们的驱动卸载函数为:
extern "C" VOID DriverUnload(IN PDRIVER_OBJECT pDriverObject)
{
DbgPrint("DriverUnload\r\n");
PDEVICE_OBJECT pDevObj;
pDevObj = pDriverObject->DeviceObject;
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展
//删除符号链接
UNICODE_STRING pLinkName = pDevExt->SymLinkName;
IoDeleteSymbolicLink(&pLinkName);
//删除设备
IoDeleteDevice(pDevObj);
}
这次,我们还是保留和上一次一样的派遣函数,直接完成掉。
我们先编写一个Win32应用程序,很简单,打开一个设备,而且是打开我们刚刚编写的驱动程序创建的设备的符号连接。源码去下:
#include<Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hfile;
hfile = CreateFileA("\\\\.\\MyDevice1_link", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);//打开设备
if (hfile == INVALID_HANDLE_VALUE)
MessageBoxA(0, "打开设备失败", "错误", 0);
getchar();
CloseHandle(hfile);
return 0;
}
驱动程序的完整源码:
#include <ntddk.h>
extern "C" VOID DriverUnload(IN PDRIVER_OBJECT pDriverObject);
extern "C" NTSTATUS DispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
typedef struct _DEVICE_EXTENSION {
UNICODE_STRING SymLinkName; //我们定义的设备扩展里只有一个符号链接名成员
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
DbgPrint("DriverEntry\r\n");
pDriverObject->DriverUnload = DriverUnload;//驱动卸载函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchRoutine;//注册派遣函数
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRoutine;
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//创建设备名称的字符串
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\MyDevice1");
//创建设备
status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
if (!NT_SUCCESS(status))
return status;
pDevObj->Flags |= DO_BUFFERED_IO;//将设备设置为缓冲I/O设备,关于缓冲I/O设备将会在以后的博文中讲
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展
//创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, L"\\??\\MyDevice1_link");
pDevExt->SymLinkName = symLinkName;
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDevObj);
return status;
}
return STATUS_SUCCESS;
}
extern "C" VOID DriverUnload(IN PDRIVER_OBJECT pDriverObject)
{
DbgPrint("DriverUnload\r\n");
PDEVICE_OBJECT pDevObj;
pDevObj = pDriverObject->DeviceObject;
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展
//删除符号链接
UNICODE_STRING pLinkName = pDevExt->SymLinkName;
IoDeleteSymbolicLink(&pLinkName);
//删除设备
IoDeleteDevice(pDevObj);
}
extern "C" NTSTATUS DispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("DispatchRoutine\r\n");
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
下面我们编译这两个程序,先运行一下应用程序,果然弹出来了一个“打开设备失败”的框。
我们把驱动加载了,然后再运行应用程序,令人惊奇的是,没有弹出框,这说明应用程序成功打开了设备!这说明了应用程序可以访问我们编写的驱动了。
同时我们可以通过DebugView发现驱动程序的派遣函数被调用了2次:
驱动开发系列的下一篇“驱动开发(7)IRP与派遣函数”将会展开这一部分的内容