windows内存管理
1.虚拟内存
我们在程序开发中,需要进行程序调试,或者程序在运行时,都需要接触内存的概念,这里的内存说的都是虚拟内存,不是真实的物理内存。windows采用虚拟内存的技术,使得每个程序运行在自己独立的空间中,进程之间不会相互干扰,程序开发时也只需要程序员管理自己进程的内存,使得开发变得相对简单。虚拟内存又使用分段和分页的机制进行管理。
在虚拟内存的技术可以实现下面的需求:
a) 无论物理内存有多大,每一个虚拟内存进程都有4GB的虚拟内存空间。
b) 4GB空间中,高2GB为系统空间,存储系统内核对象和数据,低2GB空间存储用户的对象和数据。
c) 多个虚拟内存中的数据可以由同一部分的物理内存数据映射而来。即windows尽量保证不同进程的同一份数据,在物理内存中只存储一份。
d) 当各个进程中所使用的内存数量超过物理内存的时候,操作系统能够将物理内存中暂时用不到的数据交换到硬盘中。
虚拟内存和物理内存的映射关系如下:
2.winndows中的内存管理方式:
Windows提供了以下3种方式管理内存数据
- 堆:适合用来管理大量的小型对象,使用堆管理方式可以非常方便的帮我们管理所需要的内存空间,而无需去关心一些细节问题,但是堆管理方式的效率较低,堆内存的控制不够灵活
- 虚拟内存:适合用于管理大型的对象数组或大型的结构数组,使用虚拟内存管理方式有丰富的内存管理接口,可以使我们更加精确的控制内存数据
- 虚拟内存:适合用于管理大型的对象数组或大型的结构数组,使用虚拟内存管理方式有丰富的内存管理接口,可以使我们更加精确的控制内存数据
3.代码演示堆的使用:
//堆的使用步骤:
// 1. 创建堆对象或者打开默认堆
// 2. 使用堆对象分配堆块
// 3. 使用堆内存
// 4. 释放堆内存
// 5. 销毁堆对象,注意进程的默认堆不能销毁
HANDLE hHeap = HeapCreate(0, 0, 0);
SYSTEM_INFO si;
//系统信息
GetSystemInfo(&si);
// 获取系统信息
//在堆上分配3个页面大小的内存 ,并将内存清零
LPVOID lpMem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, si.dwPageSize * 3);
//对堆进行操作
memcpy(lpMem, "hello", 5);
printf("%s", lpMem);
HeapFree(hHeap, 0, lpMem);
HeapDestroy(hHeap);
HANDLE hHeap = GetProcessHeap();
// 获取默认堆(进程创建时,系统会自动为其创建默认堆)
SYSTEM_INFO si;
// 系统信息
GetSystemInfo(&si);
// 获取系统信息
//在堆上分配3个页面大小的内存
lpMem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, si.dwPageSize * 3);
HeapFree(hHeap, 0, lpMem);
//系统分配的堆是默认堆不允许释放
//HeapDestroy( hHeap );
3.1 堆内存使用时涉及的函数总结:
API | 说明 |
---|---|
HeapCreate | 堆对象创建,返回堆句柄 |
HeapDestory | 堆对象销毁 |
HeapAlloc | 从指定的堆上分配块 |
HeapReAlloc | 重新分配内存,改变已经分配好的堆内存块大小 |
HeapSize | 获取指定堆的大小 |
HeapFree | 释放HeapAlloc和HeapReAlloc分配的堆块内存 |
GetProcessHeap | 获取当前进程中的默认堆,返回一个句柄 |
GetProcessHaaps | 获取进程中的所有堆,包括堆的数量和各个堆的句柄 |
GetSystemInfo | 获取系统信息 |
3.2.堆遍历使用到的API:
API | 说明 |
---|---|
CreateToolhelp32Snapshot | 可以分别创建进程,线程,进程模块,进程堆的快照 |
Heap32Firtst | 用来首次调用,获得第一个堆对象的信息 |
Heap32Next | 堆遍历中,获取后续堆对象的信息 |
4.虚拟内存
4.1 虚拟内存常用的函数:
虚拟内存是按照分页大小来管理的,目前一个内存分页是4KB,故而管理内存分页的时候,总是以4KB为单位管理的,管理虚拟内存通常用到下面的函数:
函数 | 说明 |
---|---|
VirtualAlloc | 在本进空间中分配一块虚拟内存 |
VirtualAllocEx | 在其他进程空间中分配一块虚拟内存 |
VirtualFree | 在本进程空间中释放一块虚拟内存 |
VirtualFreeEx | 在其他进程空间中释放一块虚拟内存 |
VirtualLock | 将内存锁定,使其不能交换到交换区 |
VirtualUnlock | 为内存解锁 |
VirtualProtect | 修改本进程中一块虚拟内存的属性 |
VirtualProtectEx | 修改其他进程中的内存属属性 |
ReadProcessMemory | 读取远程进程中的内存数据 |
WriteprocessMemory | 将数据写入远程进程 |
VirtualQuery | 查询本进程虚拟内存分布状态 |
VirtualQueryEx | 查询其他进程的虚拟内存分布状态 |
4.2 虚拟内存的三种状态:
状态 | 说明 |
---|---|
空闲(free) | 进程不能访问这种页面,此页面还没有被分配 |
保留(reserve) | 这个页面被预定了,但是还未与物理内存映射,因此这里也是不能访问的 |
提交(commit) | 内存已经被分配了,并且也与物理存储器映射了,进程已经可以访问这里 |
4.3 虚拟内存映射的三种方式:
映射方式 | 说明 |
---|---|
private | 进程私有内存,不被其他进程所共享, 一般是堆,栈 |
mapped | 从别的内存映射而来,或者说物理内存中的一块数据映射都多个进程的虚拟内存中 |
image | 从程序的PE映像映射而来,一般是映像的区段 |
4.4 虚拟内存的属性
属性按页来管理,一个页4KB,不同的页设置不同的属性,存储不同用途的数据。
虚拟内存的属性 | 描述 |
---|---|
PAGE_READONLY | 只读,只能从这个分页读取数据 |
PAGE_READWRITE | 可读可写 |
PAGE_EXECUTE | 可执行 |
PAGE_EXECUTE_READ | 可读可执行 |
PAGE_EXECUTE_READWRITE | 可读可写可执行 |
PAGE_WRITECOPY | 写时复制 |
PAGE_EXCUTE_WRITECOPY | 写时复制可执行 |
PAGE_NOACCESS | 不可访问 |
4.5 虚拟内存的使用
int main()
{
//虚拟内存的状态 3种
//1. MEM_FREE 空闲-没有使用
//2. MEM_RESERVE 保留-占用虚拟地址,但是没有映射到物理地址
//3. MEM_COMMIT 提交-映射到物理地址
LPVOID lpBuff = VirtualAlloc(
0, //预定地址 重什么地址开始申请
1, //大小 默认会以一页大小对齐
MEM_RESERVE | MEM_COMMIT, //申请并提交,可以一次性操作,也可以分开使用两次VirtualAlloc操作
PAGE_READWRITE
);
// 可以在其它进程中申请空间
// VirtualAllocEx(hProcess,.....)
memcpy(lpBuff, "hello", 10);
// 释放时参数三可以设置为MEM_DECOMIT表示释放后内存为保留状态,设置为
// MEM_FREE表示释放后内存处于空闲状态
VirtualFree(lpBuff,1,MEM_FREE);
//静态常量区 可读(不可写)
char * pName = (char*)"hello";
DWORD dwProtect;
//修改页属性
//VirtualProtect(
// pName, //要修改地址
// 10, //修改字节
// PAGE_EXECUTE_READWRITE, //新内存属性 读写执行
// &dwProtect); //旧属性
// VirtualProtectEx(hprocess) 在指定进程中修改
pName[0] = 'x';
printf("%s", pName);
}
5.内存映射
- 文件映射是一种将文件内容映射到进程虚拟内存中的技术。
- 映射成功的文件在内存中可以用视图(view)来应用这段内存,从而达到操作这段内存中对应的文件内容。
- 映射对象分为命名的和未命名(匿名的)。
- 文件映射可以让文件操作变得简单,还可以实现在不同的进程中共享数据。
5.1 内存映射使用的API
API | 说明 |
---|---|
GetSystemInfo | 获取系统信息,用于确定分配粒度 |
CreateFileMapping | 创建一个Mapping对象 |
OpenFileMapping | 打开一个已经命名的Mapping对象(可跨进程) |
MapViewOfFile | 将mapping对象的文件映射到内存 |
FlushViewOfFile | 将映射在内存中的文件写回到硬盘中 |
UnmapViewOfFile | 取消文件映射 |
5.2使用内存映射操作文件
int main()
{
//操纵一个文件,CreateFile,WriteFile,ReadFile
//1. 文件映射 速度快,无需申请空间
HANDLE hFile = CreateFile(
L"1.txt",
GENERIC_WRITE| GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL );
//2.创建一个内存映射对象
HANDLE hFileMap = CreateFileMapping(
hFile, NULL, PAGE_READWRITE, 0, 0x10, L"test");
//3.将物理地址映射到虚拟地址 SIZE_T Size = 10;
LPVOID pFileBuff = MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS, 0, 0, Size);
//4.操作这段内存,写入数据
memcpy(pFileBuff, "hello World--", 11);
//5.刷新到文件
FlushViewOfFile(pFileBuff, 10);
//6.取消映射
UnmapViewOfFile(pFileBuff);
//7.关闭句柄
CloseHandle(hFile);
CloseHandle(hFileMap);
std::cout << "Hello World!\n";
}
5.3 使用内存映射在两个进程中共享数据
进程A
#include<Windows.h>
int main()
{
//1.创建文件映射 HANDLE hFileMap = CreateFileMapping(
INVALID_HANDLE_VALUE, //创建文件映射 -1
NULL, //文件安全属性
PAGE_READWRITE, //页面属性
0, 0x0100,
L"View_1"); // 文件映射对象名
//2.物理地址转换虚拟内存地址
LPVOID lpBuff = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 10); //3.写入数据
memcpy(lpBuff, "hello,world", 10);
//4.等待读取
system("pause");
//5.释放文件映射
UnmapViewOfFile(lpBuff);
CloseHandle(hFileMap);
std::cout << "Hello World!\n";
}
进程B
#include<Windows.h>
int main()
{
//1.打开已经命名的文件映射对象"View_1"
HANDLE hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"View_1");
//2.将物理地址转换虚拟地址
LPVOID lPbuff = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS,0,0,10);
//3.读写数据
printf("buff:%s\n", lPbuff);
//4. 取消映射关系
UnmapViewOfFile(lPbuff);
CloseHandle(hFileMap);
}
6.遍历内存
使用VirtualQuery查询进程虚拟内存的使用情况
使用VirtualQueryEx查询其他进程内存使用的情况
VirtualQueryEx的函数原型如下:
SIZE_T WINAPI VirtualQueryEx(
_In_ HANDLE hProcess, // 目标进程句柄
_In_opt_ LPCVOID lpAddress, // 查询内存的目标地址
_Out_ PMEMORY_BASIC_INFORMATION lpBuffer, // 内存的信息的结构体指针
_In_ SIZE_T dwLength // 传出结构体的大小
);
// 此函数执行后会返回一个MEMORY_BASIC_INFORMATION结构体,里面包含指定查询内存地址的详细信息
// 结构体原型如下:
typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress; // 将参赛向下取整到页面大小
PVOID AllocationBase; // 区域地址,此区域包含传入的地址
DWORD AllocationProtect; // 此区域在预定时的保护属性
SIZE_T RegionSize; // 区域的大小
DWORD State; // 区域的页面状态,MEM_FREE,MEM_RESERVE,MEM_COMMIT
// 如果页面状态是MEM_FREE,则AllocationBase,AlloctionProtect,State,Protect的值都将无效,
// 如果页面状态是MEM_RESERVE,则Protect的值无效
DWORD Protect; // 页面保护属性
DWORD Type; // 页面类型MEM_IMAGE,MEM_MAPPED,MEM_PRIVATE
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
使用VirtualQuery查询本进程虚拟内存的使用情况:
#include<Windows.h>
int main()
{
//1.遍历进程 内存状态
DWORD Addres = 0, Size = 0;
//保存内存信息
MEMORY_BASIC_INFORMATION Basicinfo = {};
//遍历进程所有分页,输出内容
while (VirtualQuery((LPCVOID)Addres, &Basicinfo, sizeof(MEMORY_BASIC_INFORMATION)))
{
Size = Basicinfo.RegionSize;
printf("%08p ", Basicinfo.BaseAddress);
//当前状态
switch (Basicinfo.State)
{
case MEM_FREE: printf("空闲\n"); break;
case MEM_RESERVE: printf("保留\n"); break;
case MEM_COMMIT: printf("提交\n"); break;
default:
printf("未知\n");
break;
}
//如果是提交状态的内存区域,那么遍历所有块中的信息
if (Basicinfo.State == MEM_COMMIT)
{
//遍历所有基址是Address
LPVOID BaseBlockAddress = (LPVOID)Addres; DWORD BlockAddress = Addres;
DWORD dwBlockSize=0;
// 遍历大内存块中的小内存块
while (VirtualQuery((LPVOID)BlockAddress, &Basicinfo, sizeof(Basicinfo)))
{
if (BaseBlockAddress != Basicinfo.AllocationBase)
{
break;
}
printf(" %08X ", BlockAddress);
//查看内存状态,映射方式
switch (Basicinfo.Type)
{
case MEM_PRIVATE: printf("私有 "); break;
case MEM_MAPPED: printf("映射 "); break;
case MEM_IMAGE: printf("镜像 "); break;
default: printf("未知 "); break;
}
if (Basicinfo.Protect == 0)
printf("---");
else if (Basicinfo.Protect & PAGE_EXECUTE)
printf("E--");
else if (Basicinfo.Protect & PAGE_EXECUTE_READ)
printf("ER-");
else if (Basicinfo.Protect & PAGE_EXECUTE_READWRITE)
printf("ERW");
else if (Basicinfo.Protect & PAGE_READONLY)
printf("-R-");
else if (Basicinfo.Protect & PAGE_READWRITE)
printf("-RW");
else if (Basicinfo.Protect & PAGE_WRITECOPY)
printf("WCOPY");
else if (Basicinfo.Protect & PAGE_EXECUTE_WRITECOPY)
printf("EWCOPY");
printf("\n");
//计算所有相同块大小
dwBlockSize += Basicinfo.RegionSize;
// 累加内存块的位置
BlockAddress += Basicinfo.RegionSize;
}
//内有可能大小位空
Size = dwBlockSize? dwBlockSize:Basicinfo.RegionSize;
}
//下一个区域内存信息
Addres += Size;
}
}