设备读写方式共三种:

方式 Flag 特点

缓冲区方式读写

DO_BUFFERED_IO I/O管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区。而你的驱动程序将使用这个系统缓冲区工作。I/O管理器负责在系统缓冲区和用户模式缓冲区之间复制数据。

直接方式读写

DO_DIRECT_IO I/O管理器锁定了包含用户模式缓冲区的物理内存页,并创建一个称为MDL(内存描述符表)的辅助数据结构来描述锁定页。因此你的驱动程序将使用MDL工作。
Neither方式 0 I/O管理器仅简单地把用户模式的虚拟地址传递给你。而使用用户模式地址的驱动程序应十分小心。

1,缓冲区方式读写

其优点是比较简单的解决了将用户地址传入驱动的问题,缺点是需要用户模式和内核模式之间数据复制,可想而知,运行效率会受到影响。适合少量内存操作时使用的一种方法。

创建好设备IoCreateDevice后,需要设置读写方式

pDevObj->Flags |= DO_BUFFERED_IO

以readfile为例,首先应用程序中需要提供一段缓冲区并把缓冲区大小作为参数传入,例如:

UCHAR OutputBuffer[];
DWORD RetLen = ;
readfile(hDevice,OutputBuffer,sizeof(OutputBuffer),&RetLen,NULL);

OutputBuffer是提供的输出缓冲区,是用户模式的内存地址,操作系统将此缓冲区的数据复制到内核模式下的地址中,sizeof(OutputBuffer)是缓冲区的大小,

而RetLen是真正的输出的字节数。

那么内核模式怎么得到此内核模式地址呢?怎么得到writefile或readfile的字节数呢?答案在下面。

此内核模式下的地址可以通过此readfile创建的IRP的AssociatedIrp.SystemBuffer得到。

假如请求的IRP为PIRP pIrp(一般是派遣函数的参数),那么:

UCHAR* OutputBuffer= (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;

而readfile请求的字节数为IO_STACK_LOCATION中的Parameters.Read.Length,

writefilew为IO_STACK_LOCATION中的Parameters.Write.Length

//得到当前堆栈
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//得到readfile缓冲区大小
ULONG cbread = stack->Parameters..Read.Length;
//得到writefile缓冲区大小
ULONG cbwrite = stack->Parameters.Write.Length;

得到了内核模式下的缓冲区地址了就可以对此缓冲区操作了。比如:

UCHAR* OutputBuffer= (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
ULONG cbread = stack->Parameters..Read.Length;
memcpy(OutputBuffer,0xBB,cbread);

这样用户模式下的缓冲区内得到的数据是0xBB。

还要设置实际操作的字节数,pIrp->IoStatus.Information = cbread;(实际操作的字节数不一定要设置为缓冲区的大小,但也不应该大于缓冲区的大小)

那么用户模式下readfile的RetLen被设置为cbread。

下面是IRP_MJ_READ的派遣函数:

NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp)
{
KdPrint(("Enter DispatchRead\n"));
//对一般IRP的简单操作,后面会介绍对IRP更复杂的操作
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ulReadLength = stack->Parameters.Read.Length;
// 完成IRP
//设置IRP完成状态
pIrp->IoStatus.Status = status;
//设置IRP操作了多少字节
pIrp->IoStatus.Information = ulReadLength;
memset(pIrp->AssociatedIrp.SystemBuffer,0xAA,ulReadLength);
//处理IRP
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave DispatchRead\n"));
return status;
}

2,直接方式读写设备

操作系统会将用户模式下的缓冲区锁住,然后操作系统将这段缓冲区在内核模式地址再次映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。无论操作系统如何切换进程,内核模式地址都保持不变。

创建好设备IoCreateDevice后,需要设置读写方式Flag

pDevObj->Flags |= DO_DIRECT_IO

这里涉及到内存描述符表结构体MDL

typedef struct _MDL {
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa; //给出了用户缓冲区的虚拟地址,第一个页地址,这个地址仅在拥有数据缓冲区的用户模式进程上下文中才有效
ULONG ByteCount; //是缓冲区的字节长度
ULONG ByteOffset; //是缓冲区起始位置在一个页帧中的偏移值,那么缓冲区的首地址是mdl->StartVa+mdl->ByteOffset
} MDL, *PMDL;

用图表示内存描述符表(MDL)结构为:

由图可知用户模式的这段缓冲区在虚拟内存上是连续的,但在物理内存上可能是离散的。

下面是一些MDL相关的函数

IoAllocateMdl 创建MDL
IoBuildPartialMdl 创建一个已存在MDL的子MDL
IoFreeMdl 销毁MDL
MmBuildMdlForNonPagedPool 修改MDL以描述内核模式中一个非分页内存区域
MmGetMdlByteCount 取缓冲区字节大小(得到mdl->ByteCount)
MmGetMdlByteOffset 取缓冲区在第一个内存页中的偏移(得到mdl->ByteOffset)
MmGetMdlVirtualAddress 取虚拟地址((PVOID)(PCHAR)(mdl->StartVa+mdl->ByteOffset))
MmGetSystemAddressForMdl 创建映射到同一内存位置的内核模式虚拟地址
MmGetSystemAddressForMdlSafe 与MmGetSystemAddressForMdl相同,但Windows 2000首选
MmInitializeMdl (再)初始化MDL以描述一个给定的虚拟缓冲区
MmPrepareMdlForReuse 再初始化MDL
MmProbeAndLockPages 地址有效性校验后锁定内存页
MmSizeOfMdl 取为描述一个给定的虚拟缓冲区的MDL所占用的内存大小
MmUnlockPages 为该MDL解锁内存页

以readfile为例介绍直接方式读取设备:

//用户模式调用readfile:
UCHAR OutputBuffer[];
DWORD RetLen = ;
readfile(hDevice,OutputBuffer,sizeof(OutputBuffer),&RetLen,NULL);
//内核模式得到要读取的字节数:(与以缓冲区读写方式一样)
//得到当前堆栈
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//得到readfile要读取的字节数
ULONG cbread = stack->Parameters..Read.Length;
//另外,通过IRP的pIrp->MdlAddress得到MDL数据结构,这个结构描述了被锁定的缓冲区的内存。

下面是一个IRP_MJ_READ的派遣函数:

NTSTATUS DispathRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)
{
KdPrint(("Enter DispathRead\n")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp); ULONG ulReadLength = stack->Parameters.Read.Length;//得到读取的长度
KdPrint(("ulReadLength:%d\n",ulReadLength)); ULONG mdl_length = MmGetMdlByteCount(pIrp->MdlAddress); //mdl虚拟内存的长度
PVOID mdl_address = MmGetMdlVirtualAddress(pIrp->MdlAddress); //虚拟内存的起始地址
ULONG mdl_offset = MmGetMdlByteOffset(pIrp->MdlAddress); //虚拟内存首地址在第一页的偏移量 KdPrint(("mdl_address:0X%08X\n",mdl_address));
KdPrint(("mdl_length:%d\n",mdl_length));
KdPrint(("mdl_offset:%d\n",mdl_offset)); if (mdl_length!=ulReadLength)
{
//MDL的长度应该和读长度相等,否则该操作应该设为不成功
pIrp->IoStatus.Information = ;
status = STATUS_UNSUCCESSFUL;
}else
{
//用MmGetSystemAddressForMdlSafe得到MDL在内核模式下的映射,被映射到内核模式下的内存地址,必定在0X80000000-0XFFFFFFFF之间
PVOID kernel_address = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);
KdPrint(("kernel_address:0X%08X\n",kernel_address));
memset(kernel_address,0XAA,ulReadLength); //对内核模式下的内存地址进行操作
pIrp->IoStatus.Information = ulReadLength; //设置实际操作字节数
} pIrp->IoStatus.Status = status; IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave DispatchRead\n")); return status;
}

Windows驱动开发-设备读写方式的更多相关文章

  1. windows驱动开发-设备扩展

    设备对象Device_Object记录通用设备信息,另外一些信息记录在设备扩展里,设备扩展由程序员自己定义,由程序员指定内容和大小,由I/O管理器创建,并保存在非分页内存中. 驱动程序中,尽量避免使用 ...

  2. windows 驱动开发入门——驱动中的数据结构

    最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书--<独钓寒江 windows安全编程> 和 <windows驱动 ...

  3. Windows驱动开发(中间层)

    Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...

  4. [Windows驱动开发](一)序言

    笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...

  5. Windows 驱动开发 - 5

    上篇<Windows 驱动开发 - 4>我们已经完毕了硬件准备. 可是我们还没有详细的数据操作,比如接收读写操作. 在WDF中进行此类操作前须要进行设备的IO控制,已保持数据的完整性. 我 ...

  6. C++第三十三篇 -- 研究一下Windows驱动开发(一)内部构造介绍

    因为工作原因,需要做一些与网卡有关的测试,其中涉及到了驱动这一块的知识,虽然程序可以运行,但是不搞清楚,心里总是不安,觉得没理解清楚.因此想看一下驱动开发.查了很多资料,看到有人推荐Windows驱动 ...

  7. windows驱动开发推荐书籍

    [作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...

  8. Windows 驱动开发 - 7

    在<Windows 驱动开发 - 5>我们所说的读写操作在本篇实现. 在WDF中实现此功能主要为:EvtIoRead和EvtIoWrite. 首先,在EvtDeviceAdd设置以上两个回 ...

  9. Windows驱动开发-IRP的完成例程

    <Windows驱动开发技术详解 >331页, 在将IRP发送给底层驱动或其他驱动之前,可以对IRP设置一个完成例程,一旦底层驱动将IRP完成后,IRP完成例程立刻被处罚,通过设置完成例程 ...

随机推荐

  1. [原]SVN代码管理

    1.SVN工程结构 branches zhangsan lisi wangwu tags project-release trunk project-trunk branches:下为trunk的分支 ...

  2. Linux 命令中 find 和 xargs 命令的用法

    find 命令(一) find 命令主要作用是沿着文件层次结构向下遍历,匹配符合条件的文件,并执行相应的操作.Linux 下 find 命令提供了相当多的查找条件,功能很强大,对应的学习难度也比较大. ...

  3. jenkins windows 2.204版,免安装,推荐插件齐备.

    windows专用,已安装好推荐插件, 更新了安装源为清华源,也就是说只要官方的插件,你都可以秒速下载了.香不? 解压到一个文件夹,管理员模式运行cmdcd 文件夹名jenkins install这样 ...

  4. Python之字符(2)

    1.string.issupper()表示判断字符是否全部为小写字母. string1 = "abcdef" string2 = "ABCdef" string ...

  5. jquery移除click事件

    原文链接:https://blog.csdn.net/weixin_41228949/article/details/83142661 在html中定义click事件有两种方式,针对这两种方式有两种移 ...

  6. 第五周之Hadoop学习(五)

    在上周已经完成Hadoop的Java编程环境下的配置,这周则是通过对Eclipse的环境编程对Hadoop的API进行简单的调用 参考地址:https://blog.csdn.net/u0105237 ...

  7. [蓝桥杯2017初赛]迷宫 DFS

    题目描述 X星球的一处迷宫游乐场建在某个小山坡上.它是由10x10相互连通的小房间组成的. 房间的地板上写着一个很大的字母.我们假设玩家是面朝上坡的方向站立,则: L表示走到左边的房间,R表示走到右边 ...

  8. 模拟服务容器Ioc

    服务容器是一个用于管理类依赖和执行依赖注入的强大工具. 一个类要被容器所能够提取,必须要先注册至这个容器.既然称这个容器叫做服务容器,那么我们需要某个服务,就得先注册.绑定这个服务到容器,那么提供服务 ...

  9. LeetCode困难题(一)

    题目一: 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表. k 是一个正整数,它的值小于或等于链表的长度. 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序. 示例 ...

  10. idea右键新建选项没有类和包的创建方式

    Intelidea创建好项目之后,右键新建Java class的时候发现没有改选项,只有以下几个选项 把sec目录设为源码目录,首先打开Project Structure