
get_pageblock_bitmap_bitidx()——把兩個輔助函數get_pageblock_bitmap()、pfn_to_bitidx()組合起來直接算出位圖里的具體unsigned long字和該字內的位偏移。一、函數簽名與作用static __always_inline void get_pfnblock_bitmap_bitidx(const struct page *page, unsigned long pfn, unsigned long **bitmap_word, unsigned long *bitidx)參數參數方向含義pagein目標頁用于取 zone非 SPARSEMEM 下用于定位位圖pfnin目標頁幀號bitmap_wordout輸出指向位圖中具體那個unsigned long字的指針bitidxout輸出在該字內的位偏移0 ~ BITS_PER_LONG-1為什么拆成字指針 字內偏移位圖是unsigned long[]按字word組織。要操作某一位需要找到哪個字unsigned long *找到字內第幾位0 ~ 63/31。直接返回unsigned long * 位偏移調用方就能用test_bit/set_bit/clear_bit的底層位操作或自己做原子操作。相比前面pfn_to_bitidx()返回全局位索引這里進一步分解便于按字操作尤其原子操作需要字指針。__always_inline強制內聯(lián)。這類函數在熱路徑如set_pageblock_migratetype、compaction、內存熱插拔頻繁調用內聯(lián)后零調用開銷。二、編譯期斷言BUILD_BUG_ON#ifdef CONFIG_MEMORY_ISOLATION BUILD_BUG_ON(NR_PAGEBLOCK_BITS ! 8); #else BUILD_BUG_ON(NR_PAGEBLOCK_BITS ! 4); #endif BUILD_BUG_ON(__MIGRATE_TYPE_END (1 PB_migratetype_bits));1.NR_PAGEBLOCK_BITS必須是 8 或 4#ifdef CONFIG_MEMORY_ISOLATION BUILD_BUG_ON(NR_PAGEBLOCK_BITS ! 8); #else BUILD_BUG_ON(NR_PAGEBLOCK_BITS ! 4); #endifCONFIG_MEMORY_ISOLATION內存隔離用于內存熱插拔、CMA 隔離配置。啟用隔離時需要更多 pageblock 位如PB_migrate_skip等NR_PAGEBLOCK_BITS 8。未啟用時NR_PAGEBLOCK_BITS 4。BUILD_BUG_ON編譯期斷言若常量不等于期望值就編譯失敗。目的保證下面的位運算如* bitidx / BITS_PER_LONG等前提成立防止配置不匹配時悄悄出錯。2. 遷移類型位能容納所有遷移類型BUILD_BUG_ON(__MIGRATE_TYPE_END (1 PB_migratetype_bits));PB_migratetype_bitsPB_migrate~PB_migrate_end占用的位數。1 PB_migratetype_bits遷移類型字段能表示的最大值 1。__MIGRATE_TYPE_END遷移類型的總數/結束枚舉。斷言遷移類型數量必須小于字段容量否則編碼不下。編譯期保證防止有人加遷移類型時忘了擴字段。三、運行時斷言VM_BUG_ON_PAGEVM_BUG_ON_PAGE(!zone_spans_pfn(page_zone(page), pfn), page);VM_BUG_ON_PAGE調試配置CONFIG_DEBUG_VM下才啟用的運行時斷言。檢查page所屬的 zone必須覆蓋 pfn。若不成立說明調用方傳入了不匹配的 page 與 pfn例如 pfn 不在該 page 的 zone 內是嚴重 bug。生產內核未開CONFIG_DEBUG_VM下該檢查被編譯掉零開銷。四、核心計算bitmap get_pageblock_bitmap(page, pfn); *bitidx pfn_to_bitidx(page, pfn); word_bitidx *bitidx / BITS_PER_LONG; *bitidx (BITS_PER_LONG - 1); *bitmap_word bitmap[word_bitidx];步驟分解1. 取位圖基址bitmap get_pageblock_bitmap(page, pfn);SPARSEMEM 下取 section 的 usemap否則取 zone 的pageblock_flags。得到unsigned long *指向位圖數組開頭。2. 取該 pageblock 的起始位索引*bitidx pfn_to_bitidx(page, pfn);返回該 pageblock 在位圖中的起始位索引已乘NR_PAGEBLOCK_BITS。3. 算出該位索引落在哪個字word_bitidx *bitidx / BITS_PER_LONG;BITS_PER_LONG一個unsigned long的位數64 位系統(tǒng)為 6432 位為 32。除法得到第幾個字。注意這里用/而不是因為BITS_PER_LONG不一定是編譯期常量雖然通常是編譯器一般會優(yōu)化成移位。4. 算出字內位偏移*bitidx (BITS_PER_LONG - 1);BITS_PER_LONG - 1是低位的掩碼如 64-1 0x3F。按位與等價于*bitidx % BITS_PER_LONG得到字內偏移0 ~ BITS_PER_LONG-1。復用了*bitidx這個輸出參數先存全局位索引再改成字內偏移。5. 輸出具體字指針*bitmap_word bitmap[word_bitidx];基址 字索引 指向具體那個unsigned long。結果調用方拿到*bitmap_word指向具體字*bitidx字內位偏移。于是可以if (test_bit(*bitidx, *bitmap_word)) // 或 set_bit(*bitidx, *bitmap_word);或對整字做原子操作atomic_long_*之類。五、為什么要輸出字指針 字內偏移而不是全局位索引這是關鍵設計1. 便于原子操作位圖操作有時需要原子讀改寫如set_bit、clear_bit、test_and_set_bit。這些原子操作在底層是對一個unsigned long字做操作如lock bts。如果只知道全局位索引每次還要重新算字地址直接給出字指針更高效。而且原子操作要求同一個字被一致訪問明確字指針能避免重復計算錯誤。2. 性能調用方可能連續(xù)操作同一 pageblock 的多個屬性位如遷移類型的讀寫。一次算出字指針多次操作避免重復計算。3. 語義清晰字 字內偏移正是位操作 API 的底層模型。六、完整調用示意unsigned long *word; unsigned long bit; get_pfnblock_bitmap_bitidx(page, pfn, word, bit); /* 讀遷移類型字段的某位 */ if (test_bit(bit PB_migrate, word)) { ... } /* 原子地設置某獨立位 */ set_bit(bit PB_migrate_skip, word);注意bit是pageblock 起始位在字內的偏移具體屬性位還要 pb_bit。因為NR_PAGEBLOCK_BITS是 4 或 8通常不會跨字但嚴格來說bit pb_bit若超過BITS_PER_LONG會落到下一個字——實際實現中 pageblock 起始位一般對齊不會跨字BUILD_BUG_ON也間接保證前提。七、與前面內容的呼應前面出現過的這里的關聯(lián)get_pageblock_bitmap()這里直接調用它取基址pfn_to_bitidx()這里直接調用它取起始位索引NR_PAGEBLOCK_BITS4/8CONFIG_MEMORY_ISOLATION決定編譯期斷言PB_migrate/PB_migrate_end/PB_migratetype_bits遷移類型字段容量斷言is_standalone_pb_bit()上層用它判斷能否單獨操作底層用本函數定位VM_BUG_ON_PAGE調試斷言保證 page 與 pfn 匹配八、小結表步驟代碼結果編譯期斷言BUILD_BUG_ON(...)保證NR_PAGEBLOCK_BITS和遷移類型字段容量前提成立運行時斷言VM_BUG_ON_PAGE(...)調試下保證 pfn 在 page 的 zone 內取基址bitmap get_pageblock_bitmap(page, pfn)位圖數組起始取全局位索引*bitidx pfn_to_bitidx(page, pfn)pageblock 起始位算字索引word_bitidx *bitidx / BITS_PER_LONG第幾個字算字內偏移*bitidx (BITS_PER_LONG - 1)字內第幾位輸出字指針*bitmap_word bitmap[word_bitidx]具體字地址一句話總結get_pfnblock_bitmap_bitidx()把定位 pageblock 位圖基址和算 pageblock 起始位索引兩步合并再進一步分解為具體unsigned long字指針 字內位偏移并用BUILD_BUG_ON/VM_BUG_ON_PAGE在編譯期和調試期守住前提輸出字 偏移的形式便于調用方直接做位操作和原子操作是 pageblock 屬性讀寫的最終定位函數。