I/O缓冲策略

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_DIRECT_IO)  

I/O管理器首先检查用户缓冲区的可访问性,并在物理内存中锁定 它。然后它为该缓冲区创建一个内存描述表(MDL),并把MDL的地址 存放在IRP的MdlAddress域中。AssociatedIrp.SystemBuffer和 UserBuffer都被设置为NULL。驱动程序可以调用函数 MmGetSystemAddressForMdl得到用户缓冲区的系统空间地址,从而 进行数据操作。这个函数将调用者的缓冲区映射到非份页的地址空 间。驱动程序完成I/O请求后,系统自动从系统空间解除缓冲区的 映射。  


3、 这两种方法都不是  

这种情况比较少用,因为这需要驱动程序自己来处理缓冲问题。 I/O管理器仅把调用者缓冲区的用户空间地址放到IRP的UserBuffer 域中。我们并不推荐这种方式。  

IOCTL缓冲区的缓冲策略  
IOCTL请求涉及来自调用者的输入缓冲区和返回到调用者的输出 缓冲区。为了理解IOCTL请求,我们先来看看WIN32 API DeviceIoControl函数的原型。  
BOOL DeviceIoControl (  
HANDLE hDevice, // 设备句柄  
DWORD dwIoControlCode, // IOCTL请求操作代码  
LPVOID lpInBuffer, // 输入缓冲区地址  
DWORD nInBufferSize, // 输入缓冲区大小  

LPVOID lpOutBuffer, // 输出缓冲区地址  

DWORD nOutBufferSize, // 输出缓冲区大小  

LPDWORD lpBytesReturned, // 存放返回字节数的指针  

LPOVERLAPPED lpOverlapped // 用于同步操作的Overlapped结构体指针  

);  


IOCTL请求有四种缓冲策略,下面一一介绍。  


1、 输入输出缓冲I/O(METHOD_BUFFERED)  

I/O管理器首先分配一个非份页池,它足够大地存放调用者的输 入或输出缓冲区(不管哪个更大)。非份页缓冲区的地址放在IRP的AssociatedIrp.Sys temBuffer域中,然后把IOCTL的输入数据拷贝 到这个非份页缓冲区中,并把IRP的UserBuffer域设置成调用者输 出缓冲区的用户空间地址。当驱动程序完成IOCTL请求时,I/O管理 器将这个非份页缓冲区中的数据拷贝到调用者的输出缓冲区。 注意这里同一个非份页池同时用于输入和输出缓冲区,因此驱动 程序在向缓冲区写东西之前应该把输入的所有数据读出来。  

2、 直接输入缓冲输出I/O(METHOD_IN_DIRECT)  

I/O管理器首先检查调用者输入缓冲区的可访问性,并在物理内存 中将其锁定。然后为该输入缓冲区创建一个MDL,并把指定该MDL的 指针存放到IRP的MdlAddress域中。 同时,I/O管理器还在非份页池中分配一输出缓冲区,并把这个缓冲 区的地址存放在IRP的AssociatedIrp.SystemBuffer域中,并把IRP 的UserBuffer域设置成调用者输出缓冲区的用户空间地址。当驱动 程序完成IOCTL请求时,I/O管理器将非份页缓冲区中的数据拷贝到 调用者的输出缓冲区。  


3、 缓冲输入直接输出I/O(METHOD_OUT_DIRECT)  

I/O管理器首先检查调用者输出缓冲区的可访问性,并在物理内存中 将其锁定。然后为该输出缓冲区创建一个MDL,并把指定该MDL的指针 存放到IRP的MdlAddress域中。同时,I/O管理器还在非份页池中分配一输入缓冲区,并把这个缓冲区的地址存放在IRP的AssociatedIrp.SystemBuffer域中, 同时把 调用者用户输入缓冲区中的数据拷贝到系统缓冲区中,并把IRP的 UserBuffer域设置为NULL。  


4、 上面三种方法都不是(METHOD_NEITHER)  

I/O管理器把调用者的输入缓冲区的地址放到IRP当前I/O堆栈单元的Parameters.Devi ceIoControl.Type3InputBuffer域中,把输出缓冲 区的地址存放到IRP的UserBuffer域中。这两个地址都是用户空间地 址。  

从上面的说明可以看出,在执行缓冲I/O时,I/O管理器将在非份页池 中分配内存,如果调用者的缓冲区比较大时,分配的非份页池也将 比较大。非份页池是系统比较宝贵的资源,因此,如果调用者的缓 冲区比较大时,我们一般采用直接I/O的方式(例如磁盘读写请求等), 这样不仅节省系统资源,另一方面由于省去了I/O管理器在系统缓冲 区和调用者缓冲区之间的数据拷贝,也提高了效率,这对存在大量 数据传送的驱动程序尤其明显。  
可以注意到DDK中的Samples下,几乎所有的例程的读写请求都是直 接I/O的,而对于IOCTL请求则是缓冲区I/O的居多。  
下面以changerDisk的IRP_MJ_DEVICE_CONTROL例程中的一段程序来 加强IOCTL缓冲策略的认识和用法。在该例中所有的IOCTL请求的缓 冲策略都是METHOD_BUFFERED。  


NTSTATUS  

ChangerDiskDeviceControl(  

PDEVICE_OBJECT DeviceObject,  

PIRP Irp  

)  

{  

PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);  

ULONG ControlCode;  

ULONG InputLength, OutputLength;  

PVOID InputBuffer, OutputBuffer;  

…  

ControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;  

// IOCTL请求功能代码存放在IRP当前I/O堆栈单元的  

// Parameters.DeviceIoControl.IoControlCode中。;  


InputLength = irpStack->Parameters.DeviceIoControl.InputBufferLength;  

// 输入缓冲区的大小存放在IRP当前I/O堆栈单元的  

// Parameters.DeviceIoControl.InputBufferLength中。  


OutputLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;  

// 输出缓冲区的大小存放在IRP当前I/O堆栈单元的  

// Parameters.DeviceIoControl. OutputBufferLength中。  


InputBuffer = OutputBuffer = Irp->AssociatedIrp.SystemBuffer;  

// 由于本例中所有的IOCTL请求都是输入输出缓冲的,所以输入输出缓冲区 // 的地址就是IRP的AssociatedIrp.SystemBuffer域。  

….  

}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值