章四.驱动程序的基本结构(上)

以下内容全部来自《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));


DriverEntry返回值为NTSTATUS的数据,typedef LONG NTSTATUS;,被定义为32位长整型。

其中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能获得更详细的信息。

驱动对象页面,基本上所需的信息都能看到。



设备对象页面



之后可能做各种实验都要用的这个工具,真是十分给力啊。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值