内存的最小存储单元为字节,内存中的每一字节都有一个地址,所以在32位系统上,使用32位数来表示内存地址,一共可以表示 4G个字节
地址空间
系统中所有可以用的内存地址集合称为地址空间,比如,如果可以使用4GB的内存,那么其地址空间就是0X00000000 ~ 0XFFFFFFFF
物理内存
硬件系统中真实存在的存储空间称为物理内存,物理内存的访问通过硬件系统总线进行
4G的物理内存空间,地址空间为0X00000000 ~ 0XFFFFFFFF
1G的物理内存空间,地址空间为0X00000000 ~ 0X3FFFFFFF
虚拟地址空间
为了访问内存统一和方便,操作系统允许其上运行的程序可以访问所有4G内存空间中的地址,因此操作系统必须进行一些地址转换工作,将程序访问的地址转换为物理内存中的真实地址,供程序使用的地址空间称为虚拟地址空间,在32位系统上,可以使用的虚拟地址空间大小为4GB
当然,由于虚拟地址空间可能比真实物理地址空间大,系统会将部分虚拟地址空间中的地址转换为硬盘中的数据,在必要时将物理内存中的数据与硬盘中的数据进行交换
这种地址转换和数据交换是通过分页和分段机制实现的
进程的内存空间,用户内存空间与内核内存空间
WINDOWS 操作系统中的每个进程都有属于自己的虚拟地址空间
32位的WINDOWS操作系统将4G(64位,8TB)的虚拟内存划分为两部分,进程使用2GB,称为用户进程内存空间,内核使用2GB, 为内核内存空间
用户内存空间:0X00000000 ~ 0X7FFFFFFF
内核内存空间:0X80000000 ~ 0XFFFFFFFF
分页与分段内存管理,内存映射与地址转换
分段:“选择器+偏移”的形式来表示内存地址,所得到的地址为逻辑地址,系统会将逻辑地址转换为线性地址
线性地址与物理内存地址类似,是平坦的,使用32位数表示,地址是连续的,线性地址空间的范围是0X00000000 ~ 0XFFFFFFFF
如果系统的物理内存为4G,那么可以不使用分页,线性地址直接对应到物理地址上
分页:页的大小可以为4KB,2MB,4MB,最常用为4KB,在4KB的情况下,32位的线性地址被为了3个部分,32位地址的高10位称为页目录索引,次10位称为页表索引,余下的12位为偏移,前20位定位了页的基地址,而最后12位为地址的页内偏移(4KB)
数据共享与保护
有一些进程间共享的数据,系统的可执行代码等,在各个进程间都是一致的,因此没有必要在物理内存中为这些数据保存多份,不同进程的虚拟内存分页可以映射为同样的物理内存分页,这样可以节省物理内存的使用
为了保证这种在映射到相同物理内存页上的内存分页在进程上仍然是私有的,系统还提供了一些保护机制,如果某个进程将某个系统DLL加载入进程内存空间以后,对该DLL中的数据进行了写操作,系统会监视到该操作,并在数据写入这个新分页中。这种对内存中系统DLL数据的写入操作不会影响到其他进程,因此保护了各个进程中数据的独立性,这种机制叫COPY-ON-WRITE ,如果想进程间共享数据,也可以对特定页面不使用这种机制
虚拟内存布局,内存分工,堆与栈
进程虚拟内存分为两部分,系统内核内存空间,用户内存空间
系统内核内存空间中包括驱动程序,系统内核可执行程序,用于内存管理的数据结构,用于进程管理,线程调度的数据结构,各种中断处理程序,系统缓存等
用户内存空间包括了应用程序的代码,数据,系统和用户DLL的代码,各线程的栈,堆等
堆管理
WINDOWS 系统中,每个进程都有自己的堆,每个进程的堆的数量也有所不同,堆不是内存块,而是一种用于内存管理的对象,也是一种内存组织的形式
一个进程有若干个堆,在分配内存前需要指定哪个堆上进行分配,堆的句柄唯一标识了一个堆,在堆上分配内容之前,首先需要获得所要进行分配的堆的句柄
#include <iostream>
#include <windows.h>
void PrintHeapSize(HANDLE hHeap, LPVOID lpMem)
{
size_t dwHeapSize = HeapSize(hHeap, HEAP_NO_SERIALIZE, lpMem);
if (-1 == dwHeapSize)
{
std::cout << "HeapSize error: " << dwHeapSize << std::endl;
}
else
{
std::cout << "内存块大小: " << dwHeapSize << std::endl;
}
}
int main(int argc, char* argv[])
{
//获得系统信息,得到分页大小,内存分配粒度
SYSTEM_INFO si;
GetSystemInfo(&si);
char szTemp[MAX_PATH] = { 0 };
sprintf_s(szTemp, MAX_PATH - 1, "系统内存页大小:0x%x, 系统内存分配粒度:0x%d", \
si.dwPageSize, si.dwPageSize * 10);
std::cout << szTemp << std::endl;
HANDLE hHeap = NULL;
//创建一个最大为10分页大小的堆
if (argc == 2 && std::string(argv[1]) == "-a")
{
std::cout << "创建一个最大为10分页大小的堆" << std::endl;
hHeap = HeapCreate(HEAP_NO_SERIALIZE, si.dwPageSize, si.dwPageSize * 10);
}
else if (argc == 2 && std::string(argv[1]) == "-s")
{
//获得进程已经存在的堆
std::cout << "获得进程已经存在的堆" << std::endl;
hHeap = GetProcessHeap();
}
else
{
//创建大小为1页,可变的堆
std::cout << "创建大小为1页,可变的堆" << std::endl;
hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0, 0);
}
if (NULL != hHeap)
{
//获得当前进程堆数量
HANDLE hHeaps[MAX_PATH] = { 0 };
DWORD dwHeapNum = GetProcessHeaps(MAX_PATH, hHeaps);
if (0 == dwHeapNum)
{
std::cout << "GetProcessHeaps error : " << GetLastError() << std::endl;
}
else
{
std::cout << "当前进程堆数量: " << dwHeapNum << std::endl;
}
//在堆上分配内存,1个页面大小
LPVOID lpMem = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, si.dwPageSize * 1);
if (NULL != lpMem)
{
sprintf_s(szTemp, MAX_PATH - 1, "在堆分配内存,起始地址: 0x%x", lpMem );
std::cout << szTemp << std::endl;
PrintHeapSize(hHeap, lpMem);
//重新分配内存 , 如果用第一种创建的堆,这里会出错
LPVOID lpReAlloc = HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, lpMem, si.dwPageSize * 11);
if (NULL != lpReAlloc)
{
sprintf_s(szTemp, MAX_PATH - 1, "在堆再分配内存,起始地址: 0x%x, 原地址: 0x%x", lpReAlloc, lpMem);
std::cout << szTemp << std::endl;
PrintHeapSize(hHeap, lpReAlloc);
//释放内存
if (!HeapFree(hHeap, HEAP_NO_SERIALIZE, lpReAlloc))
{
std::cout << "HeapFree error: " << GetLastError() << std::endl;
}
}
else
{
std::cout << "HeapReAlloc error: " << GetLastError() << std::endl;
}
if (argc == 2&& std::string(argv[1]) != "-s")
{
if (!HeapDestroy(hHeap))
{
std::cout << "HeapDestroy error: " << GetLastError() << std::endl;
}
}
}
else
{
std::cout << "HeapAlloc error: " << GetLastError() << std::endl;
}
}
else
{
std::cout << "堆句柄为空" << std::endl;
}
return NULL;
}
虚拟内存管理
进程的虚拟内存地址空间内存页面存在3种状态,分别为空闲,保留, 提交,大多数情况下,一页的大小为4KB
空闲:进程不能访问这种页面,因为还没有被分配
保留:页面被保留以备将来之用,页面被分配了,但还不能用,物理地址空间中的内存还不存在与其对应的物理内存分页,也不能被访问
提交:内存已分配,可以并且已经被使用
#include <iostream>
#include <windows.h>
int main()
{
//分配内存,直接分配已提交的内存
LPVOID lpRound = LPVOID(0x100000ff);
LPVOID lpAddress = VirtualAlloc(lpRound, 4000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (NULL != lpAddress)
{
std::cout << "分配内存,直接分配已提交的内存" << std::endl;
CopyMemory(lpAddress, "hello", sizeof("hello"));
char szTemp[MAX_PATH] = {0};
sprintf_s(szTemp, MAX_PATH - 1, "成功分配, 复制成功, 地址为 0x%.8x, 内容: %s", lpAddress, lpAddress);
std::cout << szTemp << std::endl;
//获得内存信息
MEMORY_BASIC_INFORMATION mbi = { 0 };
VirtualQuery(lpAddress, &mbi, sizeof(mbi));
sprintf_s(szTemp, MAX_PATH - 1, "MEM INFO:\n BaseAddress:0x%.8x\nAllocationBase:0x%.8x\n"
"AllocationProtect:0x%.8x\nRegionSize:%u\n"
"State:0x%.8x\nProtect:0x%.8x\nType:0x%.8x",
mbi.BaseAddress, mbi.AllocationBase, mbi.AllocationProtect, mbi.RegionSize,
mbi.State, mbi.Protect, mbi.Type);
std::cout << szTemp << std::endl;
//页面变为保留状态
if (!VirtualFree(lpRound, 4000, MEM_DECOMMIT))
{
std::cout << "Free DECOMMIT" << std::endl;
}
else
{
std::cout << "页面变为保留状态" << std::endl;
VirtualQuery(lpAddress, &mbi, sizeof(mbi));
sprintf_s(szTemp, MAX_PATH - 1, "MEM INFO:\n BaseAddress:0x%.8x\nAllocationBase:0x%.8x\n"
"AllocationProtect:0x%.8x\nRegionSize:%u\n"
"State:0x%.8x\nProtect:0x%.8x\nType:0x%.8x",
mbi.BaseAddress, mbi.AllocationBase, mbi.AllocationProtect, mbi.RegionSize,
mbi.State, mbi.Protect, mbi.Type);
std::cout << szTemp << std::endl;
}
//释放内存
std::cout << "释放内存" << std::endl;
if (!VirtualFree(lpRound, 0, MEM_RELEASE))
{
std::cout << "VirtualFree error: " << GetLastError() << std::endl;
}
}
else
{
std::cout << "VirtualAlloc error: " << GetLastError() << std::endl;
}
return NULL;
}