进程的虚拟地址空间内存页面存在三种状态,分别是空闲的free、保留的reserved和提交的committed。多数情况下,一页大小是4KB:
空闲页面:进程不能访问此类页面,因为此类页面还没有被分配,任何属于这种页面的虚拟内存地址进行访问都将引发访问异常;
保留页面:页面被保留以备将来只用,此类页面已经被分配,但是还没有使用,物理地址空间中的内存中不存在其对应的物理内存分页。处理保留状态的内存分页也不能被访问;
提交页面:内存已经被分配,并且已经被使用,具有与之对应的物理地址空间中的内存分页。
VirtualAlloc函数功能是保留或提交当前进程(calling process)的内存页面,将空闲的内存页面变为保留的或提交的,将保留的页面变为提交的:
LPVOID WINAPI VirtualAlloc(
__in_opt LPVOID lpAddress, //分配的起始位置。
//如果要保留一段内存区域,函数会将其自动向最近的一个分配粒度对齐;
//如果要提交一段内存区域,函数将会向最近的一个页面对齐;
//如果为NULL,系统自行决定在什么地方分配
__in SIZE_T dwSize, //所需要分配的内存字节大小,
__in DWORD flAllocationType, //分配类型:MEM_COMMIT(提交)、MEM_RESERVED(保留)..
__in DWORD flProtect //内存保护属性:PAGE_READWRITE、PAGE_EXECUTE…
);
返回值:
成功时,返回指向分配到的内存的起始地址的指针;
失败时,返回NULL。
VirtualFree函数将当前进程内存状态从提交变为保留,或将保留变为空闲,或同时进行:
BOOL WINAPI VirtualFree(
__in LPVOID lpAddress, //需要改变状态的内存区域的起始地址
__in SIZE_T dwSize, //需要改变状态的内存区域的字节大小
__in DWORD dwFreeType //MEM_DECOMMIT—将内存变为保留状态
//MEM_RELEASE—释放内存,将内存变为空闲状态
);
返回值:
成功时,返回非零值;
失败时,返回零值。
上面两个函数的增强版本唯一的区别是用于在另一个进程的虚拟地址空间分配内存和释放内存:
LPVOID WINAPI VirtualAllocEx(
__in HANDLE hProcess, //指定另一个进程的句柄,该函数在该句柄指定的进程内分配内存
//该句柄需具有PROCESS_VM_OPERATION权限
__in_opt LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD flAllocationType,
__in DWORD flProtect
);
BOOL WINAPI VirtualFreeEx(
__in HANDLE hProcess, //指定另一个进程的句柄,该函数在该句柄指定的进程内释放内存
//该句柄需具有PROCESS_VM_OPERATION权限
__in LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD dwFreeType
);
使用VirtualAlloc和VirtualFree等函数时,其操作的基本单位是内存页面,函数会改变整个页面的状态,在设置lpAddress和dwSize参数时要注意。如果从lpAddress到(lpAddress+dwSize)内存区域跨越了两个页面,则这两个页面的状态都会改变,而不是仅改变这段内存区域。
实例代码:使用结构化异常处理机制来动态分配虚拟内存:
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h> //for exit
#define PAGELIMIT 80 //请求的内存页数量
LPTSTR lpNextPage; //下一个请求的内存页的地址指针
DWORD dwPages = 0; //目前获得的内存页数量
DWORD dwPageSize; //本机的内存页大小
INT PageFaultExceptionFilter(DWORD dwCode)
{
LPVOID lpvResult;
//如果异常不是页错误,则退出
if(dwCode != EXCEPTION_ACCESS_VIOLATION)
{
printf("Exception code=%d/n", dwCode);
return EXCEPTION_EXECUTE_HANDLER;
}
printf("Exception is a page fault/n");
//如果保留的内存页用完了,退出
if(dwPages >= PAGELIMIT)
{
printf("Exception: out of pages/n");
return EXCEPTION_EXECUTE_HANDLER;
}
//否则,请求新的提交页
lpvResult = VirtualAlloc(
(LPVOID)lpNextPage, //下一个将要提交页的地址
dwPageSize, //将要提交的页字节大小
MEM_COMMIT, //分配一个提交页
PAGE_READWRITE); //读写权限
if(lpvResult == NULL)
{
printf("VirtualAlloc failed/n");
return EXCEPTION_EXECUTE_HANDLER;
}
else
{
printf("Allocating another page/n");
}
//增加已提交的页计数,移动指针到下一未提交页起始地址
dwPages++;
lpNextPage = (LPTSTR)((PCHAR)lpNextPage + dwPageSize);
return EXCEPTION_CONTINUE_EXECUTION;
}
VOID ErrorExit(LPTSTR lpMsg)
{
printf("Error! %s with error code of %ld/n",
lpMsg, GetLastError());
exit(0);
}
VOID main(VOID)
{
LPVOID lpvBase; //要分配的起始地址
LPTSTR lpPtr;
BOOL bSuccess;
DWORD i;
SYSTEM_INFO sSysInfo;//系统信息
GetSystemInfo(&sSysInfo);
printf("This computer has page size %d/n", sSysInfo.dwPageSize);
dwPageSize = sSysInfo.dwPageSize;
//在进程的虚拟地址空间中申请保留页
lpvBase = VirtualAlloc(
NULL, //由系统指定地址
PAGELIMIT * dwPageSize, //分配大小
MEM_RESERVE, //保留页
PAGE_NOACCESS); //不允许访问
if(lpvBase == NULL)
{
ErrorExit(TEXT("VirtualAlloc reserve failed"));
}
lpPtr = lpNextPage = (LPTSTR)lpvBase; //申请的内存的起始地址
//访问内存页时使用结构化异常处理,当发生页错误时
//异常过滤器将被执行来从保留页内存块中申请新的提交页
for(i=0; i<PAGELIMIT * dwPageSize; i++)
{
__try
{
//写内存
lpPtr[i] = 'a';
}
//如果发生页错误,提交新的页
__except(PageFaultExceptionFilter(GetExceptionCode()))
{
//过滤器函数在申请提交页时失败
printf("Exiting process/n");
ExitProcess(GetLastError());
}
}
bSuccess = VirtualFree(
lpvBase,
0,
MEM_RELEASE);
printf("Release %s/n", bSuccess ? "succeeded" : "failed");
}