WIN32汇编语言教程:第10章 内存管理和文件操作 · 10.1 内存管理(7)
invoke VirtualFree,lpAddress,dwSize,dwFreeType
lpAddress和dwSize参数指定地址和地址空间的大小,dwFreeType指定释放地址空间的方式,它可以是以下的数值:
● MEM_DECOMMIT——为一个已经提交物理内存的地址空间解除提交。
● MEM_RELEASE——释放保留的地址空间。
现在来看如何使用它们来保留地址空间和释放保留的地址空间。使用VirtualAlloc函数保留一个地址空间的分配方式使用MEM_RESERVE,由于被保留的地址空间还没有提交给物理内存,是无法访问的,所以保护属性必须使用PAGE_NOACCESS标志,具体的语句是:
invoke VirtualAlloc,NULL,10485760,MEM_RESERVE,PAGE_NOACCESS .if eax mov lpAddress,eax .endif
这一段代码导致系统保留一个10 MB大小的地址空间。当在一个进程中保留地址时,没有物理内存页被提交,也没有在页文件中为它保留空间,而只是阻止了其他内存分配函数对该段地址的请求而已,保留一个地址范围并不保证将来会有可用的物理内存来提交给这些地址。
保留地址的操作是很快的,保留一个小的地址范围和保留一个大范围的地址空间的速度差不多,因为在操作期间,并没有资源分配。
如果要释放保留的地址空间,可以使用MEM_RELEASE方式调用VirtualFree函数:
invoke VirtualFree,lpAddress,0,MEM_RELEASE
lpAddress就是上面调用VirtualAlloc返回的指针,dwSize参数在这里必须为0。当使用上面的VirtualAlloc函数保留了一段地址空间以后,接下来还可以继续多次调用同样的函数提交这段地址空间中的不同页面,所以到最后不同的页面可能处在不同的状态中(提交的和没有提交的)。如果用VirtualFree函数释放这个地址空间,所有的页面必须处在相同的状态下(可以是全部提交的或全部没有提交的),否则释放操作会失败。当不同页面的状态不同的时候,最好首先将所有的已提交页面逐一解除提交,最后再使用上面举例的方法释放整个地址空间。
有时候,两次调用VirtualAlloc函数保留了两段连在一起的地址空间,对于这种情况,虽然两段地址空间实际上是连在一起的,但也无法调用VirtualFree函数将它们一次释放,必须调用两次VirtualFree函数将它们分别释放。
2. 使用保留的地址空间
要使用保留的地址,首先必须提交物理内存给该地址。提交内存到地址与保留内存同样使用VirtualAlloc函数,只是调用的方式使用MEM_COMMIT标志。在已经保留的地址段中,内存可以按一页的大小被分次提交,也可以一次提交所有的保留地址。
当内存被提交时,可能全部被分配为物理内存页,也可能一部分或全部被分配在页文件中,直到它被访问。一旦内存页已提交,系统就会像对待用其他函数分配的内存块一样来对待它们。
使用VirtualAlloc函数提交地址空间的方法是:
invoke VirtualAlloc,lpAddress,4096,MEM_COMMIT,PAGE_READWRITE .if eax mov lpMemory,eax .endif
这个语句将一个页面4 096 B的保留地址提交到物理内存。在提交的时候,lpAddress参数不能指定为NULL,而是要指定一个特定的地址来准确地指示被保留地址的哪一页会被提交。而且,页的属性现在要指定是可以访问的,不能再使用PAGE_NOACCESS,可以使用PAGE_READWRITE和PAGE_READONLY等属性。如果函数执行成功,返回的是被提交地址中第一页的起始线程地址,执行失败将返回NULL。
提交内存的时候,系统只能按页面的整数倍大小提交,函数会自动按照lpAddress和dwSize指定的范围把与这个范围同属一个页面的地址全部提交,所以当lpAddress指定的数值不是一个页的整数倍的时候,返回的lpMemory就不会和指定的lpAddress相同,而是被修改为页的边界地址。
如果要一次提交全部保留的地址空间,那么可以把保留和提交的操作合并到同一次对VirtualAlloc函数的调用中:
invoke VirtualAlloc,NULL,dwSize,MEM_RESERVE or MEM_COMMIT,PAGE_READWRITE .if eax mov lpMemory,eax .endif
这种方法与用GlobalAlloc函数直接分配一块内存没有多大的差别,惟一的好处就是可以自己指定分配的内存块地址。
如果想对已经提交的页面解除提交,让它们从提交状态返回到保留状态,可以使用VirtualFree函数,这时需要使用MEM_DECOMMIT参数:
invoke VirtualFree,lpMemory,dwSize,MEM_DECOMMIT
同样,函数操作的对象是整个页面,如果指定的内存范围不是整个页面,函数会自动将整个范围同属一个页面的地址全部解除提交。
3. 内存页的保护和锁定
除了用VirtualAlloc函数在提交内存的时候指定不同的保护方式外,也可以在以后用VirtualProtect函数来改变虚拟内存页的保护方式。比如,应用程序可以按PAGE_READWRITE来提交一个页并立即将数据写到该页中,然后马上使用VirtualProtect函数将该页的保护方式改为PAGE_READONLY,这样可以有效地保护数据不被该进程中的任何线程重写。VirtualProtect函数的用法是这样的:
invoke VirtualProtect,lpAddress,dwSize,flNewProtect,lpflOldProtect
flNewProtect是新的保护方式,取值可以参考VirtualAlloc函数中的flProtect参数,lpflOldProtect 是指向一个双字的指针,函数会在这里返回原来的保护方式,如果不需要知道原来的方式,可以把这个参数设置为NULL。
VirtualProtect函数还可以用在什么地方呢?MSDN中由Randy Kath书写的一篇文章《Managing Virtual Memory in Win32》中的例子很有代表性:
“一个用于缓冲数据的应用程序接收到一组大小变化的数据流,由于其他应用程序对CPU时间的竞争,数据流可能在某些时候超出进程的能力。为了防止这种现象发生,应用程序可以在开始时为一个缓冲区提交一些内存页,然后使用PAGE_NOACCESS保护来保护内存的顶端页,使得任何想要访问该内存的请求都会产生一个异常。应用程序也在该代码的外层代码中使用一个异常处理程序来处理访问冲突。”
“当处理能力不够的时候,缓冲区会满到这个受保护的顶端页,于是会产生一个访问冲突,这时应用程序就知道缓冲区已经到了其极限,该应用程序可以通过将页保护改变为PAGE_READWRITE来响应,允许该缓冲区接收任何附加的数据,并且继续不间断地执行。同时,应用程序加载另一个线程来减缓数据流,直到该缓冲区恢复到一个理想的操作范围。当情况恢复到正常,顶端的页又返回为PAGE_NOACCESS页,附加的线程也结束了。这样可以将页保护和异常处理程序结合使用来提供独一无二的内存管理机会。”
另外,应用程序还可以使用VirtualLock和VirtualUnlock函数,它们的功能分别是将内存页锁定在物理内存中以及解除锁定。这两个函数的语法很简单:
invoke VirtualLock,lpAddress,dwSize invoke VirtualUnlock,lpAddress,dwSize
“锁定”的意思是要求系统总是将指定的内存页保留在物理内存中,不许将它交换到磁盘页文件中。如果程序中有些内存被频繁使用,将它们保留在物理内存可以提高访问的速度。由于锁定太多的页面会导致其他页面被频繁交换到页文件中,所以Windows限制每个进程能同时锁定的页数不能超过30个。只有已经被提交的内存页才能被锁定,对一个保留的地址进行锁定操作是不能成功的。
10.1.6 其他内存管理函数
Win32中还有其他的一些内存管理函数,可以用来完成一些辅助的功能,如内存填充、移动以及测试函数等。
1. 填充和移动内存
填充和移动内存本来就可以用几句简单的代码实现,如下面的代码可以将从szSource开始的dwSize大小的内存块移动到szDest处:
mov esi,offset szSource mov edi,offset szDest mov ecx,dwSize cld rep movsb
上页:第10章 内存管理和文件操作 · 10.1 内存管理(6) 下页:第10章 内存管理和文件操作 · 10.1 内存管理(8)