|
堆(heap)是组织内存的一种重要方式,是程序在运行期动态申请空间的主要途径。与栈空间是由编译器产生的代码自动分配和释放不同,栈上的空间需要程序员自己编写代码来申请 (如HeapAlloc)和释放(如HeapFree),而且分配和释放操作应严格匹配,忘记释放或多次释放是不正确的。
与栈上的缓冲区溢出类似,如果想堆上的缓冲区写入超过其大小的内容,也会因为溢出而破坏堆上的其他内容,可能导致严重的问题,包括程序崩溃。栈是分配局部变量和存储函数调用参数及返回位置的主要场所,系统在创建每个线程时会自动为其创建栈,不需要程序员编写任何额外的代码。但在栈上分配内存分配内存的缺点是有栈上的空间较小,不适合分配较大的空间。其次,栈上的变量只是在函数内有效,所以栈只适合分配局部变量,不适合分配较长生存期的全局变量和对象。虽然也可以使用_alloca()这样的函数从栈上分配可变长度的缓冲区,但是这样会给异常处理(EH)带来麻烦,所以栈不适合分配运行期才能决定大小的缓冲区。
堆(Heap)克服了栈的以上的缺点,是程序申请和使用内存空间的另一种重要途径,应用程序通过内存分配函数(如malloc 或HeapAlloc 或new操作符获得内存空间都来自于堆)。
从操作系统的角度来看,堆是系统的内存管理功能向应用软件提供服务的一种方式,通过堆,内存管理器(memory manager)将一块较大的内存空间委托给堆管理器(Heap Manager)来管理,堆管理器将大块的内存分割成不同大小的很多个小块,当应用程序需要小块的内存时,则直接由堆管理器进行分配,当需要很大块内存时,才由内存管理器分配,这样能够缩短应用程序申请分配内存所需要的时间,提高程序运行的速度。NTDLL.DLL中实现了一个通用的堆管理器,目的为用户态的应用程序提供内存服务,通常被称为Win32堆管理器。SDK中公开了一组API来访问Win32堆管理器的功能,比如HeapAlloc, HeapFree等。
用户态的代码应该调用虚拟内存分配API来从内存管理器分配内存。虚拟内存API包括VirtualAlloc, VirtualAllocEx, VirtualFree, VirtualFreeeEx, VirtualLock, VirtualUnlock, VirtualProtect, VirtualQuery等,内核态的代码可以调用以上API所对应的内核函数,例如NtAllocateVirtualMemory, NtProtectVirtualMemory等。利用这些函数可以直接将内存管理器得到内存供应用程序使用。
在内核中,同样有处理琐碎内存的方式,与堆管理器相似,但我们把这些函数称为池管理器(Pool Manager).池管理器公开了一组驱动程序接口(DDI)以向外提供服务,包括ExAllocatePool, ExAllocatePoolWithTag, ExAllocatePoolWithTagPriority, ExAllocatePoolWithQuota, ExFreePool, ExFreePoolwithTag等。
这里将支持C的内存分配函数(malloc)和C++的内存分配运算符(new和delete)统称为CRT内存分配函数,编译器的C运行库会创建一个专门的堆供这些函数所使用,通常称为CRT堆。根据分配对块的方式不同,CRT堆有三种工作模式:SBH (Small Block Heap)模式、旧SBH(Old SBH)模式和系统模式(system Heap)。当创建CRT堆时,会选择其中的一种。对于前两种模式,CRT堆会使用虚拟内存分配API从内存管理器批发大的内存块过来,然后分割成小的堆块满足应用程序的需要。对于系统模式,CRT堆只是把堆块分配请求转发给它所基于的Win32堆,因此处于系统模式的CRT堆只是对win32堆的一种简单封装,在原来的基础上又增加了一些附加的功能。
Windows系统在创建一个新的进程时,在加载器函数执行进程的用户态初始化阶段,会调用RtlCreateHeap函数为新的进程创建第一个堆,称为进程的默认堆,有时称进程堆(Process Heap)。创建好的堆句柄会保存到进程环境块(PEB)的ProcessHeap字段中,可以使用WinDBG的dt命令来观察PEB结构。使用GetProcessHeap API可以获取当前进程的进程堆句柄:
HANDLE GetProcessHeap ( void );
实际上,GetProcessHeap函数只是简单地找到PEB结构,然后读出ProcessHeap字段的值,得到进程堆的句柄后,就可以使用HeapAlloc API从这个堆上申请空间,例如: CMyStruct *pStruct = HeapAlloc(GetProcessHeap(), 0, sizeof ( CMyStruct));除了系统为每个进程创建的默认堆,应用程序也可以通过调用HeapCreateAPI创建其他堆,这样的堆只能被发起调用的进程自己访问,通常被称为私有堆(Private Heap)。
HANDLE HeapCreate ( DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize);
其中dwInitialSize用来指定堆的初始提交大小,dwMaximumSize用来指定堆空间的最大值(保留大小),如果为0,则创建的堆可以自动增长。虽然可以使用任意大小的整数作为这两个参数的值,但系统会自动将其取整为大于该值的临近页边界(即页大小的整数倍)。
应用程序可以调用HeapDestroy来销毁进程的私有堆,HeapDestroy内部主要是调用NTDLL中的RtlDestroyHeap函数,后者会从PEB的堆列表中将要销毁的堆句柄移除,然后调用NtFreeVirtualMemory向内存管理器归还内存。应用程序不必须也不应该销毁进程的默认堆,因为进程内的很多系统函数会使用这个堆。不必担心这会导致内存泄露,因为当进程退出和销毁进程对象时,系统会两次调用内存管理器的MmCleanPorcessAddressSpace函数来释放清理进程的内存空间。 作者:shifters 发表于2011-12-7 9:25:08 原文链接 |
|