理的设备都有一个Device对象。例如对一个硬盘驱动程序,对一个物理硬盘有一个名称为Partition0的Device对象,对应整个物理磁盘,同时对硬盘的每个分区,也都有一个Device对象,它们的名称分别为PartitionX(X从1开始,每个分区对应一个数字)。
Device Extension是连接到Device对象的一个很重要的数据结构,它的数据结构是由驱动程序设计者自己来确定的,在 调用IoCreateDevice的时候应该指定它的大小,Device Extension其实是由操作系统在非份页内存池中为每个Device 对象分配的一块内存。由于驱动程序必须是完全可重入的, 因此使用任何全局变量和静态变量都不是好的办法,一般来 说和设备有关的任何需要保持的信息都应该放到Device Extension里去。
设备的缓冲策略也必须提一下,这里的Flag的缓冲策略主要 决定设备读写(功能代码IRP_MJ_READ和IRP_MJ_WRITE)时候的 缓冲策略,另外功能代码IRP_MJ_DEVICE_CONTROL时候的缓冲 策略是由IOCTL控制代码本身来决定的。两者不能混为一谈。 在下面我将专门用一节来讨论I/O的缓冲策略。
I/O请求包(IRP)
在上面的结构里面已经出现了IRP了,在这里对它做一说明。 在NT中,几乎所有的I/O都是包驱动的,可以说驱动程序和操作系统其他部份都是通过I/O请求包来进行交互的。我们 来看看一个I/O请求的执行过程。
(1) 操作系统的I/O管理器从非分页内存分配一个IRP,响应一个I/O请求。基于由客户指定的I/O函数,I/O管理器将该 IRP传递给合适的驱动程序的Dispatch例程。
(2) Dispatch例程检查请求的参数是否有效,如果有效,驱动程序根据请求的内容进行一系列的操作。否则设置错 误状态信息直接返回。
(3) 操作完成时,将数据(如果有)和状态信息存放到IRP中 并返回给I/O管理器。
(4) I/O管理器对返回的IRP进行适当的处理后将最后状态和 数据(如果有)返回给用户。
一个IRP的主要数据项如下表所示。
IRP包括一个IRP头和一个IRP stack 的区域。由于WDM的模式下都是包驱动的,所里IRP可以说是一个非常重要的东东。还有那个该死的URB(God damn URB!)
IRP主要数据项 | 说明 |
IO_STATUS_BLOCK IoStatus | 存放I/O请求的状态 |
PVOID AssociatedIrp.SystemBuffer | 如果设备执行缓冲I/O,则为指向系统空间缓冲区的指针。 否则为NULL |
PMDL MdlAddress | 如果设备执行直接I/O,指向用户空间缓冲区的内存描述表的指针 |
PVOID UserBuffer | I/O缓冲区的用户空间地址 |
BOOLEAN Cancel | 指示IRP已被取消 |
关于AssociatedIrp.SystemBuffer、MdlAddress和UserBuffer将在 下面的I/O缓冲区策略里面更详细地讨论。
NT还有更多其他的对象,例如中断对象、Controller对象、定时器对象等等,但在我们开发的驱动程序中并没有用到,因此在这里不做介绍。
I/O缓冲策略
很明显的,驱动程序和客户应用程序经常需要进行数据交换,但我们知道驱动程序和客户应用程序可能不在同一个地址空间,因此操作系统必须解决两者之间的数据交换。这就就设计到设备的I/O缓冲策略。
读写请求的I/O缓冲策略
前面说到通过设置Device对象的Flag可以选择控制处理读写请求的I/O缓冲策略。下面对这些缓冲策略分别做一介绍。
1、缓冲I/O(DO_BUFFERED_IO)
在读写请求的一开始,I/O管理器检查用户缓冲区的可访问性,然后分配与调用者的缓冲区一样大的非分页池,并把它的地址放在IRP的AssociatedIrp.SystemBuffer域中。驱动程序就利用这个域来进行实际数据的传输。
对于IRP_MJ_READ读请求,I/O管理器还把IRP的UserBuffer域设置 成调用者缓冲区的用户空间地址。当请求完成时,I/O管理器利用 这个地址将数据从驱动程序的系统空间拷贝回调用者的缓冲区。对 于IRP_MJ_WRITE写请求,UserBuffer被设置为NULL,并把用户缓冲 区的数据拷贝到系统缓冲区中。
2、 直接I/O(DO_DIRE