linux內存中的page,pte,alloc_page的flag

內核中關於內存的這幾個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個頁纔會觸發同步頁面回收.
watermark

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來標示頁當前的活躍程度。
page lru
有兩個接口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沒有設置,那這個頁妥妥的要被回收了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章