全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
本文背景:
在編程中,很多Windows或C++的內(nèi)存函數(shù)不知道有什么區(qū)別,更別談有效使用;根本的原因是,沒有清楚的理解操作系統(tǒng)的內(nèi)存管理機(jī)制,本文企圖通過簡單的總結(jié)描述,結(jié)合實(shí)例來闡明這個(gè)機(jī)制。
本文目的:
對Windows內(nèi)存管理機(jī)制了解清楚,有效的利用C++內(nèi)存函數(shù)管理和使用內(nèi)存。
本文內(nèi)容:
本文一共有六節(jié),由于篇幅較多,故按節(jié)發(fā)表。其他章節(jié)請看本人博客的Windows內(nèi)存管理及C++內(nèi)存分配實(shí)例(一)(二)(三)(五)和(六)。
4.??????內(nèi)存管理機(jī)制--內(nèi)存映射文件?(Map)
??? 和虛擬內(nèi)存一樣,內(nèi)存映射文件可以用來保留一個(gè)進(jìn)程地址區(qū)域;但是,與虛擬內(nèi)存不同,它提交的不是物理內(nèi)存或是虛擬頁文件,而是硬盤上的文件。
·????????使用場合
它有三個(gè)主要用途:
系統(tǒng)加載EXE和DLL文件
操作系統(tǒng)就是用它來加載exe和dll文件建立進(jìn)程,運(yùn)行exe。這樣可以節(jié)省頁文件和啟動時(shí)間。
訪問大數(shù)據(jù)文件
如果文件太大,比如超過了進(jìn)程用戶區(qū)2G,用fopen是不能對文件進(jìn)行操作的。這時(shí),可用內(nèi)存映射文件。對于大數(shù)據(jù)文件可以不必對文件執(zhí)行I/O操作,不必對所有文件內(nèi)容進(jìn)行緩存。
進(jìn)程共享機(jī)制
內(nèi)存映射文件是多個(gè)進(jìn)程共享數(shù)據(jù)的一種較高性能的有效方式,它也是操作系統(tǒng)進(jìn)程通信機(jī)制的底層實(shí)現(xiàn)方法。RPC、COM、OLE、DDE、窗口消息、剪貼板、管道、Socket等都是使用內(nèi)存映射文件實(shí)現(xiàn)的。
·????????系統(tǒng)加載EXE和DLL文件
ü??????EXE文件格式
每個(gè)EXE和DLL文件由許多節(jié)(Section)組成,每個(gè)節(jié)都有保護(hù)屬性:READ,WRITE,EXECUTE和SHARED(可以被多個(gè)進(jìn)程共享,關(guān)閉頁面的COPY-ON-WRITE屬性)。
以下是常見的節(jié)和作用:
| 節(jié)名 | 作用 |
| .text | .exe和.dll文件的代碼 |
| .data | 已經(jīng)初始化的數(shù)據(jù) |
| .bss | 未初始化的數(shù)據(jù) |
| .reloc | 重定位表(裝載進(jìn)程的進(jìn)程地址空間) |
| .rdata | 運(yùn)行期只讀數(shù)據(jù) |
| .CRT | C運(yùn)行期只讀數(shù)據(jù) |
| .debug | 調(diào)試信息 |
| .xdata | 異常處理表 |
| .tls | 線程的本地化存儲 |
| .idata | 輸入文件名表 |
| .edata | 輸出文件名表 |
| .rsrc | 資源表 |
| .didata | 延遲輸入文件名表 |
?
ü??????加載過程
1.??????系統(tǒng)根據(jù)exe文件名建立進(jìn)程內(nèi)核對象、頁目和頁表,也就是建立了進(jìn)程的虛擬空間。
2.??????讀取exe文件的大小,在默認(rèn)基地址0x0040 0000上保留適當(dāng)大小的區(qū)域。可以在鏈接程序時(shí)用/BASE?選項(xiàng)更改基地址(在VC工程屬性/鏈接器/高級上設(shè)置)。提交時(shí),操作系統(tǒng)會管理頁目和頁表,將硬盤上的文件映射到進(jìn)程空間中,頁表中保存的地址是exe文件的頁偏移。
3.??????讀取exe文件的.idata節(jié),此節(jié)列出exe所用到的所有dll文件。然后和
exe文件一樣,將dll文件映射到進(jìn)程空間中。如果無法映射到基地址,系統(tǒng)會重新定位。
4.???映射成功后,系統(tǒng)會把第一頁代碼加載到內(nèi)存,然后更新頁目和頁
表。將第一條指令的地址交給線程指令指針。當(dāng)系統(tǒng)執(zhí)行時(shí),發(fā)現(xiàn)代碼沒有在內(nèi)存中,會將exe文件中的代碼加載到內(nèi)存中。
??????????????
ü??????第二次加載時(shí)(運(yùn)行多個(gè)進(jìn)程實(shí)例)
1.??????建立進(jìn)程、映射進(jìn)程空間都跟前面一樣,只是當(dāng)系統(tǒng)發(fā)現(xiàn)這個(gè)exe已
??????經(jīng)建立了內(nèi)存映射文件對象時(shí),它就直接映射到進(jìn)程空間了;只是當(dāng)
?????系統(tǒng)分配物理頁面時(shí),根據(jù)節(jié)的保護(hù)屬性賦予頁面保護(hù)屬性,對于代碼
?????節(jié)賦予READ屬性,全局變量節(jié)賦予COPY-ON-WRITE屬性。
2.??????不同的實(shí)例共享代碼節(jié)和其他的節(jié),當(dāng)實(shí)例需要改變頁面內(nèi)容時(shí),會
??????拷貝頁面內(nèi)容到新頁面,更新頁目和頁表。
3.??????對于不同進(jìn)程實(shí)例需要共享的變量,exe文件有一
??????個(gè)默認(rèn)的節(jié),?給這個(gè)節(jié)賦予SHARED屬性。
4.??????你也可以創(chuàng)建自己的SHARED節(jié)
#pragma data_seg(“節(jié)名”)
Long instCount;
#pragma data_seg()
然后,你需要在鏈接程序時(shí)告訴編譯器節(jié)的默認(rèn)屬性。
/SECTION:?節(jié)名,RWS
或者,在程序里用以下表達(dá)式:
#pragma comment(linker,“/SECTION:節(jié)名,RWS”)
這樣的話編譯器會創(chuàng)建.drective節(jié)來保存上述命令,然后鏈接時(shí)會用它改變節(jié)屬性。
注意,共享變量有可能有安全隱患,因?yàn)樗梢宰x到其他進(jìn)程的數(shù)據(jù)。
C++程序:多個(gè)進(jìn)程共享變量舉例
*.cpp開始處:
#pragma?data_seg(".share")
long?shareCount=0;
#pragma?data_seg()
#pragma?comment(linker,"/SECTION:.share,RWS")
ShareCount++;
?
注意,同一個(gè)exe文件產(chǎn)生的進(jìn)程會共享shareCount,必須是處于同一個(gè)位置上的exe。
?
·????????訪問大數(shù)據(jù)文件
ü??????創(chuàng)建文件內(nèi)核對象
使用CreateFile(文件名,訪問屬性,共享模式,…)?API可以創(chuàng)建。
其中,訪問屬性有:
0?不能讀寫?(用它可以訪問文件屬性)
GENERIC_READ
GENERIC_WRITE
GENERIC_READ|GENERIC_WRITE;
共享模式:
0?獨(dú)享文件,其他應(yīng)用程序無法打開
FILE_SHARE_WRITE
FILE_SHARE_READ|FILE_SHARE_WRITE
這個(gè)屬性依賴于訪問屬性,必須和訪問屬性不沖突。
當(dāng)創(chuàng)建失敗時(shí),返回INVALID_HANDLE_VALUE。
?
C++程序如下:
試圖打開一個(gè)1G的文件:
MEMORYSTATUS memStatus;
GlobalMemoryStatus(&memStatus);
HANDLE hn=CreateFile(L"D://1G.rmvb",GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
??????????????if(hn==INVALID_HANDLE_VALUE)
????????????????????????cout<<"打開文件失敗!"<<endl;
??????????????FILE *p=fopen("D://1G.rmvb","rb");
??????????????if(p==NULL)
????????????????????????cout<<"用fopen不能打開大文件!"<<endl;
??????????????MEMORYSTATUS memStatus2;
??????????????GlobalMemoryStatus(&memStatus2);
??????????????cout<<"打開文件后的空間:"<<endl;
cout<<"減少物理內(nèi)存="<<memStatus.dwAvailPhys-memStatus2.dwAvailPhys<<endl;
cout<<"減少可用頁文件="<<memStatus.dwAvailPageFile-memStatus2.dwAvailPageFile<<endl;
cout<<"減少可用進(jìn)程空間="
<<memStatus.dwAvailVirtual-memStatus2.dwAvailVirtual<<endl<<endl;
結(jié)果如下:
?
可見,系統(tǒng)需要一些內(nèi)存來管理內(nèi)核對象,每一次運(yùn)行的結(jié)果都不一樣,但差別不會太大。
用c語言的fopen不能打開這么大的文件。理論上,32位系統(tǒng)能支持232字節(jié),但是,進(jìn)程空間只有2G,它只能表示那么大的空間。
ü??????創(chuàng)建文件映射內(nèi)核對象
API如下:
HANDLE CreateFileMapping(Handle?文件,PSECURITY_ATTRIBUTES?安全屬性,DWORD?保護(hù)屬性,DWORD?文件大小高32位,DWORD?文件大小低32位,PCTSTR??映射名稱)
“文件”是上面創(chuàng)建的句柄;
“安全屬性”是內(nèi)核對象需要的,NULL表示使用系統(tǒng)默認(rèn)的安全屬性;“保護(hù)屬性”是當(dāng)將存儲器提交給進(jìn)程空間時(shí),需要的頁面屬性:PAGE_READONLY, PAGE_READWRITE和PAGE_WRITECOPY。這個(gè)屬性不能和文件對象的訪問屬性沖突。除了這三個(gè)外,還有兩個(gè)屬性可以和它們連接使用(|)。當(dāng)更新文件內(nèi)容時(shí),不提供緩存,直接寫入文件,可用SEC_NOCACHE;當(dāng)文件是可執(zhí)行文件時(shí),系統(tǒng)會根據(jù)節(jié)賦予不同的頁面屬性,可用SEC_IMAGE。另外,SEC_RESERVE和SEC_COMMIT用于稀疏提交的文件映射,詳細(xì)介紹請參考下文。
“文件大小高32位”和“文件大小低32位”聯(lián)合起來告訴系統(tǒng),這個(gè)映射所能支持的文件大小(操作系統(tǒng)支持264B文件大小);當(dāng)這個(gè)值大于實(shí)際的文件大小時(shí),系統(tǒng)會擴(kuò)大文件到這個(gè)值,因?yàn)橄到y(tǒng)需要保證進(jìn)程空間能完全被映射。值為0默認(rèn)為文件的大小,這時(shí)候如果文件大小為0,創(chuàng)建失敗。
“映射名稱”是給用戶標(biāo)識此內(nèi)核對象,供各進(jìn)程共享,如果為NULL,則不能共享。
對象創(chuàng)建失敗時(shí)返回NULL。
創(chuàng)建成功后,系統(tǒng)仍未為文件保留進(jìn)程空間。
?
C++程序:
????????????????????????MEMORYSTATUS memStatus2;
????????????????????????GlobalMemoryStatus(&memStatus2);
HANDLE hmap=CreateFileMapping(hn,NULL,PAGE_READWRITE,0,0,L"Yeming-Map");
????????????????????????if(hmap==NULL)
????????????????????????cout<<"建立內(nèi)存映射對象失敗!"<<endl;
????????????????????????MEMORYSTATUS memStatus3;
????????????????????????GlobalMemoryStatus(&memStatus3);
????????????????????????cout<<"建立內(nèi)存映射文件后的空間:"<<endl;
cout<<"減少物理內(nèi)存="<<memStatus2.dwAvailPhys-memStatus3.dwAvailPhys<<endl;
cout<<"減少可用頁文件="<<memStatus2.dwAvailPageFile-memStatus3.dwAvailPageFile<<endl;
?????????cout<<"減少可用進(jìn)程空間="
<<memStatus2.dwAvailVirtual-memStatus3.dwAvailVirtual<<endl<<endl;
????????????結(jié)果如下:
??????
?
默認(rèn)內(nèi)存映射的大小是1G文件。沒有損失內(nèi)存和進(jìn)程空間。它所做的是建立內(nèi)核對象,收集一些屬性。
?
ü??????文件映射內(nèi)核對象映射到進(jìn)程空間
API如下:
PVOID MAPViewOfFile(HANDLE?映射對象,DWORD訪問屬性,DWORD?偏移量高32位,DWORD?偏移量低32位,SIZE_T?字節(jié)數(shù))
“映射對象”是前面建立的對象;
“訪問屬性”可以是下面的值:FILE_MAP_WRITE(讀和寫)、FILE_MAP_READ、FILE_MAP_ALL_ACCESS(讀和寫)、FILE_MAP_COPY。當(dāng)使用FILE_MAP_COPY時(shí),系統(tǒng)分配虛擬頁文件,當(dāng)有寫操作時(shí),系統(tǒng)會拷貝數(shù)據(jù)到這些頁面,并賦予PAGE_READWRITE屬性。
可以看到,每一步都需要設(shè)置這類屬性,是為了可以多點(diǎn)控制,試想,如果在這一步想有多種不同的屬性操作文件的不同部分,就比較有用。
“偏移高32位”和“偏移低32位”聯(lián)合起來標(biāo)識映射的開始字節(jié)(地址是分配粒度的倍數(shù));
“字節(jié)數(shù)”指映射的字節(jié)數(shù),默認(rèn)0為到文件尾。
?
當(dāng)你需要指定映射到哪里時(shí),你可以使用:
PVOID MAPViewOfFile(HANDLE?映射對象,DWORD訪問屬性,DWORD?偏移量高32位,DWORD?偏移量低32位,SIZE_T?字節(jié)數(shù),PVOID?基地址)
“基地址”是映射到進(jìn)程空間的首地址,必須是分配粒度的倍數(shù)。
?
C++程序:
MEMORYSTATUS memStatus3;
????????????GlobalMemoryStatus(&memStatus3);
????????????LPVOID pMAP=MapViewOfFile(hmap,FILE_MAP_WRITE,0,0,0);
????????????cout<<"映射內(nèi)存映射文件后的空間:"<<endl;
if(pMAP==NULL)
???????????????cout<<"映射進(jìn)程空間失敗!"<<endl;
????????????else
???????????????printf("首地址=%x/n",pMAP);
????????????MEMORYSTATUS memStatus4;
????????????GlobalMemoryStatus(&memStatus4);
cout<<"減少物理內(nèi)存="<<memStatus3.dwAvailPhys-memStatus4.dwAvailPhys<<endl;
cout<<"減少可用頁文件="<<memStatus3.dwAvailPageFile-memStatus4.dwAvailPageFile<<endl;
cout<<"減少可用進(jìn)程空間="
<<memStatus3.dwAvailVirtual-memStatus4.dwAvailVirtual<<endl<<endl;
結(jié)果如下:
?
進(jìn)程空間減少了1G,系統(tǒng)同時(shí)會開辟一些內(nèi)存來做文件緩存。
ü??????使用文件
1.??????對于大文件,可以用多次映射的方法達(dá)到訪問的目的。有點(diǎn)像AWE技術(shù)。
2.??????Windows只保證同一文件映射內(nèi)核對象的多次映射的數(shù)據(jù)一致性,比如,當(dāng)有兩次映射同一對象到二個(gè)進(jìn)程空間時(shí),一個(gè)進(jìn)程空間的數(shù)據(jù)改變后,另一個(gè)進(jìn)程空間的數(shù)據(jù)也會跟著改變;不保證不同映射內(nèi)核對象的多次映射的一致性。所以,使用文件映射時(shí),最好在CreateFile時(shí)將共享模型設(shè)置為0獨(dú)享,當(dāng)然,對于只讀文件沒這個(gè)必要。
????C++程序:使用1G的文件
MEMORYSTATUS memStatus4;
????????????????????????GlobalMemoryStatus(&memStatus4);
????????????????????????cout<<"讀取1G文件前:"<<endl;
????????????????????????cout<<"可用物理內(nèi)存="<<memStatus4.dwAvailPhys<<endl;
????????????????????????cout<<"可用頁文件="<<memStatus4.dwAvailPageFile<<endl;
????????????????????????cout<<"可用進(jìn)程空間="<<memStatus4.dwAvailVirtual<<endl<<endl;
????????????????????????int* pInt=(int*)pMAP;
????????????????????????cout<<"更改前="<<pInt[1000001536/4-1]<<endl;//文件的最后一個(gè)整數(shù)
????????????????????????for(int?i=0;i<1000001536/4-1;i++)
?????????????????????????????pInt[i]++;
????????????????????????pInt[1000001536/4-1]=10;
????????????????????????pInt[100]=90;
????????????????????????pInt[101]=100;
????????????????????????cout<<"讀取1G文件后:"<<endl;
????????????????????????MEMORYSTATUS memStatus5;
????????????????????????GlobalMemoryStatus(&memStatus5);
????????????????????????cout<<"可用物理內(nèi)存="<<memStatus5.dwAvailPhys<<endl;
????????????????????????cout<<"可用頁文件="<<memStatus5.dwAvailPageFile<<endl;
????????????????????????cout<<"可用進(jìn)程空間="<<memStatus5.dwAvailVirtual<<endl<<endl;
???????????
結(jié)果如下:
?
程序?qū)?G文件的各個(gè)整型數(shù)據(jù)加1,從上圖看出內(nèi)存損失了600多兆,但有時(shí)候損失不過十幾兆,可能跟系統(tǒng)當(dāng)時(shí)的狀態(tài)有關(guān)。
不管怎樣,這樣你完全看不到I/O操作,就像訪問普通數(shù)據(jù)結(jié)構(gòu)一樣方便。
?
ü??????保存文件修改
為了提高速度,更改文件時(shí)可能只更改到了系統(tǒng)緩存,這時(shí),需要強(qiáng)制保存更改到硬盤,特別是撤銷映射前。
BOOL FlushViewOfFile(PVOID?進(jìn)程空間地址,SIZE_T?字節(jié)數(shù))
“進(jìn)程空間地址”指的是需要更改的第一個(gè)字節(jié)地址,系統(tǒng)會變成頁面的地址;
“字節(jié)數(shù)”,系統(tǒng)會變成頁面大小的倍數(shù)。
寫入磁盤后,函數(shù)返回,對于網(wǎng)絡(luò)硬盤,如果希望寫入網(wǎng)絡(luò)硬盤后才返回的話,需要將FILE_FLAG_WRITE_THROUGH參數(shù)傳給CreateFile。
?
當(dāng)使用FILE_MAP_COPY建立映射時(shí),由于對數(shù)據(jù)的更改只是對虛擬頁文件的修改而不是硬盤文件的修改,當(dāng)撤銷映射時(shí),會丟失所做的修改。如果要保存,怎么辦?
你可以用FILE_MAP_WRITE建立另外一個(gè)映射,它映射到進(jìn)程的另外一段空間;掃描第一個(gè)映射的PAGE_READWRITE頁面(因?yàn)閷傩员桓?,如果頁面改變,用MoveMemory或其他拷貝函數(shù)將頁面內(nèi)容拷貝到第二次映射的空間里,然后再調(diào)用FlushViewOfFile。當(dāng)然,你要記錄哪個(gè)頁面被更改。
ü??????撤銷映射
用以下API可以撤銷映射:
BOOL??UnmapViewOfFile(PVOID pvBaseAddress)
這個(gè)地址必須與MapViewOfFile返回值相同。
?
ü??????關(guān)閉內(nèi)核對象
在不需要內(nèi)核對象時(shí),盡早將其釋放,防止內(nèi)存泄露。由于它們是內(nèi)核對象,調(diào)用CloseHandle(HANDLE)就可以了。
在CreateFileMapping后馬上關(guān)閉文件句柄;
在MapViewOfFile后馬上關(guān)閉內(nèi)存映射句柄;
最后再撤銷映射。
·????????進(jìn)程共享機(jī)制
ü??????基于硬盤文件的內(nèi)存映射
如果進(jìn)程需要共享文件,只要按照前面的方式建立內(nèi)存映射對象,然后按照名字來共享,那么進(jìn)程就可以映射這個(gè)對象到自己的進(jìn)程空間中。
C++程序如下:
HANDLE mapYeming=OpenFileMapping(FILE_MAP_WRITE,true,L"Yeming-Map");
????????????????????????if(mapYeming==NULL)
????????????????????????cout<<"找不到內(nèi)存映射對象:Yeming-Map!"<<endl;
????????????????????????MEMORYSTATUS memStatus3;
????????????????????????GlobalMemoryStatus(&memStatus3);
LPVOID pMAP=MapViewOfFile(mapYeming,FILE_MAP_WRITE,0,0,100000000);
????????????????????????cout<<"建立內(nèi)存映射文件后的空間:"<<endl;
????????????????????????if(pMAP==NULL)
????????????????????????cout<<"映射進(jìn)程空間失敗!"<<endl;
????????????????????????else
????????????????????????printf("首地址=%x/n",pMAP);
???????????
????????????????????????MEMORYSTATUS memStatus4;
????????????????????????GlobalMemoryStatus(&memStatus4);
???????????
cout<<"減少物理內(nèi)存="<<memStatus3.dwAvailPhys-memStatus4.dwAvailPhys<<endl;
cout<<"減少可用頁文件="<<memStatus3.dwAvailPageFile-memStatus4.dwAvailPageFile<<endl;
cout<<"減少可用進(jìn)程空間="<<memStatus3.dwAvailVirtual-memStatus4.dwAvailVirtual<<endl<<endl;
?
????????????????????????int* pInt=(int*)pMAP;
?????????cout<<pInt[100]<<endl;
????????
?????????結(jié)果如下:
?
在2.exe中打開之前1.exe創(chuàng)建的內(nèi)存映射對象(當(dāng)然,1.exe得處于運(yùn)行狀態(tài)),然后映射進(jìn)自己的進(jìn)程空間,當(dāng)1.exe改變文件的值時(shí),2.exe的文件對應(yīng)值也跟著改變,Windows保證同一個(gè)內(nèi)存映射對象映射出來的數(shù)據(jù)是一致的。可以看見,1.exe將值從90改為91,2.exe也跟著改變,因?yàn)樗鼈冇泄餐木彌_頁。
?
ü??????基于頁文件的內(nèi)存映射
如果只想共享內(nèi)存數(shù)據(jù)時(shí),沒有必要創(chuàng)建硬盤文件,再建立映射。可以直
接建立映射對象:
只要傳給CreateFileMapping一個(gè)文件句柄INVALID_HANDLE_VALUE就行了。所以,CreateFile時(shí),一定要檢查返回值,否則會建立一個(gè)基于頁文件的內(nèi)存映射對象。接下來就是映射到進(jìn)程空間了,這時(shí),系統(tǒng)會分配頁文件給它。
C++程序如下:
?
HANDLE hPageMap=CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,
100000000,L"Yeming-Map-Page");
????????????if(hPageMap==NULL)
????????????????????????cout<<"建立基于頁文件的內(nèi)存映射對象失敗!"<<endl;
????????????MEMORYSTATUS memStatus6;
????????????GlobalMemoryStatus(&memStatus6);
????????????cout<<"建立基于頁文件的內(nèi)存映射文件后的空間:"<<endl;
cout<<"減少物理內(nèi)存="<<memStatus5.dwAvailPhys-memStatus6.dwAvailPhys<<endl;
cout<<"減少可用頁文件="<<memStatus5.dwAvailPageFile-memStatus6.dwAvailPageFile<<endl;
cout<<"減少可用進(jìn)程空間="<<memStatus5.dwAvailVirtual-memStatus6.dwAvailVirtual<<endl<<endl;???????????
LPVOID pPageMAP=MapViewOfFile(hPageMap,FILE_MAP_WRITE,0,0,0);????????
????????????結(jié)果如下:
???????
?
可見,和基于數(shù)據(jù)文件的內(nèi)存映射不同,現(xiàn)在剛建立內(nèi)核對象時(shí)就分配了所要的100M內(nèi)存。好處是,別的進(jìn)程可以通過這個(gè)內(nèi)核對象共享這段內(nèi)存,只要它也做了映射。
?
ü??????稀疏內(nèi)存映射文件
在虛擬內(nèi)存一節(jié)中,提到了電子表格程序。虛擬內(nèi)存解決了表示很少單元格有數(shù)據(jù)但必須分配所有內(nèi)存的內(nèi)存浪費(fèi)問題;但是,如果想在多個(gè)進(jìn)程之間共享這個(gè)電子表格結(jié)構(gòu)呢?
如果用基于頁文件的內(nèi)存映射,需要先分配頁文件,還是浪費(fèi)了空間,沒有了虛擬內(nèi)存的優(yōu)點(diǎn)。
Windows提供了稀疏提交的內(nèi)存映射機(jī)制。
當(dāng)使用CreateFileMapping時(shí),保護(hù)屬性用SEC_RESERVE時(shí),其不提交物理存儲器,使用SEC_COMMIT時(shí),其馬上提交物理存儲器。注意,只有文件句柄為INVALID_HANDLE_VALUE時(shí),才能使用這兩個(gè)參數(shù)。
按照通常的方法映射時(shí),系統(tǒng)只保留進(jìn)程地址空間,不會提交物理存儲器。
當(dāng)需要提交物理內(nèi)存時(shí)才提交,利用通常的VirtualAlloc函數(shù)就可以提交。
當(dāng)釋放內(nèi)存時(shí),不能調(diào)用VirtualFree函數(shù),只能調(diào)用UnmapViewOfFile來撤銷映射,從而釋放內(nèi)存。
?
C++程序如下:
HANDLE hVirtualMap=CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE|SEC_RESERVE,0,100000000,L"Yeming-Map-Virtual");
if(hPageMap==NULL)
????????????????????????cout<<"建立基于頁文件的稀疏內(nèi)存映射對象失敗!"<<endl;
????????????MEMORYSTATUS memStatus8;
????????????GlobalMemoryStatus(&memStatus8);
????????????cout<<"建立基于頁文件的稀疏內(nèi)存映射文件后的空間:"<<endl;
cout<<"減少物理內(nèi)存="<<memStatus7.dwAvailPhys-memStatus8.dwAvailPhys<<endl;
cout<<"減少可用頁文件="<<memStatus7.dwAvailPageFile-memStatus8.dwAvailPageFile<<endl;
cout<<"減少可用進(jìn)程空間="<<memStatus7.dwAvailVirtual-memStatus8.dwAvailVirtual<<endl<<endl;
???????????
LPVOID pVirtualMAP=MapViewOfFile(hVirtualMap,FILE_MAP_WRITE,0,0,0);
????????????cout<<"內(nèi)存映射進(jìn)程后的空間:"<<endl;
????????????if(pVirtualMAP==NULL)
????????????????????????cout<<"映射進(jìn)程空間失敗!"<<endl;
????????????else
????????????????????????printf("首地址=%x/n",pVirtualMAP);
???????????
????????????MEMORYSTATUS memStatus9;
????????????GlobalMemoryStatus(&memStatus9);
???????????
cout<<"減少物理內(nèi)存="<<memStatus8.dwAvailPhys-memStatus9.dwAvailPhys<<endl;
cout<<"減少可用頁文件="<<memStatus8.dwAvailPageFile-memStatus9.dwAvailPageFile<<endl;
cout<<"減少可用進(jìn)程空間="<<memStatus8.dwAvailVirtual-memStatus9.dwAvailVirtual<<endl<<endl;
????????
結(jié)果如下:
?
用了SEC_RESERVE后,只是建立了一個(gè)內(nèi)存映射對象,和普通的一樣;不同的是,它映射完后,得到了一個(gè)虛擬進(jìn)程空間。現(xiàn)在,這個(gè)空間沒有分配任何的物理存儲器給它,你可以用VirtualAlloc?提交存儲器給它,詳細(xì)請參考上一篇<虛擬內(nèi)存(VM)>。
注意,你不可以用VirtualFree來釋放了,只能用UnmapViewOfFile來。
C++程序如下:
LPVOID pP=VirtualAlloc(pVirtualMAP,100*1000*1000,MEM_COMMIT,PAGE_READWRITE);?
????????????MEMORYSTATUS memStatus10;
????????????GlobalMemoryStatus(&memStatus10);
???????????
cout<<"減少物理內(nèi)存="<<memStatus9.dwAvailPhys-memStatus10.dwAvailPhys<<endl;
cout<<"減少可用頁文件="<<memStatus9.dwAvailPageFile-memStatus10.dwAvailPageFile<<endl;
cout<<"減少可用進(jìn)程空間="<<memStatus9.dwAvailVirtual-memStatus10.dwAvailVirtual<<endl<<endl;
?
????????????bool?result=VirtualFree(pP,100000000,MEM_DECOMMIT);
????????????if(!result)
????????????????????????cout<<"釋放失敗!"<<endl;
?????????????result=VirtualFree(pP,100000000,MEM_RELEASE);
????????????if(!result)
????????????????????????cout<<"釋放失敗!"<<endl;
?
????????????CloseHandle(hVirtualMap);
????????????MEMORYSTATUS memStatus11;
????????????GlobalMemoryStatus(&memStatus11);
cout<<"增加物理內(nèi)存="<<memStatus11.dwAvailPhys-memStatus10.dwAvailPhys<<endl;
cout<<"增加可用頁文件="<<memStatus11.dwAvailPageFile-memStatus10.dwAvailPageFile<<endl;
cout<<"增加可用進(jìn)程空間="<<memStatus11.dwAvailVirtual-memStatus10.dwAvailVirtual<<endl<<endl;
?
????????????result=UnmapViewOfFile(pVirtualMAP);
????????????if(!result)
????????????????????????cout<<"撤銷映射失敗!"<<endl;
?
????????????MEMORYSTATUS memStatus12;
????????????GlobalMemoryStatus(&memStatus12);
cout<<"增加物理內(nèi)存="<<memStatus12.dwAvailPhys-memStatus11.dwAvailPhys<<endl;
cout<<"增加可用頁文件="<<memStatus12.dwAvailPageFile-memStatus11.dwAvailPageFile<<endl;
cout<<"增加可用進(jìn)程空間="
<<memStatus12.dwAvailVirtual-memStatus11.dwAvailVirtual<<endl<<endl;
結(jié)果如下:
?
可以看見,用VirtualFree是不能夠釋放這個(gè)稀疏映射的;最后用UnmapViewOfFile得以釋放進(jìn)程空間和物理內(nèi)存。
?
總結(jié)
以上是生活随笔為你收集整理的全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动设备真机调试本地程序的Node.js
- 下一篇: c++ winpcap开发(1)