Linux的內存管理涉及到的內容非常龐雜,而且與內核的方方面面耦合在一起,想要理解透徹非常困難。
在開始學習之前進行了一些准備工作《如何展開Linux Memory Management學習?》,
1. 參考資料
遂決定以如下資料作為參考,進行Linux內存管理的研究:
《奔跑吧 Linux內核》:以第2章為藍本展開,這是目前能獲取的緊跟當前內核發展(Linux 4.0),並且講的比較全面的一本資料。
《Understanding the Linux Virtual Memory Manager》:簡單說就是雖老但經典,基於(Linux 2.4/2.6)。作者是目前仍然活躍在Linux社區MM專家。
《wowotech Memory Management》:沒有其他系列經典,也沒有條理系列的介紹MM,但是仍然值得按考。
《tolimit Linux內存源碼分析》:相對零散的介紹了內存相關分析文檔
《Linux Kernel v4.0》:當然必不可少的,是源碼了。
當逐漸深入看到MMU相關代碼時,讀一下ARM架構關於MMU的規格書有助於理解。
不然對於虛擬地址到物理地址的映射就會很虛無,這些資料包括《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition》的《Virtual Memory System Architecture》,以及相關MMU TRM。
2. Linux Memory Management框架圖
整個內存管理從宏觀上可以分為三大部分:用戶空間、內核空間和相關硬件。
用戶空間主要是libc對相關系統調用進行封裝,對用戶程序提供API,常用的有malloc、mmap、munmap、remap、madvise、mempolicy等等。
相關硬件包括MMU/TLB、L1/L2 Cache以及DDR RAM,具體到ARM架構需要對照MMU/L2 Cache以及RAM規格書。
內核空間就復雜多了,首先介紹初始化及初始化后的布局。
2.1 物理內存初始化從獲取內存大小、初始化頁表,再進行zone初始化,然后在zone中使用伙伴系統進行物理內存初始化;
2.2 頁表的映射過程講述了ARM32和ARM64兩種架構下的頁表映射,如何從虛擬地址由MMU轉化成物理頁面地址的;
2.3 內核內存的布局圖在內存被初始化之后,內核的內存布局基本上就確定了,ARM32和ARM64下布局有很大區別。在malloc一節brk中介紹了用戶空間的布局。
2.1~2.3是內存的一個靜態狀態,在有了這些基礎之后,2.4~2.9按照從低層到上層的逐個介紹了。
2.4 分配物理頁面介紹了基於伙伴系統的頁分配和釋放;
2.5 slab分配器基於伙伴系統,slab分配更小內存塊;以及基於slab的kmalloc;
2.6 vmalloc和kmalloc區別在於v,即在VMALLOC區域分配;
2.7 VMA即Virtual Memory Area,是進程內存管理的核心;
2.8 malloc和2.9 mmap都基於VMA,malloc/free用於分配/釋放一塊內存;mmap/munmap用於匿名/文件映射到用戶空間。以及mmap(補充)。
由於malloc/mmap分配內存並不是立即分配,只是在用到的時候才會觸發2.10 缺頁中斷處理。
在缺頁但頁不足的情況下,就需要進行一些操作調整內存,這些操作的基礎是2.11 page引用計數,還有頁面的2.12 反向映射RMAP技術。
在內存不足情況下觸發kswapd2.13 回收頁面,其中匿名頁面有着特殊的2.14 匿名頁面生命周期。
在kswapd回收依然無法滿足內存分配,就需要對內存進行2.16 內存規整,它依賴的技術是2.15 頁面遷移。
由於內存中存在一些內容完全一樣的頁面,使用2.17 KSM技術進行合並,同時利用COW技術,在需要時重新分配。
還介紹了2.18 Dirty COW內存漏洞,然后對內存管理數據結構和API進行了總結2.19 總結內存管理數據結構和API。
最后2.20 最新更新和展望對新技術進行了介紹。
除了以上技術,還有如下內存技術:
- swap計數把匿名頁面寫入SWAP分區從而釋放出空閑頁面
- 內存壓縮技術zram(a compressed RAM based swap device)
- zswap技術是zram和swap的一個綜合,首先將待換出頁面進行壓縮,存儲到系統RAM動態分配的內存池中;達到一定閾值后再寫入實際交換設備。
- 在內存極端不足情況下使用21 OOM(Out-Of-Memory)來殺死不重要進程獲取更多內存的技術
- 基於cgroup的Memory資源控制
- 解決多媒體對大量連續內存需求的CMA(Contiguous Memory Allocator)技術
- slub分配器
- memory hotplug內存熱插拔支持動態更換內存物理設備
==============================================================================================================================
在對內存相關技術了解過后,就是如何運用的問題了?
一方面是對內存問題進行定位;另一方面是對內存行為施加影響,進行優化。
22 內存檢測技術對Linux內存常見問題及其定位方法和工具(slub_debug/kmemleak/kasan)進行了講解。
23 一個內存Oops解析以一個內存Oops為例,介紹了內存相關異常分析。
內存sysfs節點和工具介紹了linux內存管理相關sysfs節點,以及工具;借助這些可以對內存進行優化。
擴展閱讀:
- 關於zram、zswap、zcache的區別與各自優缺點《zram vs zswap vs zcache Ultimate guide: when to use which one》
Linux內存管理框架圖
3. 代碼和測試環境搭建
3.1 QEMU
安裝QEMU以及相關編譯工具
sudo apt-get install qemu libncurses5-dev gcc-arm-linux-gnueabi build-essential
3.2 Busybox 1.24
下載Busybox 1.24代碼:
git clone https://github.com/arnoldlu/busybox.git -b 1_24_stable
編譯Busybox:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabi- #make menuconfig #P684,進行配置 make -j4 install
配置initramfs:
sudo cp -r running_kernel_initramfs/* _install/ sudo chmod +x _install/etc/init.d/rcS cd _install mkdir mnt mkdir dev cd dev sudo mknod console c 5 1 sudo mknod null c 1 3
3.3 Kernel 4.0
下載Linux Kernel 4.0代碼:
git clone https://github.com/arnoldlu/linux.git -b running_kernel_4.0
編譯Linux Kernel:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabi-
make vexpress_defconfig #P685進行配置 make bzImage -j4 ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make dtbs
3.4 運行內核
#Run Kernel+Busybox in QEMU
qemu-system-arm -M vexpress-a9 -smp 4 -m 1024M -kernel arch/arm/boot/zImage -append "rdinit=/linuxrc console=ttyAMA0 loglevel=8" -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic
至此,已經有一個完整的環境,提供shell命令。
4. 思考問答
- 在系統啟動時,ARM Linux內核如何知道系統中有多大的內存空間?
- 在32bit Linux內核中,用戶空間和內核空間的比例通常是3:1,可以修改成2:2嗎?
- 物理內存頁面如何添加到伙伴系統中,是一頁一頁添加,還是以2的幾次冪來加入呢?
- 內核的一級頁表存放在什么地方?二級頁表又存放在什么地方?
- 用戶進程的一級頁表存放在什么地方?二級頁表呢?
- 在ARM32系統中,頁表是如何映射的?在ARM64系統中,頁表又是如何映射的?
- 請簡述Linux內核在理想情況下頁面分配器(page allocator)是如何分配出連續物理頁面的。
- 在頁面分配器中,如何從分配掩碼(gfp_mask)中確定可以從哪些zone中分配內存?
- 頁面分配器是按照什么方向來掃描zone的?
- 為用戶進程分配物理內存,分配掩碼應該選用GFP_KERNEL,還是GFP_HIGHUSER_MOVABLE呢?
- slab分配器是如何分配和釋放小塊內存的?
- slab分配器中有一個着色的概念(cache color),着色有什么作用?
- slab分配其中的slab對象有沒有根據Per-CPU做一些優化?
- slab增長並導致大量不用的空閑對象,該如何解決?
- 請問kmalloc、vmalloc和malloc之間有什么區別以及實現上的差異?
- 使用用戶態的API函數malloc()分配內存時,會馬上為其分配物理內存嗎?
- 假設不考慮libc的因素,malloc分配100Byte,那么實際上內核是為其分配100Byte嗎?
- 假設兩個用戶進程打印的malloc()分配的虛擬地址是一樣的,那么在內核中這兩塊虛擬內存是否打架了呢?
- vm_normal_page()函數返回的是什么樣頁面的struct page數據結構?為什么內存管理代碼中需要這個函數?
- 請簡述get_user_page()函數的作用和實現流程?
- 請簡述follow_page()函數的作用和實現流程?
- 請簡述私有映射和共享映射的區別。
- 為什么第二次調用mmap時,Linux內核沒有捕捉到地址重疊並返回失敗呢?
- struct page數據結構中的_count和_mapcount有什么區別?
- 匿名頁面和page cache頁面有什么區別?
- struct page數據結構中有一個鎖,請問trylock_page()和lock_page()有什么區別?
- 在Linux 2.4.x內核中,如何從一個page找到所有映射該頁面的VMA?反響映射可以帶來哪些便利?
- 閱讀Linux 4.0內核RMAP機制的代碼,畫出父子進程之間VMA、AVC、anon_vma和page等數據結構之間的關系圖。
- 在Linux 2.6.34中,RMAP機制采用了新的實現,在Linux 2.6.33和之前的版本中稱為舊版本RMAP機制。那么在舊版本RMAP機制中,如果父進程有1000個子進程,每個子進程都有一個VMA,這個VMA里面有1000個匿名頁面,當所有的子進程的VMA同時發生寫復制時會是什么情況呢?
- 當page加入lru鏈表中,被其他線程釋放了這個page,那么lru鏈表如何知道這個page已經被釋放了。
- kswapd內核線程何時會被喚醒?
- LRU鏈表如何知道page的活動頻繁程度?
- kswapd按照什么原則來換出頁面?
- kswapd按照什么方向來掃描zone?
- kswapd以什么標准來退出掃描LRU?
- 手持設備例如Android系統,沒有swap分區或者swap文件,kswapd會掃描匿名頁面LRU嗎?
- swappiness的含義是什么?kswapd如何計算匿名頁面和page cache之間的掃描比重?
- 當系統充斥着大量只訪問一次的文件訪問(use-one streaming IO)時,kswapd如何來規避這種風暴?
- 在回收page cache時,對於dirty的page cache,kswapd會馬上回寫嗎?
- 內核有哪些頁面會被kswapd寫回交換分區?
- ARM32 Linux如何模擬這個Linux版本的L_PTE_YOUNG比特位呢?
- 如何理解Refault Distance算法?
- 請簡述匿名頁面的生命周期。在什么情況下會產生匿名頁面?在什么條件下會釋放匿名頁面?
- KSM是基於什么原理來合並頁面的?
- 在KSM機制里,合並過程中把page設置成寫保護的函數write_protect_page()有這樣一個判斷:。這個判斷的依據是什么?
- 如果多個VMA的虛擬頁面同時映射了同一個匿名頁面,那么此時page->index應該等於多少?
- 為什么Dirty COW小程序可以修改一個只讀文件的內容?
- 在Dirty COW內存漏洞中,如果Diryt COW程序沒有madviseThread線程,即只有procselfmemThread線程,能否修改foo文件的內容呢?
- 假設在內核空間獲取了某個文件對應的page cache頁面的struct page數據結構,而對應的VMA屬性是只讀,那么內核空間是否可以成功修改該文件呢?
- 如果用戶進程使用只讀屬性(PROT_READ)來mmap映射一個文件到用戶空間,然后使用memcpy來寫這段內存空間,會是什么樣的情況?
- 請畫出內存管理中常用的數據結構的關系圖,如mm_struct、vma、vaddr、page、pfn、pte、zone、paddr和pg_data等,並思考如下轉換關系。
- 請畫出在最糟糕的情況下分配若干個連續物理頁面的流程圖。
- 在Android中新添加了LMK(Low Memory Killer),請描述LMK和OOM Killer之間的關系。
- 請描述一致性DMA映射dma_alloc_coherent()函數在AEM中是如何管理cache一致性的?
- 請描述流式DMA映射dma_map_single()函數在ARM中是如何管理cache一致性的?
- 為什么在Linux 4.8內核中要把基於zone的LRU鏈表機制遷移到基於Node呢?