当前位置: 首页 > 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.zskr.cn/news/1476491.html

相关文章:

  • OpenRocket:零基础掌握专业火箭设计与飞行仿真
  • 树莓派摄像头监控进阶玩法:用MJPG-streamer+FRP搭建私人直播流服务器
  • 2026年 常州高端婚纱租赁/高端礼服租赁/新娘跟妆推荐榜:精致嫁衣与专业跟妆口碑之选 - 企业推荐官【官方】
  • 8类工地安全防护用品检测数据集(安全帽/反光背心/施工人员等)| 5200张YOLO安全生产监测数据集 适用于智慧工地、工业安防与目标检测研究
  • 普宁找工作用什么软件|本地求职者手机找工作的完整渠道指南 - 品牌观察
  • 数理统计课蒙特卡洛实践包:带注释Python脚本、多组模拟数据与可视化结果文件
  • BAV99与TVS管辨析:嵌入式IO保护电路设计中的常见误区与正确选型
  • 深度解析移动端免Root系统提取工具:Payload-Dumper-Android技术架构与实现原理
  • 5分钟免费搞定专业条码!Libre Barcode开源字体终极指南
  • 7天学会plc加机器视觉关于运动控制部份,配套视频在bib
  • 3分钟制作专业电子词典:AutoMdxBuilder零基础完全指南
  • 平台承担进口责任加重之后跨境卖家如何提前准备责任人信息
  • 必应推广百科:核心价值、场景适配与杭州服务商选择
  • Quartus II 11.0安装配置全攻略:从下载到验证的FPGA开发环境搭建
  • 2026年6月专业的碘化铑回收公司哪家靠谱推荐榜,高浓度碘化铑废液、低浓度碘化铑溶液、含杂质碘化铑废料、铑催化剂废液公司选择指南 - 海棠依旧大
  • 保姆级教程:用MounRiver Studio(MRS)给CH32V103和CH32F103开发板‘跑个分’
  • 【编号311】汉代丝绸之路交通数据
  • 基于Android的共享书屋平台源码+论文
  • Boost升压电路设计全解析:从工作原理到PCB布局实战
  • 别再为网卡发愁!普通PC+CODESYS V3驱动EtherCAT步进电机保姆级避坑指南
  • 2026年镇江公考/考公/公务员/省考/事业编/事业单位TOP5榜单:本地高上岸率与备考服务深度测评推荐 - 企业推荐官【官方】
  • 前台含税价格越来越敏感跨境卖家如何优化低客单页面表达
  • ARGIS制图效果展示
  • 2025-2026年建发金茂观宸电话查询:选房前需核实项目信息与合同条款 - 品牌推荐
  • Qwerty Learner:程序员如何在VSCode中边写代码边记单词的终极指南
  • Electron.NET与ASP.NET Core技术融合新范式:架构决策者的桌面应用开发革命
  • E-Hentai画廊批量下载终极方案:三步实现高效自动化管理
  • ACM能力契约模型:构建可治理的智能体操作系统
  • 普宁工厂招聘平台推荐|服装厂、内衣厂批量招普工,哪个渠道最快最准 - 品牌观察
  • 2026年镇江考公/事业编培训机构推荐榜单:省考/事业单位上岸优选与课程深度解析 - 企业推荐官【官方】