ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

page_alloc get_pfnblock_bitmap_bitidx

page_alloc get_pfnblock_bitmap_bitidx 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强制内联。这类函数在热路径如set_pageblock_migratetype、compaction、内存热插拔频繁调用内联后零调用开销。二、编译期断言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 位系统为 6432 位为 32。除法得到第几个字。注意这里用/而不是因为BITS_PER_LONG不一定是编译期常量虽然通常是编译器一般会优化成移位。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. 性能调用方可能连续操作同一 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也间接保证前提。七、与前面内容的呼应前面出现过的这里的关联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 属性读写的最终定位函数。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进