Linux使用虛擬內存技術,因此在應用層所能看到的、訪問的都是虛擬地址。對於32位系統來講(本文涉及的都是32位系統),每個進程能夠尋址的地址空間都是4G,不管物理內存有多大。應用開發者實際上是能夠不用關係內存空間的劃分,僅僅使用封裝後的接口就能夠完成開發。但在工做中,若是對地址空間沒有基本的瞭解,在程序設計和解決問題時可能會引發方向性錯誤。這裏對地址空間進行簡單介紹,下圖時網上常見的x86架構的內存區域劃分。linux
物理內存是映射到Kernel地址空間的,這個空間也是虛擬的,分佈在進程虛擬地址空間的3G~4G,0~3G爲用戶地址空間。下圖也是從網絡上得到的。編程
Linux中的用戶空間看到的是連續的虛擬地址,在被真正使用時仍是須要轉換爲真實的物理地址。根據虛擬地址來查詢物理地址的過程被稱爲walking page tables。由於Linux使用分頁機制來管理內存,基本的結構以下圖。緩存
當前的Linux設計了四級頁表,分別是PGD -> PUD -> PMD -> PTE。其中PUD和PMD不是必須的,因此係統能夠根據硬件構架使用二級、三級或四級的頁表模式。一般在32位系統中,二級頁表就能夠知足需求。下圖是一個二級頁表的查找過程。網絡
能夠看到,一個虛擬地址被分爲一級查找索引,二級查找索引和頁索引三部分。根據一級頁表PGD的基地址(保存在協處理器CP15:C2中),結合一級查找索引能夠獲取到二級頁表PTE的基地址。再將二級頁表基址與二級查找索引結合,能夠獲取到物理頁的基地址。物理頁基址與頁索引結合就是須要查找的物理頁。架構
頁表的查找過程在系統中很是頻繁,所以須要經過硬件來完成,這個硬件就是MMU。MMU被稱爲內存管理單元,它不只僅負責虛擬地址到物理地址的轉換,還負責內存訪問權限和高速緩存的管理。編程語言
上文已經說到了內存管理單元MMU,其最主要的工做就是進行地址轉換。爲了加快地址轉換的速度,一般將最近訪問的虛擬地址和轉換後的物理地址的對應保存在一個高速緩存中,這個緩存就是TLB(Translation Lookaside Buffer)。由於在程序運行時,其訪問過的地址被再次訪問的機率很高,當TLB中保存了須要訪問的地址時,就能夠免去頁表查找的過程,能夠大大提升系統性能。ide
在進行地址轉換時, 首先訪問的是TLB,在TLB查找是否有該虛擬地址的緩存。若是找到(a TLB hit),直接返回物理地址。若是在TLB中沒法找到(a TLB miss),則須要經過MMU進行頁表查找得到物理地址,同時將新的查找更新到TLB中。某些狀況下,例如使用磁盤作爲swap時,須要訪問的內存頁可能不存在page table中。這時須要將訪問頁回寫到物理內存中,同時更新page table和TLB。函數
內核中常見的內存分配函數有vmalloc、kmalloc、__get_free_pages等,分別介紹一下。工具
內核中最經常使用的內存分配函數就是kmalloc(),當須要申請小塊內存時,優先考慮使用kmalloc。kmalloc是基於slab分配器進行工做的,先簡單介紹一下slab。性能
kmalloc就是經過slab的普通高速緩存來分配內存的。kmalloc的實現也很簡單,就是在slab的普通高速緩存中尋找一個大小最匹配緩存。由於kmalloc分配的內存是物理連續的,而物理連續的內存是很是珍貴的,因此除非必要,不然大塊內存(以頁爲單位)分配應該使用vmalloc和get_free_pages。kmalloc能夠分配的最大值在不一樣的硬件架構上是不一樣的,而且使用的分配器類型也有影響。例如在ARM32上使用slub分配器時,kmalloc()可接受的最大size爲8M。可是當size大於8K時,kmalloc()的內部實現調用了__get_free_pages()。
上面講到過,highmem中有一塊vmalloc區域,這個地址空間就是經過vmalloc()分配使用的。vmalloc的特色以下,
__get_free_pages從Buddy系統中分配的頁面,其分配的頁數是2的冪數。Buddy系統是Linux用來組織和管理內存頁面的方法,用來解決內存外部碎片問題。Buddy系統把全部的空閒頁框分組爲11個塊鏈表,每一個塊鏈表分別包含大小爲1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊。最大能夠申請1024個連續頁框,對應4MB大小的連續內存。每一個頁框塊的第一個頁框的物理地址是該塊大小的整數倍。
假設要申請一個256個頁框的塊,先從256個頁框的鏈表中查找空閒塊,若是沒有,就去512個頁框的鏈表中找,找到了則將頁框塊分爲2個256個頁框的塊,一個分配給應用,另一個移到256個頁框的鏈表中。若是512個頁框的鏈表中仍沒有空閒塊,繼續向1024個頁框的鏈表查找,若是仍然沒有,則返回錯誤。頁框塊在釋放時,會主動將兩個連續的頁框塊合併爲一個較大的頁框塊。
__get_free_pages分配的是物理內存上連續的頁面,因此沒有特殊需求時也不該該一次性分配較大的內存。在須要分配大內存時,若是不需求物理上連續,能夠一頁一頁分配,而後映射到連續的虛擬地址空間上。
開發驅動時,有時須要爲硬件設備分配一段內存用於DMA傳輸,這時可能就會用到dma_alloc_coherent()。爲DMA分配的內存會有如下特色,
ION是google爲了解決不一樣Android設備的內存碎片問題,提出的一種內存管理器。它支持不一樣的內存分配機制,如CARVOUT(PMEM),物理連續內存(kmalloc),虛擬地址連續但物理不連續內存(vmalloc), IOMMU等。
ION經過Heap來管理不一樣的內存空間,每一個Heap須要實現本身的操做內存方法,好比allocate, free, map等。對ION的使用須要經過Client完成,用戶空間和內核空間均可以成爲Client。內核空間經過ion_client_create()獲取Client,用戶空間經過打開/dev/ion來獲取Client的fd。
本着誰分配誰釋放的原則,不管時應用中經過malloc分配的內存仍是內核中經過kmalloc分配的,在使用完成時必定記得使用free類函數進行釋放。代碼上很簡單,重要的是養成良好的編程習慣。同時也要注意不要對分配的內存屢次釋放,這樣一樣會致使異常。內存的分配釋放提及來很簡單,但也是最容易出問題的地方。因此就出現了大量的內存檢測工具。若是軟件開發已經開始使用內存檢測工具了,就已經太晚了。但咱們又沒法避免內存問題,即便帶有自動回收的編程語言也是不能徹底避免。內存問題在軟件開發中一直是個難題。
Linux系統中使用free命令來查看內存時,能夠看到有兩項是「buffers」和「cached」。這兩項的意義是,
buff/cache是用來緩存磁盤文件數據的,用來提升IO訪問速度。當系統運行一段時間後,會發現buff/cache佔用的內存不少,但這些內存被認爲是能夠available的。當內存短缺時,系統會觸發內存回收,這部份內存就會釋放。還有一種手動清理Cache的方法,經過下面這個命令。
$ echo 3 > /proc/sys/vm/drop_caches
但咱們不建議用命令行來強行回收Cache,這樣會破壞系統內存管理。Cache回收更多仍是應該依賴系統的內存回收機制。也能夠擴展原生的內存回收,增長本身的回收機制,像Android的lowmemorykiller那樣。若是內存回收仍然沒法解決內存短缺問題(在嵌入式系統中常常發生),能夠試圖去調整vm的一些參數,在「/proc/sys/vm/」下,這就須要對內存管理有一個正確的理解。也能夠試圖去調整磁盤掛載的參數,這也可能影響到Cache。具體的調整方法不在這裏詳細說明,若是之後寫到內存優化時會單獨寫一篇。
當系統內存不足時,內核會啓動內存回收。內存回收的時機有如下三種,
kswapd是內存回收機制中最重要的方式,它做爲守護進程在後臺週期運行,根據預約的水位進行回收。Linux系統中每個內存區域(zone)都會存在一個kswapd,同時每一個區域也定義了一組watermark來作爲參考水位。
內存回收的目的是爲了保證系統有足夠的內存能夠正常運行,但當kswapd頻繁回收時也會對系統形成壓力,有時能夠看到kswapd的CPU佔用率很高,就是由於回收過於頻繁。這種狀況下就須要根據系統狀態來進行內存調優,主要是調整watermark。基本原則是避免內存低於WMARK_MIN,根據系統運行狀態設置合理的WMARK_HIGH,選擇合適的時機啓動後臺內存回收。這些又是內存調優的話題,須要另外一篇來闡述。
本文只是對內核的內存管理作簡單的介紹,目的是對其有一個總體的認識。其中的每一部分展開來都是很大的課題,本人水平有限再也不深刻分析。文章中的圖片所有來自網絡,源頭也不是很清楚。