[Windows驱动开发](三)基础知识——驱动例程
一、NT式驱动的基本例程
1. 驱动入口函数——DriverEntry
- // 驱动程序的一般性定义
- NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath);
- //
DriverEntry的主要工作是对驱动程序进行初始化。它由系统进程System调用的。驱动被加载的时候会创建一个驱动对象,查询此驱动程序对应的注册表项。
DriverEntry被调用的时候会传进两个参数。他们分别是刚才创建的驱动对象的指针和指向设备服务键的键名字符串指针。这个字符串的内容一般是\RESGISTRY\MACHINE\SYSTEM\ControlSet\Services\[服务名]。在驱动程序中,字符串一般以UNICODE形式存在:
注:设备服务键的键名有时候需要保存下来,当函数返回的时候此值可能会被清空。DriverEntry等内核函数的返回值一般是NTSTATUS类型的。使用宏NT_SUCCESS(NTSTATUS status)可检测返回状态是否成功。
在DriverEntry中,一般需要做一下几件事情:
a. 设置驱动卸载例程
b. IRP的派发函数
c. 创建设备对象
其中,驱动卸载例程与IRP派发函数都是对驱动对象设置的。设备对象中有一个MajorFunction数字,用来存放哥哥IRP派发函数的函数指针。
在NT式驱动中,创建设备对象可以调用IoCreateDevice:
- // IoCreateDevice在WDK中的定义
- NTKERNELAPI
- 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
- __drv_out_deref(
- __drv_allocatesMem(Mem)
- __drv_when((((inFunctionClass$("DRIVER_INITIALIZE"))
- ||(inFunctionClass$("DRIVER_DISPATCH")))),
- __drv_aliasesMem)
- __on_failure(__null))
- PDEVICE_OBJECT *DeviceObject
- );
- //
DriverObject:当前驱动对象指针。
DeviceExtensionSize:设备扩展的大小。IO管理器会根据这个大小在内存中创建设备扩展,并与驱动对象关联。
DeviceName:设备对象的名称。
DeviceCharacteristics:设备对象的特征。
Exclusive:设备对象是否为内核模式下使用,一般固定为TRUE。
DeviceObject:创建好的设备对象指针。
注:设备名称字符串必须是\Device\[设备名]的形式。
让用户态的应用程序能识别设备一般有两种方法:
a. 符号链接b. 设备接口(在NT式驱动很少使用)符号链接可以理解为设备对象在用户态的名称。即:设备名称在内核态使用,符号链接在用户态使用。
创建符号链接可以使用IoCreateSymbolicLink:
- // IoCreateSymbolicLink在WDK中的定义
- NTKERNELAPI
- NTSTATUS
- IoCreateSymbolicLink(
- __in PUNICODE_STRING SymbolicLinkName,
- __in PUNICODE_STRING DeviceName
- );
- //
SymbolicLinkName:符号链接名。DeviceName:设备对象名。
注:在内核态,符号链接是以“\??\”或者“\DosDevices\”开头的。而在用户模式下则是以“\\.\”开头的。
设备扩展在被使用时可使用如下的代码:
- // Code:使用设备扩展
- PDEVICE_EXTENSION pDeviceExtension = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;
- //
2. 驱动卸载例程——DriverUnload
此例程在驱动卸载的时候被调用。在NT式驱动里面负责删除在DriverEntry中创建的设备对象,并且删除与其相关联的符号链接。同时还负责某些资源的回收工作。
删除设备对象的函数是IoDeleteDevice
- // IoDeleteDevice在WDK中的定义
- NTKERNELAPI
- VOID
- IoDeleteDevice(
- __in __drv_mustHold(Memory) __drv_freesMem(Mem) PDEVICE_OBJECT DeviceObject
- );
- //
DeviceObject:要被删除的设备对象指针。
删除符号链接的函数是IoDeleteSymbolicLink
- // IoDeleteSymbolicLink在WDK中的定义
- NTKERNELAPI
- NTSTATUS
- IoDeleteSymbolicLink(
- __in PUNICODE_STRING SymbolicLinkName
- );
- //
SymbolicLinkName:已经被注册的、待删除的符号链接。
根据驱动对象可以遍历所有由该驱动创建的设备对象。通过驱动对象的DeviceObject域可以找到驱动对象的第一个设备,然后根据设备对象的NextDevice域,就可以找到设备链表中其他的设备对象。
二、WDM式驱动的基本例程
//在WDM模型中,一个设备的操作至少需要两个设备对象共同来完成。一个是物理设备对象(Physical Device Object,简称PDO),一个是功能设备对象(Function Device Object,简称FDO)。当计算机插入某个设备的时候,总线驱动会自动创建PDO。PDO不能单独操作设备,必须与FDO一起使用。当Windows提示要安装驱动的时候,实际上安装的是WDM驱动程序,负责创建FDO,并附加到PDO上。
当一个FDO附加在PDO的时候,PDO的AttachedDevice会记录FDO的位置。PDO是底层驱动(下层驱动),FDO是高层驱动(上层驱动)。
在FDO与PDO之间还会存在过滤驱动。在FDO上面的叫做上层过滤驱动,在FDO下层的叫做下层过滤驱动。一个WDM驱动可以有很多个上层过滤驱动和下层过滤驱动。设备对象的StackSize子域表明该设备对象到最下层的物理设备中间还存在的设备对象数。
1. WDM驱动的入口函数——DriverEntry
和NT式驱动一样,WDM驱动的入口程序也是DriverEntry。但是创建设备对象的功能并不在DriverEntry中执行,而是交给了新的例程——AddDevice;同时增加了对IRP_MJ_PNP处理的派发函数。
AddDevice例程是WDM特有的,在DriverEntry中需要设置AddDevice例程的函数地址。设置的方式是在驱动对象的DriverExtension子域的AddDevice子域保存AddDevice实际例程的函数地址。AddDevice例程的名字可以随意命名。
- // AddDevice函数声明
- NTSTAUS MyAddDevice(IN PDRIVER_OBJECT pDriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject)
- // 设置AddDevice实际例程的地址
- pDriverObject->DriverExtension-> AddDevice = MyAddDevice;
- //
2. 驱动卸载例程——DriverUnload()
在WDM驱动中,实际的卸载工作被IRP_MN_REMOVE_DEVICE对应的派发函数处理了,这里的DriverUnload主要处理在DriverEntry中申请的内存。a. IRP_MN_REMOVE_DEVICE处理驱动横须内部是由IRP驱动的,IRP_MN_REMOVE_DEVICE这个IRP是当设备需要被卸载的时候,由即插即用管理器创建,并发送到驱动程序中的。IRP一般由两个号码指定该IRP的具体意义,一个是主IRP号(Major IRP),一个是辅IRP号(Minor IRP)。当设备需要被卸载的时候,会先后发出多个IRP_MJ_PNP。这些IRP的辅IRP号会有所不同。其中之一的IRP_MN_REMOVE_DEVICE。在WDM驱动程序中,对设备的卸载一般是在对IRP_MN_REMOVE_DEVICE的出口函数中进行卸载。其除了需要删除学部,取消符号链接外,还需要将FDO从PDO的堆栈中移除。需要调用IoDetachDevice:此时,FDO从设备链上删除,但是PDO还在。PDO由操作系统负责删除。
- // IoDetachDevice在WDK中的定义:
- NTKERNELAPI
- VOID
- IoDetachDevice(
- __inout PDEVICE_OBJECT TargetDevice // 下层堆栈上的设备对象
- );
- //
AddDevice基本步骤:1. AddDevice通过IoCreateDevice函数创建FDO,创建FDO的符号链接2. 在驱动设备扩展保存刚才创建的FDO的地址。3. 调用IoAttachDeviceToDeviceStack()将FDO附加到PDO上。
- // IoAttachDeviceToDeviceStack在WDK中的定义
- NTKERNELAPI
- PDEVICE_OBJECT
- IoAttachDeviceToDeviceStack(
- __in __drv_mustHold(Memory) __drv_when(return!=0, __drv_aliasesMem)
- PDEVICE_OBJECT SourceDevice,
- __in PDEVICE_OBJECT TargetDevice
- );
- //
SourceDevice:FDO附加在PDO上时,这个参数代表FDO。TargetDevice:被附加的设备。如果在FDO与PDO之间存在过滤驱动,则FDO实际上是附加在过滤驱动上的,过滤驱动则附加在PDO上。返回值:返回SourceDevice的下层设备。
- // 根据上面的一个设备扩展定义
- typedef struct _DEVICE_EXTENSION
- {
- PDEVICE_OBJECT pFunctionDeviceObject; // 设备对象(FDO)
- UNICODE_STRING ustrDeviceName; // 设备名称
- UNICODE_STRING ustrSymbolicLinkName; // 符号链接名
- PDEVICE_OBJECT pNextStackDevice; // 下一个设备对象(FDO)的地址
- } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
- //
4. 设置设备扩展pFunctionDeviceObject->Flags。DO_BUFFERED_IO:缓冲内存设备~DO_DEVICE_INITIALIZING:这个必须设置,表示完成Flags的初始化。