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