15.1 预订(Reserve)地址空区域
(1)VirutalAlloc(pvAddress,dwSize,fdwAllocationType,fdwProtect)
①PVOID pvAddress参数:内存地址,要预订的地址空间中的哪一块。
A.这个参数也说明也该函数是较底层函数的原因,因为可以由我们指定在哪个地址分配内存!但这也是有限制的,即只能在用户模式分区中分配内存,否则将会失败。
B.预订区域时,系统始终是按CPU的分配粒度来分配区域的。如果指定的pvAddress不是64K的整数倍时,则系统会将pvAddress向下取整到64K的整数倍作为预订区域的起始地址(也叫区域的基地址)。如pvAddress=300×65536 + 8192时,会被取整为300×65536。(但应注意:在提交时,起始地址始终(与区域大小一样),始终都是页面大小的整数倍)
C.当该参数为NULL时,系统会自动找一块空闲的区域来预订。但系统不保证一定会从地址空间的底部往上分配,也不保证一定从地址空间的顶部往下分配。
D.如果在fdwAllocatioType指定MEM_TOP_DOWN时会尽可能地在高地址中分配空间
②SIZE_T dwSize参数——用来指定预订区域的大小,以字节为单位
A.区域大小是以CPU页面大小的整数倍来预订的(一般为4KB)。
B.如果指定的dwSize不是4KB的整数倍,则会被向上取整。如预订大小为62KB,则最终得到的区域大小为64KB。
③DWORD fdwAllocationType参数——要预订还是提交等
类型 |
备注 |
MEM_RESERVE |
A.预留区域时并没有分配物理存储器(物理存储器指物理内存、页交换文件或文件映像),只是增加了一个描述进程虚拟地址空间使用状态的数据结构,用来记录这个区域己被预订。 B.因没有真正分配物理存储器,所以这个区域不能直接访问,否则会引起“内存访问违规”。但“预订”操作的速度相对较快。 |
MEM_COMMIT |
A.提交区域,为预订区域调拨后备的物理储存器类型。注意,提交时也并没有立刻从物理内存分配空间,而是提交给页交换文件! B.直到当第一次访问这段区域时,系统会抛出“缺页错误”并处理该错误,这时才真正映射到物理内存。这种策略叫“demand-paging”策略,这既节省了时间,也节省了内存的浪费。 |
MEM_PHYSICAL |
A.只用在“地址空间扩展(AWE)”中,该标志必须且只能与MEM_RESERVE一起使用。 B.页面必须具有可读写属性(具体该标志的使用见后面的《AWE》一节) |
MEM_TOP_DOWN |
传NULL给pvAddress,同时对MEM_TOP_DOWN与MEM_RESERVE按位或传给fdwAllocationType可以让系统从尽可能高的内存地址来预订区域。这样可以防止从进程地址空间的中间预订,从而避免引起内存碎片 |
④DWORD fdwProtect参数——给区域指定保护属性
A.预订区域时,可以使用PAGE_NOACCESS、PAGE_READWRITE、PAGE_READONLY、PAGE_EXECUTE_*等几类。
B.但预订时不能使用PAGE_WRITECOPY、PAGE_EXECUTE_WRITECOPY、PAGE_GUARD、PAGE_NOCACHE和PAGE_WRITECOMBINE,因为这些标志跟物理存储器有关。
15.2 给区域调拨(Commit)物理存储器
(1)调用VirtualAlloc时传入MEM_COMMIT标志,在给物理存储器指定页面保护属性时,可以与预订时一致,也可以完全不同!但最终的保护性页以物理页面的保护属性为准(而不是区域的保护属性)
(2)在己预订的区域中,可以无须一下子给整个区域都调拨物理存储器,可以用pvAddress和dwSize来指定要想提交的部分。(即哪个内存地址,大小为多少,同时x86中都是以4KB的页面大小为基本单位)
(3)由于系统是基于一个完全页面(4KB大小)来指定保护属性的,所以同一个物理存储页不可能有不同的保护属性。但同一区域中的另一个页面可以是另一种保护属性。
15.3 同时预订和调拨物理存储器
(1)调用VirtualAlloc,并传入MEM_RESERVE|MEM_COMMIT标志
(2)大页面内存的分配
①返回大页面分配粒度:SIZE_T GetLargePageMinimum(),如果系统不支持大页面,则返回0
②调用VirtualAlloc时传入MEM_SERVE|MEM_COMMIT|MEM_LARGE_PAGE,即必须是同时预订和提交内存,而不能分开来实现。
③同时要分配的内存块大小(dwSize)必须是大页面分配粒度的整倍数,页面的保护属性fdwProtect也必须指定为PAGE_READWRITE。
【注意】
①大页面分配到的内存是不可换页的,也就是必须驻留内存,而不会被换出到页交换文件。所以就要求物理内存页面要被锁定。
②默认下,要使用“锁定内存页面”要先得到授权。(方法是“控制面板”→“管理工具”→“本地安全策略”→“本地策略”→“用户权限分配”→“锁定内存页”中添加用户或组)
15.4 何时调拨物理存储器
(1)先预订区域而不提交物理存储器(再次提醒,实际上是提交到页交换文件的)可以节省大量的物理存储器。
(2)当应用程序要访问未提交的内存地址时,会引发异常,系统会通知我们的应用程序。我们要用的就是给应用程序设置一个异常处理程序,在这里面再去真正地执行提交物理存储器的操作,从而实现内存的“按需分配”。
15.5 撤消调拨物理存储器及释放区域
(1)VirtualFree(pvAdress,dwSize,fdwFreeType)
①撤消并释放整个区域:pvAddress指定为区域的基地址,即预订区域时VirtualAlloc的返回值。同时dwSize必须传入0,因为系统知道该区域的大小。第3个参数传MEM_RELEASE。(注意此时不能只撤消一部分区域。此外,这时提交的内存被释放且预订的虚拟地址空间也归返,这就意味着MEM_RELEASE不能得MEM_DECOMMAND一起使用!)
②撤消部分物理存储器(但保留虚拟地址空间):指定pvAddress和dwSize,并传入MEM_DECOMMIT。与预订和提交物理存储器一校址,撤消也是基于页面粒度的,即系统会撤消地址空间被pvAddress到pvAddress+dwSize覆盖的所有页面(注意,页面是个4KB的空间,即如果pvAddress是位于某个页面中间,那该页面也作为整体被撤消)。
(2)何时撤消调拨物理存储器
①由于撤消操作是以页面粒度为基本单位的,当我们进行撤消时,可能会把当前正在使用的某个变量的内存也给撤消掉。
②要安全的撤消操作,就要为每个变量或结构体增加一个标志位,用来记录它们正在被使用的情况。只有等到同一个页面所有相邻的结构不在被使用时,才能进行撤消操作。
【DemandPaging程序】利用结构化异常机制进行内存的“按需分配”
#include <windows.h> #include <tchar.h> #include <locale.h> #define PAGELIMIT 80 //请求页面的总数 LPBYTE lpNextPage=NULL; //下一次请求的页面地址 DWORD dwPageSize = 0; //页面的大小(分配粒度) DWORD dwPages = 0; //当前己经请求的页面数量 INT PageFaultExceptionFilter(DWORD dwCode){ LPVOID lpvResult; //如果不是页面错误,则退出 if (dwCode !=EXCEPTION_ACCESS_VIOLATION){ _tprintf(_T("错误发生[%d] "), dwCode); return EXCEPTION_EXECUTE_HANDLER; //当错误发生时,执行except代码块中的内容。 } _tprintf(_T("页面访问错误,")); if (dwPages >=PAGELIMIT){ _tprintf(_T("超出页面数量%d "), PAGELIMIT); return EXCEPTION_EXECUTE_HANDLER; //当错误发生时,执行except代码块中的内容。 } lpvResult = VirtualAlloc((LPVOID)lpNextPage, dwPageSize, MEM_COMMIT, PAGE_READWRITE); if (lpvResult == NULL){ _tprintf(_T("提交新页面失败! ")); return EXCEPTION_EXECUTE_HANDLER; } else{ _tprintf(_T("将重新提交一个新页面! ")); } dwPages++; lpNextPage += dwPageSize; return EXCEPTION_CONTINUE_EXECUTION; //当错误发生时,修复异常并重新执行异常代码。 } VOID ErrorExit(LPTSTR oops){ _tprintf(_T("错误!%s,出错代码为%ld "), oops, GetLastError()); exit(0); } int _tmain(){ _tsetlocale(LC_ALL, _T("chs")); LPVOID lpvBase; //要测试的内存基地址 LPTSTR lpPtr; //通用的字符指针 BOOL bSuccess; // SYSTEM_INFO si; //系统信息结构体 GetSystemInfo(&si); dwPageSize = si.dwPageSize; _tprintf(_T("CPU页面大小为%dKB. "), si.dwPageSize/1024); //在进程的虚拟空间中预订页面 lpvBase = VirtualAlloc(NULL, //系统自动选择基地址 PAGELIMIT*dwPageSize,//区域的大小 MEM_RESERVE, //预订(注意不是提交) PAGE_NOACCESS);//保护属性,不可读写 if (lpvBase == NULL){ ErrorExit(_T("预订页面失败")); } lpPtr =(LPTSTR)lpvBase; lpNextPage = (LPBYTE)lpvBase; for (DWORD i = 0; i < PAGELIMIT*dwPageSize/sizeof(TCHAR);i++){ __try{ //写入内存 lpPtr[i] = _T('a'); //因未提交物理存储器,这里会引发异常 } //如果发生页面错误,则提交另一个页面并尝试继续 __except (PageFaultExceptionFilter(GetExceptionCode())){ //以下的代码只有在“过滤函数”中提交下个页面失败时 //才会被调用 ExitProcess(GetLastError()); } } //释放区域 bSuccess = VirtualFree(lpvBase, 0, //当使用MEM_RELEASE时,必须为0 MEM_RELEASE);//撤消提交的区域(这里不用加MEM_DECOMMIT) _tprintf(_T("释放操作%s. "), bSuccess ? _T("成功") : _T("失败")); return 0; }
【VMAlloc程序】内存的垃圾回收(VirtualFree函数的应用)
/************************************************************************ Module: VMAlloc.cpp Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre ************************************************************************/ #include "../../CommonFiles/CmnHdr.h" #include "resource.h" #include <tchar.h> #include <Strsafe.h> ////////////////////////////////////////////////////////////////////////// UINT g_uPageSize = 0; //CPU页面的分配粒度 //一个虚拟的结构体,刚好是2048字节 typedef struct{ BOOL bInUse; //用来标识该块内存区域是否正在使用中 BYTE bOtherData[2048 - sizeof(BOOL)]; }SOMEDATA,*PSOMEDATA; //数组中SOMEDATA结构中的最大数量 #define MAX_SOMEDATA (50) //指向SOMEDATA类型的数组指针,即预订区域的基地址 PSOMEDATA g_pSomeData = NULL; //“内存地图”显示的区域 RECT g_rcMemMap; ////////////////////////////////////////////////////////////////////////// BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam){ chSETDLGICONS(hwnd, IDI_VMALLOC); //禁用相关的一些控件 EnableWindow(GetDlgItem(hwnd, IDC_INDEXTEXT), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_INDEX), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_USE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_CLEAR), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_GARBAGECOLLECT), FALSE); //获取“内存地图”显示区域的坐标 GetWindowRect(GetDlgItem(hwnd, IDC_MEMMAP), &g_rcMemMap); //该尺寸以相对于屏幕坐标左上角的屏幕坐标给出。 //该函数把相对于一个窗口的坐标空间的一组点映射成相对于另一窗口的坐标空间的一组点。 //函数原型:int MapWindowPoints(HWND hWndFrom, HWND hWndTo, LPPOINT lpPoints, UINT cPoints); MapWindowPoints(NULL,//NULL或HWND_DESKTOP表示定这些点在屏幕坐标上 hwnd, //转化成以hwnd窗口的相对坐标 (LPPOINT)&g_rcMemMap, 2); //销毁“内存地图”子窗口 DestroyWindow(GetDlgItem(hwnd, IDC_MEMMAP)); //显示页面大小 TCHAR szBuf[10]; StringCchPrintf(szBuf, _countof(szBuf), TEXT("%d KB"), g_uPageSize / 1024); SetDlgItemText(hwnd, IDC_PAGESIZE, szBuf); //初始化索引化编辑框 SetDlgItemInt(hwnd, IDC_INDEX, 0, FALSE); return TRUE; } ////////////////////////////////////////////////////////////////////////// void Dlg_OnDestroy(HWND hwnd){ //释放区域 if (g_pSomeData != NULL){ VirtualFree(g_pSomeData, 0, MEM_RELEASE); //dwSize必须传为0 } } ////////////////////////////////////////////////////////////////////////// //pvBase垃圾的内址地址,ddwNum多少个结构体,dwStructSize,每个结构体的大小 void GarbageCollect(PVOID pvBase, DWORD dwNum, DWORD dwStructSize){ //将结构体数组大小换算成页面的数量 UINT uMaxPages = dwNum* dwStructSize / g_uPageSize; for (UINT uPage = 0; uPage < uMaxPages;uPage++){ BOOL bAnyAllocsInThisPage = FALSE; UINT uIndex = uPage* g_uPageSize / dwStructSize; UINT uIndexLast = uIndex + g_uPageSize / dwStructSize; for (; uIndex < uIndexLast;uIndex++){ MEMORY_BASIC_INFORMATION mbi; VirtualQuery(&g_pSomeData[uIndex], &mbi, sizeof(mbi)); bAnyAllocsInThisPage = ((mbi.State == MEM_COMMIT) && *(PBOOL)((PBYTE)pvBase + dwStructSize*uIndex)); //如果该页面里面仍有一个内存块正在被使用,不回收,跳到下一个页面去处理。 if (bAnyAllocsInThisPage) break; } if (!bAnyAllocsInThisPage){ //如果该页面不在使用时,撤消该页面(注意,撤消时是整个页面被撤消的);重复撤消不会出现问题! VirtualFree(&g_pSomeData[uIndexLast - 1], dwStructSize, MEM_DECOMMIT); } } } ////////////////////////////////////////////////////////////////////////// void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotity){ UINT uIndex = 0; switch (id) { case IDCANCEL: EndDialog(hwnd, id); break; case IDC_RESERVE: g_pSomeData = (PSOMEDATA)VirtualAlloc(NULL, MAX_SOMEDATA*sizeof(SOMEDATA), MEM_RESERVE, PAGE_READWRITE); //禁用“预订区域”按钮,并启用其他相关的按钮 EnableWindow(GetDlgItem(hwnd, IDC_RESERVE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_INDEXTEXT), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_INDEX), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_USE), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_CLEAR), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_GARBAGECOLLECT), TRUE); //设置焦点 SetFocus(GetDlgItem(hwnd, IDC_INDEX)); //刷新“内存地图”区域 InvalidateRect(hwnd, &g_rcMemMap, FALSE); break; case IDC_INDEX: if (codeNotity != EN_CHANGE) break; uIndex = GetDlgItemInt(hwnd, id, NULL, FALSE); if ((g_pSomeData != NULL)&&chINRANGE(0,uIndex,MAX_SOMEDATA-1)){ MEMORY_BASIC_INFORMATION mbi; VirtualQuery(&g_pSomeData[uIndex], &mbi, sizeof(mbi)); BOOL bOk = (mbi.State == MEM_COMMIT); //指定的内存块是否己提交 if (bOk) bOk = g_pSomeData[uIndex].bInUse; EnableWindow(GetDlgItem(hwnd, IDC_USE), !bOk); EnableWindow(GetDlgItem(hwnd, IDC_CLEAR), bOk); } else{ //未提交时 EnableWindow(GetDlgItem(hwnd, IDC_USE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_CLEAR), FALSE); } break; case IDC_USE: uIndex = GetDlgItemInt(hwnd, IDC_INDEX, NULL, FALSE); //注意:新的页面总是被初始化为0;重复提交不会发生问题! VirtualAlloc(&g_pSomeData[uIndex], sizeof(SOMEDATA), MEM_COMMIT, PAGE_READWRITE); g_pSomeData[uIndex].bInUse = TRUE; EnableWindow(GetDlgItem(hwnd, IDC_USE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_CLEAR), TRUE); //设置焦点到“清除”按钮 SetFocus(GetDlgItem(hwnd, IDC_CLEAR)); //刷新“内存地图” InvalidateRect(hwnd, &g_rcMemMap, FALSE); break; case IDC_CLEAR: uIndex = GetDlgItemInt(hwnd, IDC_INDEX, NULL, FALSE); g_pSomeData[uIndex].bInUse = FALSE; EnableWindow(GetDlgItem(hwnd, IDC_USE), TRUE); EnableWindow(GetDlgItem(hwnd, IDC_CLEAR), FALSE); //重设焦点 SetFocus(GetDlgItem(hwnd, IDC_USE)); break; case IDC_GARBAGECOLLECT: //垃圾回收 GarbageCollect(g_pSomeData, MAX_SOMEDATA, sizeof(SOMEDATA)); //刷新“内存地图” InvalidateRect(hwnd, &g_rcMemMap, FALSE); break; } } ////////////////////////////////////////////////////////////////////////// void Dlg_OnPaint(HWND hwnd){ PAINTSTRUCT ps; BeginPaint(hwnd, &ps); UINT uMaxPages = MAX_SOMEDATA*sizeof(SOMEDATA) / g_uPageSize; //区域最多的页面数量 UINT uMemMapWidth = g_rcMemMap.right - g_rcMemMap.left; if (g_pSomeData == NULL){ //尚未预订内存 Rectangle(ps.hdc, g_rcMemMap.left, g_rcMemMap.top, g_rcMemMap.right - uMemMapWidth % uMaxPages, g_rcMemMap.bottom); } else{ //遍历预订的那段虚拟地址空间,并画出“内存地图” for (UINT uPage = 0; uPage < uMaxPages;uPage++){ //每个页面里面有g_uPageSize/sizeof(SOMEDATA)个结构体(本例为2个) UINT uIndex = uPage*g_uPageSize / sizeof(SOMEDATA); //该页面中的第1个结构体位于数组中的索引 UINT uIndexLast = uIndex + g_uPageSize / sizeof(SOMEDATA); //该页面中最后1个结构体在数组中的索引 for (; uIndex < uIndexLast;uIndex++){ MEMORY_BASIC_INFORMATION mbi; //查询每个结构体所在的页面的预留/提交标志 VirtualQuery(&g_pSomeData[uIndex], &mbi, sizeof(mbi)); int nBrush = 0; switch (mbi.State) { case MEM_FREE: nBrush = WHITE_BRUSH; break; case MEM_RESERVE:nBrush = GRAY_BRUSH; break; case MEM_COMMIT:nBrush = BLACK_BRUSH; break; } SelectObject(ps.hdc, GetStockObject(nBrush)); Rectangle(ps.hdc, g_rcMemMap.left+uMemMapWidth / uMaxPages*uPage, g_rcMemMap.top, g_rcMemMap.left + uMemMapWidth / uMaxPages*(uPage+1), g_rcMemMap.bottom); } } } EndPaint(hwnd, &ps); } ////////////////////////////////////////////////////////////////////////// INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ switch (uMsg) { chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); chHANDLE_DLGMSG(hwnd, WM_PAINT, Dlg_OnPaint); chHANDLE_DLGMSG(hwnd, WM_DESTROY, Dlg_OnDestroy); } return FALSE; } ////////////////////////////////////////////////////////////////////////// int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd) { //获得CPU使用的页面大小(分配粒度) SYSTEM_INFO si; GetSystemInfo(&si); g_uPageSize = si.dwPageSize; DialogBox(hInstance, MAKEINTRESOURCE(IDD_VMALLOC), NULL, Dlg_Proc); return 0; }
//resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 15_VMAlloc.rc 使用 // #define IDD_VMALLOC 1 #define IDC_PAGESIZE 100 #define IDC_RESERVE 101 #define IDI_VMALLOC 101 #define IDC_INDEXTEXT 102 #define IDI_ICON1 102 #define IDC_INDEX 103 #define IDC_USE 105 #define IDC_CLEAR 106 #define IDC_GARBAGECOLLECT 107 #define IDC_MEMMAP 108 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 103 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
//VMAlloc.rc
// Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // 中文(简体,中国) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h " END 2 TEXTINCLUDE BEGIN "#include ""winres.h"" " "