首页 理论教育 Windows中的堆管理与内存分配方式及数据结构

Windows中的堆管理与内存分配方式及数据结构

时间:2023-11-17 理论教育 版权反馈
【摘要】:4.1.2.2堆管理创建堆在Windows中,堆是一段保留或提交的页面,它建立在虚存管理之上,但不同于虚存管理,堆需要有控制模块和内存块链表等数据结构,分配的内存块通过链表连接。

Windows中的堆管理与内存分配方式及数据结构

4.1.2.1 虚存管理

(1)土虚存空间分配

函数VirtualAlloc用于在虚存中分配一段连续的指定字节(页对齐)字节的空间。若没有指定分配的起始位置,则系统会自动选择一块空间,否则系统将试图从指定的分配的起始位置开始分配(需要页对齐)。空间分配后,为空间设置分配的类型和该内存的初始保护属性。

Linux中,使用brk 函数,分配出一大块连续的空间来用作“类Windows虚存”,然后使用mmap 系统调用来分配页面对齐的空间,再设置页面的状态,最后通过mprotect函数来设置页面的访问权限。若没有指定分配的起始位置,则从起始地址开始依次向后寻找符合要求的空间,否则判断从指定地址开始是否存在符合分配要求的空间。

(2)虚存空间释放

函数VirtualFree用于释放从指定地址开始指定字节的虚拟内存(页对齐)。

其中,dwFreeType有两种类型,若dwFreeType为MEM_DECOMMIT,则取消VirtualAlloc提交的页,所有相关页面(不能有空闲的页面)都将会变成保留的。若dwFreeType为MEM_RELEASE时,则释放指定的页,且dwSize必须设置为0,否则函数调用会失败。

在Linux中,根据dwFreeType的取值,决定是将页面设置为空闲还是保留,可以将页面状态写入某一数组中,也可以采用其他方式。

(3)虚拟空间访问权限修改

函数VirtualProtect用来修改从指定位置开始,指定大小的页面的访问权限(页面)。这个待修改的页面必须是提交的,该页面修改前的访问权限将会被保存至一个指定的内存中。

在Linux中,首先判断页面是否满足条件,然后存储页面的访问权限到指定位置,最后通过函数mprotect来修改该页面的访问权限。

(4)页面加锁/解锁

函数VirtualLock 用来锁定从指定位置起,指定字节大小的页面(页对齐),函数只能锁定提交的页面,且页面的访问权限不为PAGE_NOACCESS。函数VirtualUnlock 实现的功能则相反,用来对从指定位置起,指定字节大小的页面(页对齐)解除锁定,需要解除锁定的页面必须已经被锁定。

在Linux中,首先判断待处理的页面是否满足条件,然后使用函数m lock 进行加锁操作,或是使用函数munlock 进行解锁操作,最后修改页面的状态。

(5)获取页表大小

函数GetSystem Info可以用来获取页表大小。与Linux中的getpagesize函数所实现的功能相对等。

4.1.2.2 堆管理

(1)创建堆

在Windows中,堆是一段保留或提交的页面,它建立在虚存管理之上,但不同于虚存管理,堆需要有控制模块和内存块链表数据结构,分配的内存块通过链表连接。堆有两种,一种是可增长堆,一种是不可增长堆。可增长堆有初始大小,但没有最大大小限制。而不可增长堆有初始大小和最大大小限制,任何试图使不可增长堆超过最大大小的分配操作都将失败。

函数HeapCreate用来创建一个指定初始大小和指定最大大小限制的不可增长堆(页对齐)。若最大大小限制为零,即无最大大小限制时,表示函数创建的是一个可增长堆。函数执行成功后,返回堆的句柄,句柄指向一个FILE结构,其对应的文件中存储了堆的首地址。

在Linux中,通过函数mmap 分配页对齐的页面,若是不可增长堆,则将最大大小限制的页面的状态设置为保留,将其中靠前的初始大小的页面的状态设置为提交;若是可增长堆,则将初始大小的页面的状态设置为保留和提交。

为了更好地记录堆的信息,为每个堆设计了头部来记录相关信息,同样也为堆内的每个内存块设计了头部来记录信息。(www.xing528.com)

(2)堆的分配/再分配

函数HeapAlloc用于为给定的堆分配一块指定字节大小的空间。函数Heap-ReAlloc则用于将给定的堆的大小更改为指定字节大小的空间,更改操作中,可以通过参数设置来保持内存块首地址不变,以及将新增空间清零等。

在Linux中,首先先遍历整个内存块链表,查看是否有足够大满足条件的空闲块,若有,则通过mmap 函数分配页面。若没有足够大的空闲块,则视情况不同做不同的处理。若是为不可增长堆分配空间,则在不会导致堆超过最大大小的前提下,接着上一次提交的虚存再通过brk 函数分配出一块内存空间作为“类Windows虚存”,并将状态设置为提交,然后再通过mmap 函数从该空间中分配页面。若是为可增长堆分配空间,则不需要接着上一次提交的虚存分配。

堆空间的再分配,首先先计算给定内存块之后所有地址衔接并且在内存块链表中也衔接的空闲块的大小总和,若总和可以满足分配需求,则合并这些内存块,通过mmap 函数从合并后的空间中分配页面,若不满足条件,则重新为堆分配一块指定大小的空间,将堆中的数据复制到新空间中,并释放原内存块。

(3)堆的大小

函数HeapSize用来获取给定堆的内存块的大小。这一值可以直接从堆的头部信息中读取。

(4)堆释放/销毁

函数HeapFree用于释放指定的堆,函数HeapDestroy则用于销毁指定的堆。堆释放后,堆中分配的内存块仍可用,而堆销毁后,堆中分配的内存以及堆句柄都将不可用。

要在Linux中实现释放堆的功能,只需要将堆内存块的头部中的Free标识置位,并且将所有地址衔接并且在内存块链表中也衔接的空闲内存块进行合并,同时更新相关头部信息。

要销毁指定的堆,首先也要释放堆中的内存块,先判断堆的类型,若为不可增长堆,则一次性释放堆保留或提交的所有页面。若为可增长堆,则分次释放其每次保留的页面。最后调用close函数关闭堆句柄。

(5)返回堆句柄

函数GetProcessHeap 用于返回初始堆的句柄,函数GetProcessHeaps则用于将所有堆的句柄都放入指定的句柄缓冲中。

如前文所述,堆的头部信息中,通过名为HeapHandle数据依次存储了所有堆的句柄,要返回初始堆的句柄只需要返回HeapHandle[0]即可,若要将所有堆的句柄都放入指定的句柄缓冲中,则只需将HeapHandle数组中的所有句柄放入缓冲。

4.1.2.3 内存直接修改

(1)内存拷贝

从指定位置拷贝指定字节的内存到目标位置处,若两段内存不能重叠,则使用函数CopyMemory;若两段内存可以重叠,则使用函数MoveMemory。

这两个函数分别对应于Linux中的memcpy函数和memmove函数。

(2)内存填充

函数FillMemory用于将从指定位置起的指定长度的字节都填充为指定内容,若是要填充的指定内容为零,则可以直接使用函数ZeroMemory来完成。

在Linux中,它们的功能都可以使用函数memset来完成。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈