随学随记,暂时未经编程验证 Written by HOOK_TTG(Jamie Jiang)
3、 创建设备对象
一个整个的驱动程序必须为其所处理I/O请求的每个物理、逻辑或者虚拟设备都创建一个设备对象。没有创建为设备创建设备对象的驱动程序将不能接收来自设备的任何IRPs。
在一些技术领域,与类型驱动程序或者端口驱动程序关联的微驱动程序,不必非要创建它自己的设备对象。作为替代,类型或者端口驱动程序创建设备对象,并接收设备所有的IRPs。类型或者端口驱动程序使用驱动程序特定的方法来将I/O请求传递给微驱动程序。参看你的特定技术领域文档,确定微软是否提供了为你的驱动程序创建设备对象的类型或者端口驱动程序。
驱动程序即可以调用IoCreateDevice,也可以调用IoCreateDeviceSecure例程来创建他们的设备对象。
在驱动程序创建一个设备对象的时候,它需要为IoCreateDevice或者IoCreateDeviceSecure例程提供下面的信息:
l 设备的device extension(设备扩展)所需内存大小。Device extension是一个由系统分配的存储空间,驱动程序可以用来存储设备特定的信息。
l 一个系统定义的常量DeviceType,指示设备对象的设备类型。
l 一个或者多个系统定义的常量的或运算,可以指示设备的设备特性。
l 一个名为Exclusive的布尔值,指示设备对象Flags中一个位是否需要设置成DO_EXCLUSIVE,这个标记说明这个驱动程序服务的是一个独占设备,就像一个视频、串行、并行或者声卡设备。WDM驱动程序必须将设置Exclusive为FALSE。
l 一个指向驱动程序的驱动对象。WDM功能或者过滤驱动程序从它的AddDevice的一个参数接收到指向它驱动对象的指针。
l 一个可选的设备名称字符串,是以NULL结尾的Unicode字符串。WDM驱动程序,除了总线驱动程序,不需要提供了设备名称;这样做可以避开PnP管理器的安全特征。
如果IoCreateDevice或者IoCreateDeviceSecure调用成功,那么I/O管理器将为设备对象及其所有的数据结构分配存储空间,包括被以0x00初始化的device extension。
为WDM功能和过滤驱动程序创建设备对象
WDM驱动程序,除了总线驱动程序,调用IoCreateDevice来创建设备对象。大多数WDM驱动程序在它们的AddDevice例程中创建设备对象。一些驱动程序是通过一个调度例程来调用IoCreateDevice,就像磁盘驱动程序必须对驱动程序布局IOCTLs做出回应。
除非在WDK文档中的设备特定类型章节中明确指出,否则驱动程序都需要在AddDevice例程中创建它的设备对象。
为WDM总线驱动程序创建设备对象
当WDM总线驱动程序在响应IRP_MN_QUERY_DEVICE_RELATIONS请求时枚举到一个新设备,并且如果这个设备的关联类型是BusRelations,那么就会为之创建一个PDO。
下面的条件将决定一个总线驱动程序是通过调用IoCreateDevice还是IoCreateDeviceSecure来创建设备对象:
l 如果一个设备能以原始模式使用,那么就必须调用IoCreateDeviceSecure。
l 如果设备不支持原始模式,那么总线驱动程序即可以使用IoCreateDevice也可以使用IoCreateDeviceSecure。当默认的系统安全性对总线上的设备足够用的话,那么就使用IoCreateDevice;当要指定严格的安全性时,就要使用IoCreateDeviceSecure。
为非WDM驱动程序创建设备对象
一个非WDM驱动程序使用IoCreateDevice来创建一个无名设备对象,而使用IoCreateDeviceSecure来创建一个有名的设备对象。需要注意的是,非WDM驱动程序的无名设备对象不能从用户模式访问,所以此类驱动程序通常都会创建至少一个有名字的设备对象。
4、 初始化设备对象
IoCreateDevice返回后,会给调用者返回一个设备对象的指针,这个设备对象还包含了一个设备扩展的指针,而且驱动程序还必须为代表物理、逻辑或者虚拟设备的设备对象建立某些数据域。
IoCreateDevice会设置新建创建的设备对象的StackSize域的值为1。最低级的驱动程序可以忽略这个域。当一个较高层的驱动程序调用IoAttrachDeviceToDeviceStack例程将自己关联到紧邻的下一层驱动程序时,这个例程会自动设置下一层设备对象中的StackSize域的值为上一个设备对象的值加上1。对于某些类型的设备,如果设备描述文档中指定,较高层的驱动程序可能需要给StackSize域设置一个更大的值。设置栈大小是为了确保发送到更高层驱动程序的IRPs能包含一个驱动程序特定的I/O栈存储单元,并为在驱动程序链中的所有低一层的驱动程序附加上正确的I/O栈号码。
IoCreateDevice会设置新近创建的设备对象的AlignmentRequirement域的值为处理器的数据高速缓存线数目减1,这样是为了确保直接用于I/O的缓冲器能正确对齐。IoCreateDevice返回后,最底层的物理设备驱动程序必须做以下事情:
1. 将设备的排列要求的值减1。
2. 将第一步得到值与设备对象的AlignmentRequirement域的当前值作比较。
3. 如果设备的排列要求值较大,那么就看就将其AlignmentRequirement域的值设置为第一步得到的值。否则,保留由IoCreateDevice例程为AlignmentRequirement域设置的值。
在任何较高层驱动程序通过调用IoGetDeviceObjectPointer函数将自己链接到其他驱动程序上之后,较高层驱动程序必须将新近创建的设备对象的AlignmentRequirement域赋值给紧邻的低一层驱动程序的设备对象的这个域。一般的规则是,较高层驱动程序不应该改变这个值。如果一个较高层驱动程序调用IoAttachDevice或者IoAttachDeviceToDeviceStack,这些例程会自动的将设备对象的AlignmentRequirement域的值赋给紧邻的低一层驱动程序的设备对象。
IoGetDeviceObjectPointer函数会返回两个指针,一个是低一层驱动程序的设备对象指针,一个是与之相关联的文件对象。只有FSD(或者另一个最高层驱动程序)可以使用这个返回的文件对象指针。一个调用IoGetDeviceObjectPointer函数的中间层驱动程序,应该保存这个文件对象指针,一边在驱动程序卸载的时候可以使用这个指针通过ObDereferenceObject函数来解除参照。
在一个FSD装载包含代表一个低一层驱动程序设备对象的文件对象的卷之后,中间层驱动程序就不能通过调用IoAttachDevice或者IoAttachDeviceToDeviceStack函数来将自己链接到系统或者低一层的驱动程序上。另外,当发生装载时,FSD可以根据基础的卷硬件几何结构来设置设备对象的SectorSize成员的值。更多信息参看 DEVICE_OBJECT 结构。
在每个驱动程序的创建过程中,中间层或者最底层驱动程序也可以通过DO_DIRECT_IO或者DO_BUFFERED_IO来设置设备对象的Flags域。如果驱动程序编写者决定做更多增强驱动程序性能的工作,逻辑或者虚拟设备的最高层驱动程序也可以不为缓冲的或者直接I/O设置Flags标记。
是否使用DO_DIRECT_IO或者DO_BUFFERED_IO来设置设备对象的Flags域,取决于I/O管理器随后传递驱动程序的对于所有数据传输请求过程中用户缓存器的访问权限。
驱动程序可以设置设备对象的任何其他设备相关的值。例如,对于可移动介质设备的非WDM驱动程序,如果在处理I/O操作中检测到介质中的一个变化(或者怀疑介质中有变化),那么它们Flags成员需要或上DO_VERIFY_VOLUME(也就是将Flags原来的值与DO_VERIFY_VOLUME执行或操作得到的结果作为其新的Flags值)。(参看MSDN中的 Supporting Removable Media章节。)需要大电压启动的设备的驱动程序必须将其Flags成员或上DO_POWER_INRUSH,不再系统分页调度路线上的设备的驱动程序必须将Flags程序或上DO_POWER_PAGABLE。功能和过滤驱动程序必须清除DO_DEVICE_INITIALIZING标志。
设备对象初始化完毕后,驱动程序还可以初始化任何内核定义的对象和设备扩展中的其他系统定义的数据结构。驱动程序如何在准确的时机执行这些任务,取决于它的设备、对象类型和数据的性质。一般来说,任何对象或者数据结构都可以在PnP启动和停止请求中持续存在,而且可以在AddDevice例程中初始化。有些信息需要在驱动程序处理IRP_MN_START_DEVICE请求的时候初始化,这些信息是由PnP的IRP_MN_START_DEVICE请求提供的资源信息或者设备启动或者重启动的变化信息。更多有关AddDevice例程的信息,参看MSDN中Writeing an AddDevice Routine章节。
5、 命名的设备对象
一个设备对象,就像所有的对象管理器对象,可以起个名字,也可以不要名字。当一个用户模式应用程序产生一个I/O请求,它是通过名字来指定一个操作对象的。对象管理器解析这个名字来确定这个I/O请求的接收方。
驱动程序在调用IoCreateDevice或者IoCreateDeviceSecure例程创建设备对象时,可以为设备对象指定一个名字。
一个被命名的设备对象还可以有MS-DOS设备名,这个名字是一个符号链接,由IoCreateSymbolicLink或者IoCreateUnprotectedSymbolicLink例程创建。WDM驱动程序一般不需要MS-DOS设备名。
1、 NT Device Names(NT式设备名)
一个被命名的设备对象有一个/Device/DeviceName格式的名字。这个就是所谓设备对象的NT式设备名。
WDM驱动程序的设备名
WDM驱动程序不直接给他们的设备命名。相反,为了确保驱动程序间的设备对象名字不冲突,系统规定了一个统一的命名方案。WDM驱动程序命名方案如下:
l 设备的PDO要被命名。总线驱动程序为它枚举到的设备请求一个命名的PDOs。总线驱动程序在创建设备对象的时候会为设备指定FILE_AUTOGENERATED_DEVICE_NAME设备特性。随后系统会自动生成一个设备名称。
l FDOs和过滤Dos是无命名的。功能和过滤驱动程序在创建设备对象的时候不会请求一个名字。
任何对已命名设备对象的I/O请求都会自动转到设备对象栈中最顶部的对象。因此,只有PDO需要名字。用户模式应用程序不能通过名字访问WDM设备对象;相反,应用程序通过它的设备接口来访问设备对象。更多信息,参看MSDN中的Device Interface Classes章节。
驱动程序编写者只需要为设备栈中的一个对象命名即可。操作系统基于已命名对象的安全设置。有两个不同的对象被命名,而且具有不同的安全描述符,那么发送给具有弱完全描述符的I/O请求,具有强安全描述符的设备对象也能收到。
非WDM驱动程序的设备名
一个非WDM驱动程序必须为任何已命名的设备对象明确的指定一个名字。在用于接收I/O请求的/Device对象目录中,驱动程序必须创建至少一个命名的设备对象。驱动程序在创建设备对象时,会将指定的设备名称作为参数传递给IoCreateDeviceSecure例程。
2、 MS-DOS Device Names(MS-DOS式设备名)
MS-DOS设备对象介绍
由非WDM驱动程序创建的命名的设备对象,通常由一个MS-DOS设备名称。MS-DOS设备名称是一个符号链接,在对象管理器中是一个/DosDevice/DosDeviceName格式的名字。
具有MS-DOS设备名设备的一个例子就是串行端口,COM1。它有一个/DosDevices/COM1这样的MS-DOS设备名。同样的,C驱动器有一个/DosDevices/C:名字。
WDM驱动程序通常不为它们的设备提供MS-DOS设备名。WDM驱动程序使用IoRegisterDeviceInterface例程注册一个设备接口。设备接口由它们的能力来指定设备,而不是由特定的命名约定。更多信息,参看MSDN中的 Device Interface Classes章节。
只有当设备被要求有一个配合用户模式程序工作的特有的易读易记的MS-DOS设备名时,驱动程序才会被要求提供一个MS-DOS设备名。
驱动程序为设备提供一个MS-DOS设备名,通过使用IoCreateSymbolicLink例程创建一个指向设备的符号链接。
例如,下面的代码演示创建一个格式从/DosDevices/DosDeviceName到/Device/DeviceName的符号链接。
需要注意的是,系统提供了/DosDevices目录的多重版本。驱动程序可以使用你想要的版本来创建符号链接。
为了从用户模式访问DosDevices命名空间,要是用//./后跟要打开的文件名的格式。在用户模式下,通过调用CreateFile()可以打开一个相应的设备。
用户模式应用程序可以通过使用用户模式的DefineDosDevice例程来创建一个符号链接。
本地和全局的MS-DOS 设备名
微软Windows2000和较近版本基于NT技术的Windows操作系统保持了DosDevices目录的多重版本。
在这些操作系统上,有一个全局的/DosDevices目录和多重的本地的/DosDevices目录。全局的/DosDevices目录使得Ms-DOS设备名在系统范围内都是可见的。一个本地的/DosDevices目录使得MS-DOS设备名只对特定的本地DosDevices上下文可见。
本地DosDevices上下文列举如下:
l 在Windows XP或者更近版本的Windows中,每个登录会话都有它自己的本地DosDevices上下文。系统线程和任何以LocalSystem用户权限运行的线程,不在本地的DosDevices上下文中运行。
l 在Windows 2000中,每个终端服务会话都有它自己的本地DosDevices上下文。任何作为控制台会话一部分运行的线程,不在本地DosDevices上下文中运行。
每个线程都有一个当前的DosDevices上下文,这个上下文在线程的整个生命周期中是可以改变的。线程不在本地DosDevices上下文中运行的意思是,在全局DosDevices上下文中运行。因此,系统帐户运行在全局DosDevices上下文中。
如果一个线程当前运行在一个本地DosDevices上下文中,那么它所创建的任何MS-DOS设备名都是这个本地DosDevices目录中创建的。因此,运行在一个本地DosDevices上下文中的线程不能使它的MS-DOS设备名对运行在另一个本地或者全局DosDevices上下文中的线程可见。例如,如果Windows XP上的用户配置了一个名为X:的网络驱动器,那么其他用户甚至整个系统都不会看到或者操作这个X:驱动器。
在Windows XP或者较近版本中,对象管理器在/DosDevices中查询查询一个名字的时候,他首先查找本地/DosDevices目录,然后再查找全局/DosDevices目录。如果两个目录都有这个名字,那么本地的名字将屏蔽掉全局的名字。
在Windows2000中,每当一个新的终端服务会话被初始化,系统就会通过复制全局/DosDevices目录来建立一个本地/DosDevices目录。全局目录随后的任何改变都不会影响到本地目录。
如果一个驱动程序为了能在一个标准驱动程序例程中创建符号链接,而且这个标准驱动程序要求运行在系统线程上下文中,比如 DriverEntry,那么这个驱动程序就必须在全局的/DosDevices目录中创建它的MS-DOS设备名。另外,全局/DosDevices目录以/DosDevices/Global形式存在;驱动程序可以使用一个/DosDevices/Global/DosDeviceName名字来在全局目录中指定一个名字。
需要注意的是,/DosDevice/Global在不支持本地和全局版本的/DosDevices的平台中是不存在,例如 Windows 98/Me。下面的代码演示创建一个在Windows 98/Me和Windows2000以及最近版本的操作系统中都能工作的全局符号链接:
在响应一个IOCTL请求时,驱动程序可以在一个本地/DosDevices目录中通过创建符号链接来创建一个MS-DOS设备名。当某一特定本地DosDevices上下文中的一个线程发送了IOCTL,将从当前线程上下文中驱动程序的DispatchDeviceControl例程。
更多关于标准驱动程序例程运行在哪个上下文中的信息,参看MSDN中Dispatch Routines and IRQLs章节。
系统以如下方式区分本地/DosDevices目录:
l 在Windows XP和较近版本中,本地/DosDevices目录根据登录会话的访问凭证的AuthenticationID识别。更多关于AuthenticationID的信息,参看微软Windows SDK文档中的TOKEN_STATISTICS结构。
l 在Windows2000中,本地/DosDevices目录根据终端服务会话的SessionId来识别。更多关于SessionId的信息,参看微软Windows SDK文档中的WTS_SESSION_INFO结构。
Windows NT4.0终端服务器版本支持本地/DosDevices目录,使用与Windows2000完全一样的办法。