目錄
1?? 怎樣通過object地址獲取其對應的struct slab?
2?? struct page、struct folio和struct slab類型之間轉換,怎么保證內部關鍵數據的傳遞?
3?? 怎樣判斷一個內存空間是屬于slab、page管理?
4?? struct page 結構中 __mapcount 和 page_type的理解
?
????????近期在解讀Linux slub內存分配管理器相關代碼,隨著代碼的不斷深入會不斷地自我提出新的疑問點,自己帶著疑問又再次走查代碼解答自我的疑問。這篇便是對于解讀Slub內存時的自我疑問解。
????????參考代碼:Linux-6.10
1?? 怎樣通過object地址獲取其對應的struct slab?
??????? 代碼中有 slab =folio_slab(fvirt_to_folio(object)) 邏輯,也就是通過object獲取其在內存中對應的page結構,在把page結構轉換為folio結構,最后將folio結構轉化為slab結構。也就該object對應的page結構空間,也是slab結構所在的空間。
?????? 通過slab分配邏輯也可有得出此結論。slab分配函數alloc_slab_page調用alloc_pages_node函數分配slab空間,alloc_pages_node函數返回值為分配空間對應的page結構(其實是通過獲取可用的page結構來確定可用內存),將獲取的struct page結構轉換為struct folio,再通過folio_slab()將folio轉換為struct slab,所以slab結構的空間就是page結構空間。
2?? struct page、struct folio和struct slab類型之間轉換,怎么保證內部關鍵數據的傳遞?
???????? 三個結構不僅共用內存空間,而且結構中部分關鍵元素在各自結構中偏移量也相同。如下系統編譯時,會計算部分關鍵元素在struct slab、struct folio的偏移量,將此偏移量和strcut page結構中關鍵元素的偏移量進行對比。
????????struct slab中 __page_flags 、__page_refcount 和struct page中的flags、_refcount 偏移量進行對比。
文件路徑:mm/slab.h//static_asserts 編譯靜態判斷函數,如果條件不滿足則編譯報錯。
#define SLAB_MATCH(pg, sl) \
static_assert(offsetof(struct page, pg) == offsetof(struct slab, sl)) //在編譯過程判定flag在strcut page中偏移量是否和__page_flags 元素在struct slab中的偏移量是否一致,如果不一致則編譯報錯。
SLAB_MATCH(flags, __page_flags); //判定_refcount在struct page中的偏移量是否和 __page_refcount在struct page中偏移量相同
SLAB_MATCH(_refcount, __page_refcount);
????????struct folio中flags、_mapcount、refcount和struct page中flags、_mapcount、_refcount元素偏移量對比。
文件路徑:include/linux/mm_type.h#define FOLIO_MATCH(pg, fl) \
static_assert(offsetof(struct page, pg) == offsetof(struct folio, fl))FOLIO_MATCH(flags, flags);
FOLIO_MATCH(_mapcount, _mapcount);
FOLIO_MATCH(_refcount, _refcount);
????????如果關鍵元素的偏移量不一致,則在編譯過程直接報錯。如果一致時,無論結構怎么轉換在不重新賦值給關鍵元素時,關鍵元素在各結構中值一致。例如將page結構轉換為slab結構、在不覆蓋flags元素空間時,page->flags 和 slab->_page_flags值相同。如此變保證了關鍵元素值在不同結構間的傳遞。
3?? 怎樣判斷一個內存空間是屬于slab、page管理?
?????? 上一節中可以推斷出Linux內核中struct slab、和 struct folio、strcut page存在共用空間情況,哪怎樣確認這個空間是屬于哪個結構?或者任意一內存地址空間是屬于slab還是page buddy內存管理器?
????? 內核提供一個判斷函數 static inline bool PageSlab(const struct page *page),該函數展開后如下:
static inline bool PageSlab(const struct page *page)
{struct folio = page_folio(page);return ((folio->page.page_type & (PAGE_TYPE_BASE | PG_slab)) == PAGE_TYPE_BASE);
}
從邏輯看當pag_type中不存在PG_slab標識時,則page屬于slab結構。有些反常規,常規情況會認為需要page_type中有PG_slab對應標識時才會認為該pag屬于slab結構。通過代碼進一步確認page_type變量來歷,沒有發現太多關于page_type賦值和初始化地方。但是通過struct page結構(如下),_mapcount 和 page_type共用4字節儲存單元,故對于_mapcount賦值則等同于操作pagetype。
Struct page {…union { /* This union is 4 bytes in size. *//** If the page can be mapped to userspace, encodes the number* of times this page is referenced by a page table.*/atomic_t _mapcount;/** If the page is neither PageSlab nor mappable to userspace,* the value stored here may help determine what this page* is used for. See page-flags.h for a list of page types* which are currently stored here.*/unsigned int page_type;};
…}
????? 從__init_single_page - >page_mapcount_reset函數可以獲取_mapcount = -1 即0xFFFF FFFF,則page_type 初始值也等于0xFFFF FFFF。如果該page要給slab用,則需要將PG_slab設置到page_type ,設置后page_type值為 0xFFFF EFFFF。當page已經為slab結構時,再去通過PageSlab()函數值進行類型判斷其結果為true,則表明了此結構為slab結構,該結構對應的內存空間由slab分配器進行管理。page buddy及其他類型的判斷也同此邏輯。
static inline void page_mapcount_reset(struct page *page)
{atomic_set(&(page)->_mapcount, -1);}
????????當從page buddy獲取到page給slab時,會調用__folio_set_slab函數對page.page_type進行PG_slab標識,表示該空間對應的slab結構,該空間由slab分配器進行管理。
slab page分配函數調用:alloc_slab_page -> __folio_set_slab
4?? struct page 結構中 __mapcount 和 page_type的理解
???????? 前文提到在struct page結構中_mapcount 和 page_type 共用4字節內存單元(同一union單元),初始化值相同為0xFFFF FFFF (-1,見page_mapcount_reset函數)。_mapcount代表該內存空間被映射用戶空間的引用次數(一個物理page內存可能被映射到不同用戶內存空間),page_type表示該page的類型即屬于buddy、slab、table等內存管理器或者頁表專用。實際使用該空間只能有個意義:要么表示用戶空間引用次數、要么標識page類型。具體表示什么意義可根據該空間值的范圍確認: 0xFFFF FFFF - 0xFFFF FF80 (PAGE_MAPCOUNT_RESERVE = -128 ?=0xFFFF FFF80)時表示該page被映射到用戶空間的次數,當小于PAGE_MAPCOUNT_RE-SERVE(0xFFFF FF80)時表示該page的類型。
如下兩個函數可以佐證如上邏輯:
?A.page_type_has_type函數用于判定是否有page的類型,當base_type小于 PAGE_MAPCOUN-T_RESERVE(0xFFFF FF80)是被認為有page類型。
static inline int page_type_has_type(unsigned int page_type)
{return (int)page_type < PAGE_MAPCOUNT_RESERVE;}
B.page_mapcount函數獲取page的到用戶空間映射的應用次數,當mapcount小于PAGE_MAP-COUNT_RESERVE(0xFFFF FF80)時返回0被認為沒有引用,也就是該空間被設置了Page類型(enum pagetype)。
static inline int page_mapcount(struct page *page)
{int mapcount = atomic_read(&page->_mapcount) + 1;/* Handle page_has_type() pages */if (mapcount < PAGE_MAPCOUNT_RESERVE + 1)mapcount = 0;if (unlikely(PageCompound(page)))mapcount += folio_entire_mapcount(page_folio(page));return mapcount;
}
??????? 帶著疑問走讀代碼總會有不一樣的收貨,知道自己的理解依然不夠全面、甚至有誤但帶著問題前行會讓前行更有目的,也讓自己更多注重對于細節的理解。
??????? 繼續前行,日拱一卒!