Windows下驱动程序主要分为两种,被动加载的NT驱动和动态加载的可支持热插拔的WDM驱动。本文主要描述和记录驱动程序的入口函数和卸载例程以及驱动的基本结构。
1)Windows驱动中涉及的几个重要数据结构
(1)驱动对象结构体,即DRIVER_OBJECT结构体:每个驱动程序会有唯一的一个DRIVER_OBJECT结构体与之对应,并且该对象在驱动加载时由内核中的对象管理器创建,并将该参数传入DriverEntry函数中。DRIVER_OBJECT结构体作为驱动的一个实例被内核中的I/O管理器所加载。
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT **DeviceObject;**
ULONG Flags;
PVOID **DriverStart**;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING **DriverName**;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD **DriverUnload**;
PDRIVER_DISPATCH **MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]**;
} DRIVER_OBJECT;
DRIVER_OBJECT结构中有几个重要的字段
DeviceObject:每个驱动程序有1个或多个设备对象,此处指向第一个设备对象,每个设备对象中都有指针指向下一个设备对象吗,他们是平级的设备,最后一个设备对象指向空。
DriverName:记录驱动程序的名称,使用UNICODE字符串记录,一般为\Driver\设备名称。
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;
__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:指向下一个设备对象
AttachedDevice:指向下一个设备对象,当有更高层次的驱动附加在这层驱动上时,AttachedDevice指向更高层次的结构体。
StackSize:多层驱动中IRP从最底层传到最高层,StackSize描述层数。
(3)设备扩展
驱动程序中尽量避免使用全局函数,全局函数往往导致函数的不可重入性。扩展设备用于记录一下信息:
>设备对象的反向指针
>设备状态或者驱动信息
>中断对象指针
>控制器对象指针
2)NT驱动架构
NT驱动主要函数是DriverEntry例程,卸载例程和各个IRP的派遣函数
1.驱动加载与驱动入口DriverEntry例程
DriverEntry例程完成驱动程序的初始化工作,系统进程调用驱动 程序的DriverEntry例程,传入两个参数,一个是内核创建的DRIVER_OBJECT结构体,另一个指向设备 服务键的键命字符串的指针。在DriverEntry例程中,需要设置卸载例程和IRP派遣函数,另外还需要创建和设置设备对象。
创建设备对象函数IoCreatDevive
2.DriverUnload例程
NT驱动中,DriverUnload例程用来负责删除对象设备,并将对象设备关联的符号链接删除。,另外可能还负责一部分资源回收。
删除设备对象的函数是IoDeleteDevice函数。
3)WDM驱动架构
WDM驱动是分层的,至少需要两个设备对象才能完成。其中,一个是物理设备对象PDO,另一个是功能设备对象FDO。期关系是附加关系。当PC插入某个设备时,PDO会由总线驱动自动创建,PDO不能单独完成设备操作,需要FDO配合,系统会提示检测到新设备,并要求安装驱动。此时驱动程序负责创建FDO,并附加到PDO之上。
当一个FDO附加在PDO之上时,PDO的AttachedDevice会记录FDO的位置。PDO称为底层驱动,而FDO被称为高层驱动,这里底层指更接近物理底层。实际驱动还会包含过滤驱动,就会更加复杂。每个设备对象中,包含一个StacKSize子域,用来记录到达需要几层才能到达最底层的物理设备。需要注意的是,过滤驱动是可以嵌套的,但不是必须的。
1.WDM入口程序DriverEntry
WDF驱动的入口也是DriverEntry,但不同的是它的初始化程序被分散到其他例程中去,最重要的一点就是设备对象的创建,创建设备对象任务被分配到AddDevive中。与NT驱动相比,WDM驱动
>增加了AddDevive函数,操作系统加载PDO后调用AddDevice函数,AddDevice函数负责创建FDO,并附加在PDO上。
>必须加入IRP_MJ_PNP的派遣回调函数,该函数完成计算机即插即用的处理
2.AddDevice函数
首先需要在DriverEntry例程中设置AddDevice例程的的函数地址,设置方式是驱动对象中存在DriverExtension域,其中包含AddDevice子域,将该子域指向AddDevice函数地址。AddDevice函数需要两个输入参数,驱动对象DriverObject和设备对象PDO,PDO是底层总线驱动创建的PDO设备对象,传入该参数的目的是将FDO依附在PDO上。AddDevice中操作可以分为以下步骤
1)在AddDevice中通过IoCreatDevice函数创建设备对象,该设备对象即FDO,功能驱动设备对象。
2)创建完FDO后,需要将FDO的地址保存,地址保持在设备扩展中。
3)驱动程序将FDO附加在PDO上,附加动作依靠IoAttachDeviceToDeviceStack函数完成。
4)设置FDO的Flags子域,保证设备初始化完毕,这一步是必须的。
PDEVICE_OBJECT
IoAttachDeviceToDeviceStack(
IN PDEVICE_OBJECT SourceDevice
OUT PDEVICE_OBJECT TargetDevice
);
SourceDevice:通过操作完成FDO附加在PDO上,因此这里填写PDO的地址
TargetDevice:被附加的设备,这里是PDO对象
返回值:如果中间有过滤驱动,返回过滤驱动否则返回PDO
注意:当FDO附加到PDO上后,PDO会通过AttachedDevice子域得知它的上层设备是FDO或者过滤驱动,但是上层设备需要通过扩展设备记录下层PDO设备才能完成对下层设备的寻址
AddDevice函数代码分析
NTSTATUS AddDevice(IN PDEVICE_OBJECT DriverObject,IN PDEVICE_OBJECT PhysicalDeviceObject)
{
NTSTATUS status;
PDEVICE_OBJECT fdo;
UNICODE _STRING dev_name;
//初始化字符串
//创建设备对象
status = IoCreatDevice(DriverObject,
sizeof(PDEVICE_EXTENSION),
&(UNICODE_STRING)devname,
FILE_DEVICE_UNKNOW,
0,
FALSE,
&fdo);
//得到设备扩展
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo-> DeviceExtension;
pxd->fdo = fdo;
//将FDO附加在PDO上
pdx->NextStartDevice = IoAttrachDeviceToDeviceStack(fdo,PhysicalDeviceObject);
//设置标志
fdo-> Flags = ...
}
3.DriverUnload
在NT驱动中,DriverUnload例程负责删除设备和取消符号链接,在WDM驱动中,这部分工作由IRP_MN_REMOVE_DEVICE_IRP处理函数负责。DriverUnload仅需要释放内存就可以。
4.对IRP_MN_REMOVE_DEVICE_IRP处理
在WDM的驱动程序中,对设备的卸载在IRP_MN_REMOVE_DEVICE中卸载。类似NT驱动的DriverUnload卸载,在其中需要完成删除设备、取消连接外,此函数中还需将FDO从PDO的堆栈中摘除下来,使用函数IoDetachDevice完成。此时FDO从设备链上删除。但是PDO有操作系统删除。