Mdl意为‘内存映射描述符’、‘缓冲描述符’,一个mdl就代表一个缓冲。(任意一块物理内存,可以同时映射到用户地址空间和系统地址空间的)

设备IO方式分为三种:缓冲方式、直接IO方式、直接方式

缓冲方式:将用户空间中的数据拷贝到内核缓冲,将内核缓冲中的数据拷贝到用户空间,效率低,适合少量数据交换

直接IO方式:将用户空间中的内存通过MDL机制映射到系统地址空间,效率高,适合大数据交换

直接方式:直接使用用户空间地址,效率最高,但不安全。

向设备写数据的操作通过下面的内核API执行,我们看:

NTSTATUS

NtWriteFile(IN HANDLE FileHandle,

            IN HANDLE Event OPTIONAL,

            IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,

            IN PVOID ApcContext OPTIONAL,

            OUT PIO_STATUS_BLOCK IoStatusBlock,

            IN PVOID Buffer,

            IN ULONG Length,

            IN PLARGE_INTEGER ByteOffset OPTIONAL,

            IN PULONG Key OPTIONAL)

{

    。。。

    if (DeviceObject->Flags & DO_BUFFERED_IO)

    {

        if (Length)

        {

            _SEH2_TRY

            {

                Irp->AssociatedIrp.SystemBuffer =

                    ExAllocatePoolWithTag(NonPagedPool,Length,TAG_SYSB);

                //看到没,系统内部自动分配一个内核缓冲,再将用户空间数据拷贝到内核缓冲

                RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, Buffer, Length);

            }

            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)

            {

                IopCleanupAfterException(FileObject, Irp, EventObject, NULL);

                _SEH2_YIELD(return _SEH2_GetExceptionCode());

            }

            _SEH2_END;

            Irp->Flags = (IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER);

        }

    }

    else if (DeviceObject->Flags & DO_DIRECT_IO)

    {

        if (Length)

        {

            _SEH2_TRY

            {   

                //看到没,分配一个MDL

                Mdl = IoAllocateMdl(Buffer, Length, FALSE, TRUE, Irp);

                MmProbeAndLockPages(Mdl, PreviousMode, IoReadAccess);

            }

            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)

            {

                IopCleanupAfterException(FileObject, Irp, EventObject, NULL);

                _SEH2_YIELD(return _SEH2_GetExceptionCode());

            }

            _SEH2_END;

        }

        Irp->Flags = 0;

        。。。

}

。。。

}

typedef struct _MDL {  //缓冲描述符

  struct _MDL *Next;//下一个MDL,用来构成mdl链表

  CSHORT Size;//整个mdl结构体的长度(包含后面的数组)

  CSHORT MdlFlags;

  struct _EPROCESS *Process;//所属进程

  PVOID MappedSystemVa;//该段缓冲在映射在系统空间中的地址

  PVOID StartVa;//虚拟地址(对齐4kb)

  ULONG ByteCount;//该段缓冲的长度

  ULONG ByteOffset;// StartVa+ByteOffset就是该段缓冲在Process进程地址空间中的虚拟地址

} MDL, *PMDL;

MDL结构体后面紧跟一个物理页号数组。用来记录这段缓冲的物理页面。(物理页面不一定连续)

注意:MDL结构体本身必须位于非分页内存中

PMDL

IoAllocateMdl(IN PVOID VirtualAddress,// 目标虚拟内存的其实地址

              IN ULONG Length,//目标虚拟内存的长度

              IN BOOLEAN SecondaryBuffer,//指新的mdl是插入到指定irp的关联mdl链表后面还是替换

              IN BOOLEAN ChargeQuota,

              IN PIRP Irp)//将新建的mdl插入或替换到这个irp的mdl链表中

{

    PMDL Mdl = NULL, p;

    ULONG Flags = 0;

    ULONG Size;

    if (Length >= 2GB) return NULL;

    //计算这段虚拟内存跨越了多少个虚拟页面(包括左右两端两个部分占据的页面)

    Size = ADDRESS_AND_SIZE_TO_SPAN_PAGES(VirtualAddress, Length);

    if (Size > 23)//若超过了23个虚拟页面,就采用实际占用的虚拟页面数

    {

        Size *= sizeof(PFN_NUMBER);

        Size += sizeof(MDL);

        if (Size > MAXUSHORT) return NULL;

    }

    Else //否则,使用固定大小的23个虚拟页面,这样分配内存比较快。

    {

        Size = (23 * sizeof(PFN_NUMBER)) + sizeof(MDL);

        Flags |= MDL_ALLOCATED_FIXED_SIZE;

        Mdl = IopAllocateMdlFromLookaside(LookasideMdlList);

    }

    if (!Mdl)

{

    //分配一个mdl结构体(包含后面的物理页号数组)

        Mdl = ExAllocatePoolWithTag(NonPagedPool, Size, TAG_MDL);

        if (!Mdl) return NULL;

    }

    MmInitializeMdl(Mdl, VirtualAddress, Length);

    Mdl->MdlFlags |= Flags;

    if (Irp)

    {

      if (SecondaryBuffer) //插在原mdl链表末尾

      {

            p = Irp->MdlAddress;

            while (p->Next) p = p->Next;

            p->Next = Mdl;

      }

      Else  //替换原mdl为新的mdl

      {

            Irp->MdlAddress = Mdl;

      }

   }

    return Mdl;

}

#define MmInitializeMdl(_MemoryDescriptorList, \

                        _BaseVa, \

                        _Length) \

{ \

  (_MemoryDescriptorList)->Next = (PMDL) NULL; \    //单个mdl

  (_MemoryDescriptorList)->Size = (CSHORT) (sizeof(MDL) + \

    (sizeof(PFN_NUMBER) * ADDRESS_AND_SIZE_TO_SPAN_PAGES(_BaseVa, _Length))); \

  (_MemoryDescriptorList)->MdlFlags = 0; \

  (_MemoryDescriptorList)->StartVa = (PVOID) PAGE_ALIGN(_BaseVa); \

  (_MemoryDescriptorList)->ByteOffset = BYTE_OFFSET(_BaseVa); \

  (_MemoryDescriptorList)->ByteCount = (ULONG) _Length; \

}

上文在NtWriteFile函数体内中,分配一个对应大小的mdl内存映射描述符后,又马上调用了MmProbeAndLockPages函数,这是为什么呢?这个函数会获取这段虚拟内存映射着的物理页面,记录到mdl结构体后面紧跟的数组中 ,并将这些物理页面锁定在内存,防止被置换出去(注意:如果当时那些虚拟页面尚未映射到物理内存,这个函数内部还会自动将那些虚拟页面换入物理内存的)

通过IoAllocateMdl、MmProbeAndLockPages这两步操作后,指定虚拟内存就被锁定在物理内存了,就完成了映射的准备工作。接下来,用户想要在什么时候把这段虚拟内存对应的那些物理内存映射到系统地址空间时,就可以使用MmGetSystemAddressForMdl宏达到目的。

#define  MmGetSystemAddressForMdl(Mdl) \

   Mdl->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | MDL_SOURCE_IS_NONPAGED_POOL) ? \

  Mdl->MappedSystemVa : MmMapLockedPages (Mdl,KernelMode)

这个宏的意思是如果该段虚拟内存尚未映射到系统空间,就映射

MmMapLockedPages这个函数用于映射用户空间中锁定的页面到内核地址空间中(实际上内核地址空间中有一块专用区段叫mdl区段,专用于mdl映射,这个函数就是将用户空间内存映射到内核中的那个区段中的)

VOID IoFreeMdl(PMDL Mdl)

{

    MmPrepareMdlForReuse(Mdl);

    if (!(Mdl->MdlFlags & MDL_ALLOCATED_FIXED_SIZE))

        ExFreePoolWithTag(Mdl, TAG_MDL);

    else

        IopFreeMdlFromLookaside(Mdl, LookasideMdlList);

}

另外:系统空间的非分页内存本身就是锁定在内存的,也可以使用mdl

VOID  MmBuildMdlForNonPagedPool(IN PMDL Mdl)

{

    PPFN_NUMBER MdlPages, EndPage;

    PFN_NUMBER Pfn, PageCount;

    PVOID Base;

    PMMPTE PointerPte;

    Mdl->Process = NULL;

    MdlPages = (PPFN_NUMBER)(Mdl + 1);

Base = Mdl->StartVa;

//非分页内存本身就位于系统空间,不用重新映射,直接使用

    Mdl->MappedSystemVa = (PVOID)((ULONG_PTR)Base + Mdl->ByteOffset);

    PageCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(Mdl->MappedSystemVa,Mdl->ByteCount);

    EndPage = MdlPages + PageCount;

    PointerPte = MiAddressToPte(Base);

    do

    {

        Pfn = PFN_FROM_PTE(PointerPte++);

        *MdlPages++ = Pfn;//关键。填充mdl结构体后面的物理页号数组

    } while (MdlPages < EndPage);

    Mdl->MdlFlags |= MDL_SOURCE_IS_NONPAGED_POOL;//标记来源本身就是非分页内存。

    if (!MiGetPfnEntry(Pfn)) Mdl->MdlFlags |= MDL_IO_SPACE;

}

通过MmBuildMdlForNonPagedPool后,也可以使用MmGetSystemAddressForMdl宏获得系统空间地址。

[12]Windows内核情景分析 --- MDI的更多相关文章

  1. [1]windows 内核情景分析---说明

    本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...

  2. windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数

    windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数 1.KeRaiseIrql函数 这个 KeRaiseIrql() 只是简单地调用 hal 模块的 KfRa ...

  3. 几个常用内核函数(《Windows内核情景分析》)

    参考:<Windows内核情景分析> 0x01  ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...

  4. [14]Windows内核情景分析 --- 文件系统

    文件系统 一台机器上可以安装很多物理介质来存放资料(如磁盘.光盘.软盘.U盘等).各种物理介质千差万别,都配备有各自的驱动程序,为了统一地访问这些物理介质,windows设计了文件系统机制.应用程序要 ...

  5. [7] Windows内核情景分析---线程同步

    基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ...

  6. [4]Windows内核情景分析---内核对象

    写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ...

  7. [11]Windows内核情景分析---设备驱动

    设备驱动 设备栈:从上层到下层的顺序依次是:过滤设备.类设备.过滤设备.小端口设备[过.类.过滤.小端口] 驱动栈:因设备堆栈原因而建立起来的一种堆栈 老式驱动:指不提供AddDevice的驱动,又叫 ...

  8. [2]windows内核情景分析--系统调用

    Windows的地址空间分用户模式与内核模式,低2GB的部分叫用户模式,高2G的部分叫内核模式,位于用户空间的代码不能访问内核空间,位于内核空间的代码却可以访问用户空间 一个线程的运行状态分内核态与用 ...

  9. [15]Windows内核情景分析 --- 权限管理

    Windows系统是支持多用户的.每个文件可以设置一个访问控制表(即ACL),在ACL中规定每个用户.每个组对该文件的访问权限.不过,只有Ntfs文件系统中的文件才支持ACL. (Ntfs文件系统中, ...

随机推荐

  1. Visio 画图

    流程图 圆角矩形表示"开始"与"结束" 矩形表示行动方案.普通工作环节用 菱形表示问题判断或判定(审核/审批/评审)环节 平行四边形表示输入输出 箭头代表工作流 ...

  2. 配置ssm 时, web.xml 文件无 # 自动代码提示

    环境:STS 版本:spring-tool-suite-3.8.1.RELEASE-e4.6-win32-x86_64 配置ssm 时, web.xml 文件无 如下图蓝色圈范围内的提示 问题与 链接 ...

  3. 洛谷P3041 视频游戏的连击Video Game Combos [USACO12JAN] AC自动机+dp

    正解:AC自动机+dp 解题报告: 传送门! 算是个比较套路的AC自动机+dp趴,,, 显然就普普通通地设状态,普普通通地转移,大概就f[i][j]:长度为i匹配到j 唯一注意的是,要加上所有子串的贡 ...

  4. UVA11491 奖品的价值

    奖品的价值C804 运行时间限制:1000ms: 运行空间限制:51200KB 试题描述 你是一个电视节目的获奖嘉宾.主持人在黑板上写出一个 n 位非负整数(不以 0 开头),邀请你删除其中的 d 个 ...

  5. 29-2-电容触摸屏控制芯片GT911

    1.接口说明 GT9 非单层多点系列(以下简称 GT9 系列) 与主机接口共有 6 PIN,分别为: VDD. GND. SCL.SDA. INT. RESET. 主控的 INT 口线需具有上升沿或下 ...

  6. wc 统计命令

    [root@localhost ~]# wc /etc/passwd // 统计行数.单词数.字符数 /etc/passwd [root@localhost ~]# wc -l /etc/passwd ...

  7. Java基础知识(JAVA之泛型)

    什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”.一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参.那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似 ...

  8. vue-router-transiton

    <template> <transition name="slide-left" mode="out-in"> <router-v ...

  9. (转)Springboot日志配置(超详细,推荐)

    Spring Boot-日志配置(超详细) 更新日志: 20170810 更新通过 application.yml传递参数到 logback 中. Spring Boot-日志配置超详细 默认日志 L ...

  10. SSM基础整合

    1.表现层: 依赖jar包:spring+JSP+文件上传组件+dubbo+fastdfs web.xml配置:乱码过滤器+DispatcherServlet前端控制器 <!-- 配置post乱 ...