当前位置: 首页 > news >正文

Linux内核学习轨迹第五部:内存管理子系统-物理内存管理:伙伴系统(Buddy System)深度拆解(第三小节)

伙伴系统(Buddy System)深度拆解与源码分析

伙伴系统(Buddy System)是Linux物理内存管理的核心,负责管理系统中所有的空闲物理页,解决了连续物理内存分配的外部碎片问题,是整个内存管理子系统的基石。所有的物理内存分配,无论是用户态的页分配,还是内核的slab分配,最终都会落到伙伴系统的分配与释放接口。

3.1 伙伴系统解决的核心问题

内存分配最核心的两个问题:
  1. 内部碎片:分配的内存大于实际需要的内存,导致内存浪费,比如需要100字节,分配了4KB页,剩下的4000字节无法被利用,由slab分配器解决;
  2. 外部碎片:系统中有足够的空闲内存,但都是分散的小页,无法分配连续的大内存块,由伙伴系统解决。
传统的内存分配算法,比如首次适配、最佳适配,都会导致严重的外部碎片,系统运行一段时间后,无法分配连续的大内存,哪怕总空闲内存足够。伙伴系统通过分阶管理、合并空闲块的机制,完美解决了外部碎片问题,同时保证了分配和释放的高效性。

3.2 伙伴系统的核心原理

伙伴系统的核心思想非常简单:
  1. 分阶管理:把物理内存按2的幂次划分为不同阶数的块,order从0到MAX_ORDER-1(默认MAX_ORDER=11),对应块大小为2^0~2^10个连续页(4KB~4MB);
  2. 空闲块管理:每个Zone的free_area[MAX_ORDER]数组,对应每个阶数的空闲块链表,相同大小的空闲块挂在同一个链表中;
  3. 分配逻辑:当需要分配n个连续页时,找到大于等于n的最小2的幂次对应的阶数,如果该阶数有空闲块,直接分配;如果没有,向上找更大的阶数,把大的块拆分为两个小的伙伴块,直到得到需要的大小,剩下的块挂到对应阶数的链表中;
  4. 释放逻辑:释放内存块时,检查它的伙伴块是否也是空闲的,如果是,就把两个伙伴块合并为一个更大的块,继续向上检查合并,直到伙伴块不是空闲的,把最终的块挂到对应阶数的链表中。
什么是伙伴块:两个块必须满足以下三个条件,才是伙伴块,才能合并:
  1. 两个块的大小相同,都是2^order个页;
  2. 两个块的物理地址是连续的;
  3. 第一个块的起始物理地址,必须是2^(order+1)个页的整数倍。
举个例子:order=0的块(1页),伙伴块是相邻的1页,且起始地址是2页的整数倍;order=1的块(2页),伙伴块是相邻的2页,起始地址是4页的整数倍,以此类推。

3.3 伙伴系统的核心数据结构

伙伴系统的核心数据结构是struct free_area,每个Zone的free_area[MAX_ORDER]数组,定义在include/linux/mmzone.h中:
struct free_area { // 该阶数的空闲块链表 struct list_head free_list[MIGRATE_TYPES]; // 该阶数的空闲块总数 unsigned long nr_free; };
核心字段解析
  1. free_list[MIGRATE_TYPES]:空闲块链表,按迁移类型分类,每个迁移类型对应一个链表,这是内核防碎片的核心优化;
  2. r_free:该阶数的空闲块总数,统计该阶数有多少个空闲块。
迁移类型(MIGRATE_TYPES)
为了进一步减少内存碎片,内核把空闲块按迁移类型分类,不同类型的内存分配,从对应的迁移类型链表中分配,避免不可移动的页分散在内存中,导致无法合并大的空闲块。
核心迁移类型:

迁移类型

核心含义

适用场景

MIGRATE_UNMOVABLE

不可移动页,物理地址固定,不能移动

内核分配的不可移动对象,比如内核栈、slab对象

MIGRATE_MOVABLE

可移动页,物理地址可以移动,通过反向映射更新页表

用户态的页、页缓存、可移动的内核对象

MIGRATE_RECLAIMABLE

可回收页,不能移动,但可以通过回收释放

可回收的slab对象,比如目录项缓存、inode缓存

MIGRATE_HIGHATOMIC

高优先级原子分配,用于中断上下文等不能睡眠的场景

原子内存分配

MIGRATE_CMA

连续内存分配,用于设备驱动的大连续内存分配

设备驱动、多媒体、GPU

MIGRATE_ISOLATE

隔离的页,不能被分配,用于内存热插拔、碎片整理

内存热插拔、碎片整理

核心设计思想:不可移动的页只能从MIGRATE_UNMOVABLE类型的链表中分配,可移动的页从MIGRATE_MOVABLE类型的链表中分配,避免不可移动的页分散在可移动的内存中,导致大的连续块被拆分,无法合并,从根源上减少内存碎片。

3.4 伙伴系统核心流程源码解析

伙伴系统的核心函数定义在mm/page_alloc.c中,我们基于Linux 6.6内核,拆解分配、释放、合并的核心流程。

3.4.1 核心分配流程:alloc_pages()

伙伴系统的核心分配入口是alloc_pages()宏,最终会落到__alloc_pages_nodemask()函数,这是伙伴系统分配的核心函数,被称为「内核内存分配的心脏」。
分配流程的核心步骤
alloc_pages(gfp_mask, order)
__alloc_pages_nodemask(gfp_mask, order, nodemask)
1. 解析分配参数:gfp_mask、order、允许分配的节点/Zone
2. 快速路径分配:get_page_from_freelist()
├→ 按zonelist的顺序遍历Zone,检查空闲内存是否高于水位线
├→ 从对应order、迁移类型的free_list中取空闲块
├→ 如果块大小大于需要的order,拆分块,剩下的块挂到对应阶数的链表
├→ 分配成功,返回page结构体
└→ 分配失败,进入慢速路径
3. 慢速路径分配:__alloc_pages_slowpath()
├→ 唤醒kswapd内核线程,异步回收内存
├→ 直接内存回收,同步释放内存
├→ 内存碎片整理,合并空闲块
├→ OOM Killer,杀死进程释放内存
└→ 所有方法都失败,返回NULL,分配失败
核心函数深度解析

1.快速路径get_page_from_freelist()

这是伙伴系统最常用的分配路径,无锁、无睡眠,性能极高,99%的内存分配都会在快速路径完成。
  1. 核心逻辑:遍历允许的Zone列表,检查Zone的空闲内存是否高于分配要求的水位线,如果满足,就从对应order、迁移类型的空闲链表中取出第一个空闲块;如果该阶数没有空闲块,就向上找更大的阶数,找到后拆分块,把剩余的部分挂到低阶的链表中,返回分配的页。
  2. 块拆分逻辑示例:需要分配order=0(1页),当前order=0没有空闲块,order=1有空闲块,就把order=1的2页块拆分为两个order=0的伙伴块,一个分配出去,另一个挂到order=0的空闲链表中。

2.慢速路径__alloc_pages_slowpath()

只有当快速路径分配失败时,才会进入慢速路径,会执行内存回收、碎片整理、OOM等操作,可能会阻塞睡眠。

核心执行顺序:

  1. 先唤醒kswapd内核线程,异步回收内存,然后再次尝试快速路径分配;
  2. 如果还是失败,执行直接内存回收,同步回收不活跃的页,再次尝试分配;
  3. 如果还是失败,执行内存碎片整理,合并空闲的块,尝试分配连续的大内存;
  4. 如果还是失败,且分配允许OOM,调用OOM Killer,杀死占用内存多的进程,释放内存;
  5. 所有方法都失败后,返回NULL,分配失败。

3.4.2 核心释放流程:__free_pages()

内存释放的核心入口是__free_pages()函数,最终落到free_one_page()函数,完成页的释放、伙伴块的检查与合并。
释放流程的核心步骤
__free_pages(struct page *page, unsigned int order)
free_the_page(page, order)
free_one_page(page_zone(page), page, pfn, order, migratetype)
1. 检查页的合法性,清除页的标志位,重置引用计数
2. 循环检查当前块的伙伴块是否空闲:
├→ 计算当前块的伙伴块的pfn
├→ 检查伙伴块是否空闲、是否是相同阶数、相同迁移类型
├→ 如果是,把伙伴块从空闲链表中移除,和当前块合并为更大的块
├→ order加1,继续向上检查合并
└→ 如果伙伴块不空闲,停止合并
3. 把最终合并后的块,挂到对应order、迁移类型的空闲链表中
4. 更新Zone的空闲页数统计,唤醒等待空闲内存的进程
核心合并逻辑示例:释放一个order=0的页,它的伙伴块也是空闲的,就合并为order=1的块;如果这个order=1的块的伙伴块也是空闲的,继续合并为order=2的块,直到伙伴块不空闲,把最终的块挂到对应order的链表中。

3.5 伙伴系统的工程实践与避坑指南

1.gfp_mask标志位的正确使用

伙伴系统分配的核心参数是gfp_mask标志位,决定了分配的行为、允许的睡眠、分配的Zone、迁移类型等,很多内核驱动的bug都是因为gfp_mask使用错误导致的。

a.高频使用的标志位与适用场景:

标志位

核心含义

适用场景

GFP_KERNEL

内核常规分配,允许睡眠、允许IO、允许回收

内核进程上下文的常规内存分配,最常用

GFP_ATOMIC

原子分配,不允许睡眠,不能阻塞

中断上下文、软中断、自旋锁持有期间的内存分配

GFP_USER

用户态内存分配,允许睡眠、IO、回收

为用户进程分配内存

GFP_HIGHUSER

从高端内存分配,用于用户态内存

用户态的大内存分配

GFP_DMA

从ZONE_DMA分配,用于ISA设备DMA

老旧ISA设备驱动

GFP_DMA32

从ZONE_DMA32分配,用于32位DMA

32位PCI设备驱动

__GFP_NOFAIL

分配不允许失败,会一直重试直到成功

绝对不能失败的核心分配,谨慎使用

__GFP_NOWARN

分配失败不打印警告日志

预期可能失败的分配,避免日志刷屏

b.避坑指南:中断上下文、自旋锁持有期间,绝对不能使用允许睡眠的标志位(比如GFP_KERNEL),必须使用GFP_ATOMIC,否则会导致内核死锁、崩溃。

2.内存碎片化的排查与优化

系统运行时间长了之后,会出现内存碎片化,表现为:总空闲内存很多,但无法分配连续的大内存,比如大页分配失败,内核日志出现page allocation failure。

排查方法:

  1. 查看伙伴系统的空闲块分布:cat /proc/buddyinfo,查看每个Zone、每个order的空闲块数量,如果高阶的空闲块很少,低阶的很多,说明碎片化严重;
  2. 查看内存碎片化程度:cat /sys/kernel/debug/extfrag/extfrag_index,数值越接近1,碎片化越严重。

优化方案:

  1. 手动触发内存碎片整理:echo 1 > /proc/sys/vm/compact_memory;
  2. 开启自动碎片整理:echo 1 > /proc/sys/vm/compact_unevictable_allowed;
  3. 内核启动参数设置kernelcore和movablecore,把大部分内存划分为ZONE_MOVABLE,减少碎片化;
  4. 调整vm.extfrag_threshold参数,降低碎片整理的触发阈值,提前整理碎片。

3.page allocation failure故障定位

内核日志出现page allocation failure: order:5, mode:0x...,说明伙伴系统分配连续内存失败,是线上常见的内存故障。

定位流程:

  1. 从日志中获取分配的order、mode(gfp_mask)、失败的Zone;
  2. 查看/proc/buddyinfo,确认对应Zone的对应order是否有空闲块;
  3. 查看/proc/meminfo,确认系统的空闲内存、Slab占用、页缓存占用、匿名页占用;
  4. 查看/proc/zoneinfo,确认Zone的水位线、空闲页数、低内存预留;
  5. 常见根因:内存碎片化严重、内存水位线设置过高、内存被slab/页缓存占用、进程内存泄漏、OOM配置不合理。

临时解决方案:

  1. 手动触发内存回收:echo 3 > /proc/sys/vm/drop_caches,释放页缓存、目录项、inode缓存;
  2. 手动触发碎片整理:echo 1 > /proc/sys/vm/compact_memory;
  3. 调大vm.min_free_kbytes,提升内存水位线,预留更多空闲内存。

4.MAX_ORDER的调优

MAX_ORDER默认是11,对应最大的连续块是1024页(4MB),如果需要分配更大的连续物理内存,比如1GB大页,需要调整MAX_ORDER和内核启动参数。

避坑指南:MAX_ORDER不能随意调大,否则会导致伙伴系统的内存开销增加,碎片整理难度变大,除非有明确的大连续内存分配需求,否则保持默认值即可。

http://www.jsqmd.com/news/964762/

相关文章:

  • 【Android】PhotoArt--一款融入了ai技术的照片画质增强神器
  • STM8 PWM驱动详解:从库函数配置到硬件原理与调试实践
  • 2026年6月专业的苏州冷水机组减震器哪家强排行榜推荐榜,弹簧减振器/橡胶减振器/阻尼减振器/吊式减振器/空气减振器公司选择指南 - 海棠依旧大
  • C语言没有行指针、列指针、指针数组、数组指针、多级指针。。。等等这些概念
  • 高中教资科三资料|学科知识与教学能力备考资料合集
  • 树莓派摄像头监控进阶玩法:用MJPG-streamer+FRP搭建私人直播流服务器
  • 论文过关全靠它?书匠策AI官网www.shujiangce.com 降重降AIGC实测,这波操作我服了!
  • 请做coser的主人9下载2026官方正版
  • 避坑指南:Halcon 18安装时这3个选项千万别乱选!新手常犯的配置错误与优化建议
  • 广东天鹅绒瓷砖源头厂家推荐及选择参考 - 品牌排行榜
  • TikTokDownload分布式批量下载系统:架构设计与高性能实现原理
  • XHS-Downloader终极指南:从小红书内容采集到批量下载的完整解决方案
  • 联想拯救者BIOS高级设置终极解锁指南:免费简单教程
  • Sunshine游戏串流性能调优完全指南:从入门到精通的技术手册
  • 2026年6月有实力的苏州阻燃PE袋公司怎么选择推荐榜,FRL-01/FRL-02/FRL-03型阻燃PE袋公司选择指南 - 海棠依旧大
  • Synopsys ICC Layout窗口高效操作手册:从图层管理、对象查询到隐藏的热键技巧
  • 中国芯片设计公司的成本创新之路:从价格战到技术壁垒
  • 2026年 常州高端婚纱租赁/高端礼服租赁/新娘跟妆推荐榜:精致嫁衣与专业跟妆口碑之选 - 企业推荐官【官方】
  • 别再死记硬背了!用“快递分拣”的故事,5分钟搞懂Hadoop MapReduce核心流程
  • 基于Android+LLM大模型的人工智能历史模拟交互系统源码+论文
  • 8类工地安全防护用品检测数据集(安全帽/反光背心/施工人员等)| 5200张YOLO安全生产监测数据集 适用于智慧工地、工业安防与目标检测研究
  • 你的AI编程导师:如何用快马平台智能解答Java基础概念与生成示例
  • 2025-2026年荟茗挂件电话查询:使用前请核实产品材质与定制流程 - 品牌推荐
  • Unlock-Music:如何在浏览器中一键解锁加密音乐文件?终极免费方案揭秘![特殊字符]
  • 普宁找工作用什么软件|本地求职者手机找工作的完整渠道指南 - 品牌观察
  • FauxPilot架构解析:构建企业级本地AI代码助手的技术实现
  • 深度解析移动端免Root系统提取工具:Payload-Dumper-Android技术架构与实现原理
  • 数理统计课蒙特卡洛实践包:带注释Python脚本、多组模拟数据与可视化结果文件
  • BAV99与TVS管辨析:嵌入式IO保护电路设计中的常见误区与正确选型
  • 3分钟制作专业电子词典:AutoMdxBuilder零基础完全指南