在linux下,使用top,vmstat,free等命令查看系統或者進程的內存使用狀況時,常常看到buff/cache memeory,swap,avail Mem等,他們都表明什麼意思呢?這篇文章未來聊一聊Linux下的內存管理並解答這個問題。html
討論Linux下的內存管理其實就是討論Linux下虛擬內存的實現方式,本人不是內核專家,因此這篇文章只會介紹一些概念性的東西,不會深刻實現細節,有些地方描述的有可能不精確。node
在早些時候,物理內存比較有限,人們但願程序可使用的內存空間能超過實際物理內存,因而出現了虛擬內存的概念,不過隨着時間的推移,虛擬內存的意義已經遠遠的超過了最初的想法。linux
虛擬內存是Linux管理內存的一種技術。它使得每一個應用程序都認爲本身擁有獨立且連續的可用的內存空間(一段連續完整的地址空間),而實際上,它一般是被映射到多個物理內存段,還有部分暫時存儲在外部磁盤存儲器上,在須要時再加載到內存中來。數據庫
每一個進程所能使用的虛擬地址大小和CPU位數有關,在32位的系統上,虛擬地址空間大小是4G,在64位系統上,是2^64=?(算不過來了)。而實際的物理內存可能遠遠小於虛擬地址空間的大小。segmentfault
虛擬地址和進程息息相關,不一樣進程裏的同一個虛擬地址指向的物理地址不必定同樣,因此離開進程談虛擬地址沒有任何意義。後端
注意:網上不少文章將虛擬內存等同於交換空間,其實描述不夠嚴謹,交換空間只是虛擬內存這張大藍圖中的一部分。緩存
下面這張表很直觀的表述了它們之間的關係安全
進程X 進程Y +-------+ +-------+ | VPFN7 |--+ | VPFN7 | +-------+ | 進程X的 進程Y的 +-------+ | VPFN6 | | Page Table Page Table +-| VPFN6 | +-------+ | +------+ +------+ | +-------+ | VPFN5 | +----->| .... |---+ +-------| .... |<---+ | | VPFN5 | +-------+ +------+ | +------+ | +------+ | | +-------+ | VPFN4 | +--->| .... |---+-+ | PFN4 | | | .... | | | | VPFN4 | +-------+ | +------+ | | +------+ | +------+ | | +-------+ | VPFN3 |--+ | | .... | | | +--->| PFN3 |<---+ +----| .... |<---+--+ | VPFN3 | +-------+ | | +------+ | | | +------+ | +------+ | +-------+ | VPFN2 | +-+--->| .... |---+-+-+ | PFN2 |<------+ | .... | | | VPFN2 | +-------+ | +------+ | | +------+ +------+ | +-------+ | VPFN1 | | | +----->| FPN1 | +----| VPFN1 | +-------+ | | +------+ +-------+ | VPFN0 |----+ +------->| PFN0 | | VPFN0 | +-------+ +------+ +-------+ 虛擬內存 物理內存 虛擬內存 PFN(the page frame number): 頁編號
當進程執行一個程序時,須要先從先內存中讀取該進程的指令,而後執行,獲取指令時用到的就是虛擬地址,這個地址是程序連接時肯定的(內核加載並初始化進程時會調整動態庫的地址範圍),爲了獲取到實際的數據,CPU須要將虛擬地址轉換成物理地址,CPU轉換地址時須要用到進程的page table,而page table裏面的數據由操做系統維護。服務器
注意:Linux內核代碼訪問內存時用的都是實際的物理地址,因此不存在虛擬地址到物理地址的轉換,只有應用層程序才須要。數據結構
爲了轉換方便,Linux將虛擬內存和物理內存都拆分爲固定大小的頁,x86的系統通常內存頁大小是4K,每一個頁都會分配一個惟一的編號,這就是頁編號(PFN).
從上面的圖中能夠看出,虛擬內存和物理內存的page之間經過page table進行映射。進程X和Y的虛擬內存是相互獨立的,且page table也是獨立的,它們之間共享物理內存。進程能夠隨便訪問本身的虛擬地址空間,而page table和物理內存由內核維護。當進程須要訪問內存時,CPU會根據進程的page table將虛擬地址翻譯成物理地址,而後進行訪問。
注意:並非每一個虛擬地址空間的page都有對應的Page Table相關聯,只有虛擬地址被分配給進程後,也即進程調用相似malloc函數以後,系統纔會爲相應的虛擬地址在Page Table中添加記錄,若是進程訪問一個沒有和Page Table關聯的虛擬地址,系統將會拋出SIGSEGV信號,致使進程退出,這也是爲何咱們訪問野指針時會常常出現segmentfault的緣由。換句話說,雖然每一個進程都有4G(32位系統)的虛擬地址空間,但只有向系統申請了的那些地址空間才能用,訪問未分配的地址空間將會出segmentfault錯誤。Linux會將虛擬地址0不映射到任何地方,這樣咱們訪問空指針就必定會報segmentfault錯誤。
更大的地址空間:而且是連續的,使得程序編寫、連接更加簡單
進程隔離:不一樣進程的虛擬地址之間沒有關係,因此一個進程的操做不會對其它進程形成影響
數據保護:每塊虛擬內存都有相應的讀寫屬性,這樣就能保護程序的代碼段不被修改,數據塊不能被執行等,增長了系統的安全性
內存映射:有了虛擬內存以後,能夠直接映射磁盤上的文件(可執行文件或動態庫)到虛擬地址空間,這樣能夠作到物理內存延時分配,只有在須要讀相應的文件的時候,纔將它真正的從磁盤上加載到內存中來,而在內存吃緊的時候又能夠將這部份內存清空掉,提升物理內存利用效率,而且全部這些對應用程序來講是都透明的
共享內存:好比動態庫,只要在內存中存儲一份就能夠了,而後將它映射到不一樣進程的虛擬地址空間中,讓進程以爲本身獨佔了這個文件。進程間的內存共享也能夠經過映射同一塊物理內存到進程的不一樣虛擬地址空間來實現共享
物理內存管理:物理地址空間所有由操做系統管理,進程沒法直接分配和回收,從而系統能夠更好的利用內存,平衡進程間對內存的需求
其它:有了虛擬地址空間後,交換空間和COW(copy on write)等功能都能很方便的實現
page table能夠簡單的理解爲一個memory mapping的鏈表(固然實際結構很複雜),裏面的每一個memory mapping都將一塊虛擬地址映射到一個特定的資源(物理內存或者外部存儲空間)。每一個進程擁有本身的page table,和其它進程的page table沒有關係。
每一個memory mapping就是對一段虛擬內存的描述,包括虛擬地址的起始位置,長度,權限(好比這段內存裏的數據是否可讀、寫、執行), 以及關聯的資源(如物理內存page,swap空間上的page,磁盤上的文件內容等)。
當進程申請內存時,系統將返回虛擬內存地址,同時爲相應的虛擬內存建立memory mapping並將它放入page table,但這時系統不必定會分配相應的物理內存,系統通常會在進程真正訪問這段內存的時候纔會分配物理內存並關聯到相應的memory mapping,這就是所謂的延時分配/按需分配。
每一個memory mapping都有一個標記,用來表示所關聯的物理資源類型,通常分兩大類,那就是anonymous和file backed,在這兩大類中,又分了一些小類,好比anonymous下面有更具體的shared和copy on write類型, file backed下面有更具體的device backed類型。下面是每一個類型所表明的意思:
這種類型表示memory mapping對應的物理資源存放在磁盤上的文件中,它所包含的信息包括文件的位置、offset、rwx權限等。
當進程第一次訪問對應的虛擬page的時候,因爲在memory mapping中找不到對應的物理內存,CPU會報page fault中斷,而後操做系統就會處理這個中斷並將文件的內容加載到物理內存中,而後更新memory mapping,這樣下次CPU就能訪問這塊虛擬地址了。以這種方式加載到內存的數據通常都會放到page cache中,關於page cache會在後面介紹到.
通常程序的可執行文件,動態庫都是以這種方式映射到進程的虛擬地址空間的。
和file backed相似,只是後端映射到了磁盤的物理地址,好比當物理內存被swap out後,將被標記爲device backed。
程序本身用到的數據段和堆棧空間,以及經過mmap分配的共享內存,它們在磁盤上找不到對應的文件,因此這部份內存頁被叫作anonymous page。anonymous page和file backed最大的差異是當內存吃緊時,系統會直接刪除掉file backed對應的物理內存,由於下次須要的時候還能從磁盤加載到內存,但anonymous page不能被刪除,只能被swap out。
不一樣進程的Page Table裏面的多個memory mapping能夠映射到相同的物理地址,經過虛擬地址(不一樣進程裏的虛擬地址可能不同)能夠訪問到相同的內容,當一個進程裏面修改內存的內容後,在另外一個進程中能夠當即讀取到。這種方式通常用來實現進程間高速的共享數據(如mmap)。當標記爲shared的memory mapping被刪除回收時,須要更新物理page上的引用計數,便於物理page的計數變0後被回收。
copy on write基於shared技術,當讀這種類型的內存時,系統不須要作任何特殊的操做,而當要寫這塊內存時,系統將會生成一塊新的內存並拷貝原來內存中的數據到新內存中,而後將新內存關聯到相應的memory mapping,而後執行寫操做。Linux下不少功能都依賴於copy on write技術來提升性能,好比fork等。
經過上面的介紹,咱們能夠簡單的將內存的使用過程總結以下:
進程向系統發出內存申請請求
系統會檢查進程的虛擬地址空間是否被用完,若是有剩餘,給進程分配虛擬地址
系統爲這塊虛擬地址建立相應的memory mapping(可能多個),並將它放進該進程的page table
系統返回虛擬地址給進程,進程開始訪問該虛擬地址
CPU根據虛擬地址在該進程的page table中找到了相應的memory mapping,可是該mapping沒有和物理內存關聯,因而產生缺頁中斷
操做系統收到缺頁中斷後,分配真正的物理內存並將它關聯到相應的memory mapping
中斷處理完成後,CPU就能夠訪問該內存了
固然缺頁中斷不是每次都會發生,只有系統以爲有必要延遲分配內存的時候才用的着,也即不少時候在上面的第3步系統會分配真正的物理內存並和memory mapping關聯。
操做系統只要實現了虛擬內存和物理內存之間的映射關係,就能正常工做了,但要使內存訪問更高效,還有不少東西須要考慮,在這裏咱們能夠看看跟內存有關的一些其它概念以及它們的做用。
MMU是CPU的一個用來將進程的虛擬地址轉換成物理地址的模塊,簡單點說,這個模塊的輸入是進程的page table和虛擬地址,輸出是物理地址。將虛擬地址轉換成物理地址的速度直接影響着系統的速度,因此CPU包含了這個模塊用來加速。
上面介紹到,MMU的輸入是page table,而page table又存在內存裏面,跟CPU的cache相比,內存的速度很慢,因此爲了進一步加快虛擬地址到物理地址的轉換速度,Linux發明了TLB,它存在於CPU的L1 cache裏面,用來緩存已經找到的虛擬地址到物理地址的映射,這樣下次轉換前先查一下TLB,若是已經在裏面了就不須要調用MMU了.
因爲實際狀況下物理內存要比虛擬內存少不少,因此操做系統必須很當心的分配物理內存,以使內存的使用率達到最大化。一個節約物理內存的辦法就是隻加載當前正在使用的虛擬page對應的數據到內存。好比,一個很大的數據庫程序,若是你只是用了查詢操做,那麼負責插入刪除等部分的代碼段就不必加載到內存中,這樣就能節約不少物理內存,這種方法就叫作物理內存頁按需分配,也能夠稱做延時加載。
其實現原理很簡單,就是當CPU訪問一個虛擬內存頁的時候,若是這個虛擬內存頁對應的數據還沒加載到物理內存中,則CPU就會通知操做系統發生了page fault,而後由操做系統負責將數據加載進物理內存。因爲將數據加載進內存比較耗時,因此CPU不會等在那裏,而是去調度其它進程,當它下次再調度到該進程時,數據已經在物理內存上了。
Linux主要使用這種方式來加載可執行文件和動態庫,當程序被內核開始調度執行時,內核將進程的可執行文件和動態庫映射到進程的虛擬地址空間,並只加載立刻要用到的那小部分數據到物理內存中,其它的部分只有當CPU訪問到它們時纔去加載。
當一個進程須要加載數據到物理內存中,但實際的物理內存已經被用完時,操做系統須要回收一些物理內存中的page以知足當前進程的須要。
對於file backed的內存數據,即物理內存裏面的數據來自於磁盤上的文件,那麼內核將直接將該部分數據從內存中移除掉來釋放出更多的內存,當下次有進程須要訪問這部分數據時,再將它從磁盤上加載到內存中來。可是,若是這部分數據被修改過且沒被寫入文件,那這部分數據就變成了髒數據,髒數據不能被直接刪掉,只能被移動到交換空間上去。(可執行文件和動態庫文件不會被修改,但經過mmap+private的方式映射到內存的磁盤文件有可能被修改,這種方式映射的內存比較特殊,沒修改以前是file backed,修改後但沒有寫回磁盤以前就變成了anonymous的)
對於anonymous的內存數據,在磁盤上沒有對應的文件,這部分數據不能直接被刪除,而是被系統移到交換空間上去。交換空間就是磁盤上預留的一塊特殊空間,被系統用來臨時存放內存中不常被訪問的數據,當下次有進程須要訪問交換空間上的數據時,系統再將數據加載到內存中。因爲交換空間在磁盤上,因此訪問速度要比內存慢不少,頻繁的讀寫交換空間會帶來性能問題。
關於swap空間的詳細介紹請參考Linux交換空間
有了虛擬內存以後,進程間共享內存變得特別的方便。進程全部的內存訪問都經過虛擬地址來實現,而每一個進程都有本身的page tables。當兩個進程共享一塊物理內存時,只要將物理內存的頁號映射到兩個進程的page table中就能夠了,這樣兩個進程就能夠經過不一樣的虛擬地址來訪問同一塊物理內存。
從上面的那個圖中能夠看出,進程X和進程Y共享了物理內存頁PFN3,在進程X中,PFN3被映射到了VPFN3,而在進程Y中,PFN3被映射到了VPFN1,但兩個進程經過不一樣的虛擬地址訪問到的物理內存是同一塊。
page table裏面的每條虛擬內存到物理內存的映射記錄(memory mapping)都包含一份控制信息,當進程要訪問一塊虛擬內存時,系統能夠根據這份控制信息來檢查當前的操做是不是合法的。
爲何須要作這個檢查呢?好比有些內存裏面放的是程序的可執行代碼,那麼就不該該去修改它;有些內存裏面存放的是程序運行時用到的數據,那麼這部份內存只能被讀寫,不該該被執行;有些內存裏面存放的是內核的代碼,那麼在用戶態就不該該去執行它;有了這些檢查以後會大大加強系統的安全性。
因爲CPU的cache有限,因此TLB裏面緩存的數據也有限,而採用了huge page後,因爲每頁的內存變大(好比由原來的4K變成了4M),雖然TLB裏面的紀錄數沒變,但這些紀錄所能覆蓋的地址空間變大,至關於一樣大小的TLB裏面能緩存的映射範圍變大,從而減小了調用MMU的次數,加快了虛擬地址到物理地址的轉換速度。
爲了提升系統性能,Linux使用了一些跟內存管理相關的cache,而且儘可能將空閒的內存用於這些cache。這些cache都是系統全局共享的:
Buffer Cache
用來緩衝塊設備上的數據,好比磁盤,當讀寫塊設備時,系統會將相應的數據存放到這個cache中,等下次再訪問時,能夠直接從cache中拿數據,從而提升系統效率。它裏面的數據結構是一個塊設備ID和block編號到具體數據的映射,只要根據塊設備ID和塊的編號,就能找到相應的數據。
Page Cache
這個cache主要用來加快讀寫磁盤上文件的速度。它裏面的數據結構是文件ID和offset到文件內容的映射,根據文件ID和offset就能找到相應的數據(這裏文件ID多是inode或者path,本人沒有仔細去研究)。
從上面的定義能夠看出,page cache和buffer cache有重疊的地方,不過實際狀況是buffer cache只緩存page cache不緩存的那部份內容,好比磁盤上文件的元數據。因此通常狀況下和page cache相比,Buffer Cache的大小基本能夠忽略不計。
固然,使用cache也有一些很差的地方,好比須要時間和空間去維護cache,cache一旦出錯,整個系統就掛了。
有了上面介紹的知識,再來看看咱們剛開始提出來的問題,以top命令的輸出爲例:
KiB Mem : 500192 total, 349264 free, 36328 used, 114600 buff/cache KiB Swap: 524284 total, 524284 free, 0 used. 433732 avail Mem
KiB Mem表明物理內存,KiB Swap表明交換空間,它們的單位都是KiB。
total、used和free沒什麼好介紹的,就是總共多少,而後用了多少,還剩多少。
buff/cached表明了buff和cache總共用了多少,buff表明buffer cache佔了多少空間,因爲它主要用來緩存磁盤上文件的元數據,因此通常都比較小,跟cache比能夠忽略不計;cache表明page cache和其它一些佔用空間比較小且大小比較固定的cache的總和,基本上cache就約等於page cache,page cache的準確值能夠經過查看/proc/meminf中的Cached獲得。因爲page cache是用來緩存磁盤上文件內容的,因此佔有空間很大,Linux通常會盡量多的將空閒物理內存用於page cache。
avail Mem表示可用於進程下一次分配的物理內存數量,這個大小通常比free大一點,由於除了free的空間外,系統還能當即釋放出一些空間來。
那麼怎麼判斷當前內存使用狀況出現了異常呢?有下面幾點供參考:
Mem free的值比較小,而且buff/cache的值也小
free的值比較少並不必定表明有問題,由於Linux會盡量多的將內存用於page cache,可是若是buff/cache的值也小,就說明內存吃緊了,系統沒有足夠多的內存用於cache,若是當前服務器部署是一個須要頻繁的讀寫磁盤的應用,如FTP服務器,那麼對性能的影響將會很是大。
Swap used的值比較大,
這種狀況比上面的更嚴重,正常狀況下swap應該不多被使用,used值比較大說明交換空間被使用的比較多,若是經過vmstat命令看到swap in/out的比較頻繁的話,說明系統內存嚴重不足,總體性能已經受到嚴重影響