Windows核心编程 第十七章 -内存映射文件(上)
第1?7章?內(nèi)存映射文件
????對(duì)文件進(jìn)行操作幾乎是所有應(yīng)用程序都必須進(jìn)行的,并且這常常是人們爭(zhēng)論的一個(gè)問(wèn)題。應(yīng)用程序究竟是應(yīng)該打開文件,讀取文件并關(guān)閉文件,還是打開文件,然后使用一種緩沖算法,從文件的各個(gè)不同部分進(jìn)行讀取和寫入呢?M?i?c?r?o?s?o?f?t提供了一種兩全其美的方法,那就是內(nèi)存映射文件。
????與虛擬內(nèi)存一樣,內(nèi)存映射文件可以用來(lái)保留一個(gè)地址空間的區(qū)域,并將物理存儲(chǔ)器提交給該區(qū)域。它們之間的差別是,物理存儲(chǔ)器來(lái)自一個(gè)已經(jīng)位于磁盤上的文件,而不是系統(tǒng)的頁(yè)文件。一旦該文件被映射,就可以訪問(wèn)它,就像整個(gè)文件已經(jīng)加載內(nèi)存一樣。
內(nèi)存映射文件可以用于3個(gè)不同的目的:
??????系統(tǒng)使用內(nèi)存映射文件,以便加載和執(zhí)行?.?e?x?e和D?L?L文件。這可以大大節(jié)省頁(yè)文件空間和應(yīng)用程序啟動(dòng)運(yùn)行所需的時(shí)間。
??????可以使用內(nèi)存映射文件來(lái)訪問(wèn)磁盤上的數(shù)據(jù)文件。這使你可以不必對(duì)文件執(zhí)行?I?/?O操作,并且可以不必對(duì)文件內(nèi)容進(jìn)行緩存。
??????可以使用內(nèi)存映射文件,使同一臺(tái)計(jì)算機(jī)上運(yùn)行的多個(gè)進(jìn)程能夠相互之間共享數(shù)據(jù)。
????Wi?n?d?o?w?s確實(shí)提供了其他一些方法,以便在進(jìn)程之間進(jìn)行數(shù)據(jù)通信,但是這些方法都是使用內(nèi)存映射文件來(lái)實(shí)現(xiàn)的,這使得內(nèi)存映射文件成為單個(gè)計(jì)算機(jī)上的多個(gè)進(jìn)程互相進(jìn)
行通信的最有效的方法。本章將要介紹內(nèi)存映射文件的各種使用方法。
17.1?內(nèi)存映射的可執(zhí)行文件和D?L?L文件
????當(dāng)線程調(diào)用C?r?e?a?t?e?P?r?o?c?e?s?s時(shí),系統(tǒng)將執(zhí)行下列操作步驟:
????1)?系統(tǒng)找出在調(diào)用C?r?e?a?t?e?P?r?o?c?e?s?s時(shí)設(shè)定的.?e?x?e文件。如果找不到這個(gè).?e?x?e文件,進(jìn)程將無(wú)法創(chuàng)建,C?r?e?a?t?e?P?r?o?c?e?s?s將返回FA?L?S?E。
????2)?系統(tǒng)創(chuàng)建一個(gè)新進(jìn)程內(nèi)核對(duì)象。
????3)?系統(tǒng)為這個(gè)新進(jìn)程創(chuàng)建一個(gè)私有地址空間。
????4)?系統(tǒng)保留一個(gè)足夠大的地址空間區(qū)域,用于存放該?.?e?x?e文件。該區(qū)域需要的位置在?.?ex?e文件本身中設(shè)定。按照默認(rèn)設(shè)置,.?e?x?e文件的基地址是0?x?0?0?4?0?0?0?0?0(這個(gè)地址可能不同于在6?4位Windows?2000上運(yùn)行的6?4位應(yīng)用程序的地址),但是,可以在創(chuàng)建應(yīng)用程序的?.?e?x?e文件時(shí)重載這個(gè)地址,方法是在鏈接應(yīng)用程序時(shí)使用鏈接程序的?/?B?A?S?E選項(xiàng)。
????5)?系統(tǒng)注意到支持已保留區(qū)域的物理存儲(chǔ)器是在磁盤上的?.?e?x?e文件中,而不是在系統(tǒng)的頁(yè)文件中。當(dāng).?e?x?e文件被映射到進(jìn)程的地址空間中之后,系統(tǒng)將訪問(wèn)?.?e?x?e文件的一個(gè)部分,該部分列出了包含?.?e?x?e文件中的代碼要調(diào)用的函數(shù)的?D?L?L文件。然后,系統(tǒng)為每個(gè)?D?L?L文件調(diào)用L?o?a?d?L?i?b?r?a?r?y函數(shù),如果任何一個(gè)D?L?L需要更多的D?L?L,那么系統(tǒng)將調(diào)用L?o?a?d?L?i?b?r?a?r?y函數(shù),以便加載這些D?L?L。每當(dāng)調(diào)用L?o?a?d?L?i?b?r?a?r?y來(lái)加載一個(gè)D?L?L時(shí),系統(tǒng)將執(zhí)行下列操作步驟,它們均類似上面的第4和第5個(gè)步驟:
????1)?系統(tǒng)保留一個(gè)足夠大的地址空間區(qū)域,用于存放該?D?L?L文件。該區(qū)域需要的位置在
D?L?L文件本身中設(shè)定。按照默認(rèn)設(shè)置,?M?i?c?r?o?s?o?f?t的Visual?C++建立的?D?L?L文件基地址是0?x?1?0?0?0?0?0?0?0(這個(gè)地址可能不同于在?6?4位Windows?2000上運(yùn)行的6?4位D?L?L的地址)但是,你可以在創(chuàng)建D?L?L文件時(shí)重載這個(gè)地址,方法是使用鏈接程序的?/?B?A?S?E選項(xiàng)。Wi?n?d?o?w?s提供的所有標(biāo)準(zhǔn)系統(tǒng)D?L?L都擁有不同的基地址,這樣,如果加載到單個(gè)地址空間,它們就不會(huì)重疊。
????2)?如果系統(tǒng)無(wú)法在該D?L?L的首選基地址上保留一個(gè)區(qū)域,其原因可能是該區(qū)域已經(jīng)被另一個(gè)D?L?L或.?e?x?e占用,也可能是因?yàn)樵搮^(qū)域不夠大,此時(shí)系統(tǒng)將設(shè)法尋找另一個(gè)地址空間的區(qū)域來(lái)保留該D?L?L。如果一個(gè)D?L?L無(wú)法加載到它的首選基地址,這將是非常不利的,原因有二。首先,如果系統(tǒng)沒有再定位信息,它就無(wú)法加載該D?L?L(可以在D?L?L創(chuàng)建時(shí),使用鏈接程序的/?F?I?X?E?D開關(guān),從D?L?L中刪除再定位信息,這能夠使D?L?L變得比較小,但是這也意味著該D?L?L必須加載到它的首選地址中,否則它就根本無(wú)法加載)。第二,系統(tǒng)必須在?D?L?L中執(zhí)行某些再定位操作。在Windows?98中,系統(tǒng)可以在頁(yè)面被轉(zhuǎn)入R?A?M時(shí)執(zhí)行再定位操作。在Windows?2000中,這些再定位操作需要由系統(tǒng)的頁(yè)文件提供更多的存儲(chǔ)器,它們也增加了加載D?L?L所需要的時(shí)間量。
????3)?系統(tǒng)會(huì)注意到支持已保留區(qū)域的物理存儲(chǔ)器位于磁盤上的?D?L?L文件中,而不是在系統(tǒng)的頁(yè)文件中。如果由D?L?L無(wú)法加載到它的首選基地址,Windows?2000必須執(zhí)行再定位操作,那么系統(tǒng)也將注意到D?L?L的某些物理存儲(chǔ)器已經(jīng)被映射到頁(yè)文件中。
如果由于某個(gè)原因系統(tǒng)無(wú)法映射?.?e?x?e和所有必要的D?L?L文件,那么系統(tǒng)就會(huì)向用戶顯示一個(gè)消息框,并且釋放進(jìn)程的地址空間和進(jìn)程對(duì)象。?C?r?e?a?t?e?P?r?o?c?e?s?s函數(shù)將向調(diào)用者返回FA?L?S?E,調(diào)用者可以調(diào)用G?e?t?L?a?s?t?E?r?r?o?r函數(shù),以便更好地了解為什么無(wú)法創(chuàng)建該進(jìn)程。
當(dāng)所有的.?e?x?e和D?L?L文件都被映射到進(jìn)程的地址空間之后,系統(tǒng)就可以開始執(zhí)行?.?e?x?e文件的啟動(dòng)代碼。當(dāng).?e?x?e文件被映射后,系統(tǒng)將負(fù)責(zé)所有的分頁(yè)、緩沖和高速緩存的處理。例如,如果.?e?x?e文件中的代碼使它跳到一個(gè)尚未加載到內(nèi)存的指令地址,那么就會(huì)出現(xiàn)一個(gè)錯(cuò)誤。系統(tǒng)能夠發(fā)現(xiàn)這個(gè)錯(cuò)誤,并且自動(dòng)將這頁(yè)代碼從該文件的映像加載到一個(gè)?R?A?M頁(yè)面。然后,系統(tǒng)將這個(gè)R?A?M頁(yè)面映射到進(jìn)程的地址空間中的相應(yīng)位置,并且讓線程繼續(xù)運(yùn)行,就像這頁(yè)代碼已經(jīng)加載了一樣。當(dāng)然,這一切是應(yīng)用程序看不見的。當(dāng)進(jìn)程中的線程每次試圖訪問(wèn)尚未加載到R?A?M的代碼或數(shù)據(jù)時(shí),該進(jìn)程就會(huì)重復(fù)執(zhí)行。
17.1.1?可執(zhí)行文件或D?L?L的多個(gè)實(shí)例不能共享靜態(tài)數(shù)據(jù)
????當(dāng)為正在運(yùn)行的應(yīng)用程序創(chuàng)建新進(jìn)程時(shí),系統(tǒng)將打開用于標(biāo)識(shí)可執(zhí)行文件映像的文件映射對(duì)象的另一個(gè)內(nèi)存映射視圖,并創(chuàng)建一個(gè)新進(jìn)程對(duì)象和(為主線程創(chuàng)建)一個(gè)新線程對(duì)象。系統(tǒng)還要將新的進(jìn)程I?D和線程I?D賦予這些對(duì)象。通過(guò)使用內(nèi)存映射文件,同一個(gè)應(yīng)用程序的多個(gè)正在運(yùn)行的實(shí)例就能夠共享R?A?M中的相同代碼和數(shù)據(jù)。
????這里有一個(gè)小問(wèn)題需要注意。進(jìn)程使用的是一個(gè)平面地址空間。當(dāng)編譯和鏈接你的程序時(shí),所有的代碼和數(shù)據(jù)都被合并在一起,組成一個(gè)很大的結(jié)構(gòu)。數(shù)據(jù)與代碼被分開,但僅限于跟在.?e?x?e文件中的代碼后面的數(shù)據(jù)而已?。圖1?7?-?1簡(jiǎn)單說(shuō)明了應(yīng)用程序的代碼和數(shù)據(jù)究竟是如何加載到虛擬內(nèi)存中,然后又被映射到應(yīng)用程序的地址空間中的。
????作為一個(gè)例子,假設(shè)應(yīng)用程序的第二個(gè)實(shí)例正在運(yùn)行。系統(tǒng)只是將包含文件的代碼和數(shù)據(jù)的虛擬內(nèi)存頁(yè)面映射到第二個(gè)應(yīng)用程序的地址空間,如圖1?7?-?2所示。
如果應(yīng)用程序的一個(gè)實(shí)例改變了駐留在數(shù)據(jù)頁(yè)面中的某些全局變量,那么該應(yīng)用程序的所有實(shí)例的內(nèi)存內(nèi)容都將改變。這種類型的改變可能帶來(lái)災(zāi)難性的后果,因此是決不允許的。
系統(tǒng)運(yùn)用內(nèi)存管理系統(tǒng)的c?o?p?y?-?o?n?-?w?r?i?t?e(寫入時(shí)拷貝)特性來(lái)防止進(jìn)行這種改變。每當(dāng)應(yīng)用程序嘗試將數(shù)據(jù)寫入它的內(nèi)存映射文件時(shí),系統(tǒng)就會(huì)抓住這種嘗試,為包含應(yīng)用程序嘗試寫入數(shù)據(jù)的內(nèi)存頁(yè)面分配一個(gè)新內(nèi)存塊,再拷貝該頁(yè)面的內(nèi)容,并允許該應(yīng)用程序?qū)?shù)據(jù)寫入這個(gè)新分配的內(nèi)存塊。結(jié)果,同一個(gè)應(yīng)用程序的所有其他實(shí)例的運(yùn)行都不會(huì)受到影響。圖?1?7?-?3顯示了當(dāng)應(yīng)用程序的第一個(gè)實(shí)例嘗試改變數(shù)據(jù)頁(yè)面2時(shí)出現(xiàn)的情況。
????系統(tǒng)分配一個(gè)新的虛擬內(nèi)存頁(yè)面,并且將數(shù)據(jù)頁(yè)面2的內(nèi)容拷貝到新頁(yè)面中。第一個(gè)實(shí)例的地址空間發(fā)生了變更,這樣,新數(shù)據(jù)頁(yè)面就被映射到與原始地址頁(yè)面相同位置上的地址空間中。這時(shí)系統(tǒng)就可以讓進(jìn)程修改全局變量,而不必?fù)?dān)心改變同一個(gè)應(yīng)用程序的另一個(gè)實(shí)例的數(shù)據(jù)。
當(dāng)應(yīng)用程序被調(diào)試時(shí),將會(huì)發(fā)生類似的事件。比如說(shuō),你正在運(yùn)行一個(gè)應(yīng)用程序的多個(gè)實(shí)例,并且只想調(diào)試其中的一個(gè)實(shí)例。你訪問(wèn)調(diào)試程序,在一行源代碼中設(shè)置一個(gè)斷點(diǎn)。調(diào)試程序修改了你的代碼,將你的一個(gè)匯編語(yǔ)言指令改為能使調(diào)試程序自行激活的指令。因此你再次遇到了同樣的問(wèn)題。當(dāng)調(diào)試程序修改代碼時(shí),它將導(dǎo)致應(yīng)用程序的所有實(shí)例在修改后的匯編語(yǔ)言指令運(yùn)行時(shí)激活該調(diào)試程序。為了解決這個(gè)問(wèn)題,系統(tǒng)再次使用?c?o?p?y?-?o?n?-?w?r?i?t?e內(nèi)存。當(dāng)系統(tǒng)發(fā)現(xiàn)調(diào)試程序試圖修改代碼時(shí),它就分配一個(gè)新內(nèi)存塊,將包含該指令的頁(yè)面拷貝到新的內(nèi)存頁(yè)面中,并且允許調(diào)試程序修改頁(yè)面拷貝中的代碼。
? ? Windows?98?當(dāng)一個(gè)進(jìn)程被加載時(shí),系統(tǒng)要查看文件映像的所有頁(yè)面。系統(tǒng)立即為通常用c?o?p?y?-?o?n?-?w?r?i?t?e屬性保護(hù)的那些頁(yè)面提交頁(yè)文件中的存儲(chǔ)器。這些頁(yè)面只是被提交而已,它們并不被訪問(wèn)。當(dāng)文件映像中的頁(yè)面被訪問(wèn)時(shí),系統(tǒng)就加載相應(yīng)的頁(yè)面。如果該頁(yè)面從來(lái)沒有被修改,它就可以從內(nèi)存中刪除,并在必要時(shí)重新加載。但是,如果文件的頁(yè)面被修改了,系統(tǒng)就將修改過(guò)的頁(yè)面轉(zhuǎn)到頁(yè)文件中以前被提交的頁(yè)面之一。
? ? Windows?2000與Windows?98之間的行為特性的唯一差別,是在你加載一個(gè)模塊的
兩個(gè)拷貝并且可寫入的數(shù)據(jù)尚未被修改的時(shí)候顯示出來(lái)的。在這種情況下,在Windows?2000下運(yùn)行的進(jìn)程能夠共享數(shù)據(jù),而在Windows?98下,每個(gè)進(jìn)程都可以得到它自己的數(shù)據(jù)拷貝。如果只加載模塊的一個(gè)拷貝,或者可寫入的數(shù)據(jù)已經(jīng)被修改(這是通常的情況),那么Windows?2000與Windows?98的行為特性是完全相同的。
17.1.2?在可執(zhí)行文件或D?L?L的多個(gè)實(shí)例之間共享靜態(tài)數(shù)據(jù)
? ? 全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù)不能被同一個(gè)?.?e?x?e或D?L?L文件的多個(gè)映像共享,這是個(gè)安全的默認(rèn)設(shè)置。但是,在某些情況下,讓一個(gè)?.?e?x?e文件的多個(gè)映像共享一個(gè)變量的實(shí)例是非常有用和方便的。例如,Wi?n?d?o?w?s沒有提供任何簡(jiǎn)便的方法來(lái)確定用戶是否在運(yùn)行應(yīng)用程序的多個(gè)實(shí)例。但是,如果能夠讓所有實(shí)例共享單個(gè)全局變量,那么這個(gè)全局變量就能夠反映正在運(yùn)行的實(shí)例的數(shù)量。當(dāng)用戶啟動(dòng)應(yīng)用程序的一個(gè)實(shí)例時(shí),新實(shí)例的線程能夠簡(jiǎn)單地查看全局變量的值(它已經(jīng)被另一個(gè)實(shí)例更新);如果這個(gè)數(shù)量大于?1,那么第二個(gè)實(shí)例就能夠通知用戶,該應(yīng)用程序只有一個(gè)實(shí)例可以運(yùn)行,而第二個(gè)實(shí)例將終止運(yùn)行。
? ? 本節(jié)將介紹一種方法,它允許你共享?.?e?x?e或D?L?L文件的所有實(shí)例的變量。不過(guò)在介紹這個(gè)方法之前,首先讓我們介紹一些背景知識(shí)。
每個(gè).?e?x?e或D?L?L文件的映像都由許多節(jié)組成。按照規(guī)定,每個(gè)標(biāo)準(zhǔn)節(jié)的名字均以圓點(diǎn)開頭。例如,當(dāng)編譯你的程序時(shí),編譯器會(huì)將所有代碼放入一個(gè)名叫?.?t?e?x?t的節(jié)中。該編譯器還將所有未經(jīng)初始化的數(shù)據(jù)放入一個(gè).?b?s?s節(jié),而已經(jīng)初始化的所有數(shù)據(jù)則放入.?d?a?t?a節(jié)中。
每一節(jié)都擁有與其相關(guān)的一組屬性,這些屬性如表1?7?-?1所示。
????表1?7?-?2顯示了比較常見的一些節(jié)的名字,并且說(shuō)明了每一節(jié)的作用。除了編譯器和鏈接程序創(chuàng)建的標(biāo)準(zhǔn)節(jié)外,也可以在使用下面的命令進(jìn)行編譯時(shí)創(chuàng)建自己的節(jié):
? ? 當(dāng)編譯器對(duì)這個(gè)代碼進(jìn)行編譯時(shí),它創(chuàng)建一個(gè)新節(jié),稱為?S?h?a?r?e?d,并將它在編譯指示后面看到的所有已經(jīng)初始化(i?n?i?t?i?a?l?i?z?e?d)的數(shù)據(jù)變量放入這個(gè)新節(jié)中。在上面這個(gè)例子中,變量放入S?h?a?r?e?d節(jié)中。該變量后面的#pragma?dataseg()一行告訴編譯器停止將已經(jīng)初始化的變量放入S?h?a?r?e?d節(jié),并且開始將它們放回到默認(rèn)數(shù)據(jù)節(jié)中。需要記住的是,編譯器只將已經(jīng)初始化的變量放入新節(jié)中。例如,如果我從前面的代碼段中刪除初始化變量(如下面的代碼所示),那么編譯器將把該變量放入S?h?a?r?e?d節(jié)以外的節(jié)中。
Microsoft?的Visual?C++編譯器提供了一個(gè)A?l?l?o?c?a?t?e說(shuō)明符,使你可以將未經(jīng)初始化的數(shù)據(jù)放入你希望的任何節(jié)中。請(qǐng)看下面的代碼:
?
? ? 上面的注釋清楚地指明了指定的變量將被放入哪一節(jié)。若要使?A?l?l?o?c?a?t?e聲明的規(guī)則正確地起作用,那么首先必須創(chuàng)建節(jié)。如果刪除前面這個(gè)代碼中的第一行?#pragma?data_seg,上面的代碼將不進(jìn)行編譯。
? ? 之所以將變量放入它們自己的節(jié)中,最常見的原因也許是要在?.?e?x?e或D?L?L文件的多個(gè)映像之間共享這些變量。按照默認(rèn)設(shè)置,.?e?x?e或D?L?L文件的每個(gè)映像都有它自己的一組變量。然而,可以將你想在該模塊的所有映像之間共享的任何變量組合到它自己的節(jié)中去。當(dāng)給變量分組時(shí),系統(tǒng)并不為.?e?x?e或D?L?L文件的每個(gè)映像創(chuàng)建新實(shí)例。
? ? 僅僅告訴編譯器將某些變量放入它們自己的節(jié)中,是不足以實(shí)現(xiàn)對(duì)這些變量的共享的。還必須告訴鏈接程序,某個(gè)節(jié)中的變量是需要加以共享的。若要進(jìn)行這項(xiàng)操作,可以使用鏈接程序的命令行上的/?S?E?C?T?I?O?N開關(guān):
/SECTION:name,attributes
????在冒號(hào)的后面,放入你想要改變其屬性的節(jié)的名字。在我們的例子中,我們想要改變
S?h?a?r?e?d節(jié)的屬性。因此應(yīng)該創(chuàng)建下面的鏈接程序開關(guān):
/SECTION:Shared,RWS
在逗號(hào)的后面,我們?cè)O(shè)定了需要的屬性。用?R代表?R?E?A?D,W代表?W?E?I?T?E,E代表E?X?E?C?U?T?E,S代表S?H?A?R?E?D。上面的開關(guān)用于指明位于S?h?a?r?e?d節(jié)中的數(shù)據(jù)是可以讀取、寫入和共享的數(shù)據(jù)。如果想要改變多個(gè)節(jié)的屬性,必須多次設(shè)定?/?S?E?C?T?I?O?N開關(guān),也就是為你要改變屬性的每個(gè)節(jié)設(shè)定一個(gè)/?S?E?C?T?I?O?N開關(guān)。
也可以使用下面的句法將鏈接程序開關(guān)嵌入你的源代碼中:
#pragma?comment(linker,”/SECTION:Shared,RWS”)
? ? 這一行代碼告訴編譯器將上面的字符串嵌入名字為“?.?d?r?e?c?t?v?e”的節(jié)。當(dāng)鏈接程序?qū)⑺械?/span>.?o?b?j模塊組合在一起時(shí),鏈接程序就要查看每個(gè)?.?o?b?j模塊的“.?d?r?e?c?t?v?e”節(jié),并且規(guī)定所有的字符串均作為命令行參數(shù)傳遞給該鏈接程序。我一直使用這種方法,因?yàn)樗浅7奖恪H绻麑⒃创a文件移植到一個(gè)新項(xiàng)目中,不必記住在Visual?C++的Project?Settings(項(xiàng)目設(shè)置)對(duì)話框中設(shè)置鏈接程序開關(guān)。
? ? 雖然可以創(chuàng)建共享節(jié),但是,由于兩個(gè)原因,?M?i?c?r?o?s?o?f?t并不鼓勵(lì)你使用共享節(jié)。第一,用這種方法共享內(nèi)存有可能破壞系統(tǒng)的安全。第二,共享變量意味著一個(gè)應(yīng)用程序中的錯(cuò)誤可能影響另一個(gè)應(yīng)用程序的運(yùn)行,因?yàn)樗鼪]有辦法防止某個(gè)應(yīng)用程序?qū)?shù)據(jù)隨機(jī)寫入一個(gè)數(shù)據(jù)塊。
? ? 假設(shè)你編寫了兩個(gè)應(yīng)用程序,每個(gè)應(yīng)用程序都要求用戶輸入一個(gè)口令。然而你又決定給應(yīng)用程序添加一些特性,使用戶操作起來(lái)更加方便些:如果在第二個(gè)應(yīng)用程序啟動(dòng)運(yùn)行時(shí),用戶正在運(yùn)行其中的一個(gè)應(yīng)用程序,那么第二個(gè)應(yīng)用程序就可以查看共享內(nèi)存的內(nèi)容,以便獲得用戶的口令。這樣,如果程序中的某一個(gè)已經(jīng)被使用,那么用戶就不必重新輸入他的口令。
? ? 這聽起來(lái)沒有什么問(wèn)題。畢竟沒有別的應(yīng)用程序而只有你自己的應(yīng)用程序加載了?D?L?L,并且知道到什么地方去查找包含在共享節(jié)中的口令。但是,黑客正在窺視著你的行動(dòng),如果他們想要得到你的口令,只需要編寫一段很短的程序,加載到你的公司的?D?L?L文件中,然后監(jiān)控共享內(nèi)存塊。當(dāng)用戶輸入口令時(shí),黑客的程序就能知道該用戶的口令。
? ? 黑客精心編制的程序也可能試圖反復(fù)猜測(cè)用戶的口令并將它們寫入共享內(nèi)存。一旦該程序猜測(cè)到正確的口令,它就能夠?qū)⒏鞣N命令發(fā)送給兩個(gè)應(yīng)用程序中的一個(gè)。如果有一種辦法只為某些應(yīng)用程序賦予訪問(wèn)權(quán),以便加載一個(gè)特定的?D?L?L,那么這個(gè)問(wèn)題也許是可以解決的。但是目前還不行,因?yàn)槿魏纬绦蚨寄軌蛘{(diào)用L?o?a?d?L?i?b?r?a?r?y函數(shù)來(lái)顯式加載D?L?L。
17.1.3?AppInst示例應(yīng)用程序
????書上是寫了一個(gè)測(cè)試程序,不用書上的了,自己也簡(jiǎn)單的寫了個(gè)測(cè)試程序,通過(guò)內(nèi)存映射來(lái)實(shí)現(xiàn)共享。根據(jù)程序運(yùn)行的內(nèi)存映射原理,自己創(chuàng)建一個(gè)節(jié),改變其屬性達(dá)到共享數(shù)據(jù)的功能。
17.2?內(nèi)存映射數(shù)據(jù)文件
????操作系統(tǒng)使得內(nèi)存能夠?qū)⒁粋€(gè)數(shù)據(jù)文件映射到進(jìn)程的地址空間中。因此,對(duì)大量的數(shù)據(jù)進(jìn)行操作是非常方便的。
為了理解用這種方法來(lái)使用內(nèi)存映射文件的功能,讓我們看一看如何用?4種方法來(lái)實(shí)現(xiàn)一個(gè)程序,以便將文件中的所有字節(jié)的順序進(jìn)行倒序。
17.2.1?方法1:一個(gè)文件,一個(gè)緩存
? ? 第一種方法也是理論上最簡(jiǎn)單的方法,它需要分配足夠大的內(nèi)存塊來(lái)存放整個(gè)文件。該文件被打開,它的內(nèi)容被讀入內(nèi)存塊,然后該文件被關(guān)閉。文件內(nèi)容進(jìn)入內(nèi)存后,我們就可以對(duì)所有字節(jié)的順序進(jìn)行倒序,方法是將第一個(gè)字節(jié)倒騰為最后一個(gè)字節(jié),第二個(gè)字節(jié)倒騰為倒數(shù)第二個(gè)字節(jié),依次類推。這個(gè)倒騰操作將一直進(jìn)行下去直到文件的中間位置。當(dāng)所有的字節(jié)都已經(jīng)倒騰之后,就可以重新打開該文件,并用內(nèi)存塊的內(nèi)容來(lái)改寫它的內(nèi)容。
? ? 這種方法實(shí)現(xiàn)起來(lái)非常容易,但是它有兩個(gè)缺點(diǎn)。首先,必須分配一個(gè)與文件大小相同的內(nèi)存塊。如果文件比較小,那么這沒有什么問(wèn)題。但是如果文件非常大,比如說(shuō)有?2?G?B大,那該怎么辦呢?一個(gè)3?2位的系統(tǒng)不允許應(yīng)用程序提交那么大的物理內(nèi)存塊。因此大文件需要使用不同的方法。
? ? 第二,如果進(jìn)程在運(yùn)行過(guò)程的中間被中斷,也就是說(shuō)當(dāng)?shù)剐蚝蟮淖止?jié)被重新寫入該文件時(shí)進(jìn)程被中斷,那么文件的內(nèi)容就會(huì)遭到破壞。防止出現(xiàn)這種情況的最簡(jiǎn)單的方法是在對(duì)它的內(nèi)容進(jìn)行倒序之前先制作一個(gè)原始文件的拷貝。如果整個(gè)進(jìn)程運(yùn)行成功,那么可以刪除該文件的拷貝。這種方法需要更多的磁盤空間。
17.2.2?方法2:兩個(gè)文件,一個(gè)緩存
? ? 在第二種方法中,你打開現(xiàn)有的文件,并且在磁盤上創(chuàng)建一個(gè)長(zhǎng)度為?0的新文件。然后分配一個(gè)比較小的內(nèi)部緩存,比如說(shuō)?8?KB。你找到離原始文件結(jié)尾還有?8?KB的位置,將這最后的8?KB讀入緩存,將字節(jié)倒序,再將緩存中的內(nèi)容寫入新創(chuàng)建的文件。這個(gè)尋找、讀入、倒序和寫入的操作過(guò)程要反復(fù)進(jìn)行,直到到達(dá)原始文件的開頭。如果文件的長(zhǎng)度不是?8?KB的倍數(shù),那么必須進(jìn)行某些特殊的處理。當(dāng)原始文件完全處理完畢之后,將原始文件和新文件關(guān)閉,并刪除原始文件。
? ? 這種方法實(shí)現(xiàn)起來(lái)比第一種方法要復(fù)雜一些。它對(duì)內(nèi)存的使用效率要高得多,因?yàn)樗恍枰峙湟粋€(gè)8?KB的緩存塊,但是它存在兩個(gè)大問(wèn)題。首先,它的處理速度比第一種方法要慢,原因是在每個(gè)循環(huán)操作過(guò)程中,在執(zhí)行讀入操作之前,必須對(duì)原始文件進(jìn)行尋找操作。第二,這種方法可能要使用大量的硬盤空間。如果原始文件是?400?MB,那么隨著進(jìn)程的不斷運(yùn)行,新文件就會(huì)增大為400?MB。在原始文件被刪除之前,兩個(gè)文件總共需要占用?800?MB的磁盤空間。這比應(yīng)該需要的空間大400?MB。由于存在這個(gè)缺點(diǎn),因此引來(lái)了下一個(gè)方法。
17.2.3?方法3:一個(gè)文件,兩個(gè)緩存
????如果使用這個(gè)方法,那么我們假設(shè)程序初始化時(shí)分配了兩個(gè)獨(dú)立的?8?KB緩存。程序?qū)⑽募牡谝粋€(gè)8?KB讀入一個(gè)緩存,再將文件的第二個(gè)8?KB?讀入另一個(gè)緩存。然后進(jìn)程將兩個(gè)緩存的內(nèi)容進(jìn)行倒序,并將第一個(gè)緩存的內(nèi)容寫回文件的結(jié)尾處,將第二個(gè)緩存的內(nèi)容寫回同一個(gè)文件的開始處。每個(gè)迭代操作不斷進(jìn)行(以8?KB為單位,從文件的開始和結(jié)尾處移動(dòng)文件塊)。如果文件的長(zhǎng)度不是16?KB的倍數(shù),并且有兩個(gè)8?KB的文件塊相重疊,那么就需要進(jìn)行一些特殊的處理。這種特殊處理比上一種方法中的特殊處理更加復(fù)雜,不過(guò)這難不倒經(jīng)驗(yàn)豐富的編程員。
? ? 與前面的兩種方法相比,這種方法在節(jié)省硬盤空間方面有它的優(yōu)點(diǎn)。由于所有內(nèi)容都是從同一個(gè)文件讀取并寫入同一個(gè)文件,因此不需要增加額外的磁盤空間,至于內(nèi)存的使用,這種方法也不錯(cuò),它只需要使用16?KB的內(nèi)存。當(dāng)然,這種方法也許是最難實(shí)現(xiàn)的方法。與第一種方法一樣,如果進(jìn)程被中斷,本方法會(huì)導(dǎo)致數(shù)據(jù)文件被破壞。
下面讓我們來(lái)看一看如何使用內(nèi)存映射文件來(lái)完成這個(gè)過(guò)程。
17.2.4?方法4:一個(gè)文件,零緩存
? ? 當(dāng)使用內(nèi)存映射文件對(duì)文件內(nèi)容進(jìn)行倒序時(shí),你打開該文件,然后告訴系統(tǒng)將虛擬地址空間的一個(gè)區(qū)域進(jìn)行倒序。你告訴系統(tǒng)將文件的第一個(gè)字節(jié)映射到該保留區(qū)域的第一個(gè)字節(jié)。然后可以訪問(wèn)該虛擬內(nèi)存的區(qū)域,就像它包含了這個(gè)文件一樣。實(shí)際上,如果在文件的結(jié)尾處有一個(gè)單個(gè)0字節(jié),那么只需要調(diào)用C運(yùn)行期函數(shù)_?s?t?r?r?e?v,就可以對(duì)文件中的數(shù)據(jù)進(jìn)行倒序操作。
? ? 這種方法的最大優(yōu)點(diǎn)是,系統(tǒng)能夠?yàn)槟愎芾硭械奈募彺娌僮鳌2槐胤峙淙魏蝺?nèi)存,或者將文件數(shù)據(jù)加載到內(nèi)存,也不必將數(shù)據(jù)重新寫入該文件,或者釋放任何內(nèi)存塊。但是,內(nèi)存映射文件仍然可能出現(xiàn)因?yàn)殡娫垂收现惖倪M(jìn)程中斷而造成數(shù)據(jù)被破壞的問(wèn)題。
總結(jié)
以上是生活随笔為你收集整理的Windows核心编程 第十七章 -内存映射文件(上)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Windows核心编程 第十五章 在应用
- 下一篇: PowerShell-1.入门及其常用