內核中關於內存的這幾個flag容易混淆:pte,page,alloc_page,他們的功能相互關聯,下面簡要分析一下他們的區別和其中的重要flag的場景
- 頁表項的flag
使用頁表項的有兩個對象:CPU和MMU,其中主要是CPU設置頁表,MMU使用頁表.
MMU的功能有兩個:
1.將虛擬地址轉換爲物理地址
2.檢查訪問權限是否合法,它是arch完全相關的 - page的flag
page是物理地址空間管理的元數據,它是純粹的軟件概念,基本是通用的 - alloc_page的flag mask
根據物理內存的稀有程序,頁幀受到區別對待,分成了ZONE_DMA,ZONE_DMA32,ZONE_NORMAL,ZONE_HIGH;而根據得到的物理頁用途不同,一種是在內核運行期間一直佔用,另一個種給進程使用,在進程結束就會釋放回buddy,這兩個頁的生命週期對內存碎片的影響程度不同,從這個角度根據可移動性也進行了劃分;另一個是當前申請物理內存的緊急程度也不同,在內存緊張的情況下特別是當空閒頁小於low水位時得到的對待也不同;
分配物理頁不僅需要size,還需要上面這些指示標誌.
alloc_page
1.指示首先要從哪個ZONE中申請頁,它並沒有直接使用ZONE_DMA區域的值而是使用flag中的低3位存儲zone信息,其中什麼也不設置即全是0代表ZONE_NORMAL,它是比較特殊的;
第4位表示遷移類型的__GFP_MOVABLE
,可以和前面3位zone信息的任何一個組合使用
prefer zone | ZONE_MOVABLE | ZONE_DMA32 | ZONE_HIGHMEM | ZONE_DMA |
---|---|---|---|---|
zone modifier | bit3 | bit2 | bit1 | bit0 |
數組GFP_ZONE_TABLE
中存儲其了不同zone信息和遷移類型的組合關係,其中一些是互斥的,例如0x3既可以用DMA又可以用HIGHMEM,互相矛盾,標記爲BAD:
* bit result
* =================
* 0x0 => NORMAL
* 0x1 => DMA or NORMAL
* 0x2 => HIGHMEM or NORMAL
* 0x3 => BAD (DMA+HIGHMEM)
* 0x4 => DMA32 or DMA or NORMAL
...
讓開發者直接使用這些位信息是非常不友好的,所以使用宏的形式對這些位信息進行了包裝.
標記 | 用途 |
---|---|
GFP_DMA | 從ZONE_DMA中分配 |
__GFP_HIGHMEM | 從ZONE_HIGHMEM中分配 |
GFP_DMA32 | 從ZONE_DMA32中分配 |
__GFP_MOVABLE | 分配的頁面是可遷移的,而不是從ZONE_MOVABLE中申請 |
內核還提供了一種zone分配fallback機制,當首選的ZONE空閒頁滿足不了需求的時候,可以從fallback的zone中申請,通常爲MOVABLE=>HIGHMEM=>NORMAL=>DMA32=>DMA
,假如從NORMAL申請失敗則嘗試從DMA32中申請,如果還失敗則從DMA區域中申請.
和遷移類型協作的時候,首先嚐試同一個ZONE中的其他遷移類型(fallback),如果失敗則嘗試同一個node的其他zone.
2.指示申請者的上下文,例如中斷上下文中進行頁申請時不能休眠
Flag | Description |
---|---|
__GFP_WAIT | 申請頁的上下文不是緊急的,可以進行睡眠或者重新調度 |
__GFP_HIGH | 在內核線程或者是高優先級的進程中申請的,它可以使用emergecy pool的內存 在申請page時會檢查當前空閒頁數量是否小於watermark,它允許水位降低到普通上下文水位的一半 |
__GFP_IO | 申請頁的上下文正在做底層的IO操作,如果內存緊張不允許發生底層IO來獲取空閒頁 |
__GFP_FS | 文件系統中申請頁,如果內存緊張不要通過收縮文件系統數據來獲取空閒頁 |
__GFP_NOWARN | 申請頁失敗時會打印WARNING信息,抑制這種信息打印,調用者會處理這種場景 |
__GFP_REPEAT | 內存緊張時多次嘗試回收頁,最終沒有回收到任何頁時返回失敗 |
__GFP_NOFAIL | 內存緊張時無限嘗試回收頁,直至回收到可用頁 |
__GFP_NORETRY | 內存緊張時不需要多次進行頁回收操作 |
其中__GFP_IO
和__GFP_FS
標記使用場景:在文件系統或者IO棧上操作中進行頁申請操作,此時如果空閒頁到達了min水位進行同步頁面回收,在頁面回收時進行髒頁回寫或者swap操作,這些操作同樣需要進行申請頁,這樣形成一個死鎖狀態.
在文件系統上進行頁申請需要標記__GFP_FS
,在IO棧操作時標記__GFP_IO
,而標記__GFP_IO
時一般也標記了__GFP_FS
,文件系統操作通常離不開IO操作.
提供了常用場景的flag組合給開發者使用,驅動開發者不需要關心內存管理的細節.
flag | 組成的flag | 使用場景 |
---|---|---|
GFP_NOWAIT | (GFP_ATOMIC & ~__GFP_HIGH) | 調用者不能休眠或者調度,但是不會使用emergency pool的內存,它可能分配失敗,中斷上下文使用 |
GFP_ATOMIC | (__GFP_HIGH) | 調用者不能休眠或者調度,允許使用emergency pool的內存,優先級最高的api,中斷上下文中使用 |
GFP_NOIO | (__GFP_WAIT) | 調用者可以休眠,調度走,觸發頁面回收時不進行IO操作 |
GFP_NOFS | (__GFP_WAIT | __GFP_IO) | 調用者可以休眠,但是內存緊張的時候不能進行文件系統內容回收 |
GFP_KERNEL | (__GFP_WAIT | __GFP_IO | __GFP_FS) | 調用者可以休眠,但是內存緊張的時候不能進行任何的回寫操作 普通的進程上下文使用,內核中使用最廣泛的上下文場景 |
GFP_TEMPORARY | (__GFP_WAIT| __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE) | 調用者可以休眠,但是內存緊張的時候不能進行任何的回寫操作,優先從RECLAIM類型中分配 |
GFP_USER | (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL) | 用戶進程申請的,但是內核或者設備也可以直接訪問;主要是設備將buffer映射到用戶空間,但是驅動還能操作DMA進行操縱buffer |
GFP_HIGHUSER | (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM) | 和GFP_USER類似,但是設備可以訪問高端內存 |
分配頁面時,buddy會檢查當前頁分配後是否過低,有個水位watermark在啓動時根據zone的大小計算high,low,min的數量.當空閒頁小於low時,會喚醒kswapd進行回收操作並且返回頁給申請者,kswapd發現空閒頁數量大於high時才終止頁回收,這樣可以減少頻繁的觸發頁回收操作;
如果繼續頻繁申請頁,到達min時,此時就會開始同步直接頁回收,一直回收到足夠的頁之後纔會返回分配的頁.
對於GFP的flag中__GFP_HIGH
會讓系統對於min水位容忍度更強,平常min=28個頁,低於這個閾值時就會觸發同步頁面回收,使用`__GFP_HIGH`時低於min=24個頁纔會觸發同步頁面回收.
reflink:https://lwn.net/Articles/594725/
https://lwn.net/Kernel/Index/#gfp_t
pte flag
pte是給MMU使用的,各個廠家的MMU都不一樣,它是arch相關的.
在x86上cr3加載一級頁表項PGD,而如何通過虛擬地址找到物理地址呢?通常我們看到內核中通過pgd_val, pte_val,pgd_offset等操縱頁表項,這些內核的代碼都是給CPU執行的,是屬於軟件的,而MMU就是將這些軟件行爲進行固化成電路,原理是相同的,不再贅述.
pte的flag定義在include/asm-x86_64/pgtable.h
#define _PAGE_BIT_PRESENT 0
#define _PAGE_BIT_RW 1
#define _PAGE_BIT_USER 2
#define _PAGE_BIT_ACCESSED 5 //當頁被訪問時設置,MMU自動設置該位,頁回收時根據該標誌位在inactive_list,active_list中移動位置
#define _PAGE_BIT_DIRTY 6 //當pte present時有物理頁映射,它描述dirty,writeback過程使用。當pte no present時,複用該標誌位來表示後備存儲是否是file。
#define _PAGE_BIT_PSE 7 /* 4 MB (or 2MB) page */ 系統設置page爲4k,當設置PSE設置的是4M/2M的大頁
#define _PAGE_BIT_GLOBAL 8 /* Global TLB entry PPro+ */
#define _PAGE_BIT_NX 63 /* No execute: only valid after cpuid check */
通常頁表就是給MMU用的,頁表項記錄虛實地址對應關係和頁的權限,但是Linux通過MMU異常實現了一些複雜的功能,例如COW,按需分配物理頁,swap等.
PRESENT | RW | USER | address | VMA權限 | 場景 |
---|---|---|---|---|---|
0 | READ | USER | 合法的VMA內 | MAY_READ|MAY_EXEC | 1.如果pte爲空,映射一個ZERO PAGE 2.pte中_PAGE_BIT_DIRTY置位,則按需讀文件 3.如果pte中地址不爲空,它表示swap信息,完成swap in操作 |
1 | * | USER | 合法的VMA內 | NONE | 非法錯誤,見下面補充描述 |
0 | * | USER | 不在合法的VMA內 | VM_GROUPUP | 擴充棧 |
0 | WRITE | USER | 合法的VMA內 | MAY_READ | 按需分配物理頁 |
1 | WRITE | USER | 合法的VMA內 | MAY_WRITE | COW按需分配頁 |
0 | * | KERNEL | addr >VMALLOC_START && addr < VMALLOC_END | vmap_struct只有分配類型的標誌:IO,memory | vmalloc區域頁表同步 |
用戶空間編程的時候爲了防止錯誤訪問,可能會mmap一段沒有權限的區域來充當gap,多線程的線程私有空間都需要一些gap區域來防止越界訪問,這部分屬於設計上的防護措施,包括內核中的gap區域都利用了MMU對於權限訪問的檢查。
page flag
page的flag定義在include/linux/page-flags.h
,它是軟件通用的。每個物理頁都需要有一個page對象來管理,內存越大,page佔用內存越多。例如8G內存,頁幀大小4k,每個page佔64bytes,總共就需要128M=8G/4k*64
的內存,在實際使用中需要嚴格管理page對象,所以它裏面有很多的union的使用,在page處於不同管理下中代表不同功能。
#define PG_locked 0 //當把頁內容寫入磁盤或者從磁盤載入到頁時,置位,在此期間不能對該頁數據進行讀/寫;在IO完成後清除該標誌位
#define PG_error 1 //當把頁內容寫入磁盤或者從磁盤載入到頁時,如果發生IO錯誤,置位來向調用者傳遞錯誤信息;調用者進行異常處理後,清除標誌
#define PG_referenced 2 //和PG_active一起完成lru算法,見下面
#define PG_uptodate 3 //標記當前內容是否有效,當讀IO成功完成後,置位;寫操作發生錯誤後,清除標誌
#define PG_dirty 4 //當內存和磁盤中內容不一致的時候,需要寫入磁盤永久保存。當發生寫操作後(write/truncate等)標記,writeback定時回寫髒頁,回寫完成後清除標誌
#define PG_lru 5 //頁在活動或非活動頁鏈表中,參與lru算法 ,見下面
#define PG_active 6 //置位表示在active_list中,否則在Inactive_list中;和PG_referenced一起完成lru算法
#define PG_slab 7 //page被slab使用
#define PG_owner_priv_1 8 /* Owner use. If pagecache, fs may use*/
#define PG_arch_1 9 //在x86 上沒有使用
#define PG_reserved 10 //設置標誌使頁面不被交換或者標記沒有被使用。
#define PG_private 11 /* If pagecache, has fs-private data */
#define PG_writeback 12 //writeback正在將頁寫到磁盤上
#define PG_nosave 13 /* Used for system suspend/resume */
#define PG_compound 14 //複合頁
#define PG_swapcache 15 //頁屬於swap的page cache
#define PG_mappedtodisk 16 /* Has blocks allocated on-disk */
#define PG_reclaim 17 //頁面回收內存對頁已經做了寫入磁盤的標記
#define PG_nosave_free 18 /* Used for system suspend/resume */
#define PG_buddy 19 //page時空閒的,在buddy管理中
1.內核中連續的page組合在一起,buddy中管理頁是以2^order
的形式管理的,從掛載在free_area的不同list來區分當前連續頁的數量;當申請頁時,也是以2^order的形式申請的,但是從buddy移除掉之後就不知道究竟有幾個連續的頁,通過PG_compound來標記是複合頁,通過lru.next來實際管理之間的關係。
2.隨着linux的長時間運行,空閒頁面會越來越少,爲了防止linux內核進入請求頁面的僵局中,Linux內核採用頁面回收算法從用戶進程和內核page cache中回收內存,另外slab也是可以回收的,不過只是那些註冊了收縮函數的的slab,例如dcache和icache,根據需要把要回收頁框的內容交換到磁盤上的交換區。頁面回收算法使用LRU的思想,但是給了它兩次機會。它使用PG_referenced來標示頁最近是否被引用過,使用PG_active來標示頁當前的活躍程度。
有兩個接口mark_page_accessed和page_referenced,使用mark_page_accessed處理剛剛引用過的頁在LRU中的位置,根據當前頁在inactive_list/active_list上和PG_referenced進行狀態遷移;
inactive,unreferenced -> inactive,referenced
inactive,referenced -> active,unreferenced
active,unreferenced -> active,referenced
shrink_list中使用page_referenced進行清除當前PG_referenced標誌,如果當前頁在inactive_list並且當前PG_referenced沒有設置,那這個頁妥妥的要被回收了。