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)
参数
| 参数 | 方向 | 含义 |
|---|---|---|
page | in | 目标页(用于取 zone,非 SPARSEMEM 下用于定位位图) |
pfn | in | 目标页帧号 |
bitmap_word | out | 输出:指向位图中具体那个unsigned long字的指针 |
bitidx | out | 输出:在该字内的位偏移(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); #endif
CONFIG_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_bits:PB_migrate~PB_migrate_end占用的位数。1 << PB_migratetype_bits:迁移类型字段能表示的最大值 + 1。__MIGRATE_TYPE_END:迁移类型的总数/结束枚举。断言:迁移类型数量必须小于字段容量,否则编码不下。
编译期保证,防止有人加迁移类型时忘了扩字段。
三、运行时断言(VM_BUG_ON_PAGE)
VM_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 位系统为 64,32 位为 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_BITS(4/8) | CONFIG_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 属性读写的最终定位函数。