問題&原因
1览徒、問題
最近發(fā)現(xiàn)游戲中MapViewOfFile的時候會報錯:GetLastError() == ERROR_ACCESS_DENIED砾淌、ERROR_NOT_ENOUGH_MEMORY郑现、ERROR_OUTOFMOMORY.
代碼中的處理是釋放所有的映射空間缕坎,再重新映射琅催。但是再次映射的時候仍然會繼續(xù)報錯傅联,導(dǎo)致釋放映射空間的操作很費cpu,導(dǎo)致游戲卡頓厲害浩蓉。
2派继、原因
CreateFileMapping中指定的是Mapping的大小宾袜,在使用MapViewOfFile時捻艳,位置和大小之和不能超過Mapping,Mapping的大小是不能調(diào)整的庆猫,如要改變只能關(guān)閉后重新創(chuàng)建认轨;而View的大小除了不能超出Mapping之外,還會受到進程的虛擬地址空間的限制月培,View要使用連續(xù)的虛擬地址空間嘁字,由于程序運行過程中會有很多分配和釋放內(nèi)存的操作,導(dǎo)致地址空間形成很多“碎片”杉畜,在程序運行一端時間后纪蜒,可能虛擬內(nèi)存剩余的總量較大,但最大的連續(xù)地址空間卻不大此叠,在MapViewOfFile時如果找不到足夠的連續(xù)空間則會失敗纯续,所以MapViewOfFile最好在程序初始化時一次分配足夠的大小。
學(xué)習(xí)
CreateFileMapping 函數(shù)
HANDLE CreateFileMapping(
? HANDLE hFile,?????????????????????? //物理文件句柄
? LPSECURITY_ATTRIBUTES lpAttributes, //安全設(shè)置
? DWORD flProtect,??????????????????? //保護設(shè)置
? DWORD dwMaximumSizeHigh,??????????? //高位文件大小(因為我們的游戲是32位的灭袁,所以此處設(shè)為 0)
? DWORD dwMaximumSizeLow,???????????? //低位文件大小
? LPCTSTR lpName????????????????????? //共享內(nèi)存名稱
);
文件映射問題?
內(nèi)存映射文件并不是簡單的文件I/O操作猬错,實際用到了Windows的核心編程技術(shù)--內(nèi)存管理。所以茸歧,如果想對內(nèi)存映射文件有更深刻的認(rèn)識倦炒,必須對Windows操作系統(tǒng)的內(nèi)存管理機制有清楚的認(rèn)識,內(nèi)存管理的相關(guān)知識非常復(fù)雜软瞎,超出了本文的討論范疇逢唤,在此就不再贅述拉讯,感興趣的讀者可以參閱其他相關(guān)書籍。下面給出使用內(nèi)存映射文件的一般方法:?
? 首先要通過CreateFile()函數(shù)來創(chuàng)建或打開一個文件內(nèi)核對象智玻,這個對象標(biāo)識了磁盤上將要用作內(nèi)存映射文件的文件遂唧。在用CreateFile()將文件映像在物理存儲器的位置通告給操作系統(tǒng)后,只指定了映像文件的路徑吊奢,映像的長度還沒有指定盖彭。為了指定文件映射對象需要多大的物理存儲空間還需要通過CreateFileMapping()函數(shù)來創(chuàng)建一個文件映射內(nèi)核對象以告訴系統(tǒng)文件的尺寸以及訪問文件的方式。在創(chuàng)建了文件映射對象后页滚,還必須為文件數(shù)據(jù)保留一個地址空間區(qū)域召边,并把文件數(shù)據(jù)作為映射到該區(qū)域的物理存儲器進行提交。由MapViewOfFile()函數(shù)負(fù)責(zé)通過系統(tǒng)的管理而將文件映射對象的全部或部分映射到進程地址空間裹驰。此時隧熙,對內(nèi)存映射文件的使用和處理同通常加載到內(nèi)存中的文件數(shù)據(jù)的處理方式基本一樣,在完成了對內(nèi)存映射文件的使用時幻林,還要通過一系列的操作完成對其的清除和使用過資源的釋放贞盯。這部分相對比較簡單,可以通過UnmapViewOfFile()完成從進程的地址空間撤消文件數(shù)據(jù)的映像沪饺、通過CloseHandle()關(guān)閉前面創(chuàng)建的文件映射對象和文件對象躏敢。
內(nèi)存映射文件相關(guān)函數(shù)?
在使用內(nèi)存映射文件時,所使用的API函數(shù)主要就是前面提到過的那幾個函數(shù)整葡,下面分別對其進行介紹:?
HANDLE?? CreateFile(LPCTSTR?? lpFileName,?
DWORD?? dwDesiredAccess,?
DWORD?? dwShareMode,?
LPSECURITY_ATTRIBUTES?? lpSecurityAttributes,?
DWORD?? dwCreationDisposition,?
DWORD?? dwFlagsAndAttributes,?
HANDLE?? hTemplateFile);?
? 函數(shù)CreateFile()即使是在普通的文件操作時也經(jīng)常用來創(chuàng)建件余、打開文件,在處理內(nèi)存映射文件時遭居,該函數(shù)來創(chuàng)建/打開一個文件內(nèi)核對象啼器,并將其句柄返回,在調(diào)用該函數(shù)時需要根據(jù)是否需要數(shù)據(jù)讀寫和文件的共享方式來設(shè)置參數(shù)dwDesiredAccess和dwShareMode俱萍,錯誤的參數(shù)設(shè)置將會導(dǎo)致相應(yīng)操作時的失敗端壳。?
?HANDLE?? CreateFileMapping(HANDLE?? hFile,?
LPSECURITY_ATTRIBUTES?? lpFileMappingAttributes,?
DWORD?? flProtect,?
DWORD?? dwMaximumSizeHigh,?
DWORD?? dwMaximumSizeLow,?
LPCTSTR?? lpName);?
? CreateFileMapping()函數(shù)創(chuàng)建一個文件映射內(nèi)核對象,通過參數(shù)hFile指定待映射到進程地址空間的文件句柄(該句柄由CreateFile()函數(shù)的返回值獲惹鼓ⅰ)损谦。由于內(nèi)存映射文件的物理存儲器實際是存儲于磁盤上的一個文件,而不是從系統(tǒng)的頁文件中分配的內(nèi)存腥寇,所以系統(tǒng)不會主動為其保留地址空間區(qū)域成翩,也不會自動將文件的存儲空間映射到該區(qū)域,為了讓系統(tǒng)能夠確定對頁面采取何種保護屬性赦役,需要通過參數(shù)flProtect來設(shè)定麻敌,保護屬性PAGE_READONLY、PAGE_READWRITE和PAGE_WRITECOPY分別表示文件映射對象被映射后掂摔,可以讀取术羔、讀寫文件數(shù)據(jù)赢赊。在使用PAGE_READONLY時,必須確保CreateFile()采用的是GENERIC_READ參數(shù)级历;PAGE_READWRITE則要求CreateFile()采用的是GENERIC_READ|GENERIC_WRITE參數(shù)释移;至于屬性PAGE_WRITECOPY則只需要確保CreateFile()采用了GENERIC_READ和GENERIC_WRITE其中之一即可。DWORD型的參數(shù)dwMaximumSizeHigh和dwMaximumSizeLow也是相當(dāng)重要的寥殖,指定了文件的最大字節(jié)數(shù)玩讳,由于這兩個參數(shù)共64位,因此所支持的最大文件長度為16EB嚼贡,幾乎可以滿足任何大數(shù)據(jù)量文件處理場合的要求熏纯。dwMaximumSizeHigh,dwMaximumSizeLow:如果這兩個參數(shù)為0粤策,則文件映射對象的最大長度等于hFile指定的文件長度樟澜。
LPVOID?? MapViewOfFile(HANDLE?? hFileMappingObject,?
DWORD?? dwDesiredAccess,?
DWORD?? dwFileOffsetHigh,?
DWORD?? dwFileOffsetLow,?
DWORD?? dwNumberOfBytesToMap);?
MapViewOfFile()函數(shù)負(fù)責(zé)把文件數(shù)據(jù)映射到進程的地址空間,參數(shù)hFileMappingObject為CreateFileMapping()返回的文件映像對象句柄叮盘。參數(shù)dwDesiredAccess則再次指定了對文件數(shù)據(jù)的訪問方式秩贰,而且同樣要與CreateFileMapping()函數(shù)所設(shè)置的保護屬性相匹配。雖然這里一再對保護屬性進行重復(fù)設(shè)置看似多余柔吼,但卻可以使應(yīng)用程序能更多的對數(shù)據(jù)的保護屬性實行有效控制毒费。MapViewOfFile()函數(shù)允許全部或部分映射文件,在映射時嚷堡,需要指定數(shù)據(jù)文件的偏移地址以及待映射的長度蝗罗。其中艇棕,文件的偏移地址由DWORD型的參數(shù)dwFileOffsetHigh和dwFileOffsetLow組成的64位值來指定蝌戒,而且必須是操作系統(tǒng)的分配粒度的整數(shù)倍,對于Windows操作系統(tǒng),分配粒度固定為64KB。當(dāng)然闷袒,也可以通過如下代碼來動態(tài)獲取當(dāng)前操作系統(tǒng)的分配粒度:?
SYSTEM_INFO?? sinf;?
GetSystemInfo(&sinf);?
DWORD?? dwAllocationGranularity?? =?? sinf.dwAllocationGranularity;?
參數(shù)dwNumberOfBytesToMap指定了數(shù)據(jù)文件的映射長度携添,這里需要特別指出的是,對于Windows?? 9x操作系統(tǒng)忧设,如果MapViewOfFile()無法找到足夠大的區(qū)域來存放整個文件映射對象,將返回空值(NULL);但是在Windows?? 2000下彩扔,MapViewOfFile()只需要為必要的視圖找到足夠大的一個區(qū)域即可,而無須考慮整個文件映射對象的大小僻爽。?
在完成對映射到進程地址空間區(qū)域的文件處理后虫碉,需要通過函數(shù)UnmapViewOfFile()完成對文件數(shù)據(jù)映像的釋放,該函數(shù)原型聲明如下:?
BOOL?? UnmapViewOfFile(LPCVOID?? lpBaseAddress);?
唯一的參數(shù)lpBaseAddress指定了返回區(qū)域的基地址胸梆,必須將其設(shè)定為MapViewOfFile()的返回值敦捧。在使用了函數(shù)MapViewOfFile()之后须板,必須要有對應(yīng)的UnmapViewOfFile()調(diào)用,否則在進程終止之前兢卵,保留的區(qū)域?qū)o法釋放习瑰。除此之外,前面還曾由CreateFile()和CreateFileMapping()函數(shù)創(chuàng)建過文件內(nèi)核對象和文件映射內(nèi)核對象秽荤,在進程終止之前有必要通過CloseHandle()將其釋放甜奄,否則將會出現(xiàn)資源泄漏的問題。?