linux内存页块划分及位图存储机制

page_alloc.c - mm/page_alloc.c - Linux source code v5.4.285 - Bootlin Elixir Cross Referencer

一. 什么是页块(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)。

  • 结果

    • 默认页块大小为 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#L448struct 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_UNMOVABLEMIGRATE_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. ​初始化


free_area_init_core -> setup_usemap

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_NORMALZONE_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 longreturn 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_flagsbitidx = 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_bitidxend_bitidx的位宽(如3位),生成掩码0b111

pfn_to_bitidx:将物理页帧号(PFN)转换为对应内存块(Pageblock)在位图(pageblock_flags)中的起始位索引

示例:
  • 参数
    • zone_start_pfn = 0x1234(未对齐到内存块大小)。
    • pageblock_nr_pages = 1024
    • pfn = 0x1500(目标页 PFN)。
  • 计算过程
    1. 对齐 Zone 起始 PFN
      round_down(0x1234, 1024) = 0x1000
    2. PFN 偏移
      0x1500 - 0x1000 = 0x500
    3. 内存块索引
      0x500 >> 10 = 1(第 1 个内存块)。
    4. 位索引
      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 快速定位可迁移或可回收的内存块

page_alloc.c - mm/page_alloc.c - Linux source code v5.4.285 - Bootlin Elixir Cross Referencer

page_alloc.c - mm/page_alloc.c - Linux source code v5.4.285 - Bootlin Elixir Cross Referencer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/31297.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

chebykan与代码3

目录 参考文献有 ‘’ 中文 各自讲了什么 切比雪夫多项式有两类吗&#xff1f;这里存疑 KAN变体 期刊 切比雪夫と爱因斯坦の约定 维度标签的含义 爱因斯坦求和约定 参考文献有 ‘’ 中文 [1] 神经网络&#xff1a;全面基础 [2] 通过sigmoid函数的超层叠近似 [3] 多层前…

DETR详解

1.概述 DETR&#xff0c;全称为Detection Transformer&#xff0c;是Facebook在ECCV2020上提出的基于Transformer的端到端目标检测网络最大的特点就是&#xff1a;不需要预定义的先验anchor&#xff0c;也不需要NMS的后处理策略&#xff0c;就可以实现端到端的目标检测。但是&…

南昌长空STONE 60A-M 无人机电调深度测评:轻量化设计与工业级安全的融合典范

引言 在无人机技术不断革新的今天&#xff0c;电调作为动力系统的核心组件&#xff0c;其性能直接影响飞行稳定性与操控体验。STONE 系列凭借 “轻量化设计” 理念&#xff0c;在竞争激烈的市场中独树一帜。本文将深度解析 STONE 60A-M 电调的技术亮点与实际表现&#xff0c;探…

初阶数据结构(C语言实现)——4.2队列

目录 2.队列2.1队列的概念及结构2.2队列的实现2.2.1 初始化队列2.2.2 销毁队列2.2.3 队尾入队列2.2.4 队头出队列2.2.5获取队列头部元素2.2.6 获取队列队尾元素2.2.7获取队列中有效元素个数2.2.8 检测队列是否为空&#xff0c;如果为空返回非零结果&#xff0c;如果非空返回0 3…

C++和OpenGL实现3D游戏编程【连载24】——父物体和子物体之间的坐标转换

欢迎来到zhooyu的C++和OpenGL游戏专栏,专栏连载的所有精彩内容目录详见下边链接: 🔥C++和OpenGL实现3D游戏编程【总览】 父子物体的坐标转换 1、本节要实现的内容 前面章节我们了解了父物体与子物体的结构,它不仅能够表示物体之间的层次关系,更重要的一个作用就是展示物…

怎么实现: 大语言模型微调案例

怎么实现: 大语言模型微调案例 目录 怎么实现: 大语言模型微调案例输入一个反常识的问题:首都在北京天安门之后对输出模型进行测试:首都在北京天安门微调代码:测试微调模型代码:微调输出模型结构输出模型参数大小对比Qwen 2.5_0.5:53MB输出模型:951MB 是一样的,没有进行…

知乎后台管理系统:数据库系统原理实验1——数据库基础概念

实验背景 通过练习绘制语义网络&#xff0c;加深对于基本概念之间关系的理解和掌握。掌握在VISIO中绘制能准确表达基本概念之间关系的语义网络的技能。了解并比较数据模型的Chen’s表示法和UML表示法。理解关系模型设计中的完整性约束的重要性。掌握在Linux操作系统下远程访问…

超过 37000 台 VMwareESXi 服务器可能受到持续攻击威胁

近日&#xff0c;威胁监测平台影子服务器基金会&#xff08;The Shadowserver Foundation&#xff09;发布报告&#xff0c;指出超 3.7 万个互联网暴露的威睿&#xff08;VMware&#xff09;ESXi 实例存在严重安全隐患&#xff0c;极易受到 CVE-2025-22224 漏洞的攻击。该漏洞属…

Linux《基础开发工具(中)》

在之前的Linux《基础开发工具&#xff08;上&#xff09;》当中已经了解了Linux当中到的两大基础的开发工具yum与vim&#xff1b;了解了在Linux当中如何进行软件的下载以及实现的基本原理、知道了编辑器vim的基本使用方式&#xff0c;那么接下来在本篇当中将接下去继续来了解另…

Vue3 Pinia 符合直觉的Vue.js状态管理库

Pinia 符合直觉的Vue.js状态管理库 什么时候使用Pinia 当两个关系非常远的组件&#xff0c;要传递参数时使用Pinia组件的公共参数使用Pinia

知识库Dify和cherry无法解析影印pdf word解决方案

近期收到大量读者反馈&#xff1a;上传pdf/图文PDF到Dify、Cherry Studio等知识库时&#xff0c;普遍存在格式错乱、图片丢失、表格失效三大痛点。 在试用的几款知识库中除了ragflow具备图片解析的能力外&#xff0c;其他的都只能解析文本。 如果想要解析扫描件&#xff0c…

Webservice创建

Webservice创建 服务端创建 3层架构 service注解&#xff08;commom模块&#xff09; serviceimpl&#xff08;server&#xff09; 服务端拦截器的编写 客户端拦截器 客户端调用服务端&#xff08;CXF代理&#xff09; 客户端调用服务端&#xff08;动态模式调用&a…

腾讯云低代码开发应用

创建客户端应用 如上所示&#xff0c;登录腾讯云微搭低代码业务控制台&#xff0c;开始搭建企业官网应用 如上所示&#xff0c;在腾讯云微搭低代码业务控制台中&#xff0c;开始创建企业官网应用 如上所示&#xff0c;在腾讯云微搭低代码业务控制台中&#xff0c;开始编辑企业官…

【Java开发指南 | 第三十四篇】IDEA没有Java Enterprise——解决方法

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 1、新建Java项目2、单击项目名&#xff0c;并连续按两次shift键3、在搜索栏搜索"添加框架支持"4、勾选Web应用程序5、最终界面6、添加Tomcat 1、新建Java项目 2、单击项目名&#xff0c;并连续按两次…

深度学习原理与Pytorch实战

深度学习原理与Pytorch实战 第2版 强化学习人工智能神经网络书籍 python动手学深度学习框架书 TransformerBERT图神经网络&#xff1a; 技术讲解 编辑推荐 1.基于PyTorch新版本&#xff0c;涵盖深度学习基础知识和前沿技术&#xff0c;由浅入深&#xff0c;通俗易懂&#xf…

uniapp项目运行失败Error: getaddrinfo *.bspapp.com 文件查找失败uview-ui及推荐MarkDown软件 Typora

一、uniapp项目运行失败Error: getaddrinfo *.bspapp.com 文件查找失败uview-ui 在运行一个uniapp项目时&#xff0c;出现报错 文件查找失败&#xff1a;uview-ui&#xff0c;Error: getaddrinfo ENOTFOUND 960c0a.bspapp.com。hostname异常&#xff0c;报错的详细信息如下&…

什么是vue的keep-alive?它是如何实现的?具体缓存了什么内容?

文章目录 一、keep-alive 的核心作用二、实现原理1. 缓存管理策略2. 核心源码解析&#xff08;Vue 2.x 简化版&#xff09;3. 缓存生命周期 三、缓存的具体内容1. 缓存对象结构2. 具体缓存内容 四、使用示例1. 基础用法2. 配置缓存策略 五、注意事项六、实现流程图解 Vue 的 k…

pytest基础知识

pytest知识了解 pytest的基础知识了解&#xff1a;Python测试框架之pytest详解_lovedingd的博客-CSDN博客_pytest框架 (包含设置断点&#xff0c;pdb&#xff0c;获取最慢的10个用例的执行耗时) pytest-pytest.main()运行测试用例&#xff0c;pytest参数&#xff1a; pytest-…

Liunx(CentOS-6-x86_64)使用Nginx部署Vue项目

一&#xff1a;编译vue项目和上传到linux系统 通过本地编译器编译后的文件 上传服务器后的 二&#xff1a;安装 node&#xff08;版本 v16.20.2&#xff09;和npm&#xff08; 8.19.4或 9.6.5&#xff09; 备注一&#xff1a;安装nodejs就是安装node和npm&#xff0c; su…

分布式锁—Redisson的同步器组件

1.Redisson的分布式锁简单总结 Redisson分布式锁包括&#xff1a;可重入锁、公平锁、联锁、红锁、读写锁。 (1)可重入锁RedissonLock 非公平锁&#xff0c;最基础的分布式锁&#xff0c;最常用的锁。 (2)公平锁RedissonFairLock 各个客户端尝试获取锁时会排队&#xff0c;按照队…