在WIN32中,进程可以使用的整个地址空间就是应该堆。而堆又分为默认堆和私有堆也叫动态堆,一个进程只有一个默认堆,但是可以又多个私有堆,私有堆位于默认堆中。
标准内存管理函数总是在默认堆中分配和释放内存。
内存映射文件函数相对不同,他是为了文件操作方便而设立的,当堆文件进行操作的时候,先打开文件,然后将文件直接映射到进程的地址空间中,然后我们就可以通过指针像访问内存一样访问文件了。
下面就来介绍一下各种操作内存的函数。
- GlobalMemoryStatus: 使用方法
invoke GlobalMemoryStatus,lpBuffer
lpBuffer指向一个MEMORYSTATUS结构,该结构定义如下:
MEMORYSTATUS STRUCT dwLength DWORD ? ;本结构长度 dwMemoryLoad DWORD ? ;已用内存百分比 dwTotalPhys DWORD ? ;物理内存总量 dwAvailPhys DWORD ? ;可用物理内存 dwTotalPageFile DWORD ? ;交换文件的总大小 dwAvaiPageFile DWORD ? ;交换文件中空闲部分的大小 dwTotalVirtual DWORD ? ;用户可用的地址空间 dwAvailVirtual DWORD ? ;当前空闲的地址空间 MEMORYSTATUS ENDS
设置该结构第一个字段的数值后调用该函数,函数就会将这些系统信息返回到这个结构中,但是因为这个结构里面字段都是32位的,如果是64位的系统的话,显示的数值是不正确的。所以我感觉这个函数现在也没什么用,除非自己再定义一个结构,里面都是64位的字段。、
-
标准内存管理函数
-
GlobalAlloc和LocalAlloc:这两个函数都是分配内存的函数,前者是WIN32下的,后者是WIN16下的,虽然这两个函数在MSDN中已经明确声明不建议使用,因为这两个函数都是为了兼容保留下来的,在WIN32下这两个函数完全一样,都是在底层直接调用了HeapAlloc。但是这里还是稍微介绍以下GlobalAlloc函数。使用方法:
invoke GlobalAlloc,GMEM_FIXED OR GMEM_ZEROINIT,dwBytes
参数GMEM_FIXED表示申请一个固定内存块(固定内存块就是说这块内存的线性地址是固定不变的),第二个参数指出申请的是以字节为单位的内存大小。最后一个参数表示申请的内存块的大小。内存申请成功,函数返回指向内存开始的指针。失败则返回NULL
-
GlobalFree。使用方法:
invoke GlobalFree,lpMemory
该函数释放一块申请的内存,参数是指向那个内存的指针。释放成功函数返回NULL,失败则返回这个指针。
-
可移动内存块
-
可移动内存块就是说这个内存块在不使用的时候允许Windows改变它的线性地址,这样做的目的就是防止内存的碎片化,使内存的利用率更高。
-
使用GlobalAlloc函数也可以申请一个可移动的内存块,只要添加参数GMEM_MOVEABLE就可以,与申请固定内存不一样的是,申请可移动内存返回的不是指针,而是一个句柄,就是申请的内存块的句柄。
-
-
-
GlobalLock:
invoke GlobalLock,hMemory
参数就是申请的可移动内存块的句柄,该函数将会锁定该内存块,这样我们在使用这块内存的时候就不用担心系统会将它移动到别的地方了。用该函数锁定内存块后,函数会返回一个指向该内存块的指针。
-
GlobalUnlock:
invoke GlobalUnlock,hMemory
看到这个函数的名字就知道,这个函数就是解锁被锁定的内存块的。参数是内存块的句柄。当我们不使用了该内存块时应该将该内存块解锁。
-
如果说一个程序是多线程的,在不同的线程中都对该内存进行锁定了,并且当某个线程不用了所以就调用函数将内存解锁了,但是其他线程还在使用它怎么办?Windows会为每个可移动内存提供一个计数器,当该内存被锁定一次,计数器就加一,被解锁一次计数器就减一。只有当计数器为0的时候内存才是真正被解锁。所以不用担心一个线程在使用的时候另一个线程解锁了内存系统会将内存移到别的地方。
-
-
GlobalFree:
invoke GlobalFree,hMemory
该函数释放一个可移动内存块。并且不管内存是否被锁定都可以释放。释放成功返回NULL。参数是内存句柄。
-
GlobalReAlloc:
invoke GlobalReAlloc,hMemory,dwBytes,GMEM_ZEROINIT OR GMEM_MOVEABLE
该函数可以调整一个一个已经被申请后的可移动内存块的大小,第一个参数是内存句柄,第二个参数是新的大小,后面是内存属性。即使内存处于锁定状态下该函数也能成功调用。
-
分配可移动内存块时要使用GMEM_MOVEABLE标志,如果再配合GMEM_DISCARDABLE标志,生成的内存块就是可丢弃的内存块,这样就表示当系统缺内存的时候可以将这块内存丢弃。
-
-
- 上述函数都是由于WIN32为了兼容而存在的,系统仅仅保证这些函数再使用的时候不会出错,而在底层已经不会再去做碎片的拼合工作了,在WIN32下要防止内存碎片化,最好的办法是使用内存池(ps:这个我也不懂)。
-
堆管理函数
-
之前有说到Windows的堆分为默认堆和私有堆,默认堆是程序在初始化的时候系统自动创建的,而私有堆则需要我们自己手动创建,并且私有堆也是在默认堆中创建的,私有堆相当于在默认堆中保留了一大块内存,用堆管理函数可以在这个保留的内存块中分配内存。一个进程可以有多个私有堆,只能有一个默认堆。
-
一个进程如果有多个线程,这些线程在同一时间内只能有一个能够分配和释放默认堆中的内存块。也就是说对默认堆的访问必须是按顺序的。而私有堆的空间是预留的,不同的线程在不同的私有堆中可以同时分配内存。
- HeapCreate:
该函数执行创建一个私有堆,函数执行成功返回堆的句柄。flOptions参数指定堆的属性,可以指定HEAP_NO_SERIALIZE和HEAP_GENERATE_EXCEPTIONS两种。后者用来指定函数失败时的返回值,不指定就返回NULL,指定就返回一个出错码。出错代码的值一般都大于0c0000000h。而前一个属性则是用来控制对私有堆的访问是否要进行独占性的检测,为什么要指定这个属性呢。当从堆中分配内存时,系统又下面的操作步骤:invoke HeapCreate,flOptions,dwInitialSize,dwMaximumSize .if eax && (eax < 0c0000000h) mov hHeap,eax .endif
-
- 将新内存块添加给内存块链接表
- 通过将空闲内存块标记为"已分配"来分配新内存块。
- 寻找一个空闲内存块的地址
- 遍历已分配的内存的空闲的内存块的链接表
- 当两个线程都在执行申请内存,而第一个线程执行了前两步之后系统切换到第二个线程,而第二个线程也执行前两部,并且它们找到的是同一个空闲内存块,那么这样就会起冲突。解决办法就是让单个线程独占对堆和它的链接表的访问权,只有执行完完整的四步之后,才允许第二个线程开始第一个步骤
- dwInitialSize参数指定创建堆时分配给堆的物理内存,随着堆中内存的分配,当这些内存被使用完时,堆的长度可以自动扩展而dwMaximumSize参数指定了能够扩展到的最大值。函数会自动将这两个参数的值调整为页面大小的整数倍。如果dwMaximumSize参数指定为0,则没有最大限制。
-
HeapDestroy:
invoke HeapDestroy,hHeap
如果一个私有堆不需要了可以用这个函数将它释放。函数会释放堆中包含的所有内存块,运行成功返回TRUE。如果进程结束时没有调用这个函数进行释放,系统会自动进行释放。
-
HeapAlloc:
invoke HeapAlloc,hHeap,dwFlags,dwBytes .if eax && (eax < 0c0000000h) mov lpMemory,eax .endif
参数hHeap是之前HeapCreate函数创建堆返回的堆句柄。最后一个参数dwBytes是需要分配的内存块的字节数。而第二个参数是一个标志,它可以是下面这些属性的组合:
-
HEAP_NO_SERIALIZE:如果之前用HeapCreate函数申请堆时指定了这个属性,那么以后这个堆中使用的所有的HeapAlloc函数都不进行独占检测。如果没有指定,那么在这里可以用HeapAlloc函数单独指定本次分配操作不进行独占检测。
-
HEAP_GENERATE_EXCEPTIONS:如果申请内存失败函数则返回具体的出错原因,当然如果用HeapCreate函数申请堆的时候已经指定过这个标志了,这里就不用再指定了。
-
HEAP_ZERO_MEMORY:将分配的内存内的数据全部初始化为0。
-
如果函数分配内存成功,则返回值是指向内存的起始位置的指针,如果分配内存失败,则有两种情况:如果参数dwFlags指定了HEAP_GENERATE_EXCEPTIONS标志,那么返回值可能是下面数值:
-
STATUS_NO_MEMORY:表示内存不够。
-
STATUS_ACCESS_VIOLATION:表示参数不正确或者堆被破坏了。
-
如果没有指定,就返回NULL
-
-
-
- HeapFree:
该函数释放在堆中分配的内存块,第一个参数是堆句柄,最后一个参数是内存块的指针。第二个参数也可以使用HEAP_NO_SERIALIZE标志,含义和之前的相同。invoke HeapFree,hHeap,dwFlags,lpMemory
- HeapReAlloc:
invoke HeapReAlloc,hHeap,dwFlags,lpMemory,dwBytes .if eax && (eax < 0c0000000h) mov lpMemory,eax .endif
该函数可以修改内存块的大小,执行成功则返回指向内存块的指针。hHeap为堆句柄,dwFlags为标志,lpMemory为内存块的指针,dwBytes为内存块新的大小。这里标志有两个属性和之前的略有不同:
-
HEAP_ZERO_MEMORY:当扩大内存块时,将新增的部分初始化为0,当缩小内存块时本参数无效。
-
HEAP_REALLOC_IN_PLACE_ONLY:当内存块的高处被其他内存块占据,这时要扩大内存块就必须移动内存块,如果没有指定这个标志,函数会在需要的时候自动移动内存块,指定了就说明不允许移动内存块,这时如果高处被占据函数会执行失败。
-
-
-
虚拟内存管理函数
-
Windows负责在适当的时间把线程地址映射到物理内存或磁盘上的交换文件上,这就是虚拟内存的基本概念。程序运行的时候,进程中每个地址都可以是下列三种中的一种:
-
占用状态:线程地址已经映射到实际的物理内存当中。也称为已提交状态
-
自由地址:线程地址没有映射到物理内存中,也没有被程序使用
-
保留状态:虽然线程地址没有映射到物理内存中,但是它不会被使用,知道程序希望使用它为止。
-
-
虚拟内存管理函数提供转换虚拟地址空间页状态的能力,一个应用程序可以把内存状态从已提交改变为保留或者把保护模式由可读写改变为只读。应用程序也可以锁定一页内存,不让它被交换到磁盘中。
-
VirtualAlloc:
invoke VirtualAlloc,lpAddress,dwSize,flAllocationType,flProtect
该函数保留或提交一段地址空间,如果是提交操作,函数执行成功返回提交的地址中第一页的起始地址。lpAddress参数指定需要保留或提交的地址空间的位置,如果填NULL则函数会自行在某个最方便的位置保留地址空间范围。dwSize参数表示函数应该分配的地址范围大小,它可以是0到2GB的任意值,系统会把它进位到一个页面的整数倍大小。flProtect参数用来指定保护类型。flAllocationType参数用来决定如何分配地址,它可以是下列取值的组合:
-
MEM_COMMIT:为指定地址空间提交物理内存。
-
MEM_RESERVE:保留指定地址空间,不分配物理内存。
-
MEM_TOP_DOWN:尽可能使用高端的地址空间。
-
-
VirtualFree:
invoke VirtualFree,lpAddress,dwSize,dwFreeType
该函数释放或解除提交的地址空间。lpAddress和dwSize参数指定地址和空间的大小,dwFreeType指定释放地址空间的方式,它可以是下面的数值:
-
MEM_DECOMMIT:为一个已提交的物理内存的地址空间解除提交
-
MEM_RELEASE:释放保留的地址空间
-
-
VirtualLock:
invoke VirtualLock,lpAddress,dwSize
该函数将内存页锁定在物理内存中,锁定的意思就是要求系统总是将指定的内存页保留在物理内存中,不允许将它交换到磁盘文件中。
-
VirtualLock:
invoke VirtualUnLock,lpAddress,dwSize
该函数就是解除锁定。
-