[12]Windows内核情景分析 --- MDI
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]windows 内核情景分析---说明
本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...
- windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数
windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数 1.KeRaiseIrql函数 这个 KeRaiseIrql() 只是简单地调用 hal 模块的 KfRa ...
- 几个常用内核函数(《Windows内核情景分析》)
参考:<Windows内核情景分析> 0x01 ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...
- [14]Windows内核情景分析 --- 文件系统
文件系统 一台机器上可以安装很多物理介质来存放资料(如磁盘.光盘.软盘.U盘等).各种物理介质千差万别,都配备有各自的驱动程序,为了统一地访问这些物理介质,windows设计了文件系统机制.应用程序要 ...
- [7] Windows内核情景分析---线程同步
基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ...
- [4]Windows内核情景分析---内核对象
写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ...
- [11]Windows内核情景分析---设备驱动
设备驱动 设备栈:从上层到下层的顺序依次是:过滤设备.类设备.过滤设备.小端口设备[过.类.过滤.小端口] 驱动栈:因设备堆栈原因而建立起来的一种堆栈 老式驱动:指不提供AddDevice的驱动,又叫 ...
- [2]windows内核情景分析--系统调用
Windows的地址空间分用户模式与内核模式,低2GB的部分叫用户模式,高2G的部分叫内核模式,位于用户空间的代码不能访问内核空间,位于内核空间的代码却可以访问用户空间 一个线程的运行状态分内核态与用 ...
- [15]Windows内核情景分析 --- 权限管理
Windows系统是支持多用户的.每个文件可以设置一个访问控制表(即ACL),在ACL中规定每个用户.每个组对该文件的访问权限.不过,只有Ntfs文件系统中的文件才支持ACL. (Ntfs文件系统中, ...
随机推荐
- 图->连通性->无向图的连通分量和生成树
文字描述 对无向图进行遍历时,对于连通图,仅需从图中任一顶点出发,进行深度优先搜索或广度优先搜索,便可访问到图中所有顶点.但对非连通图,则需从多个顶点出发搜索,每一次从一个新的起始点出发进行搜索过程得 ...
- django上下文处理器
上下文处理器(context processors)上下文处理器是可以返回一些数据,在全局模板中都可以使用.比如登录后的用户信息,在很多页面中都需要使用,那么我们可以放在上下文处理器中,就没有必要在每 ...
- npm 安装包报错 rollbackFailedOptional
npm config rm proxynpm config rm https-proxy 然后使用npm install -g cnpm --registry=https://registry.npm ...
- python datetime 模块
import datetime datetime.datetime.now() 打印本地当前时间 >>> print(datetime.datetime.now()) 2017-12 ...
- Linux-eth0 eth0:1 ifcfg-lo ifcfg-lo:0 和eth0.1关系、ifconfig以及虚拟IP实现介绍
eth0 eth0:1 和eth0.1三者的关系对应于物理网卡.子网卡.虚拟VLAN网卡的关系:物理网卡:物理网卡这里指的是服务器上实际的网络接口设备,这里我服务器上双网卡,在系统中看到的2个物理网卡 ...
- 火币网API文档——REST 行情、交易API简介
REST API 简介 火币为用户提供了一套全新的API,可以帮用户快速接入火币PRO站及HADAX站的交易系统,实现程序化交易. 访问地址 适用站点 适用功能 适用交易对 https://api.h ...
- JsonDataObjects 简单实用
下载地址https://github.com/ahausladen/JsonDataObjects Simple example var Obj: TJsonObject; begin Obj := ...
- shell进阶函数
函数的定义和用途 函数function是由若干条shell命令组成的语句块,实现shell代码的重用和模块化编程. 函数和shell程序的异同点 它与shell程序形式上是相似的,不同的是它不是一个单 ...
- vue/cli 3.0 脚手架【进阶】 使用 amfe-flexible 和 postcss-px2rem进行移动端适
安装vue-cli3 npm install -g @vue/cli 创建项目 vue-cli-test 脚手架-项目-成功-运行项目 基于vue-cli配置移动端自适应 转自:http://hj ...
- Spark Sql数仓报-Metastore contains multiple versions
Spark版本为2.1.0,Hadoop版本为2.7.1,元数据存储在mysql中,异常信息如下: Exception in thread "main" java.lang.Run ...