linux内存页块划分及位图存储机制
linux内存页块划分及位图存储机制
一. 什么是页块(Pageblock)?
- 定义
:页块是物理内存中的一个连续区域,由
2^pageblock_order
个物理页(Page)组成。 - 作用 :页块是内存碎片管理的最小单位,用于跟踪内存区域的 迁移类型(Migratetype) (如可移动、不可移动等),优化内存分配和碎片整理。
1.1. pageblock_order
的默认值
常规配置 :
#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE #define pageblock_order HUGETLB_PAGE_ORDER #else #define pageblock_order (MAX_ORDER - 1) #endif
- 默认情况
:若未启用巨型页(HugeTLB)的动态大小配置,
pageblock_order
通常设置为MAX_ORDER - 1
。 MAX_ORDER
:伙伴系统的最大分配阶数,通常为11
(对应2^10 = 1024
页,即4MB
,假设页大小为4KB
)。
- 默认情况
:若未启用巨型页(HugeTLB)的动态大小配置,
结果 :
- 默认页块大小为
2^(MAX_ORDER-1)
页(例如MAX_ORDER=11
时,页块为2^10 = 1024
页 =4MB
)。 - 每个页块在全局位图
pageblock_flags
中占用若干位(如3位用于迁移类型)。
- 默认页块大小为
1.2. 为什么选择 MAX_ORDER - 1
?
设计目标 :
- 对齐伙伴系统
:确保页块大小与伙伴系统的最大连续内存块(
MAX_ORDER
)对齐,避免跨页块分配。 - 减少碎片 :较大的页块能更高效地隔离不可移动内存(如内核对象),减少长期内存碎片。
- 迁移类型管理 :每个页块独立维护迁移类型,方便内存碎片整理时批量移动页面。
权衡 :
- 内存粒度 :页块越大,管理更粗粒度,可能浪费内存;页块越小,管理更细粒度,但元数据开销增加。
- 性能 :较大的页块减少位图操作次数,提高效率。
二、什么是页块位图?
1. 核心作用与背景
pageblock_flags
是 Linux 内存管理中的一个关键数据结构,主要用于跟踪和管理
内存块(pageblock)
的特性。通过
pageblock_flags
,内核可以高效地记录每个内存块的属性,例如迁移类型、分配状态等,从而优化内存分配与回收策略。
2. 数据结构与存储方式
存储位置 :
pageblock_flags
以位图(bitmap)形式存储在struct zone
结构体中(字段unsigned long *pageblock_flags
),每个 内存块对应位图中的若干比特位 。// https://elixir.bootlin.com/linux/v5.4.285/source/include/linux/mmzone.h#L448 struct zone { #ifndef CONFIG_SPARSEMEM /* * Flags for a pageblock_nr_pages block. See pageblock-flags.h. * In SPARSEMEM, this map is stored in struct mem_section */ unsigned long *pageblock_flags; // 指向位图的指针 #endif /* CONFIG_SPARSEMEM */ }
单个
pageblock_flags
元素可表示的页块和页的数量为: 32位系统 :32/4=8 个页块=8*1024页。
64位系统 :64/4=16 个页块=16*1024页。
位图管理 :每个内存块的标志位由
NR_PAGEBLOCK_BITS
定义,用于存储以下信息: 迁移类型 (如
MIGRATE_UNMOVABLE
、MIGRATE_MOVABLE
等)。内存块的其他状态(如是否跳过内存碎片整理等)
#define PB_migratetype_bits 3 /* Bit indices that affect a whole block of pages */ enum pageblock_bits { PB_migrate, PB_migrate_end = PB_migrate + PB_migratetype_bits - 1, /* 3 bits required for migrate types */ PB_migrate_skip,/* 1位标记是否跳过压缩(PB_migrate_skip),避免重复处理低效内存块 */ /* * Assume the bits will always align on a word. If this assumption * changes then get/set pageblock needs updating. */ NR_PAGEBLOCK_BITS };
3. 初始化
->
static void __ref setup_usemap(struct pglist_data *pgdat,
struct zone *zone,
unsigned long zone_start_pfn,
unsigned long zonesize)
{
unsigned long usemapsize = usemap_size(zone_start_pfn, zonesize);
zone->pageblock_flags = NULL;
if (usemapsize) {
zone->pageblock_flags =
memblock_alloc_node(usemapsize, SMP_CACHE_BYTES,
pgdat->node_id);
if (!zone->pageblock_flags)
panic("Failed to allocate %ld bytes for zone %s pageblock flags on node %d\n",
usemapsize, zone->name, pgdat->node_id);
}
}
setup_usemap
的作用是为内存管理区(Zone)分配并初始化
pageblock_flags
位图,该位图用于跟踪每个内存块(Pageblock)的迁移类型(Migratetype)和其他状态。
参数说明
struct pglist_data *pgdat
- 指向 NUMA 节点的
pglist_data
结构,描述节点的内存布局。
struct zone *zone
- 目标内存管理区(如
ZONE_NORMAL
、ZONE_DMA
)。
unsigned long zone_start_pfn
- 该 Zone 的起始物理页帧号(Page Frame Number)。
unsigned long zonesize
- 该 Zone 的总页数。
代码逻辑分解
1. 计算
pageblock_flags
位图大小
#define PB_migratetype_bits 3
/* Bit indices that affect a whole block of pages */
enum pageblock_bits {
PB_migrate,
PB_migrate_end = PB_migrate + PB_migratetype_bits - 1,
/* 3 bits required for migrate types */
PB_migrate_skip,/* 1位标记是否跳过压缩(PB_migrate_skip),避免重复处理低效内存块 */
/*
* Assume the bits will always align on a word. If this assumption
* changes then get/set pageblock needs updating.
*/
NR_PAGEBLOCK_BITS
};
#define pageblock_nr_pages (1UL << pageblock_order)
static unsigned long __init usemap_size(unsigned long zone_start_pfn, unsigned long zonesize)
{
unsigned long usemapsize;
zonesize += zone_start_pfn & (pageblock_nr_pages-1); // 步骤1:对齐修正
usemapsize = roundup(zonesize, pageblock_nr_pages); // 步骤2:向上对齐到pageblock整数倍
usemapsize = usemapsize >> pageblock_order; // 步骤3:计算pageblock数量
usemapsize *= NR_PAGEBLOCK_BITS; // 步骤4:总位数 = 块数 × 4位
usemapsize = roundup(usemapsize, 8 * sizeof(unsigned long));// 步骤5:位对齐到unsigned long
return usemapsize / 8; // 步骤6:转换为字节
}
- **
usemap_size()
**: 计算位图所需内存大小(单位:字节)。 参数 :根据 Zone 的起始页帧(
zone_start_pfn
)和总页数(zonesize
)。 内部逻辑 :
举例:某一个zone的start pfn = 0X1234;end pfn = 0X3600;zonesize = 0X23CC
操作 目的 结果 计算对齐修正值 处理Zone起始地址未按pageblock对齐的情况(如Zone起始于一个pageblock中间),修正总页数以包含不完整的起始pageblock 修正值:zone_start_pfn & (pageblock_nr_pages-1)=0x234
zonesize += zone_start_pfn & (pageblock_nr_pages-1)=0x23CC+0x234=0x2600向上取整对齐 确保总页数是pageblock大小的整数倍(例如:1024页的倍数),避免部分pageblock无法被位图覆盖 usemapsize = roundup(zonesize, pageblock_nr_pages) = roundup(0x2600, 1024) = 0x2800页 计算pageblock数量 右移 pageblock_order
位(等价于除以pageblock_nr_pages
),得到Zone内完整的pageblock数量usemapsize = usemapsize » pageblock_order; = 0x2800 » 10 = 10 计算总位数 每个pageblock需要 NR_PAGEBLOCK_BITS
(4位)来存储状态,总位数 = pageblock数量 × 440 最终位图大小(字节) 总位数 / 8(1字节=8位) 40 / 8 = 5。(32bit系统上,需要两个 pageblock_flags
元素)
2. 分配
pageblock_flags
内存
if (usemapsize) {
zone->pageblock_flags = memblock_alloc_node(usemapsize, SMP_CACHE_BYTES, pgdat->node_id);
// ...
}
-
条件判断
:仅在
usemapsize > 0
时分配内存(Zone 包含至少一个完整内存块)。 -
分配函数
:
memblock_alloc_node
-
- 用途
- 在内核启动早期(伙伴系统未初始化时),从
memblock
分配器分配内存。
-
参数
:
usemapsize
- 分配的字节数。
SMP_CACHE_BYTES
- 对齐到缓存行(通常 64 字节),避免伪共享(False Sharing)。
pgdat->node_id
- 在指定 NUMA 节点上分配内存,确保 NUMA 亲和性。
-
结果
:
zone->pageblock_flags
指向分配的位图内存。
三. 实际应用场景
3.1、内存碎片整理(Memory Compaction) :
碎片整理器根据页块的迁移类型,将可移动页面(如用户态数据)迁移到其他页块,腾出连续内存。页块大小决定了迁移操作的最小单位。
3.1.1、设置迁移类型
:
set_pageblock_migratetype(struct page *page, int migratetype)
,用于在页释放时将其所属内存块的迁移类型标记为正确值
#define PB_migratetype_bits 3 /* Bit indices that affect a whole block of pages */ enum pageblock_bits { PB_migrate, PB_migrate_end = PB_migrate + PB_migratetype_bits - 1, /* 3 bits required for migrate types */ PB_migrate_skip,/* If set the block is skipped by compaction */ /* * Assume the bits will always align on a word. If this assumption * changes then get/set pageblock needs updating. */ NR_PAGEBLOCK_BITS }; #define set_pageblock_flags_group(page, flags, start_bitidx, end_bitidx) \ set_pfnblock_flags_mask(page, flags, page_to_pfn(page), \ end_bitidx, \ (1 << (end_bitidx - start_bitidx + 1)) - 1) void set_pageblock_migratetype(struct page *page, int migratetype) { if (unlikely(page_group_by_mobility_disabled && migratetype < MIGRATE_PCPTYPES)) migratetype = MIGRATE_UNMOVABLE; set_pageblock_flags_group(page, (unsigned long)migratetype, PB_migrate, PB_migrate_end); } /** * set_pfnblock_flags_mask - Set the requested group of flags for a pageblock_nr_pages block of pages * @page: The page within the block of interest * @flags: The flags to set * @pfn: The target page frame number * @end_bitidx: The last bit of interest * @mask: mask of bits that the caller is interested in */ void set_pfnblock_flags_mask(struct page *page, unsigned long flags, unsigned long pfn, unsigned long end_bitidx, unsigned long mask) { unsigned long *bitmap; unsigned long bitidx, word_bitidx; unsigned long old_word, word; BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 4); BUILD_BUG_ON(MIGRATE_TYPES > (1 << PB_migratetype_bits)); // 获取目标页块对应的位图指针及位偏移 bitmap = get_pageblock_bitmap(page, pfn); //page_zone(page)->pageblock_flags bitidx = pfn_to_bitidx(page, pfn); // 计算位图中的具体位置(原子操作) word_bitidx = bitidx / BITS_PER_LONG; bitidx &= (BITS_PER_LONG-1); VM_BUG_ON_PAGE(!zone_spans_pfn(page_zone(page), pfn), page); // 位操作:对齐掩码和标志值 bitidx += end_bitidx; mask <<= (BITS_PER_LONG - bitidx - 1); flags <<= (BITS_PER_LONG - bitidx - 1); // 原子更新位图 word = READ_ONCE(bitmap[word_bitidx]); for (;;) { old_word = cmpxchg(&bitmap[word_bitidx], word, (word & ~mask) | flags); if (word == old_word) break; word = old_word; } } static inline int pfn_to_bitidx(struct page *page, unsigned long pfn) { // 步骤1:计算对齐后的Zone起始PFN,并调整pfn为相对于该起始的偏移 pfn = pfn - round_down(page_zone(page)->zone_start_pfn, pageblock_nr_pages); // 步骤2:将偏移转换为内存块索引,再乘以每块占用的位数,得到位索引 return (pfn >> pageblock_order) * NR_PAGEBLOCK_BITS; }
迁移特性开关检查 :
page_group_by_mobility_disabled
全局标志(由系统内存状态决定)若为真,表示禁用按迁移类型分组。此时强制将基础迁移类型(如
MIGRATE_MOVABLE
)设为
MIGRATE_UNMOVABLE
,避免碎片整理操作。
生成掩码:
(1 « (end_bitidx - start_bitidx + 1)) - 1
计算
start_bitidx
到
end_bitidx
的位宽(如3位),生成掩码
0b111
pfn_to_bitidx:
将物理页帧号(PFN)转换为对应内存块(Pageblock)在位图(
pageblock_flags
)中的
起始位索引
示例:
参数 :
zone_start_pfn = 0x1234
(未对齐到内存块大小)。pageblock_nr_pages = 1024
。pfn = 0x1500
(目标页 PFN)。 计算过程 :
对齐 Zone 起始 PFN :
round_down(0x1234, 1024) = 0x1000
。 PFN 偏移 :
0x1500 - 0x1000 = 0x500
。 内存块索引 :
0x500 >> 10 = 1
(第 1 个内存块)。 位索引 :
1 * 4 = 4
。 结果 :
该页位于第 1 个内存块,其状态位在位图的第 4 位。
3.1.2、
获取迁移类型
:
get_pageblock_migratetype(struct page *page)
,从页的元数据中提取所属内存块的迁移类型
3.1.3、
初始化与校验
:在系统启动时,内核会检查每个迁移类型的内存块是否达到最小数量(
pageblock_nr_pages
),以决定是否启用迁移优化特性
3.2、连续内存分配器(CMA) :
- CMA 预留的连续内存以页块为单位管理,
pageblock_order
影响 CMA 区域的最小粒度。
3.3、巨型页(HugeTLB) :
- 若启用动态巨型页大小(
CONFIG_HUGETLB_PAGE_SIZE_VARIABLE
),pageblock_order
可能与巨型页大小对齐。
3.4、内存热插拔 :
- 动态调整内存区域时,通过
pageblock_flags
快速定位可迁移或可回收的内存块