WindowsAPI查缺补漏-内存管理
WindowsAPI查缺补漏-内存管理
碎碎念
x64下虚拟地址空间
空指针赋值分区,为0x00000000`00000000~0x00000000`0000FFFF,大小约为64KB。用于帮助开发人员捕捉对NULL指针的赋值,例如这样malloc
失败时返回NULL,帮助开发人员调错:
1 | LPINT pInt = (LPINT)malloc(sizeof(INT)); |
用户模式分区,为0x00000000`00010000~0x00007FFF`FFFFFFFF,大小约为128TB。用于每个进程使用,动态链接库也装载到这里,需要内存管理单元MMU将虚地址映射为物理地址。因为这分区用不着这么大,操作系统选择不支持,如Windows Server 2016只支持24TB,Windows 10只支持8TB。
64KB禁入分区,为0x00007FFF`FFFF0000~0x00007FFF`FFFFFFFF,大小约为64KB。Windows系统保留,禁止访问。
内核模式分区,为0x00008000`00000000~0xFFFFFFFF`FFFFFFFF,大小约为16777208TB。操作系统代码, 如线程调度、内存管理、文件系统、网络支持、设备驱动等代码,应用程序访问时引发访问违例。因为这块地儿实在太大了,大部分不会被使用。
系统信息
GetSystemInfo
获取系统信息:
1 | VOID GetSystemInfo( |
SYSTEM_INFO
1 | typedef struct _SYSTEM_INFO { |
对于wProcessorArchitecture字段枚举值:
枚举值 | 含义 |
---|---|
PROCESSOR_ARCHITECTURE_INTEL | x64 |
PROCESSOR_ARCHITECTURE_AMD64 | x64 |
PROCESSOR_ARCHITECTURE_IA64 | IA-64 |
PROCESSOR_ARCHITECTURE_ARM | ARM |
PROCESSOR_ARCHITECTURE_ARM64 | ARM64 |
PROCESSOR_ARCHITECTURE_UNKNOWN | 未知 |
对于dwPageSize在x86或x64下都为0x00010000。
对于lpMinimumApplicationAddress和lpMaximumApplicationAddress分别为0x00010000和0x00007FFFFFFFFFFF。dwAllocationGranularity为0x00010000。
GlobalMemoryStatusEx
获取内存当前使用情况:
1 | BOOL WINAPI GlobalMemoryStatusEx( |
例如:
1 | MEMORYSTATUSEX ms = { 0 }; |
MEMORYSTATUSEX
1 | typedef struct _MEMORYSTATUSEX { |
GetProcessMemoryInfo
获取指定进程的内存使用情况。一个进程地址空间中被保存在物理内存中的那些页面称为它的工作集,即进程的虚拟地址空间中当前驻留在物理内存中的页面集。
1 | BOOL WINAPI GetProcessMemoryInfo( |
PROCESS_MEMORY_COUNTERS
1 | typedef struct _PROCESS_MEMORY_COUNTERS { |
虚拟地址空间
进程虚拟地址空间可以处于以下状态之一:
预定/保留状态:预定一块虚拟地址空间区域供未来使用,相当于占用。
已提交状态:映射物理地址,只有在提交后才可以被访问。
空闲状态:未预定未提交,进程无法访问,读写导致访问违例或异常。
VirtualAlloc
在调用进程虚拟地址空间预定、提交或预定提交一块地址空间内存区域,并自动初始化为0:
1 | LPVOID WINAPI VirtualAlloc( |
举个例子:
1 | LPVOID lp = VirtualAlloc((LPVOID)(500 * 1024 * 1024 + 8192), 7 * 1024, MEM_RESERVE, PAGE_READWRITE); |
对于例子中的lpAddress参数,系统自动会转化为向下取整后分配粒度的整数倍,这里分配粒度为64KB。所以上述例子返回的分配空间基地址为500MB处。
对于例子中的dwSize参数,系统自动会转化为能够完全覆盖500MB~500MB+8192+7*1024空间区域的页面大小。
以上规则适用于下面各函数。
若lpAddress指定地址不合法或没有闲置区域,或闲置区域不够则返回NULL。
对于flAllocationType指定内存分配的类型:
枚举值 | 含义 |
---|---|
MEM_RESERVE | 预定保留一块虚拟地址空间区域供将来使用,被释放前其他内存分配函数无法使用该空间。 |
MEM_COMMIT | 提交已预定的虚拟地址空间区域,只有提交后才可以被访问。 |
MEM_TOP_DOWN | 预定一块区域并打算使用很久,则通知系统分配尽可能高的内存地址,避免引起内存碎片。使用时lpAddress为NULL。 |
flProtect指定要分配的空间区域内存保护属性:
枚举值 | 含义 |
---|---|
PAGE_NOACCESS | 禁止已提交页面所有访问权限 |
PAGE_READONLY | 已提交的页面可以读取 |
PAGE_READWRITE | 已提交的页面可以读写 |
PAGE_EXECUTE | 已提交的页面可以执行 |
PAGE_EXECUTE_READ | 已提交的页面可以读取执行 |
PAGE_EXECUTE_READWRITE | 已提交的页面可以读写执行 |
VirtualFree
解除提交或释放调用进程虚拟地址空间中页面区域:
1 | BOOL WINAPI VirtualFree( |
对于dwFreeType参数二选一:
枚举值 | 含义 |
---|---|
MEM_DECOMMIT | 解除提交已提交的空间区域 |
MEM_RELEASE | 释放空间区域 |
VirtualAllocEx/VirtualFreeEx
在另一个进程虚拟地址中分配、释放内存,略。
VirtualProtect
更改已提交页区域保护属性:
1 | BOOL WINAPI VirtualProtect( |
更改其他进程页面保护属性用VirtualProtectEx
函数。
VirtualQuery
查询调用进程虚拟地址空间一篇页面区域信息:
1 | SIZE_T WINAPI VirtualQuery( |
MEMORY_BASIC_INFORMATION
1 | typedef struct _MEMORY_BASIC_INFORMATION { |
堆管理
堆管理是对虚拟地址空间操作的高级封装。堆分为默认堆和私有堆,默认堆在进程开始时即默认分配1MB堆。
HeapCreate
创建一个私有堆:
1 | HANDLE WINAPI HeapCreate( |
对于flOptions有:
枚举值 | 含义 |
---|---|
HEAP_CREATE_ENABLE_EXECUTE | 可执行 |
HEAP_GENERATE_EXCEPTIONS | 分配失败则抛出异常 |
HEAP_NO_SERIALIZE | 多个线程对同一个堆同时操作(危险!) |
HeapDestroy
销毁堆:
1 | BOOL WINAPI HeapDestroy( |
HeapAlloc
从堆中分配一块内存:
1 | LPVOID WINAPI HeapAlloc( |
参数dwFlags为:
枚举值 | 含义 |
---|---|
HEAP_ZERO_MEMORY | 分配的清零 |
HEAP_GENERATE_EXCEPTIONS | 分配失败抛出异常 |
HEAP_NO_SERIALIZE | 不进行独占检查(危险!) |
指定HEAP_GENERATE_EXCEPTIONS标志后抛出的异常代码:
枚举值 | 含义 |
---|---|
STATUS_NO_MEMORY | 缺少可用内存或堆损坏 |
STATUS_ACCESS_VIOLATION | 堆损坏或不正确的函数参数 |
例如创建一个不限最大大小的私有堆,并从堆中分配1024字节内存:
1 | LPVOID lp = NULL; |
HeapReAlloc
调整内存块大小:
1 | LPVOID WINAPI HeapReAlloc( |
dwFlags参数选项:
枚举值 | 含义 |
---|---|
HEAP_ZERO_MEMORY | 当重新分配内存块比原来大,则超出部分初始化为0,原部分不影响 |
HEAP_REALLOC_IN_PLACE_ONLY | 不移动内容块。不指定时可能会调整原内存块地址并移动原部分 |
HEAP_GENERATE_EXCEPTIONS | 略 |
HEAP_NO_SERIALIZE | 略 |
HeapFree
释放堆中分配的内存块:
1 | BOOL WINAPI HeapFree( |
dwFlags可以是HEAP_NO_SERIALIZE。
HeapLock/HeapUnlock
锁定/解锁堆,锁定时该线程暂时成为指定堆的所有者,其他线程需要对这个堆操作时只能等待。这俩函数不要使用,在其他堆操作函数中会被自动调用。
1 | BOOL WINAPI HeapLock( |
HeapSize
获取内存实际大小:
1 | SIZE_T WINAPI HeapSize( |
HeapValidate
验证整个堆或堆中某个内存块的完整性:
1 | BOOL WINAPI HeapValidate( |
GetProcessHeaps
获取调用进程的所有堆的句柄:
1 | DWORD WINAPI GetProcessHeaps( |
HeapWalk
枚举指定堆中内存块:
1 | BOOL WINAPI HeapWalk( |
这个函数不详细讲。
内存管理杂项
CopyMemory/RtlCopyMemory
把一块内存从一个位置复制到另一个位置:
1 | VOID CopyMemory( |
RtlCopyMemory
和CopyMemory
就完全是memcpy
。
MoveMemory/RtlMoveMemory
把一块内存从一个位置移动到另一个位置:
1 | VOID MoveMemory( |
RtlMoveMemory
和MoveMemory
就完全是memmove
。
RtlEqualMemory
比较两个内存块指定字节是否相同:
1 | BOOL RtlEqualMemory( |
这玩意儿就完全是memcmp
。