Windows内核中的内存管理
内存管理的要点
- 内核内存是在虚拟地址空间的高2GB位置,且由所有进程所共享,进程进行切换时改变的只是进程的用户分区的内存
- 驱动程序就像一个特殊的DLL,这个DLL被加载到内核的地址空间中,DriverEntry和AddDevice例程在系统的system进程中运行,派遣函数会运行在应用程序的进程上下文中所能访问的地址空间是这个进程的虚拟地址空间利用_EPROCESS结构可以查看该进程的相关信息
- 当程序的中断级别在DISPATCH_LEVEL之上时,必须使用非分页内存,否则会造成系统蓝屏,在编译WDK相关例程时,可以使用如下的宏指定某个例程或者某个全局变量是位于分页内存还是运行于非分页内存
#define PAGEDCODE code_seg("PAGE") //分页内存
#define LOCKEDCODE code_seg() //非分页内存
#define INITCODE code_seg("INIT") //指定在相关函数执行完成后就从内存中卸载下来
#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")
在使用时直接使用#pragma直接加载这些宏即可比如:
#pragma PAGEDCODE
VOID DoSomething()
{
PAGED_CODE()
//函数体
}
其中PAGED_CODE是一个WDK中提供的一个宏,只在debug版本中生效,用于判断当前的中断请求级别,当级别高于DISPATCH_LEVEL(包含这个级别)时会产生一个断言
内核中的堆申请函数
PVOID
ExAllocatePool(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
PVOID
ExAllocatePoolWithTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
PVOID
ExAllocatePoolWithQuota(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
PVOID
ExAllocatePoolWithQuotaTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
PoolType:这是一个枚举变量,用来表示分配内存的种类,如果为PagedPool表示分配的是分页内存,如果是NonPagedPool表示分配的是非分页内存
NumberOfBytes:分配内存的大小,为了效率最好分配4的倍数
上面这些函数主要分为带有标记和不带标记的两种,其中有Quota的是按配额分配,带有标记的函数可以通过这个标记来判断这块内存最后有没有被分配,标记是一个字符串,但是这个字符串是用单引号引起来的。一般给4个字符,由于IntelCPU采用的是高位优先的存储方式,所以为了阅读方便,一般将这个字符串倒着写
这些函数分配的内存一般使用下面的函数来释放
VOID
ExFreePool(
IN PVOID P
);
NTKERNELAPI
VOID
ExFreePoolWithTag(
IN PVOID P,
IN ULONG Tag
);
在驱动中使用链表
WDK给程序员提供了两种基本的链表结构,分别是单向链表和双向链表
双向链表的结构体定义如下:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink; //指向下一个节点
struct _LIST_ENTRY *Blink; //指向上一个节点
} LIST_ENTRY, *PLIST_ENTRY;
初始化链表使用宏InitializeListHead,它需要传入一个链表的头节点指针它的定义如下
VOID
InitializeListHead(
IN PLIST_ENTRY ListHead
);
这个宏只是简单的将链表头的Flink和Blink指针指向它本身。
利用宏IsListEmpty可以检查一个链表是否为空,它也是只简单的检查这两个指针是否指向其自身
在定义自己的数据结构的时候需要将这个结构体放到自定义结构体中,比如
typedef struct _MYSTRUCT
{
LIST_ENTRY listEntry;
ULONG i;
ULONG j;
}MYSTRUCT, *PMYSTRUCT
一般插入链表有两种方法,头插法和尾插法,DDK根据这两种方法都给出了具体的函数可供操作:
//头插法,采用头插法只改变链表数据的顺序,链表头仍然是链表中的第一个元素
VOID
InsertHeadList(
IN PLIST_ENTRY ListHead, //链表头指针
IN PLIST_ENTRY Entry //对应节点中的LIST_ENTRY指针
);
//尾插法
VOID
InsertTailList(
IN PLIST_ENTRY ListHead,
IN PLIST_ENTRY Entry
);
删除节点使用的是这样两个函数,同样采用的是从头部开始删除和从尾部开始删除,就是查找链表中节点的方向不同。
//从头部开始删除
PLIST_ENTRY
RemoveHeadList(
IN PLIST_ENTRY ListHead
);
//从尾部开始删除
PLIST_ENTRY
RemoveTailList(
IN PLIST_ENTRY ListHead
);
这两个函数都是传入头节点的指针,返回被删除那个节点的指针,这里有一个问题,我们如何根据返回PLIST_ENTRY结构找到对应的用户定义的数据,如果我们将LIST_ENTRY,这个节点放在自定义结构体的首部的时候,返回的地址就是结构体的地址,如果是放在其他位置,则需要根据结构体的定义来进行转化,对此WDK提供了这样一个宏来帮我们完成这个工作:
PCHAR
CONTAINING_RECORD(
IN PCHAR Address,
IN TYPE Type,
IN PCHAR Field
);
这个宏返回自定义结构体的首地址,传入的是第一个参数是结构体中某个成员的地址,第二个参数是结构体名,第三个参数是我们传入第一个指针的类型在结构体中对应的成员变量值,比如对于上面那个MYSTRUCT结构体可以这样使用
typedef struct _MY_LIST_DATA
{
LIST_ENTRY list;
ULONG i;
}MY_LIST_DATA, *PMY_LIST_DATA;
PLIST_ENTRY pListData = RemoveHeadList(&head);//head是链表的头节点
PMYSTRUCT pData = CONTAINING_RECORD(pListData, MYSTRUCT, list);
Lookaside结构
频繁的申请和释放内存将造成内存空洞,即出现大量小块的不连续的内存片段,这个时候即使内存仍有剩余,但是我们也申请不了内存,一般在操作系统空闲的时候会进行内存整理,将空洞内存进行合并,如果驱动需要频繁的从内存中申请释放相同大小的内存块,DDK提供了Lookaside内存容器,在初始时它先向系统申请了一块比较大的内存,以后程序每次申请内存的时候不是直接在Windows堆中进行分配,而是在这个容器中,Lookaside结构会智能的避免产生内存空洞,如果申请的内存过多,lookaside结构中的内存不够时,他会自动向操作系统申请更多的内存,如果lookaside内部有大量未使用的内存时,他会自动释放一部分,总之它是一个智能的自动调整内存大小的一个容器。一般应用于以下几个方面:
1. 程序每次申请固定大小的内存
2. 申请和回收的操作十分频繁
使用时首先初始化Lookaside对象,调用函数
VOID
ExInitializeNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside,
IN PALLOCATE_FUNCTION Allocate OPTIONAL,
IN PFREE_FUNCTION Free OPTIONAL,
IN ULONG Flags,
IN SIZE_T Size,
IN ULONG Tag,
IN USHORT Depth
);
或者
VOID
ExInitializePagedLookasideList(
IN PPAGED_LOOKASIDE_LIST Lookaside,
IN PALLOCATE_FUNCTION Allocate OPTIONAL,
IN PFREE_FUNCTION Free OPTIONAL,
IN ULONG Flags,
IN SIZE_T Size,
IN ULONG Tag,
IN USHORT Depth
);
这两个函数一个是操作的是非分页内存,一个是分页内存。
Lookaside:这个参数是一个NPAGED_LOOKASIDE_LIST的指针,在初始化前需要创建这样一个结构体的变量,但是不用填写其中的数据。
Allocate:这个参数是一个分配内存的回调函数,一般这个值填NULL
Free:这是一个释放的函数,一般也填NULL
这两个函数有点类似于C++中的构造与析构函数,如果我们对申请的内存没有特殊的初始化的操作,一般这个两个都给NULL
Flags:这是一个保留字节,必须为NULL
Size:指明明我们每次在lookaside容器中申请的内存块的大小
每次申请的内存块的标志,这个标志与上面的WithTag函Tag:数申请内存时填写的标志相同
Depth:系统保留,必须填0
创建容器之后,可以用下面两个函数来分配内存
PVOID
ExAllocateFromNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside
);
PVOID
ExAllocateFromPagedLookasideList(
IN PPAGED_LOOKASIDE_LIST Lookaside
);
用下面两个函数来释放内存
VOID
ExFreeToNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside,
IN PVOID Entry
);
VOID
ExFreeToPagedLookasideList(
IN PPAGED_LOOKASIDE_LIST Lookaside,
IN PVOID Entry
);
最后可以使用下面两个函数来释放Lookaside对象
VOID
ExDeleteNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside
);
VOID
ExDeletePagedLookasideList(
IN PPAGED_LOOKASIDE_LIST Lookaside
);
其他内存函数
- 内存拷贝函数
VOID
RtlCopyMemory(
IN VOID UNALIGNED *Destination,
IN CONST VOID UNALIGNED *Source,
IN SIZE_T Length
);
需要注意的是这个函数没有考虑到内存重叠的情况,假如内存发生重叠例如这样:
这个时候AC内存块和BD内存块有部分重叠,如果将AC拷贝到BD那么会改变AC的值,这样在拷贝到BD中的值也会发生变化,有可能造成错误,为了保证重叠也可以正常拷贝,可以使用函数
void MoveMemory(
__in PVOID Destination,
__in const VOID* Source,
__in SIZE_T Length
);
填充内存一般使用函数
void FillMemory(
[out] PVOID Destination,
[in] SIZE_T Length,
[in] BYTE Fill
);
另外DDK另外提供了一个将内存清零的函数
VOID
RtlZeroMemory(
IN VOID UNALIGNED *Destination,
IN SIZE_T Length
);
内存比较函数
ULONG
RtlEqualMemory(
CONST VOID *Source1,
CONST VOID *Source2,
SIZE_T Length
);
这个函数返回的是两块内存中相同的字节数,如果要比较两块内存是否完全相同,可以将返回值与Length相比较,如果相等则说明两块内存相同,否则不相同,另外为了实现这个功能DDK提供了一个与该函数同名的宏来判断,具体在编写代码时可以根据情况判断调用的是函数还是宏。
在内核中,对于内存的读写要相当的谨慎,稍不注意就可能产生一个新漏洞或者造成系统的蓝屏崩溃,有时在读写内存前需要判断该内存是否合法可供读写,DDK提供了两个函数来判断内存是否可读可写
VOID
ProbeForRead(
IN CONST VOID *Address,
IN SIZE_T Length,
IN ULONG Alignment//当前内存是以多少字节对齐的
);
VOID
ProbeForWrite(
IN CONST VOID *Address,
IN SIZE_T Length,
IN ULONG Alignment
);
这两个函数在内存不可读写的时候引发一个异常,需要用结构化异常进行处理,这里使用结构化异常的方式与在应用层的使用方式相同
其他数据结构
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
};
struct {
DWORD LowPart;
LONG HighPart;
} u;
LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;
这个结构用来表示64位二进制的整形数据,它是一个共用体,占内存大小是64位8个字节,从定义上来看可以看做一个LONGLONG型数据,也可以看做两个4字节的数据。
Windows内核中的内存管理的更多相关文章
- [3]windows内核情景分析--内存管理
32位系统中有4GB的虚拟地址空间 每个进程有一个地址空间,共4GB,(具体分为低2GB的用户地址空间+高2GB的内核地址空间) 各个进程的用户地址空间不同,属于各进程专有,内核地址空间部分则几乎完全 ...
- 初探Linux内核中的内存管理
Linux内核设计与实现之内存管理的读书笔记 初探Linux内核管理 内核本身不像用户空间那样奢侈的使用内存; 内核不支持简单快捷的内存分配机制, 用户空间支持? 这种简单快捷的内存分配机制是什么呢? ...
- Unity游戏开发中的内存管理_资料
内存是手游的硬伤——Unity游戏Mono内存管理及泄漏http://wetest.qq.com/lab/view/135.html 深入浅出再谈Unity内存泄漏http://wetest.qq.c ...
- 6.关于QT中的内存管理,动态的制作,动态库的调用,静态库的制作
一 QT的内存管理 1 QT中的内存管理是QObject来管理的 2 QT中的内存管理没有cocos2dx中的引用计数 3 组件能够指定父对象 QTimer *timer = QTime ...
- php中的内存管理的介绍(转)
本篇文章给大家带来的内容是关于php中的内存管理的介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 一.php内存管理概述——Zend引擎 由于计算机的内存由操作系统进行管理,所以 ...
- Windows内核中的CPU架构-8-任务段TSS(task state segment)
Windows内核中的CPU架构-8-任务段TSS(task state segment) 任务段tss(task state segment)是针对于CPU的一个概念. 举一个简单的例子,你一个电脑 ...
- C++中的内存管理
在C++中也是少不了对内存的管理,在C++中只要有new的地方,在写代码的时候都要想着delete. new分配的时堆内存,在函数结束的时候不会自动释放,如果不delete我分配的堆内存,则会造成内存 ...
- cocos2dx中的内存管理机制及引用计数
1.内存管理的两大策略: 谁申请,谁释放原则(类似于,谁污染了内存,最后由谁来清理内存)--------->适用于过程性函数 引用计数原则(创建时,引用数为1,每引用一次,计数加1,调用结束时, ...
- Cocos2d-x开发中C++内存管理
由于开始并没有介绍C++语言,C++的内存管理当然也没进行任何的说明,为了掌握Cocos2d-x中的内存管理机制,是有必要先了解一些C++内存管理的知识.C++内存管理非常复杂,如果完全地系统地介绍可 ...
随机推荐
- 一起talk C栗子吧(第一百二十六回:C语言实例--statickeyword)
各位看官们,大家好,上一回中咱们说的内置宏的样例.这一回咱们说的样例是:static关键字. 闲话休提.言归正转. 让我们一起talk C栗子吧! 看官们,C语言提供了static关键字.它常常出如今 ...
- UVA 10465 Homer Simpson(全然背包: 二维目标条件)
UVA 10465 Homer Simpson(全然背包: 二维目标条件) http://uva.onlinejudge.org/index.php? option=com_onlinejudge&a ...
- Swift 是猴还是猿?
欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:段义鹏 导语 Swift和Objective-C是目前开发 Apple App的两门主要语言.Swift自2014年发布到目前为止其行业 ...
- 在Visual Studio 中开发Office Add-in
作者:陈希章 发表于2017年7月13日 "Talk is cheap, show me the code",我们就用代码来说话吧.这一篇将给大家介绍如何开始Office Add- ...
- Natas Wargame Level27 Writeup(SQL表的注入/溢出与截取)
前端: <html> <head> <!-- This stuff in the header has nothing to do with the level --&g ...
- VUE-CLI Vue安装及开发,npm run build无法查看项目的问题
Vue-cli 本地安装vue项目 需要安装node.js,用node命令行npm的方式安装Vue 步骤: 1.进入项目地址安装 npm install vue-cli -g 2.初始化一下 ESli ...
- 10个最有用的 IntelliJ IDEA 插件
IntelliJ IDEA鼓舞了许多Java开发人员编写插件,从J2EE到代码编辑工具再到游戏.现在,它拥有了一个强大的插件生态系统,超过1500可用的插件以及几乎每周都有新的插件出现.在这篇文章中, ...
- Effective Java 第三版——9. 使用try-with-resources语句替代try-finally语句
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- OC学习14——谓词
一.谓词的基本概念与使用 1.谓词(NSPredicate)用于定义一个逻辑条件,通过该条件可执行搜索或内存中的过滤操作.上一篇文章中介绍的集合都提供了使用谓词对集合进行过滤的方法.OC中的谓词操作是 ...
- Xamarin Android Fragment的两种加载方式
android Fragment的重点: 3.0版本后引入,即minSdk要大于11 Fragment需要嵌套在Activity中使用,当然也可以嵌套到另外一个Fragment中,但这个被嵌套的Fra ...