随学随记,暂时未经编程验证 Written by HOOK_TTG(Jamie Jiang)
6、 Device Extensions设备扩展
对于大多数中间层或者最底层的驱动程序,设备扩展是设备对象非常重要的数据结构。它的内部结构是由驱动程序定义的,通常用于:
l 维护设备状态信息。
l 为任何内核定义的对象或者其他系统资源提供存储空间,比如自旋锁,驱动程序需要用到这些信息。
l 容纳任何驱动程序必须在系统空间常驻的用于执行其I/O操作的数据。
由于大多数总线、功能和过滤驱动程序(底层和中间层驱动程序)运行在一个任意的线程上下文中,所以设备扩展是每个驱动程序主要的存储信息的地方,用于维护驱动程序需要的设备状态和所有其他设备特定数据。例如,任何执行了CustomTimerDpc或者CustomDpc例程的驱动程序,通常需要为其请求到的内核定义的定时器和DPC对象在设备扩展中提供一个存储区。
每个有ISR的驱动程序必须为一个指针提供储存区,这个指针指向一套内核定义的中断对象,大多数设备驱动对象在设备扩展中存储这个指针。每个驱动程序在其创建一个设备对象的时候就确定了设备扩展所需的内存大小,而且每个设备定义其设备扩展的内容和结构。
I/O管理器的IoCreateDevice和IoCreateDeviceSecure例程从非分页内存池中为设备对象分配内存。
每个标准驱动程序例程会接收到一个IRP,还会接收到一个指向设备对象的指针,这个设备对象就代表被请求I/O操作的目标设备。这些驱动程序例程可以通过这个指针访问相应的设备扩展。通常,DeviceObject指针也是一个底层驱动程序ISR的输入参数。
下图显示一个底层驱动程序设备对象的设备扩展中典型的一组驱动程序定义数据。 高层的驱动程序不需要为下列中断对象指针提供存储区,IoConnectInterrupt返回的指针、传递给KeSynchronizeExecution和IoDisconnectInterrupt的指针。不过,如果驱动程序有一个CustomTimerDpc例程,那么高层驱动程序需要为定时器和DPC对象提供存储区,
除了要为一个中断对象指针提供存储区外,如果底层设备驱动程序的ISR在不同的向量上处理两个或更多设备中断,或者其有多于一个的ISR,那么这个驱动程序必须为中断自旋锁提供存储区。更多关于注册一个ISR的信息,参看MSDN中的 Registering an ISR章节。
通常,驱动程序在他们的设备扩展中存储其设备对象的指针,就像上图所示那样。驱动程序还应该保存一个设备扩展中设备的资源列表备份。
高层驱动程序通常还会在其设备扩展中保存一个指向低一层驱动程序设备对象的指针。高层驱动程序在一个IRP中建立低一层驱动程序的I/O栈后,还必须向IoCallDriver例程传递一个指向低一层驱动程序设备对象的指针。参看MSDN中的 Handling IRPs章节。
还需要注意,为低一层驱动程序分配IRPs的高层驱动程序还需要指定这些新的IRPs应该有多少栈位置。尤其是,如果一个较高层驱动程序调用IoMakeAssociateIrp、IoAllocateIrp或者IoInitializeIrp,为了为这些支持例程提供正确的StackSize参数,那么它必须访问紧邻的低一层驱动程序的目标设备对象并读取它的StackSize值。
当一个较高层驱动程序能通过IoAttackDeviceToDeviceStack返回的指针从紧邻的低一层驱动程序的设备对象中读取数据,那么这个驱动程序必须遵循这些执行指导方针:
l 永远不要尝试向低一层驱动程序的设备对象写入数据。
这个指导方针的唯一例外就是文件系统,文件系统会设置和清除低一层可移动介质驱动器的设备对象的Flags中的DO_VERIFY_VOLUME掩码位。
l 永远不要尝试访问低一层驱动程序的设备扩展,原因如下:
¡ 在两个驱动程序之间,还没有一个安全的方法来同步访问一个单一设备扩展。
¡ 一对的驱动程序不能单独升级其中一个,比如实现一个后门通讯方案,在不改变现有驱动程序源程序的情况下,在他们之间不能插入一个中间层驱动程序,而且也不能被重新编译,也不能容易的从一个Windows平台移到下一个。
为了保持底层驱动程序从一个Windows平台或者版本到下一个版本之间的互通性,高层驱动程序必须重新使用给它们的IRPs或者必须创建一个新的IRPs,而且它们还必须使用IoCallDriver向底层驱动程序传递请求。
7、 设备对象的属性
每个设备对象都有某些属性,用于描述设备和设备对象如何与系统相配合。
设备对象属性包括:
l 设备类型。指定设备的硬件类型。
l 设备特性。指定设备的补充信息。
l 独占访问。指定设备对象是否代表一个独占设备。如果设备是独占的,那么在某一时刻设备只能打开一个设备句柄。(如果底层设备支持重叠I/O,那么同一进程的多个线程可以通过这一个句柄发送操作请求。)
l 安全描述符。设备对象有一个控制设备访问权的安全描述符。
对于其中的每一个属性,在设备对象被创建的时候都会被设置一个默认值。设备对象的属性值也可以放在注册表中。
1) 指定设备类型
每个设备对象都有一个设备类型,它保存在DEVICE_OBJECT结构的DeviceType成员中。这个设备类型为驱动程序描述了底层硬件的类型。
每个创建设备对象的内核模式驱动程序必须在调用IoCreateDevice时指定一个合适的设备类型。IoCreateDevice例程使用提供的设备类型来初始化DEVICE_OBJECT结构中的DeviceType成员。
系统定义了下列设备类型值,列表如下,按字母次序排列:
这些常量在ntddk.h和wdm.h中定义。检查这些文件看看是否定义了其他设备类型。
这个FILE_DEVICE_DISK类型包括了软盘和固定磁盘设备,还有磁盘分区。
中间层驱动程序通常要指定设备类型。例如,系统提供的容错磁盘驱动程序,ftdisk,创建FILE_DEVICE_DISK类型的设备对象;它不为其管理的景象集、带区集和卷集定义新的设备类型。
FILE_DEVICE_XXX在0到32767之间的值是为微软专用的。所有的驱动程序编写者必须为属于系统定义的设备类型的设备使用这些系统定义的常量。
如果一个硬件的类型不匹配任何定义的类型,那么可以指定一个FILE_DEVICE_UNKNOW值,也可以使用32768至65535之间的一个值。
2) 指定设备特性
每个设备对象可以有一个或者多个设备特性。设备特性保存在设备对象的DEVICE_OBJECT结构的Characteristics成员中。
大多数驱动程序仅仅指定FILE_DEVICE_SECURE_OPEN特性。这就确保了拥有同样安全设定的任何打开请求可以被接受到设备的名字空间中。更多信息,参看MSDN中 Controlling Device Namespace Access章节。
FILE_AUTOGENERATED_DEVICE_NAME特性仅被PDOs使用。FILE_FLOPPY_DISKETTE、FILE_REMOVEABLE_MEDIA和FILE_WRITE_ONCE_MEDIA特性用于特定存储设备。更多可能的设备特性标记的描述,参看DEVICE_OBJECT的Characteristics成员。
某些设备特性,比如FILE_AUTOGENERATED_DEVICE_NAME,仅用于个别设备对象。当驱动程序通过调用IoCreateDevice或者IoCreateDeviceSecure来创建设备对象的时候,它们可以为这些个别的设备对象指定一个设备特性设置。
下面的特性适用于整个设备栈:
FILE_DEVICE_SECURE_OPEN
FILE_FLOPPY_DISKETTE
FILE_READ_ONLY_DEVICE
FILE_REMOVABLE_MEDIA
FILE_WRITE_ONCE_MEDIA
驱动程序可以通过调用IoCreateDevice或者IoCreateDeviceSecure来设置用于整个设备栈的设备特性。另外,无论设备还是设备的安装类,用于整个设备栈的设备特性可以设置在注册表中。
PnP管理器为设备特性确定注册表设定值列举如下:
l 如果设定值是指定用于个别设备的,那么PnP管理器使用那个值。
l 另外,如果设定值是指定用于设备安装类的,那么PnP管理器使用那个值。
l 否则,PnP管理器使用一个零值作为注册表设定值。
如果在注册中的一个设备特性被设置成用于整个设备栈,或者被设置成用于栈中的任一FDO或者过滤DO,那么PnP管理器为栈中的每个设备对象设置一个特性。(如果设备具有原始模式功能,那就没有FDO,那么PnP管理器会使用PDO替代。)
3) 指定设备对象是否独占访问
如果一个设备启用了独占访问,那么在某一时刻只能打开一个设备句柄。为了让I/O管理器强制独占访问设备,必须为设备栈中的命名设备对象设置独占属性。
由于一个WDM设备栈有一个PDO还一个FDO,那么其独占属性只能使用INF文件来设置,通过使用一个 INF AddReg 指令。PDO是栈中的一个命名设备,但是由总线驱动程序代表功能驱动程序创建这个PDO。指示总线驱动程序为PDO设置独占标记的唯一方法就是通过类或者设备INF文件。(调用IoCreateDevice例程创建FDO;位FDO设置独占标记是没有效果的。)
设备对象不能入栈的驱动程序,比如非WDM驱动程序和以原始模式操作的设备,可以使用IoCreateDeviceSecure例程为它们的命名设备对象设置独占属性。
I/O管理器在命名设备对象的每个名字的基础上执行独占性,不管后缀名。例如,假设一个设备对象有一个名字“/Device/DeviceName”,那么I/O管理器为一个打开“/Device/DeviceName/Filename1”的请求执行独占性,通过“/Device/DeviceName/FileName2”方式。如果在设备栈中的两个对象都命名了(不推荐),I/O管理器为每个对象允许一个句柄被打开。在这样的情形下,驱动程序必须在它们DispatchCreate例程中执行它们自己的独占性。I/O管理器也不为另一个文件举兵的打开强制执行独占性。
4) 将设备对象的属性放在注册表中
设备对象的属性可以设置在注册表中,列举如下:
l 对于WDM驱动程序,可以为每个设备模型的属性设置,或者为整个设备安装类设置。(更多关于设备安装类的信息,参看MSDN中的Device Setup Classes章节。)
l 对于非WDM驱动程序,可以为一个命名设备对象的设备安装类的属性设置。驱动程序在使用IoCreateDeviceSecure创建设备对象的时候设置设备安装类。更多关于如何指定一个设备安装类,参看IoCreateDeviceSecure。
任何注册表中的设置都会覆盖掉驱动程序创建设备对象时提供的属性。
在设备安装过程中使用的注册设定值由一个INF文件指定,或者也可以在安装之后,通过调用device installation functions(设备安装函数)来指定。
在安装过程中设置设备对象注册属性
为了在安装过程中设置设备对象属性,你必须提供一个描述这些属性的INF文件。你可以为设备或设备安装类指定设备对象属性。
描述如下:
l 对于个别设备,为设备将属性设置在add-registry-section。INF AddReg指令在设备的DDInstall中。HW节指定设备的add-registry-section。
l 对于一个设备安装类,设备安装类的属性被设置在add-registry-section。INF 在ClassInstall32节中的AddReg指令指定了类的类描述节add-rehistry-section。
在add-registry-section中,下列关键字可以用来为个别设备对象属性。
Keyword | Device object property |
DeviceType | Device type |
DeviceCharacteristics | Device characteristics |
Exclusive | Exclusive |
Security | Security descriptor |
更多使用这些关键字的信息,参看MSDN中 INF AddReg Directive。
这些设置可以由用户模式组件通过使用设备安装函数来设置。
在安装后设置设备对象注册属性
用户模式程序可以使用设备安装函数来获取和设置作为驱动程序的设备对象属性的注册设定值。通常这些函数由安装软件来使用,但是它们可以被任何用户模式程序使用。(这个程序必须由具有管理员访问权限的用户执行。)
SetupDiGetDeviceRegistryProperty和SetupDiSetDeviceRegistryProperty函数用来获取和设置每个指定属性的注册键值。Property参数指定了要获取或设置的属性。PropertyBuffer指向属性的目标缓冲区(在获取属性的时候)或者源缓冲区(在设置属性的时候)。
Property参数值与实际属性之间的对应关系如下:
Value for Property parameter | Device object property |
SPDRP_CHARACTERISTICS | Device characteristics |
SPDRP_DEVTYPE | Device type |
SPDRP_EXCLUSIVE | Exclusive |
SPDRP_SECURITY Security | Security descriptor as a SECURITY_DESCRIPTOR structure |
SPDRP_SECURITY_SDS | Security descriptor as an SDDL string |
需要注意的是,提供了两个不同的途径来获取或者设置安全描述符。你可以指定SPDRP_SECURITY作为SECURITY_DESCRIPTOR结构的安全描述符,也可以指定SPDRP_SECURITY_SDS作为一个SDDL字符串安全描述符。
对于Windows XP或者更近版本的操作系统,程序还可以获取和设置一个设备安装类的属性值。使用SetupDiGetClassRegistryProperty和SetupDiSetClassRegistryProperty函数来获取和设置一个设备安装类的属性值。
更多关于使用SetupDiXxx函数的信息,参看MSDN中的Using Device Installation Functions。