Linux內(nèi)存管理機(jī)制

Linux內(nèi)存管理涉及的面比較廣泛而且比較復(fù)雜,這里只抽取部分知識(shí)來(lái)講解

一 早期的內(nèi)存分配機(jī)制

在早期的計(jì)算機(jī)中,要運(yùn)行一個(gè)程序,需要把程序全部加載到物理內(nèi)存(可以理解為內(nèi)存條上的內(nèi)存, 所有的程序運(yùn)行都是在內(nèi)存中運(yùn)行,cpu運(yùn)行程序時(shí),如果要訪問(wèn)外部存儲(chǔ)如磁盤(pán),那么必須先把磁盤(pán)內(nèi)存拷貝到內(nèi)存中cpu才能操作,內(nèi)存是cpu和外部存儲(chǔ)的橋梁),如果,我們的一個(gè)計(jì)算機(jī)只運(yùn)行一個(gè)程序,那么只有這個(gè)程序所需要的內(nèi)存空間不超過(guò)物理內(nèi)存空間的大小,就不會(huì)有問(wèn)題。但是,我們正在希望的是在某個(gè)時(shí)候同時(shí)運(yùn)行多個(gè)程序。那么這個(gè)時(shí)候,就會(huì)有個(gè)一個(gè)問(wèn)題,計(jì)算機(jī)如何把有限的物理內(nèi)存分配給多個(gè)程序使用呢?
某臺(tái)計(jì)算機(jī)總的物理內(nèi)存大小是128M,現(xiàn)在同時(shí)運(yùn)行兩個(gè)程序A和B,A需占用內(nèi)存10M,B需占用內(nèi)存110。計(jì)算機(jī)在給程序分配內(nèi)存時(shí)會(huì)采取這樣的方法:先將內(nèi)存中的前10M分配給程序A,接著再?gòu)膬?nèi)存中剩余的118M中劃分出110M分配給程序B。這種分配方法可以保證程序A和程序B都能運(yùn)行,但是這種簡(jiǎn)單的內(nèi)存分配策略問(wèn)題很多。


早期的內(nèi)存分配方法

問(wèn)題1:進(jìn)程的地址空間隔離,因?yàn)樗麄冎苯釉L問(wèn)的物理地址,所以惡意程序可以隨意修改別的進(jìn)程的內(nèi)存數(shù)據(jù),以達(dá)到破壞的目的。有些非惡意的,但是有 bug 的程序也可能不小心修改了其它程序的內(nèi)存數(shù)據(jù),就會(huì)導(dǎo)致其它程序的運(yùn)行出現(xiàn)異常。這種情況對(duì)用戶來(lái)說(shuō)是無(wú)法容忍的,因?yàn)橛脩粝M褂糜?jì)算機(jī)的時(shí)候,其中一個(gè)任務(wù)失敗了,至少不能影響其它的任務(wù)。

問(wèn)題2:內(nèi)存使用效率低,接著上面的例子來(lái)說(shuō),計(jì)算機(jī)總的內(nèi)存大小是128M,A占用10M,B占用110M,如果此時(shí)要運(yùn)行一個(gè)占用內(nèi)存20M的C程序,那么怎么辦呢?
做法是把當(dāng)前沒(méi)有運(yùn)行的B程序內(nèi)存全部拷貝到磁盤(pán)空間,釋放內(nèi)存,然后再將C程序加載到內(nèi)存,待再次運(yùn)行B時(shí),再將其他沒(méi)有運(yùn)行的程序拷貝到磁盤(pán),騰出內(nèi)存給B使用,就這樣拆了東墻補(bǔ)西墻,可以看到這里內(nèi)存拷貝都是整個(gè)程序內(nèi)存開(kāi)拷貝,非常低效,注意這里是程序的整塊內(nèi)存與磁盤(pán)拷入烤出,存在大量的數(shù)據(jù)裝入裝出,導(dǎo)致效率十分低下。

問(wèn)題3:程序運(yùn)行的地址不確定。當(dāng)內(nèi)存中的剩余空間可以滿足程序 C 的要求后,操作系統(tǒng)會(huì)在剩余空間中隨機(jī)分配一段連續(xù)的 20M 大小的空間給程序 C 使用,因?yàn)槭请S機(jī)分配的,所以程序運(yùn)行的地址是不確定的。

二 分段

前面描述了早期的內(nèi)存分配機(jī)制和存在的問(wèn)題,那么我們這部分就來(lái)解決這些問(wèn)題,
為了解決這些問(wèn)題,人們提出了以一種方案,增加中間層,利用一種間接的地址訪問(wèn)方法訪問(wèn)物理內(nèi)存。按照這種方法,程序中訪問(wèn)的內(nèi)存地址不在是真實(shí)的物理地址,而是一個(gè)虛擬地址,然后操作系統(tǒng)將這個(gè)虛擬地址映射待到適當(dāng)?shù)奈锢淼刂?。這樣只要操作系統(tǒng)處理好虛擬地址到物理內(nèi)存地址的映 射,就可以保證不同的程序最終訪問(wèn)的內(nèi)存地址位于不同的區(qū)域,彼此沒(méi)有重疊,就可以達(dá)到內(nèi)存地址空間隔離的效果。
當(dāng)創(chuàng)建一個(gè)進(jìn)程時(shí),操作系統(tǒng)會(huì)為該進(jìn)程分配一個(gè) 4GB 大小的虛擬進(jìn)程地址空間。之所以是 4GB ,是因?yàn)樵?32 位的操作系統(tǒng)中,一個(gè)指針長(zhǎng)度是 4 字節(jié),而 4 字節(jié)指針的尋址能力是從 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即為 4GB 大小的容量。與虛擬地址空間相對(duì)的,還有一個(gè)物理地址空間,這個(gè)地址空間對(duì)應(yīng)的是真實(shí)的物理內(nèi)存。如果你的計(jì)算機(jī)上安裝了 512M 大小的內(nèi)存,那么這個(gè)物理地址空間表示的范圍是 0x00000000~0x1FFFFFFF 。當(dāng)操作系統(tǒng)做虛擬地址到物理地址映射時(shí),只能映射到這一范圍,操作系統(tǒng)也只會(huì)映射到這一范圍。當(dāng)進(jìn)程創(chuàng)建時(shí),每個(gè)進(jìn)程都會(huì)有一個(gè)自己的 4GB 虛擬地址空間。要注意的是這個(gè) 4GB 的地址空間是“虛擬”的,并不是真實(shí)存在的,而且每個(gè)進(jìn)程只能訪問(wèn)自己虛擬地址空間中的數(shù)據(jù),無(wú)法訪問(wèn)別的進(jìn)程中的數(shù)據(jù),通過(guò)這種方法實(shí)現(xiàn)了進(jìn)程間的地址隔離。那是不是這 4GB 的虛擬地址空間應(yīng)用程序可以隨意使用呢?很遺憾,在 Windows 系統(tǒng)下,這個(gè)虛擬地址空間被分成了 4 部分: NULL 指針區(qū)、用戶區(qū)、 64KB 禁入?yún)^(qū)、內(nèi)核區(qū)。應(yīng)用程序能使用的只是用戶區(qū)而已,大約 2GB 左右 ( 最大可以調(diào)整到 3GB) 。內(nèi)核區(qū)為 2GB ,內(nèi)核區(qū)保存的是系統(tǒng)線程調(diào)度、內(nèi)存管理、設(shè)備驅(qū)動(dòng)等數(shù)據(jù),這部分?jǐn)?shù)據(jù)供所有的進(jìn)程共享,但應(yīng)用程序是不能直接訪問(wèn)的。如下圖


“549A3ABC-8DC7-489F-AD07-6FEA29AB0B76”的副本.jpg

人們之所以要?jiǎng)?chuàng)建一個(gè)虛擬地址空間,目的是為了解決進(jìn)程地址空間隔離的問(wèn)題。但程序要想執(zhí)行,必須運(yùn)行在真實(shí)的內(nèi)存上,所以,必須在虛擬地址與物理地址間建立一種映射關(guān)系。這樣,通過(guò)映射機(jī)制,當(dāng)程序訪問(wèn)虛擬地址空間上的某個(gè)地址值時(shí),就相當(dāng)于訪問(wèn)了物理地址空間中的另一個(gè)值。人們想到了一種分段 (Sagmentation) 的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。比如說(shuō)虛擬地址空間中某個(gè)10M 大小的空間映射到物理地址空間中某個(gè)10M 大小的空間,可以多對(duì)一,就是說(shuō)可虛擬地址的多個(gè)10M大小的空間可以映射到相同的物理地址空間的某個(gè)10M。這種思想理解起來(lái)并不難,操作系統(tǒng)保證不同進(jìn)程的地址空間被映射到物理地址空間中不同的區(qū)域上,這樣每個(gè)進(jìn)程最終訪問(wèn)到的

物理地址空間都是彼此分開(kāi)的。通過(guò)這種方式,就實(shí)現(xiàn)了進(jìn)程間的地址隔離。還是以實(shí)例說(shuō)明,假設(shè)有兩個(gè)進(jìn)程 A 和 B ,進(jìn)程 A 所需內(nèi)存大小為 10M ,其虛擬地址空間分布在 0x00000000 到 0x00A00000 ,進(jìn)程 B 所需內(nèi)存為 100M ,其虛擬地址空間分布為 0x00000000 到 0x06400000 。那么按照分段的映射方法,進(jìn)程 A 在物理內(nèi)存上映射區(qū)域?yàn)?0x00100000 到 0x00B00000 ,,進(jìn)程 B 在物理內(nèi)存上映射區(qū)域?yàn)?0x00C00000 到 0x07000000 。于是進(jìn)程 A 和進(jìn)程 B 分別被映射到了不同的內(nèi)存區(qū)間,彼此互不重疊,實(shí)現(xiàn)了地址隔離。從應(yīng)用程序的角度看來(lái),進(jìn)程 A 的地址空間就是分布在 0x00000000 到 0x00A00000 ,在做開(kāi)發(fā)時(shí),開(kāi)發(fā)人員只需訪問(wèn)這段區(qū)間上的地址即可。應(yīng)用程序并不關(guān)心進(jìn)程 A 究竟被映射到物理內(nèi)存的那塊區(qū)域上了,所以程序的運(yùn)行地址也就是相當(dāng)于說(shuō)是確定的了


分段方式的內(nèi)存映射方法

這 種分段的映射方法雖然解決了上述中的問(wèn)題一和問(wèn)題三,但并沒(méi)能解決問(wèn)題二,即內(nèi)存的使用效率問(wèn)題。在分段的映射方法中,每次換入換出內(nèi)存的都是整個(gè)程序,這樣會(huì)造成大量的磁盤(pán)訪問(wèn)操作,導(dǎo)致效率低下。所以這種映射方法還是稍顯粗糙,粒度比較大。實(shí)際上,程序的運(yùn)行有局部性特點(diǎn),在某個(gè)時(shí)間段內(nèi),程序只是訪 問(wèn)程序的一小部分?jǐn)?shù)據(jù),也就是說(shuō),程序的大部分?jǐn)?shù)據(jù)在一個(gè)時(shí)間段內(nèi)都不會(huì)被用到。基于這種情況,人們想到了粒度更小的內(nèi)存分割和映射方法,這種方法就是分頁(yè)(Paging)

二 分頁(yè)

分頁(yè)就是解決分段中整個(gè)程序的所有內(nèi)存與磁盤(pán)換入換出的問(wèn)題,就是把內(nèi)存分為更小的粒度便于按需與磁盤(pán)置換空間,沒(méi)必要置換整塊內(nèi)存
分頁(yè)的基本方法是,將地址空間分成許多的頁(yè)。每頁(yè)的大小由 CPU 決定,然后由操作系統(tǒng)選擇頁(yè)的大小。目前 Inter 系列的 CPU 支持 4KB 或 4MB 的頁(yè)大小,而 PC 上目前都選擇使用 4KB 。按這種選擇, 4GB 虛擬地址空間共可以分成 1048576 個(gè)頁(yè), 512M 的物理內(nèi)存可以分為 131072 個(gè)頁(yè)。顯然虛擬空間的頁(yè)數(shù)要比物理空間的頁(yè)數(shù)多得多。

在分段的方法中,每次程序運(yùn)行時(shí)總是把程序全部裝入內(nèi)存,而分頁(yè)的方法則有所不同。分頁(yè)的思想是程序運(yùn)行時(shí)用到哪頁(yè)就為哪頁(yè)分配內(nèi)存,沒(méi)用到的頁(yè)暫時(shí)保留在硬盤(pán)上。當(dāng)用到這些頁(yè)時(shí)再在物理地址空間中為這些頁(yè)分配內(nèi)存,然后建立虛擬地址空間中的頁(yè)和剛分配的物理內(nèi)存頁(yè)間的映射。

下面通過(guò)介紹一個(gè)可執(zhí)行文件的裝載過(guò)程來(lái)說(shuō)明分頁(yè)機(jī)制的實(shí)現(xiàn)方法。一個(gè)可執(zhí)行文件 (PE 文件 ) 其實(shí)就是一些編譯鏈接好的數(shù)據(jù)和指令的集合,它也會(huì)被分成很多頁(yè),在 PE 文件執(zhí)行的過(guò)程中,它往內(nèi)存中裝載的單位就是頁(yè)。當(dāng)一個(gè) PE 文件被執(zhí)行時(shí),操作系統(tǒng)會(huì)先為該程序創(chuàng)建一個(gè) 4GB 的進(jìn)程虛擬地址空間。前面介紹過(guò),虛擬地址空間只是一個(gè)中間層而已,它的功能是利用一種映射機(jī)制將虛擬地址空間映射到物理地址空間,所以,創(chuàng)建 4GB 虛擬地址空間其實(shí)并不是要真的創(chuàng)建空間,只是要?jiǎng)?chuàng)建那種映射機(jī)制所需要的數(shù)據(jù)結(jié)構(gòu)而已,這種數(shù)據(jù)結(jié)構(gòu)就是頁(yè)目和頁(yè)表。

當(dāng)創(chuàng)建完虛擬地址空間所需要的數(shù)據(jù)結(jié)構(gòu)后,進(jìn)程開(kāi)始讀取 PE 文件的第一頁(yè)。在 PE 文件的第一頁(yè)包含了 PE 文件頭和段表等信息,進(jìn)程根據(jù)文件頭和段表等信息,將 PE 文件中所有的段一一映射到虛擬地址空間中相應(yīng)的頁(yè) (PE 文件中的段的長(zhǎng)度都是頁(yè)長(zhǎng)的整數(shù)倍 ) 。這時(shí) PE 文件的真正指令和數(shù)據(jù)還沒(méi)有被裝入內(nèi)存中,操作系統(tǒng)只是根據(jù) PE 文件的頭部等信息建立了 PE 文件和進(jìn)程虛擬地址空間中頁(yè)的映射關(guān)系而已。當(dāng) CPU 要訪問(wèn)程序中用到的某個(gè)虛擬地址時(shí),當(dāng) CPU 發(fā)現(xiàn)該地址并沒(méi)有相相關(guān)聯(lián)的物理地址時(shí), CPU 認(rèn)為該虛擬地址所在的頁(yè)面是個(gè)空頁(yè)面, CPU 會(huì)認(rèn)為這是個(gè)頁(yè)錯(cuò)誤 (Page Fault) , CPU 也就知道了操作系統(tǒng)還未給該 PE 頁(yè)面分配內(nèi)存, CPU 會(huì)將控制權(quán)交還給操作系統(tǒng)。操作系統(tǒng)于是為該 PE 頁(yè)面在物理空間中分配一個(gè)頁(yè)面,然后再將這個(gè)物理頁(yè)面與虛擬空間中的虛擬頁(yè)面映射起來(lái),然后將控制權(quán)再還給進(jìn)程,進(jìn)程從剛才發(fā)生頁(yè)錯(cuò)誤的位置重新開(kāi)始執(zhí)行。由于此時(shí)已為 PE 文件的那個(gè)頁(yè)面分配了內(nèi)存,所以就不會(huì)發(fā)生頁(yè)錯(cuò)誤了。隨著程序的執(zhí)行,頁(yè)錯(cuò)誤會(huì)不斷地產(chǎn)生,操作系統(tǒng)也會(huì)為進(jìn)程分配相應(yīng)的物理頁(yè)面來(lái)滿足進(jìn)程執(zhí)行的需求。

分頁(yè)方法的核心思想就是當(dāng)可執(zhí)行文件執(zhí)行到第 x 頁(yè)時(shí),就為第 x 頁(yè)分配一個(gè)內(nèi)存頁(yè) y ,然后再將這個(gè)內(nèi)存頁(yè)添加到進(jìn)程虛擬地址空間的映射表中 , 這個(gè)映射表就相當(dāng)于一個(gè) y=f(x) 函數(shù)。應(yīng)用程序通過(guò)這個(gè)映射表就可以訪問(wèn)到 x 頁(yè)關(guān)聯(lián)的 y 頁(yè)了。
其實(shí)這個(gè)頁(yè)錯(cuò)誤更專業(yè)的說(shuō)法是缺頁(yè)異常,此時(shí)會(huì)產(chǎn)生頁(yè)中斷就是把進(jìn)程需要的數(shù)據(jù)從磁盤(pán)上拷貝到物理內(nèi)存中,如果內(nèi)存已經(jīng)滿了,操作系統(tǒng)可以暫時(shí)將不用頁(yè)退避到磁盤(pán), 調(diào)入馬上要使用的頁(yè),另一種說(shuō)法是找一個(gè)頁(yè)覆蓋,當(dāng)然如果被覆蓋的頁(yè)曾經(jīng)被修改過(guò),需要將此頁(yè)寫(xiě)回磁盤(pán)。

三 地址空間
  • 虛擬地址的由來(lái),其實(shí)這個(gè)在分段的知識(shí)點(diǎn)里面已經(jīng)說(shuō)了,進(jìn)程隔離,物理內(nèi)存進(jìn)行映射,是一個(gè)大內(nèi)存的程序可以在較小的物理內(nèi)存上運(yùn)行
    我們平時(shí)操作的內(nèi)存其實(shí)都是通過(guò)操作虛擬地址的內(nèi)存單元。通過(guò)MMU(內(nèi)存管理單元)的映射來(lái)間接的操作我們的物理地址


    15774762-b0ec5de9cf127f27.png

對(duì)虛擬內(nèi)存的理解

第一層理解

1.每個(gè)進(jìn)程都有自己獨(dú)立的4G內(nèi)存空間,各個(gè)進(jìn)程的內(nèi)存空間具有類似的結(jié)構(gòu)。

2.一個(gè)新進(jìn)程建立的時(shí)候,將會(huì)建立起自己的內(nèi)存空間,此進(jìn)程的數(shù)據(jù),代碼等從磁盤(pán)拷貝到自己的進(jìn)程空間,哪些數(shù)據(jù)在哪里,都由進(jìn)程控制表中的task_struct記錄,task_struct中記錄中一條鏈表,記錄中內(nèi)存空間的分配情況,哪些地址有數(shù)據(jù),哪些地址無(wú)數(shù)據(jù),哪些可讀,哪些可寫(xiě),都可以通過(guò)這個(gè)鏈表記錄。

3.每個(gè)進(jìn)程已經(jīng)分配的內(nèi)存空間,都與對(duì)應(yīng)的磁盤(pán)空間映射。

問(wèn)題:計(jì)算機(jī)明明沒(méi)有那么多內(nèi)存(n個(gè)進(jìn)程的話就需要n*4G)內(nèi)存建立一個(gè)進(jìn)程,就要把磁盤(pán)上的程序文件拷貝到進(jìn)程對(duì)應(yīng)的內(nèi)存中去,對(duì)于一個(gè)程序?qū)?yīng)的多個(gè)進(jìn)程這種情況,浪費(fèi)內(nèi)存!

第二層理解

1.每個(gè)進(jìn)程的4G內(nèi)存空間只是虛擬內(nèi)存空間,每次訪問(wèn)內(nèi)存空間的某個(gè)地址,都需要把地址翻譯為實(shí)際物理內(nèi)存地址。

2.所有進(jìn)程共享同一物理內(nèi)存,每個(gè)進(jìn)程只把自己目前需要的虛擬內(nèi)存空間映射并存儲(chǔ)到物理內(nèi)存上。

3.進(jìn)程要知道哪些內(nèi)存地址上的數(shù)據(jù)在物理內(nèi)存上,哪些不在,還有在物理內(nèi)存上的哪里,需要用頁(yè)表來(lái)記錄。

4.頁(yè)表的每一個(gè)表項(xiàng)分兩部分,第一部分記錄此頁(yè)是否在物理內(nèi)存上,第二部分記錄物理內(nèi)存頁(yè)的地址(如果在的話)。

5.當(dāng)進(jìn)程訪問(wèn)某個(gè)虛擬地址,去看頁(yè)表,如果發(fā)現(xiàn)對(duì)應(yīng)的數(shù)據(jù)不在物理內(nèi)存中,則缺頁(yè)異常。

6.缺頁(yè)異常的處理過(guò)程,就是把進(jìn)程需要的數(shù)據(jù)從磁盤(pán)上拷貝到物理內(nèi)存中,如果內(nèi)存已經(jīng)滿了,沒(méi)有空地方了,那就找一個(gè)頁(yè)覆蓋,當(dāng)然如果被覆蓋的頁(yè)曾經(jīng)被修改過(guò),需要將此頁(yè)寫(xiě)回磁盤(pán)。

總結(jié):

優(yōu)點(diǎn):

1.既然每個(gè)進(jìn)程的內(nèi)存空間都是一致而且固定的,所以鏈接器在鏈接可執(zhí)行文件時(shí),可以設(shè)定內(nèi)存地址,而不用去管這些數(shù)據(jù)最終實(shí)際的內(nèi)存地址,這是有獨(dú)立內(nèi)存空間的好處。

2.當(dāng)不同的進(jìn)程使用同樣的代碼時(shí),比如庫(kù)文件中的代碼,物理內(nèi)存中可以只存儲(chǔ)一份這樣的代碼,不同的進(jìn)程只需要把自己的虛擬內(nèi)存映射過(guò)去就可以了,節(jié)省內(nèi)存。

3.在程序需要分配連續(xù)的內(nèi)存空間的時(shí)候,只需要在虛擬內(nèi)存空間分配連續(xù)空間,而不需要實(shí)際物理內(nèi)存的連續(xù)空間,可以利用碎片。

另外,事實(shí)上,在每個(gè)進(jìn)程創(chuàng)建加載時(shí),內(nèi)核只是為進(jìn)程“創(chuàng)建”了虛擬內(nèi)存的布局,具體就是初始化進(jìn)程控制表中內(nèi)存相關(guān)的鏈表,實(shí)際上并不立即就把虛擬內(nèi)存對(duì)應(yīng)位置的程序數(shù)據(jù)和代碼(比如.text .data段)拷貝到物理內(nèi)存中,只是建立好虛擬內(nèi)存和磁盤(pán)文件之間的映射就好(叫做存儲(chǔ)器映射),等到運(yùn)行到對(duì)應(yīng)的程序時(shí),才會(huì)通過(guò)缺頁(yè)異常,來(lái)拷貝數(shù)據(jù)。還有進(jìn)程運(yùn)行過(guò)程中,要?jiǎng)討B(tài)分配內(nèi)存,比如malloc時(shí),也只是分配了虛擬內(nèi)存,即為這塊虛擬內(nèi)存對(duì)應(yīng)的頁(yè)表項(xiàng)做相應(yīng)設(shè)置,當(dāng)進(jìn)程真正訪問(wèn)到此數(shù)據(jù)時(shí),才引發(fā)缺頁(yè)異常。

對(duì)虛擬地址空間對(duì)應(yīng)實(shí)際物理地址的理解
虛擬地址和物理地址

四 共享內(nèi)存

共享內(nèi)存是進(jìn)程間通信中最簡(jiǎn)單的方式之一。共享內(nèi)存允許兩個(gè)或更多進(jìn)程訪問(wèn)同一塊內(nèi)存,就如同 malloc() 函數(shù)向不同進(jìn)程返回了指向同一個(gè)物理內(nèi)存區(qū)域的指針。當(dāng)一個(gè)進(jìn)程改變了這塊地址中的內(nèi)容的時(shí)候,其它進(jìn)程都會(huì)察覺(jué)到這個(gè)更改。
采用共享內(nèi)存通信的一個(gè)顯而易見(jiàn)的好處是效率高,因?yàn)檫M(jìn)程可以直接讀寫(xiě)內(nèi)存,而不需要任何數(shù)據(jù)的拷貝。對(duì)于像管道和消息隊(duì)列等通信方式,則需要在內(nèi)核和用戶空間進(jìn)行四次的數(shù)據(jù)拷貝,而共享內(nèi)存則只拷貝兩次數(shù)據(jù):一次從輸入文件到共享內(nèi)存區(qū),另一次從共享內(nèi)存區(qū)到輸出文件。實(shí)際上,進(jìn)程之間在共享內(nèi)存時(shí),并不總是讀寫(xiě)少量數(shù)據(jù)后就解除映射,有新的通信時(shí),再重新建立共享內(nèi)存區(qū)域。而是保持共享區(qū)域,直到通信完畢為止,這樣,數(shù)據(jù)內(nèi)容一直保存在共享內(nèi)存中,并沒(méi)有寫(xiě)回文件。共享內(nèi)存中的內(nèi)容往往是在解除映射時(shí)才寫(xiě)回文件的。因此,采用共享內(nèi)存的通信方式效率是非常高的。

  • linux的數(shù)據(jù)拷貝次數(shù)
    關(guān)于共享內(nèi)存的拷貝次數(shù)有說(shuō)法是2次,有的說(shuō)法是0次,為什么會(huì)出現(xiàn)這兩種說(shuō)呢?為了解釋這個(gè)說(shuō)下,我們先來(lái)了解下以下幾個(gè)知識(shí)
    拷貝次數(shù):統(tǒng)計(jì)cpu拷貝數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)到一個(gè)存儲(chǔ)區(qū)的次數(shù).

    傳統(tǒng) IO 數(shù)據(jù)拷貝原理
    在正式分析零拷貝機(jī)制原理之前,我們先來(lái)看下傳統(tǒng) IO 在數(shù)據(jù)拷貝的基本原理,從數(shù)據(jù)拷貝 (I/O 拷貝) 的次數(shù)以及上下文切換的次數(shù)進(jìn)行對(duì)比分析。

    傳統(tǒng) IO:

    從上圖可以看出
    1.用戶空間進(jìn)程內(nèi)發(fā)起 read() 系統(tǒng)調(diào)用到內(nèi)核空間(第一次上下文切換,從用戶空間到內(nèi)核空間)
    2.通過(guò) DMA 引擎建數(shù)據(jù)從磁盤(pán)拷貝到內(nèi)核態(tài)空間的緩沖區(qū)中(第一次拷貝
    3.將內(nèi)核態(tài)空間緩沖區(qū)的數(shù)據(jù)原封不動(dòng)的拷貝到用戶態(tài)空間的緩存區(qū)中(第二次拷貝),同時(shí)內(nèi)核態(tài)空間切換到用戶態(tài)空間(第二次上下文切換),read() 系統(tǒng)調(diào)用結(jié)束
    4.用戶空間發(fā)起write系統(tǒng)調(diào)用
    5.操作系統(tǒng)由用戶空間切換到內(nèi)核空間(第三次上下文切換),將用戶態(tài)空間的緩存區(qū)數(shù)據(jù)原封不動(dòng)的拷貝到內(nèi)核態(tài)空間輸出的緩存區(qū)中(第三次拷貝
    6.write系統(tǒng)調(diào)用返回,操作系統(tǒng)由內(nèi)核態(tài)空間切換到用戶態(tài)空間(第四次上下文切換),通過(guò) DMA 引擎將數(shù)據(jù)從內(nèi)核態(tài)空間的 緩存區(qū)數(shù)據(jù)拷貝到協(xié)議引擎中(第四次拷貝),注意write的系統(tǒng)調(diào)用不一定會(huì)馬上觸發(fā)緩沖區(qū)的數(shù)據(jù)到磁盤(pán)中,內(nèi)核會(huì)把要寫(xiě)的數(shù)據(jù)暫時(shí)存在緩沖區(qū)中,積累到一定數(shù)量后再一 次寫(xiě)入磁盤(pán),這也是減少磁盤(pán)的寫(xiě)操作

從上面的分析來(lái)看,傳統(tǒng) IO 方式,一共在用戶態(tài)空間與內(nèi)核態(tài)空間之間發(fā)生了 4 次上下文的切換,4 次數(shù)據(jù)的拷貝過(guò)程,其中包括 2 次 DMA 拷貝和 2 次 I/O 拷貝(內(nèi)核態(tài)與用戶應(yīng)用程序之間發(fā)生的拷貝),這里從系統(tǒng)的角度來(lái)看,是拷貝了4次,從cpu的角度來(lái)看,是拷貝了兩次

內(nèi)核空間緩沖區(qū)一大用處是為了減少磁盤(pán)I/O操作,因?yàn)樗鼤?huì)從磁盤(pán)中預(yù)讀更多的數(shù)據(jù)到緩沖區(qū)中。這樣當(dāng)用戶空間從內(nèi)核空間讀取數(shù)據(jù)時(shí),會(huì)查看內(nèi)核緩沖區(qū)是否有數(shù)據(jù),有數(shù)據(jù)的話,直接從內(nèi)核緩沖區(qū)讀取,沒(méi)有的話,內(nèi)核才會(huì)從磁盤(pán)拷貝數(shù)據(jù),而且一般都會(huì)預(yù)讀取更多的數(shù)據(jù)到緩存,
用戶空間緩存區(qū)的用處是減少 用戶空間到內(nèi)核空間的上下文切換時(shí)間,也就是系統(tǒng)調(diào)用時(shí)間。用戶空間從內(nèi)核空間一次復(fù)制較大的(不能太大)地?cái)?shù)據(jù)到用戶空間,當(dāng)用戶空間程序每次需要數(shù)據(jù)時(shí),直接從用戶空間的緩沖中獲取,避免頻繁read系統(tǒng)調(diào)用,切換到內(nèi)核空間。下面這篇文章對(duì)緩沖區(qū)有很好的解釋
文件 I/O 的內(nèi)核緩沖

什么是DMA
DMA(Direct Memory Access)—直接內(nèi)存訪問(wèn) :DMA是允許外設(shè)組件將 I/O 數(shù)據(jù)直接傳送到主存儲(chǔ)器中并且傳輸不需要 CPU 的參與,以此將 CPU 解放出來(lái)去完成其他的事情。

零拷貝:
維基上是這么描述零拷貝的:零拷貝描述的是CPU不執(zhí)行拷貝數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)域到另一個(gè)存儲(chǔ)區(qū)域的任務(wù),這通常用于通過(guò)網(wǎng)絡(luò)傳輸一個(gè)文件時(shí)以減少CPU周期和內(nèi)存帶寬,這里零拷貝強(qiáng)調(diào)的是cpu不執(zhí)行拷貝數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)域到另一個(gè)存儲(chǔ)區(qū)域任務(wù),也就是說(shuō)這里的拷貝次數(shù)是統(tǒng)計(jì)cpu拷貝數(shù)據(jù)從一個(gè)存儲(chǔ)區(qū)到一個(gè)存儲(chǔ)區(qū)的次數(shù).
從描述中已經(jīng)了解到零拷貝技術(shù)給我們帶來(lái)的好處:
1、節(jié)省了 CPU 周期,空出的 CPU 可以完成更多其他的任務(wù)
2、減少了內(nèi)存區(qū)域之間數(shù)據(jù)拷貝,節(jié)省內(nèi)存帶寬
3、減少用戶態(tài)和內(nèi)核態(tài)之間數(shù)據(jù)拷貝,提升數(shù)據(jù)傳輸效率
4、應(yīng)用零拷貝技術(shù),減少用戶態(tài)和內(nèi)核態(tài)之間的上下文切換

很顯然內(nèi)存共享按照零拷貝的概念來(lái)說(shuō),它就是零拷貝,下面展示下用mmap來(lái)實(shí)現(xiàn)內(nèi)存共享的過(guò)程
mmap():內(nèi)存映射,簡(jiǎn)而言之就是將用戶空間的一段內(nèi)存區(qū)域映射到內(nèi)核空間,映射成功后,用戶對(duì)這段內(nèi)存區(qū)域的修改可以直接反映到內(nèi)核空間,同樣,內(nèi)核空間對(duì)這段區(qū)域的修改也直接反映用戶空間。那么對(duì)于內(nèi)核空間<---->用戶空間兩者之間需要大量數(shù)據(jù)傳輸?shù)炔僮鞯脑捫适欠浅8叩?br> mmap()系統(tǒng)調(diào)用使得進(jìn)程之間通過(guò)映射同一個(gè)普通文件實(shí)現(xiàn)共享內(nèi)存。普通文件被映射到進(jìn)程地址空間后,進(jìn)程可以像訪問(wèn)普通內(nèi)存一樣對(duì)文件進(jìn)行訪問(wèn),不必再調(diào)用read(),write()等操作。

注:實(shí)際上,mmap()系統(tǒng)調(diào)用并不是完全為了用于共享內(nèi)存而設(shè)計(jì)的。它本身提供了不同于一般對(duì)普通文件的訪問(wèn)方式,進(jìn)程可以像讀寫(xiě)內(nèi)存一樣對(duì)普通文件的操作。而Posix或System V的共享內(nèi)存IPC則純粹用于共享目的,當(dāng)然mmap()實(shí)現(xiàn)共享內(nèi)存也是其主要應(yīng)用之一


mmap (1).png

從上圖可以看出用戶空間的讀寫(xiě)沒(méi)有調(diào)用read和write,是直接通過(guò)映射的內(nèi)核空間來(lái)讀寫(xiě),這樣與內(nèi)核空間沒(méi)有拷貝數(shù)據(jù),但是內(nèi)核空間與在讀寫(xiě)過(guò)程中發(fā)生兩次拷貝,但是不屬于CPU拷貝,是DMA拷貝,
所以共享內(nèi)存是發(fā)生兩次拷貝的說(shuō)法是從整個(gè)系統(tǒng)來(lái)看的,它確實(shí)發(fā)生了兩次DMA拷貝,0次拷貝的說(shuō)法是從cpu角度來(lái)看的,cpu沒(méi)有發(fā)生內(nèi)核空間和用戶空間的拷貝。兩種說(shuō)法都沒(méi)問(wèn)題

針對(duì)mmap的用法直接給出兩個(gè)例子吧
一個(gè)進(jìn)程寫(xiě)共享內(nèi)存

#include "TestMmapWrite.h"
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

typedef struct
{
    char name[32];
    int age;
} people;

void testMMapWrtire(){
    people* p_map;
    char temp = 'o';

    int fd = open("../../2.txt", O_CREAT|O_RDWR|O_TRUNC, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return ;
    }
  // 調(diào)整fd所指的文件的大小到length
    ftruncate(fd, sizeof(people)*10);
    printf("initialize mmap start\n");
    p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return;
    }
    printf("initialize start\n");
    for(int i = 0; i < 10; i++)
    {
        memcpy( ( *(p_map+i) ).name, &temp, 1);
        printf("copy name[0]=%c,name=%s\n", (*(p_map+i) ).name[0],(*(p_map+i) ).name);
        ( *(p_map+i) ).name[1] = 0;
        ( *(p_map+i) ).age = 20+i;
        temp += 1;
        printf("copy name=%s,temp=%c\n", (*(p_map+i) ).name,temp);
    }
    printf("initialize over\n");

    close(fd);
    //該調(diào)用在進(jìn)程地址空間中解除一個(gè)映射關(guān)系
    munmap(p_map, sizeof(people)*10);
      //一般說(shuō)來(lái),進(jìn)程在映射空間的對(duì)共享內(nèi)容的改變并不直接寫(xiě)回到磁盤(pán)文件中,往//往在調(diào)用munmap()后才執(zhí)行該操作??梢酝ㄟ^(guò)調(diào)用msync()實(shí)現(xiàn)磁盤(pán)上文件內(nèi)容與共享內(nèi)存區(qū)的內(nèi)容一致。
//    msync(p_map, sizeof(people)*10, MS_SYNC );
    printf("umap ok \n");
}

一個(gè)進(jìn)程讀共享內(nèi)存

#include <iostream>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>

using namespace std;
typedef struct
{
    char name[32];
    int age;
} people;

int main(int argc, char** argv)
{
    people* p_map;
    struct stat filestat;

    int fd = open("../../2.txt", O_CREAT|O_RDWR, 00777);
    if (-1 == fd)
    {
        printf("open file error = %s\n", strerror(errno));
        return -1;
    }
  // 獲取fd所指的文件的詳細(xì)信息
    fstat(fd, &filestat);
    printf("open file length = %d\n", filestat.st_size);
   p_map = (people*)mmap(NULL, filestat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    //p_map = (people*)mmap(NULL, sizeof(people)*10, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if (MAP_FAILED == p_map)
    {
        printf("mmap file error = %s\n", strerror(errno));
        return -1;
    }
    printf("umap p_map=%p name=%s\n",p_map->name);
//    for(int i = 0; i < 10; i++)
//    {
//        printf("name = %s, age = %d\n",(*(p_map+i)).name, (*(p_map+i)).age);
//    }
//    sleep(10);
    for(int i = 0; i < 10; i++)
    {
        printf("after name = %s, age = %d\n",(*(p_map+i)).name, (*(p_map+i)).age);
    }
    printf("umap p_map=%p name=%s\n",p_map->name);
    close(fd);
    munmap(p_map, sizeof(people)*10);
    printf("umap ok \n");
    return 0;
}

參考鏈接:
對(duì)虛擬地址空間對(duì)應(yīng)實(shí)際物理地址的理解
虛擬地址和物理地址
文件 I/O 的內(nèi)核緩沖
Linux 內(nèi)核詳解以及內(nèi)核緩沖區(qū)技術(shù)
linux零拷貝共享內(nèi)存
linux內(nèi)存映射mmap原理分析和共享內(nèi)存的兩篇轉(zhuǎn)載文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文:Linux內(nèi)存管理 說(shuō)明:本文在原文基礎(chǔ)上稍加改動(dòng)以便閱讀理解。 摘要 本章首先以應(yīng)用程序開(kāi)發(fā)者的角度審視L...
    wingjay閱讀 4,414評(píng)論 2 18
  • 本文轉(zhuǎn)載自 https://juejin.im/post/59f8691b51882534af254317 參考:...
    xingdong閱讀 2,893評(píng)論 0 3
  • 在linux下,使用top,free等命令查看系統(tǒng)或者進(jìn)程的內(nèi)存使用情況時(shí),經(jīng)常看到buff/cache meme...
    analanxingde閱讀 766評(píng)論 0 2
  • 1.內(nèi)存的頁(yè)面置換算法 (1)最佳置換算法(OPT)(理想置換算法):從主存中移出永遠(yuǎn)不再需要的頁(yè)面;如無(wú)這樣的...
    杰倫哎呦哎呦閱讀 3,613評(píng)論 1 9
  • 傾盆大雨落下 小小的一把傘 也是一片安穩(wěn)的天
    Elvis_Han大少閱讀 136評(píng)論 0 1

友情鏈接更多精彩內(nèi)容