以下内容全部来自《Windows驱动开发技术详解》,作者张帆、史彩成等,属摘抄型笔记。
///
数据结构是计算机程序的核心,I/O管理器定义了一些数据结构,这些数据结构是编写驱动程序时所必须掌握的。
1.驱动对象结构
每个驱动程序都会有唯一的驱动对象与之对应,驱动对象在驱动加载时,被内核中的I/O管理器加载。
//驱动对象,以结构体的形式存在于内核驱动程序
typedef struct _DRIVER_OBJECT
{
CSHORT Type; //标识驱动对象的类型
CSHORT Size; //标识驱动对象的大小
PDEVICE_OBJECT DeviceObject; //驱动创建的设备对象
ULONG Flags; //指定这个设备的缓冲策略
PVOID DriverStart; //标识驱动PE映像的基地址
ULONG DriverSize; //驱动的大小
PVOID DriverSection; // 驱动节,指向LDR_DATA_TABLE_ENTRY结构
PDRIVER_EXTENSION DriverExtension; //指向Device Extension结构
UNICODE_STRING DriverName; //驱动程序的名字
PUNICODE_STRING HardwareDatabase; //HKEY_LOCAL_MACHINE/Hardware下的路径
PFAST_IO_DISPATCH FastIoDispatch; //文件驱动派遣函数
PDRIVER_INITIALIZE DriverInit; //驱动程序初始化例程
PDRIVER_STARTIO DriverStartIo; //StartIo例程入口
PDRIVER_UNLOAD DriverUnload; //驱动卸载例程
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; //派遣例程
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;
DeviceObject:每个驱动程序会有一个或多个设备对象,每个设备对象都有一个指针指向下一个驱动对象,最后一个设备对象指向空。设备对象是由程序员自己创建的,而且非操作系统完成;在驱动被卸载的时候,遍历每个设备对象,并将其删除
DeviceName:驱动程序的名字
HardwareDatabase:硬件数据库键名
FastIoDispatch:文件驱动的派遣函数
DriverStartIo:记录StartIO例程的函数地址,用于串行化操作
DriverUnload:指定驱动卸载时所用回调函数的地址
MajorFunction:函数指针数组,处理IRP派遣函数
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;
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;
DriverObject:指向驱动程序中的驱动对象(同属于一个驱动程序的设备对象指向的是统一驱动对象)
NextDevice:指向下一个设备对象
AttachedDevice:指向更高一层的设备对象
Flags:32位无符号整形
DeviceExtension:指向设备拓展对象
DeviceType:设备类型,当写虚拟设备时,类型为FILE_DEVICE_UNKNOWN
从下图可以更清楚设备对象之间的关系。
3.设备拓展
每个设备对象都会指定一个设备拓展对象,记录设备自己定义的特殊结构体。在驱动程序中,尽量避免使用全局变量,对全局变量进行同步会产生较大的代价。最好的方法是将全局变量存在设备拓展中。
拓展设备由程序员指定内容和大小,由I/O管理器创建,并保存在非分页内存中,在驱动程序的头文件中定义。
4.驱动入口函数DriverEntry
DriverEntry主要是对驱动程序进行才初始化工作,它是由系统进程所调用的。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegPath)
驱动加载的时候,系统进程System启动新的线程,调用执行体组件只能够的对象管理器,创建一个驱动对象(结构DRIVER_OBJECT),并进行初始化。
而pRegPath至存在于函数生命周期内,如果之后需要使用,则需要将其拷贝到安全的地方。
至于UNICODE_STRING结构:
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
Length:记录这个字符串用多少 字节记录,如果字符串有N个字符,那么Length为2N。
MaximumLength:记录Buffer的大小,此结构能记录的最大字节数。
Buffer:使用的是UNICODE字符串。
在驱动中可以直接打印UNICODE的信息,
KdPrint(("%S\n", pRegPath->Buffer));
其中0x000000000--0x7FFFFFFF被认为是正确的状态;而0x80000000--0xFFFFFFFF被认为是错误的状态。
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
NT_SUCCESS()是一个判断返回是否正确的宏,可以在驱动中尝试使用。
以下为常用的NTSTATUS值,除过第一个是正确数值,其他都代表不同的错误。
5.创建设备对象
在NT式驱动中,创建设备对象是由IoCreateDevice内核函数完成的。
NTSTATUS
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT *DeviceObject
);
DriverObject:每个驱动有唯一驱动对象,确可以有多个设备对象,此指针指向驱动对象。
DeviceExtensionSize:设备拓展的大小,I/O管理器会根据这个大小,在内存创建设备拓展,并与驱动对相关联。
DeviceName:设置设备对象的名字。
DeviceObject:输出参数,设备对象的地址。
IoCreateDevice(pDriverObject, sizeof(PDEVICE_EXTENSION),
&devName, FILE_DEVICE_UNKNOWN,
0, TRUE, &pDeviceObject);
pDeviceObject->Flags |= DO_BUFFERED_IO;
如果在IoCreateDevice中没有指定设备对象 的名字,I/O管理器会自动分配一个数字作为设备的设备名。如果指定了设备名,只能被内核程序识别,应用程序无法识别这个设备,如果需要应用程序能够识别,还需要通过符号链接将设备名称暴露。
在创建设备对象时,设备类型设定的为FILE_DEVICE_UNKNOWN,说明此设备为常用设备之外的设备,一般虚拟设备常使用此作为设备类型。
而此后将Flags设置为DO_BUFFER_IO缓冲区设备。
6.DriverUnload例程
NT式驱动,DriverUnload一般负责删除在DriverEntry中创建的设备对象,并且将设备对象所关联的符号链接删除。
#pragma PAGECODE
VOID FrameUnload(IN PDRIVER_OBJECT pDriverObject)
{
PDEVICE_OBJECT pNextDev;
KdPrint(("Enter FrameUnload\r\n"));
//由驱动对象得到设备对象
pNextDev = pDriverObject->DeviceObject;
while (pNextDev != NULL)
{
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pNextDev->DeviceExtension;
//删除设备对象符号链接
UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
IoDeleteSymbolicLink(&pLinkName);
pNextDev = pNextDev->NextDevice;
IoDeleteDevice(pDevExt->pDevice);
}
}
7.用WinObj观察驱动对象和设备对象
在微软的SysinternalsSuite工具包中有一个WinObj工具,可以查看驱动对象、设备对象、符号链接,以及设备对象的名字。
8.用DeviceTree观察驱动对象和设备对象
DDK自带的工具包有一个DeviceTree,比WinObj能获得更详细的信息。
驱动对象页面,基本上所需的信息都能看到。
设备对象页面
之后可能做各种实验都要用的这个工具,真是十分给力啊。