windows内存管理
windows提供3种方式管理内存:
- 堆:适合管理大量小型对象,方便,但效率低,控制不灵活;
- 虚拟内存:适合管理大型对象数组,有丰富接口,控制更精确;
- 文件映射:适合管理大型数据流,以及同一机器上的不同进程间共享数据。
还值得一提的是页交换:当各个进程所使用的内存数量超出物理内存的时候,操作系统还能够将物理内存中暂时用不到的数据交换到硬盘中。
堆
堆内存管理是建立在虚拟内存管理基础之上的。
创建和获取:
- HeapCreate(),在进程中创建一个堆并返回句柄
- GetProcessHeap(),获取当前进程中的一个堆的句柄
- GetProcessHeaps(),获取所有堆的句柄
- HeapDestroy():销毁由HeapCreate()创建的堆
HeapCreate()的参数有点东西,去看msdn吧。
进程创建时有一个默认堆,不能销毁。可以不创建而直接GetProcessHeap()。
申请和释放:
- HeapAlloc(),从指定的堆上分配块
- HeapReAlloc(),改变已经分配好的堆内存块大小
- HeapFree(),释放上面两个申请的内存
还有用来遍历的三兄弟:
- CreateToolhelp32Snapshot()
- Heap32First()
- Heap32Next()
其它:
- GetSystemInfo()
- HeapSize()
SYSTEM_INFO有页大小成员,分配大小一般是页大小的倍数。
int main()
{
HANDLE hHeap = HeapCreate(0,0,0);
SYSTEM_INFO si = { 0 };
GetSystemInfo(&si);
LPVOID pBuf = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, si.dwPageSize * 2);
printf("heap size: 0x%x\n", HeapSize(hHeap, 0, pBuf));
memcpy(pBuf, "hello\n", 6);
printf(pBuf);
HeapFree(hHeap, 0, pBuf);
HeapDestroy(hHeap);
system("pause");
return 0;
}
虚拟内存
在CPU性能过剩的今天,提升内存容量与频率是提高计算机性能的有力手段。对于内存管理,有这样几个条件:
- 一个进程不能随意访问其它进程内存;
- 进程需要更多内存时,可以动态分配;
- 每个进程的内存管理方式一样;
- 内存需要有属性,如rwe;
- 程序员无法察觉这些设计意图
于是,windows引出虚拟内存的概念,除了上面的,还有以下特点:
- 每个进程有4GB虚拟内存空间;
- 低2GB是用户空间,低2GB用户代码无法访问高2GB系统空间;
- 虚拟地址到物理地址的映射由os内核完成;
- 一个进程的虚拟空间只有一部分映射到部分物理内存;
- 尽量保证不同进程的同一数据,物理内存中只有一份。
- 物理内存用爆时,将但是不用的数据swap到硬盘中。
具体的虚拟地址到物理地址映射,是使用分段和分页(主要)实现的。
虚拟内存三种状态
- free空闲,进程不能访问这种页面,此页面还没有被分配
- reserve保留,这个页面被预定了。但是还未与物理内存映射,因此这里也是不能访问的
- commit提交,内存已经被分配了,并且也与物理存储器映射了,进程已经可以访问这里
刚创建的虚拟内存只是逻辑上存在,没有与物理内存建立映射,这时的虚拟内存就是空闲的free或者说未分配的unallocated。
用VirtualAlloc()
分配某个内存区域(region)的行为叫做预定。
对于一个虚拟空间,大部分区域是空闲的,与提交的区域相比就像海洋和陆地。
虚拟内存映射的三种方式
- image : 从程序的PE映像映射而来,一般是映像的区段
- private : 进程私有内存,不被其他进程所共享, 一般是堆,栈
- mapped: 从别的内存映射而来
虚拟内存的页面属性
PAGE_NOACCESS
PAGE_READONLY
PAGE_READWRITE
PAGE_EXECUTE
PAGE_EXECUTE_READWRITE
PAGE_WRITE_COPY
: 写时拷贝 (用户不能使用)
API及代码示例
- VirtualAlloc():分配、预定一块虚拟内存
- VirtualAllocEx()
这俩api可以与物理内存建立映射。大小只能是分页大小的整数倍。
系统分配空间时,起始地址是内存分配粒度的整数倍,x86架构下,粒度是64kB(0x10000)。
LPVOID WINAPI VirtualAlloc(
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
If the lpAddress parameter is NULL, this value is rounded up to the next page boundary.
- VirtualFree()
- VirtualFreeEx()
BOOL WINAPI VirtualFree(
_In_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD dwFreeType
);
If the dwFreeType parameter is MEM_RELEASE, this parameter must be 0 (zero).
int main()
{
LPVOID pVirAlloc = VirtualAlloc(0,
0x10000,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
memcpy(pVirAlloc, "hello", 7);
printf("%s\n", pVirAlloc);
VirtualFree(pVirAlloc, 0, MEM_RELEASE);
system("pause");
return 0;
}
- VirtualProtect():修改保护属性
- VirtualProtectEx()
这个api可以实现改写常量区。改写时不会完全按照size参数修改,而是按照4kB分页来改。
第二个参数?????
int main()
{
char* p = "hello";
DWORD dwOldProtect;
VirtualProtect(p, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect);
p[1] = 'a';
printf("%s\n", p);
system("pause");
return 0;
}
- VirtualLock()
- VirtualUnlock()
- ReadProcessMemory() :读取远程进程内存;
- WriteProcessMemory() :将数据写入远程进程内存
- VirtualQuery():查询内存状态
- VirtualQueryEx()
带Ex的就是可以对其它进程操作。
文件映射
文件映射是将文件映射到进程虚拟内存的高效机制,直接建立内核对象,将文件直接映射到虚拟内存,跨过物理内存这一过程,读写文件时直接读写硬盘。内部设计当然贼麻烦。
映射成功的文件可以用视图(View)来引用这段内存,映射对象分为:
- 命名的
- 未命名的
命名的映射对象可以跨进程读写。
映射
作用:
- 操作大文件
- 进程间共享内存
- IPC
API | Desc |
---|---|
GetSystemInfo() | 用于获取分配粒度 |
CreateFileMapping() | 创建mapping对象 |
OpenFileMapping() | 打开已命名的mapping对象,可跨进程。 |
MapViewOfFile() | 将mapping对象文件映射进内存 |
UnmapViewOfFile() | 取消文件映射 |
FlushViewOfFile() | 将内存映射写回硬盘 |
获得映射对象后还要记着映射进内存。最后取消映射。
HANDLE WINAPI CreateFileMapping(
_In_ HANDLE hFile,
_In_opt_ LPSECURITY_ATTRIBUTES lpAttributes,
_In_ DWORD flProtect,
_In_ DWORD dwMaximumSizeHigh,
_In_ DWORD dwMaximumSizeLow, // 映射文件低4字节 0表示映射整个文件
_In_opt_ LPCTSTR lpName
);
LPVOID WINAPI MapViewOfFile(
_In_ HANDLE hFileMappingObject,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwFileOffsetHigh,
_In_ DWORD dwFileOffsetLow, //文件低4字节 这个值必须是内存分页的倍数
_In_ SIZE_T dwNumberOfBytesToMap //映射文件的大小 0表示映射所有
);
简单映射
int main()
{
HANDLE hFile = CreateFile(
L"..\\Debug\\CPPTest.exe",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
HANDLE hFileMap = CreateFileMapping(hFile,
NULL,
PAGE_READWRITE,
0,
0,
NULL);
LPVOID pBuf = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
printf("%s\n", pBuf);
//*(DWORD*)pBuf = 0x4d5a;
UnmapViewOfFile(pBuf);
CloseHandle(hFile);
CloseHandle(hFileMap);
system("pause");
return 0;
}
/*
MZ?
*/
文件共享
创建映射时要记住参数是INVALID_HANDLE_VALUE
。
创建并写:
int main()
{
//创建命名的文件映射
HANDLE hMap = CreateFileMappingW(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
16,
L"file_mapping_test");
if (NULL == hMap || INVALID_HANDLE_VALUE == hMap)
{
return FALSE;
}
//创建view,映射到内存
LPVOID pBuf = MapViewOfFile(
hMap,
FILE_MAP_ALL_ACCESS,
0,
0,
16);
if (NULL == pBuf)
{
return FALSE;
}
wcscpy_s(pBuf, 6, L"hello");
while (*(PBYTE)pBuf)
Sleep(500);
UnmapViewOfFile(pBuf);
CloseHandle(hMap);
system("pause");
return 0;
}
读取:
int main()
{
//创建命名的文件映射
HANDLE hMap = OpenFileMapping(
FILE_MAP_ALL_ACCESS,
FALSE,
L"file_mapping_test");
if (NULL == hMap || INVALID_HANDLE_VALUE == hMap)
{
return FALSE;
}
//创建view,映射到内存
LPVOID pBuf = MapViewOfFile(
hMap,
FILE_MAP_ALL_ACCESS,
0,
0,
16);
if (NULL == pBuf)
{
return FALSE;
}
wprintf_s(L"%s\n", pBuf);
*(PBYTE)pBuf = 0;
UnmapViewOfFile(pBuf);
CloseHandle(hMap);
system("pause");
return 0;
}
遍历内存
需要的api为VirtualQueryEx()
,有一个_out_
参数为内存结构体。
typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress;
PVOID AllocationBase;
DWORD AllocationProtect;
SIZE_T RegionSize;
DWORD State; // MEM_COMMIT, MEM_FREE , MEM_RESERVE
DWORD Protect;
DWORD Type; // MEM_IMAGE, MEM_MAPPED, MEM_PRIVATE
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
BaseAddress
包含于VirtualAlloc()
分配的AllocationBase
区域。
如果state为
MEM_FREE
,AllocationBase, AllocationProtect, Protect, Type
成员无效;MEM_RESERVE
,Protect
无效
#include <stdio.h>
#include <windows.h>
int main()
{
MEMORY_BASIC_INFORMATION stcMem;
LPVOID pAddr = 0;
LPVOID pLastBase = (LPVOID)-1;
DWORD dwSize = 0;
DWORD aProtectTypes[] = {
PAGE_NOACCESS,
PAGE_READONLY,
PAGE_READWRITE,
PAGE_WRITECOPY,
PAGE_EXECUTE,
PAGE_EXECUTE_READ,
PAGE_EXECUTE_READWRITE,
PAGE_EXECUTE_WRITECOPY,
};
char *aProtectDesc[] = {
"---",
"r--",
"rw-",
"rw-",
"cow",
"--e",
"r-e",
"rwe",
"ewcopy"
};
int cProtect = _countof(aProtectTypes);
while (VirtualQuery(pAddr, &stcMem, sizeof(stcMem)) )
{
if (pLastBase != stcMem.AllocationBase)
{
pLastBase = stcMem.AllocationBase;
printf("\nAllocationBase: %p ", stcMem.AllocationBase);
printf("\t size: 0x%08x", stcMem.RegionSize);
printf("\t mem state:");
switch (stcMem.State)
{
case MEM_FREE:
printf("free\n");
break;
case MEM_RESERVE:
printf("reserve\n");
break;
case MEM_COMMIT:
printf("commit\n");
break;
default:
printf("Unknown mem type\n");
break;
}
}
else
{
printf("\t pAddr: %p \n", pAddr);
printf("\t\t RegionSize: 0x%x\n", stcMem.RegionSize);
printf("\t\t mem type: ");
// 查看该内存的类型
switch (stcMem.Type)
{
case MEM_PRIVATE: printf(" private \n"); break;
case MEM_MAPPED: printf(" mapped \n"); break;
case MEM_IMAGE: printf(" image \n"); break;
default:
printf("Unknown mem type \n");
break;
}
printf("\t\t protect: ");
for (int i = 0; i < cProtect; ++i)
{
if (stcMem.Protect & aProtectTypes[i])
{
printf(aProtectDesc[i]);
printf("\n");
}
}
}
dwSize = stcMem.RegionSize;
pAddr = (LPVOID)((ULONGLONG)pAddr + dwSize);
}
system("pause");
return 0;
}