WIN32汇编语言教程:第10章 内存管理和文件操作 · 10.1 内存管理(6)
其中dwBytes指定了新的大小,dwFlags为标志,可以组合指定的标志有:
● HEAP_GENERATE_EXCEPTIONS——参见HeapAlloc函数的说明。
● HEAP_NO_SERIALIZE——参见HeapAlloc函数的说明。
● HEAP_ZERO_MEMORY——当扩大内存块的时候,将新增的部分初始化为0,当缩小内存的时候,本参数无效。
● HEAP_REALLOC_IN_PLACE_ONLY——与GlobalReAlloc函数类似,当内存块的高处已经被其他内存块占据的时候,要扩大内存块必须将它移动位置,当没有指定这个标志的时候,函数会在需要的时候自动移动内存块,如果指定了这个标志,则不允许内存块移动,这时,当内存块高处不是空闲的时候,函数的执行会失败。
如果函数执行成功,返回值是指向新内存块的指针,显而易见,当缩小或扩大内存块时指定了HEAP_REALLOC_IN_PLACE_ONLY标志,则这个指针必定和原来的相同,否则的话,它既有可能和原来的指针相同也有可能不同。
3. 其他堆管理函数
除了上面的一些函数,堆管理函数中还有HeapLock,HeapUnlock,GetProcessHeaps,HeapCompact,HeapSize,HeapValidate和HeapWalk等函数。
GetProcessHeaps函数用来列出进程中所有的堆(注意:不要和用来获取默认堆句柄的GetProcessHeap函数搞混),HeapWalk用来列出一个堆中所有的内存块,HeapValidate函数用来检验一个堆中所有内存块的有效性。这3个函数平时很少使用,一般在调试的时候使用。
GetProcessHeaps函数的用法是:
invoke GetProcessHeaps,NumberOfHeaps,lpHeaps
其中lpHeaps是一个指针,指向用来接收堆句柄的缓冲区,NumberOfHeaps参数指定了这个缓冲区中可以存放句柄的数量,显然,缓冲区的长度应该等于NumberOfHeaps乘以4字节。函数执行后,进程中所有堆的句柄全部返回到缓冲区中,其中也包括默认堆的句柄。
HeapWalk函数的用法是:
.repeat invoke HeapWalk,hHeap,lpEntry push eax ;检测缓冲区中的内存块信息 pop eax .until !eax
hHeap是需要操作的堆句柄,lpEntry指向一个包含有PROCESS_HEAP_ENTRY结构的缓冲区。调用HeapWalk函数时,函数每次在PROCESS_HEAP_ENTRY结构中返回一个内存块的信息,如果还有其他内存块,函数返回TRUE,程序可以一直循环调用HeapWalk函数直到函数返回FALSE为止。在多线程的程序中使用HeapWalk,必须首先使用HeapLock函数将堆锁定,否则调用会失败。
HeapValidate用来验证堆的完整性或堆中某个内存块的完整性:
invoke HeapValidate,hHeap,dwFlags,lpMemory
其中hHeap指定要验证的堆。如果lpMemory为NULL,那么函数顺序验证堆中所有的内存块;如果lpMemory指定了一个内存块,则只验证这个内存块。dwFlags是标志,可以指定HEAP_NO_SERIALIZE 标志。如果验证结果是所有的内存块都完好无损,函数返回非0值,否则函数返回0。
HeapLock函数和HeapUnlock函数用来锁定堆和解锁堆。这两个函数主要用于线程的同步,当在一个线程中调用HeapLock函数时,这个线程暂时成为这个堆的所有者,也就是说只有这个线程能对堆进行操作(包括分配内存、释放、调用HeapWalk等函数),在别的线程中对这个堆的操作会等待在那里,直到所有者线程调用HeapUnlock解锁为止。这两个函数的语法如下:
invoke HeapLock,hHeap invoke HeapUnlock,hHeap
如果函数执行成功,返回值为非0值,否则函数返回0。一般来说,很少在程序中使用这两个函数,而总是使用HEAP_NO_SERIALIZE标志来进行同步控制,指定了这个标志的话,HeapAlloc,HeapReAlloc,HeapSize和HeapFree等函数会在内部自己调用HeapLock和HeapUnlock函数。
HeapCompact函数用于合并堆中的空闲内存块并释放不在使用中的内存页面:
invoke HeapCompact,hHeap,dwFlags
HeapSize函数返回堆中某个内存块的大小,这个大小就是使用HeapAlloc以及HeapReAlloc时指定的大小:
invoke HeapSize,hHeap,dwFlags,lpMemory
lpMemory指定了需要返回大小的内存块,函数的返回值是内存块的大小,如果执行失败,函数返回−1。
10.1.5 虚拟内存管理函数
不管某个进程实际可用的物理内存是多少,每个进程可以使用的地址空间总是2 GB,用户程序不必考虑一个线程地址对应的物理内存究竟安排在什么地方——是在真正的物理内存中?在磁盘交换文件中?还是根本没有物理内存与之对应。
一个进程的整个地址空间是客观存在的,但是否有内存与该段地址空间中的地址相关联是另外的问题,Windows负责在适当的时间把线程地址映射到物理内存或磁盘上的交换文件上,这就是虚拟内存的基本概念。
在程序运行的时候,进程中每个地址都可以处于下列3种状态的1种中:
● 占用状态——线程地址已经映射到实际的物理内存中。也称为已提交状态。
● 自由状态——没有映射到物理内存中,线程地址当前也没有被程序使用。
● 保留状态——虽然线程地址没有映射到物理内存中,但它不会被使用,直到程序希望使用它为止。
进程开始的时候,所有地址都是处于自由状态的,这意味着它们都是自由空间并且可以被提交到物理内存,或者为将来使用而保留起来。任何自由状态地址在能够被使用前,必须首先被分配为保留状态或已提交状态。
当使用标准内存管理函数分配内存的时候,用户无法指定内存块位于哪个线程地址,或者不要位于哪个线程地址,而使用虚拟内存管理函数可以做到这一点。但这样做的理由是什么呢?考虑这样一种情况:程序需要一个内存块用做缓冲区,随着程序的运行,这个内存块可能随时需要扩展,最大可能扩展为100 MB大小,所以希望系统在分配其他内存块的时候不要使用这个内存块后面100 MB大小范围内的地址空间,这样,就可以随时将内存块扩大而不必移动它的位置。
除了这样一个主要的用途外,虚拟内存管理函数还提供转换虚拟地址空间页状态的能力,一个应用程序可以把内存的状态从已提交改变为保留,或把保护的模式从 PAGE _READWRITE (可读写)改变为 PAGE_READONLY(只读),从而防止对某段地址空间的写访问;应用程序也可以锁定一页内存,不让它被交换到磁盘中。
虚拟内存管理函数是一组名字以Virtual开头的函数,主要包括下面几种:
● VirtualAlloc和VirtualFree——进行地址空间的分配和释放工作。
● VirtualLock和VirtualUnlock——对内存页进行锁定和解锁。
● VirtualQuery或VirtualQueryEx——查询内存页的状态。
● VirtualProtect或VirtualProtectEx——改变内存页的保护属性。
1. 保留和释放地址空间
保留或提交一段地址空间,使用VirtualAlloc函数,释放或解除提交地址空间,则使用VirtualFree函数。先来看Virtualalloc函数的使用方法:
invoke VirtualAlloc,lpAddress,dwSize,flAllocationType,flProtect
lpAddress参数指定需要保留或提交的地址空间的位置,参数可以使用NULL值也可以指定一个具体的地址。NULL值表示由函数自行在某个最方便的位置保留地址范围,非NULL值指定了一个准确的初始地址。如果函数返回NULL,表示执行失败,否则返回一个指针,指向被保留地址范围的开始位置。
dwSize参数表示函数应该分配的地址范围大小,它可以是0 B~2 GB的任意值,但系统会自动把它进位到一个页面的整数倍大小。另外,虽然参数的最大值可以指定为2 GB,但实际上能够被保留的最大值是该进程中最大的连续自由地址空间。
flAllocationType参数用来决定如何分配地址,它可以是以下取值的组合:
● MEM_COMMIT——为指定地址空间提交物理内存。
● MEM_RESERVE——保留指定地址空间,不分配物理内存。
● MEM_TOP_DOWN——尽可能使用高端的地址空间。
flProtect参数用来指定保护的类型,它可以是以下取值之一:
● PAGE_READONLY——为已提交物理内存的地址空间设定只读属性。
● PAGE_READWRITE——为已提交物理内存的地址空间设定可读写属性。
● PAGE_EXECUTE——为已提交物理内存的地址空间设定可执行属性。
● PAGE_EXECUTE_READ——为已提交物理内存的地址空间设定可读和可执行属性。
● PAGE_EXECUTE_READWRITE——为已提交物理内存的地址空间设定可读、可写和可执行属性。
● PAGE_NOACCESS——将保留的地址空间设定为不可存取模式。
VirtualFree函数的使用语法是:
上页:第10章 内存管理和文件操作 · 10.1 内存管理(5) 下页:第10章 内存管理和文件操作 · 10.1 内存管理(7)