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

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

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

在早期的計算機中,要運行一個程序胃珍,需要把程序全部加載到物理內(nèi)存(可以理解為內(nèi)存條上的內(nèi)存, 所有的程序運行都是在內(nèi)存中運行梁肿,cpu運行程序時,如果要訪問外部存儲如磁盤觅彰,那么必須先把磁盤內(nèi)存拷貝到內(nèi)存中cpu才能操作吩蔑,內(nèi)存是cpu和外部存儲的橋梁),如果填抬,我們的一個計算機只運行一個程序烛芬,那么只有這個程序所需要的內(nèi)存空間不超過物理內(nèi)存空間的大小,就不會有問題。但是蛀骇,我們正在希望的是在某個時候同時運行多個程序厌秒。那么這個時候,就會有個一個問題擅憔,計算機如何把有限的物理內(nèi)存分配給多個程序使用呢鸵闪?
某臺計算機總的物理內(nèi)存大小是128M,現(xiàn)在同時運行兩個程序A和B暑诸,A需占用內(nèi)存10M蚌讼,B需占用內(nèi)存110。計算機在給程序分配內(nèi)存時會采取這樣的方法:先將內(nèi)存中的前10M分配給程序A个榕,接著再從內(nèi)存中剩余的118M中劃分出110M分配給程序B篡石。這種分配方法可以保證程序A和程序B都能運行,但是這種簡單的內(nèi)存分配策略問題很多西采。


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

問題1:進程的地址空間隔離凰萨,因為他們直接訪問的物理地址,所以惡意程序可以隨意修改別的進程的內(nèi)存數(shù)據(jù)械馆,以達到破壞的目的胖眷。有些非惡意的,但是有 bug 的程序也可能不小心修改了其它程序的內(nèi)存數(shù)據(jù)霹崎,就會導(dǎo)致其它程序的運行出現(xiàn)異常珊搀。這種情況對用戶來說是無法容忍的,因為用戶希望使用計算機的時候尾菇,其中一個任務(wù)失敗了境析,至少不能影響其它的任務(wù)。

問題2:內(nèi)存使用效率低派诬,接著上面的例子來說劳淆,計算機總的內(nèi)存大小是128M,A占用10M千埃,B占用110M,如果此時要運行一個占用內(nèi)存20M的C程序憔儿,那么怎么辦呢?
做法是把當(dāng)前沒有運行的B程序內(nèi)存全部拷貝到磁盤空間放可,釋放內(nèi)存谒臼,然后再將C程序加載到內(nèi)存,待再次運行B時耀里,再將其他沒有運行的程序拷貝到磁盤蜈缤,騰出內(nèi)存給B使用,就這樣拆了東墻補西墻冯挎,可以看到這里內(nèi)存拷貝都是整個程序內(nèi)存開拷貝底哥,非常低效,注意這里是程序的整塊內(nèi)存與磁盤拷入烤出,存在大量的數(shù)據(jù)裝入裝出趾徽,導(dǎo)致效率十分低下续滋。

問題3:程序運行的地址不確定。當(dāng)內(nèi)存中的剩余空間可以滿足程序 C 的要求后孵奶,操作系統(tǒng)會在剩余空間中隨機分配一段連續(xù)的 20M 大小的空間給程序 C 使用疲酌,因為是隨機分配的,所以程序運行的地址是不確定的了袁。

二 分段

前面描述了早期的內(nèi)存分配機制和存在的問題朗恳,那么我們這部分就來解決這些問題,
為了解決這些問題,人們提出了以一種方案载绿,增加中間層粥诫,利用一種間接的地址訪問方法訪問物理內(nèi)存。按照這種方法崭庸,程序中訪問的內(nèi)存地址不在是真實的物理地址怀浆,而是一個虛擬地址,然后操作系統(tǒng)將這個虛擬地址映射待到適當(dāng)?shù)奈锢淼刂芳阶浴_@樣只要操作系統(tǒng)處理好虛擬地址到物理內(nèi)存地址的映 射揉稚,就可以保證不同的程序最終訪問的內(nèi)存地址位于不同的區(qū)域,彼此沒有重疊熬粗,就可以達到內(nèi)存地址空間隔離的效果。
當(dāng)創(chuàng)建一個進程時余境,操作系統(tǒng)會為該進程分配一個 4GB 大小的虛擬進程地址空間驻呐。之所以是 4GB ,是因為在 32 位的操作系統(tǒng)中芳来,一個指針長度是 4 字節(jié)含末,而 4 字節(jié)指針的尋址能力是從 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即為 4GB 大小的容量即舌。與虛擬地址空間相對的佣盒,還有一個物理地址空間,這個地址空間對應(yīng)的是真實的物理內(nèi)存顽聂。如果你的計算機上安裝了 512M 大小的內(nèi)存肥惭,那么這個物理地址空間表示的范圍是 0x00000000~0x1FFFFFFF 。當(dāng)操作系統(tǒng)做虛擬地址到物理地址映射時紊搪,只能映射到這一范圍蜜葱,操作系統(tǒng)也只會映射到這一范圍。當(dāng)進程創(chuàng)建時耀石,每個進程都會有一個自己的 4GB 虛擬地址空間牵囤。要注意的是這個 4GB 的地址空間是“虛擬”的,并不是真實存在的,而且每個進程只能訪問自己虛擬地址空間中的數(shù)據(jù)揭鳞,無法訪問別的進程中的數(shù)據(jù)炕贵,通過這種方法實現(xiàn)了進程間的地址隔離。那是不是這 4GB 的虛擬地址空間應(yīng)用程序可以隨意使用呢野崇?很遺憾鲁驶,在 Windows 系統(tǒng)下,這個虛擬地址空間被分成了 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ū)動等數(shù)據(jù)胧谈,這部分?jǐn)?shù)據(jù)供所有的進程共享忆肾,但應(yīng)用程序是不能直接訪問的。如下圖


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

人們之所以要創(chuàng)建一個虛擬地址空間菱肖,目的是為了解決進程地址空間隔離的問題客冈。但程序要想執(zhí)行,必須運行在真實的內(nèi)存上稳强,所以场仲,必須在虛擬地址與物理地址間建立一種映射關(guān)系。這樣退疫,通過映射機制渠缕,當(dāng)程序訪問虛擬地址空間上的某個地址值時,就相當(dāng)于訪問了物理地址空間中的另一個值褒繁。人們想到了一種分段 (Sagmentation) 的方法亦鳞,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。比如說虛擬地址空間中某個10M 大小的空間映射到物理地址空間中某個10M 大小的空間棒坏,可以多對一燕差,就是說可虛擬地址的多個10M大小的空間可以映射到相同的物理地址空間的某個10M。這種思想理解起來并不難俊抵,操作系統(tǒng)保證不同進程的地址空間被映射到物理地址空間中不同的區(qū)域上谁不,這樣每個進程最終訪問到的

物理地址空間都是彼此分開的。通過這種方式徽诲,就實現(xiàn)了進程間的地址隔離刹帕。還是以實例說明吵血,假設(shè)有兩個進程 A 和 B ,進程 A 所需內(nèi)存大小為 10M 偷溺,其虛擬地址空間分布在 0x00000000 到 0x00A00000 蹋辅,進程 B 所需內(nèi)存為 100M ,其虛擬地址空間分布為 0x00000000 到 0x06400000 挫掏。那么按照分段的映射方法侦另,進程 A 在物理內(nèi)存上映射區(qū)域為 0x00100000 到 0x00B00000 ,尉共,進程 B 在物理內(nèi)存上映射區(qū)域為 0x00C00000 到 0x07000000 褒傅。于是進程 A 和進程 B 分別被映射到了不同的內(nèi)存區(qū)間,彼此互不重疊袄友,實現(xiàn)了地址隔離殿托。從應(yīng)用程序的角度看來,進程 A 的地址空間就是分布在 0x00000000 到 0x00A00000 剧蚣,在做開發(fā)時支竹,開發(fā)人員只需訪問這段區(qū)間上的地址即可。應(yīng)用程序并不關(guān)心進程 A 究竟被映射到物理內(nèi)存的那塊區(qū)域上了鸠按,所以程序的運行地址也就是相當(dāng)于說是確定的了


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

這 種分段的映射方法雖然解決了上述中的問題一和問題三礼搁,但并沒能解決問題二,即內(nèi)存的使用效率問題目尖。在分段的映射方法中馒吴,每次換入換出內(nèi)存的都是整個程序,這樣會造成大量的磁盤訪問操作卑雁,導(dǎo)致效率低下募书。所以這種映射方法還是稍顯粗糙,粒度比較大测蹲。實際上,程序的運行有局部性特點鬼吵,在某個時間段內(nèi)扣甲,程序只是訪 問程序的一小部分?jǐn)?shù)據(jù),也就是說齿椅,程序的大部分?jǐn)?shù)據(jù)在一個時間段內(nèi)都不會被用到琉挖。基于這種情況涣脚,人們想到了粒度更小的內(nèi)存分割和映射方法示辈,這種方法就是分頁(Paging)

二 分頁

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

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

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

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

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

三 地址空間
  • 虛擬地址的由來惠毁,其實這個在分段的知識點里面已經(jīng)說了犹芹,進程隔離,物理內(nèi)存進行映射鞠绰,是一個大內(nèi)存的程序可以在較小的物理內(nèi)存上運行
    我們平時操作的內(nèi)存其實都是通過操作虛擬地址的內(nèi)存單元腰埂。通過MMU(內(nèi)存管理單元)的映射來間接的操作我們的物理地址


    15774762-b0ec5de9cf127f27.png

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

第一層理解

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

2.一個新進程建立的時候盐固,將會建立起自己的內(nèi)存空間,此進程的數(shù)據(jù)丈挟,代碼等從磁盤拷貝到自己的進程空間榛臼,哪些數(shù)據(jù)在哪里拼窥,都由進程控制表中的task_struct記錄洛姑,task_struct中記錄中一條鏈表罢杉,記錄中內(nèi)存空間的分配情況官扣,哪些地址有數(shù)據(jù)是复,哪些地址無數(shù)據(jù)懦傍,哪些可讀只锭,哪些可寫,都可以通過這個鏈表記錄洒嗤。

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

問題:計算機明明沒有那么多內(nèi)存(n個進程的話就需要n*4G)內(nèi)存建立一個進程渔隶,就要把磁盤上的程序文件拷貝到進程對應(yīng)的內(nèi)存中去羔挡,對于一個程序?qū)?yīng)的多個進程這種情況,浪費內(nèi)存间唉!

第二層理解

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

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

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

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

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

6.缺頁異常的處理過程,就是把進程需要的數(shù)據(jù)從磁盤上拷貝到物理內(nèi)存中垒玲,如果內(nèi)存已經(jīng)滿了陆馁,沒有空地方了,那就找一個頁覆蓋合愈,當(dāng)然如果被覆蓋的頁曾經(jīng)被修改過叮贩,需要將此頁寫回磁盤。

總結(jié):

優(yōu)點:

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

2.當(dāng)不同的進程使用同樣的代碼時,比如庫文件中的代碼膘茎,物理內(nèi)存中可以只存儲一份這樣的代碼桃纯,不同的進程只需要把自己的虛擬內(nèi)存映射過去就可以了酷誓,節(jié)省內(nèi)存。

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

另外玫氢,事實上,在每個進程創(chuàng)建加載時壮锻,內(nèi)核只是為進程“創(chuàng)建”了虛擬內(nèi)存的布局琐旁,具體就是初始化進程控制表中內(nèi)存相關(guān)的鏈表,實際上并不立即就把虛擬內(nèi)存對應(yīng)位置的程序數(shù)據(jù)和代碼(比如.text .data段)拷貝到物理內(nèi)存中猜绣,只是建立好虛擬內(nèi)存和磁盤文件之間的映射就好(叫做存儲器映射)灰殴,等到運行到對應(yīng)的程序時,才會通過缺頁異常掰邢,來拷貝數(shù)據(jù)牺陶。還有進程運行過程中,要動態(tài)分配內(nèi)存辣之,比如malloc時掰伸,也只是分配了虛擬內(nèi)存,即為這塊虛擬內(nèi)存對應(yīng)的頁表項做相應(yīng)設(shè)置怀估,當(dāng)進程真正訪問到此數(shù)據(jù)時狮鸭,才引發(fā)缺頁異常。

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

四 共享內(nèi)存

共享內(nèi)存是進程間通信中最簡單的方式之一多搀。共享內(nèi)存允許兩個或更多進程訪問同一塊內(nèi)存歧蕉,就如同 malloc() 函數(shù)向不同進程返回了指向同一個物理內(nèi)存區(qū)域的指針。當(dāng)一個進程改變了這塊地址中的內(nèi)容的時候康铭,其它進程都會察覺到這個更改惯退。
采用共享內(nèi)存通信的一個顯而易見的好處是效率高,因為進程可以直接讀寫內(nèi)存从藤,而不需要任何數(shù)據(jù)的拷貝催跪。對于像管道和消息隊列等通信方式,則需要在內(nèi)核和用戶空間進行四次的數(shù)據(jù)拷貝夷野,而共享內(nèi)存則只拷貝兩次數(shù)據(jù):一次從輸入文件到共享內(nèi)存區(qū)懊蒸,另一次從共享內(nèi)存區(qū)到輸出文件。實際上悯搔,進程之間在共享內(nèi)存時榛鼎,并不總是讀寫少量數(shù)據(jù)后就解除映射,有新的通信時,再重新建立共享內(nèi)存區(qū)域者娱。而是保持共享區(qū)域,直到通信完畢為止苏揣,這樣黄鳍,數(shù)據(jù)內(nèi)容一直保存在共享內(nèi)存中,并沒有寫回文件平匈。共享內(nèi)存中的內(nèi)容往往是在解除映射時才寫回文件的框沟。因此,采用共享內(nèi)存的通信方式效率是非常高的增炭。

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

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

    傳統(tǒng) IO:

    從上圖可以看出
    1.用戶空間進程內(nèi)發(fā)起 read() 系統(tǒng)調(diào)用到內(nèi)核空間(第一次上下文切換,從用戶空間到內(nèi)核空間)
    2.通過 DMA 引擎建數(shù)據(jù)從磁盤拷貝到內(nèi)核態(tài)空間的緩沖區(qū)中(第一次拷貝
    3.將內(nèi)核態(tài)空間緩沖區(qū)的數(shù)據(jù)原封不動的拷貝到用戶態(tài)空間的緩存區(qū)中(第二次拷貝)欲鹏,同時內(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ù)原封不動的拷貝到內(nèi)核態(tài)空間輸出的緩存區(qū)中(第三次拷貝
    6.write系統(tǒng)調(diào)用返回,操作系統(tǒng)由內(nèi)核態(tài)空間切換到用戶態(tài)空間(第四次上下文切換)赔嚎,通過 DMA 引擎將數(shù)據(jù)從內(nèi)核態(tài)空間的 緩存區(qū)數(shù)據(jù)拷貝到協(xié)議引擎中(第四次拷貝)膘盖,注意write的系統(tǒng)調(diào)用不一定會馬上觸發(fā)緩沖區(qū)的數(shù)據(jù)到磁盤中,內(nèi)核會把要寫的數(shù)據(jù)暫時存在緩沖區(qū)中尤误,積累到一定數(shù)量后再一 次寫入磁盤侠畔,這也是減少磁盤的寫操作

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

內(nèi)核空間緩沖區(qū)一大用處是為了減少磁盤I/O操作揖盘,因為它會從磁盤中預(yù)讀更多的數(shù)據(jù)到緩沖區(qū)中。這樣當(dāng)用戶空間從內(nèi)核空間讀取數(shù)據(jù)時锌奴,會查看內(nèi)核緩沖區(qū)是否有數(shù)據(jù)兽狭,有數(shù)據(jù)的話,直接從內(nèi)核緩沖區(qū)讀取,沒有的話箕慧,內(nèi)核才會從磁盤拷貝數(shù)據(jù)服球,而且一般都會預(yù)讀取更多的數(shù)據(jù)到緩存,
用戶空間緩存區(qū)的用處是減少 用戶空間到內(nèi)核空間的上下文切換時間颠焦,也就是系統(tǒng)調(diào)用時間斩熊。用戶空間從內(nèi)核空間一次復(fù)制較大的(不能太大)地數(shù)據(jù)到用戶空間,當(dāng)用戶空間程序每次需要數(shù)據(jù)時伐庭,直接從用戶空間的緩沖中獲取粉渠,避免頻繁read系統(tǒng)調(diào)用,切換到內(nèi)核空間圾另。下面這篇文章對緩沖區(qū)有很好的解釋
文件 I/O 的內(nèi)核緩沖

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

零拷貝:
維基上是這么描述零拷貝的:零拷貝描述的是CPU不執(zhí)行拷貝數(shù)據(jù)從一個存儲區(qū)域到另一個存儲區(qū)域的任務(wù)集乔,這通常用于通過網(wǎng)絡(luò)傳輸一個文件時以減少CPU周期和內(nèi)存帶寬去件,這里零拷貝強調(diào)的是cpu不執(zhí)行拷貝數(shù)據(jù)從一個存儲區(qū)域到另一個存儲區(qū)域任務(wù),也就是說這里的拷貝次數(shù)是統(tǒng)計cpu拷貝數(shù)據(jù)從一個存儲區(qū)到一個存儲區(qū)的次數(shù).
從描述中已經(jīng)了解到零拷貝技術(shù)給我們帶來的好處:
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)存共享按照零拷貝的概念來說,它就是零拷貝,下面展示下用mmap來實現(xiàn)內(nèi)存共享的過程
mmap():內(nèi)存映射识椰,簡而言之就是將用戶空間的一段內(nèi)存區(qū)域映射到內(nèi)核空間绝葡,映射成功后,用戶對這段內(nèi)存區(qū)域的修改可以直接反映到內(nèi)核空間腹鹉,同樣藏畅,內(nèi)核空間對這段區(qū)域的修改也直接反映用戶空間。那么對于內(nèi)核空間<---->用戶空間兩者之間需要大量數(shù)據(jù)傳輸?shù)炔僮鞯脑捫适欠浅8叩?br> mmap()系統(tǒng)調(diào)用使得進程之間通過映射同一個普通文件實現(xiàn)共享內(nèi)存功咒。普通文件被映射到進程地址空間后愉阎,進程可以像訪問普通內(nèi)存一樣對文件進行訪問,不必再調(diào)用read()力奋,write()等操作榜旦。

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


mmap (1).png

從上圖可以看出用戶空間的讀寫沒有調(diào)用read和write咐旧,是直接通過映射的內(nèi)核空間來讀寫驶鹉,這樣與內(nèi)核空間沒有拷貝數(shù)據(jù),但是內(nèi)核空間與在讀寫過程中發(fā)生兩次拷貝休偶,但是不屬于CPU拷貝梁厉,是DMA拷貝,
所以共享內(nèi)存是發(fā)生兩次拷貝的說法是從整個系統(tǒng)來看的踏兜,它確實發(fā)生了兩次DMA拷貝,0次拷貝的說法是從cpu角度來看的八秃,cpu沒有發(fā)生內(nèi)核空間和用戶空間的拷貝碱妆。兩種說法都沒問題

針對mmap的用法直接給出兩個例子吧
一個進程寫共享內(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)用在進程地址空間中解除一個映射關(guān)系
    munmap(p_map, sizeof(people)*10);
      //一般說來,進程在映射空間的對共享內(nèi)容的改變并不直接寫回到磁盤文件中昔驱,往//往在調(diào)用munmap()后才執(zhí)行該操作疹尾。可以通過調(diào)用msync()實現(xiàn)磁盤上文件內(nèi)容與共享內(nèi)存區(qū)的內(nèi)容一致骤肛。
//    msync(p_map, sizeof(people)*10, MS_SYNC );
    printf("umap ok \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所指的文件的詳細信息
    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;
}

參考鏈接:
對虛擬地址空間對應(yīng)實際物理地址的理解
虛擬地址和物理地址
文件 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)容合作請聯(lián)系作者
  • 序言:七十年代末纳本,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子腋颠,更是在濱河造成了極大的恐慌繁成,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淑玫,死亡現(xiàn)場離奇詭異巾腕,居然都是意外死亡,警方通過查閱死者的電腦和手機絮蒿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門尊搬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人土涝,你說我怎么就攤上這事佛寿。” “怎么了但壮?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵冀泻,是天一觀的道長。 經(jīng)常有香客問我茵肃,道長腔长,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任验残,我火速辦了婚禮捞附,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己鸟召,他們只是感情好胆绊,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著欧募,像睡著了一般压状。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跟继,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天种冬,我揣著相機與錄音,去河邊找鬼舔糖。 笑死娱两,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的金吗。 我是一名探鬼主播十兢,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼摇庙!你這毒婦竟也來了旱物?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤卫袒,失蹤者是張志新(化名)和其女友劉穎宵呛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玛臂,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡烤蜕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了迹冤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讽营。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泡徙,靈堂內(nèi)的尸體忽然破棺而出橱鹏,到底是詐尸還是另有隱情,我是刑警寧澤堪藐,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布莉兰,位于F島的核電站,受9級特大地震影響礁竞,放射性物質(zhì)發(fā)生泄漏糖荒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一模捂、第九天 我趴在偏房一處隱蔽的房頂上張望捶朵。 院中可真熱鬧蜘矢,春花似錦、人聲如沸综看。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽红碑。三九已至舞吭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間析珊,已是汗流浹背羡鸥。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忠寻,地道東北人兄春。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像锡溯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哑姚,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

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