news 2026/6/10 5:33:00

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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不能随意调大,否则会导致伙伴系统的内存开销增加,碎片整理难度变大,除非有明确的大连续内存分配需求,否则保持默认值即可。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/6 23:58:15

我该怎么从 0 到 1 构建出一个企业级 RAG 系统

从当下AI Agent 开发的招聘热门给大家分享一下企业级 RAG 的构建流程,以及如何才能从0 到 1 构建出一个让客户满意的 RAG 知识库系统。 引言 前段时间很火的 LLM WIKI,Osbian这些概念火的太快,很多人都在说:还搁这儿学 RAG 呢&a…

作者头像 李华
网站建设 2026/6/6 23:56:25

WarcraftHelper终极指南:5分钟让魔兽争霸III完美适配现代电脑

WarcraftHelper终极指南:5分钟让魔兽争霸III完美适配现代电脑 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 魔兽争霸III作为一代经典RTS…

作者头像 李华
网站建设 2026/6/6 23:53:08

如何用iTop在15分钟内搭建企业级IT服务管理系统?终极指南

如何用iTop在15分钟内搭建企业级IT服务管理系统?终极指南 【免费下载链接】iTop A simple, web based CMDB & IT Service Management tool 项目地址: https://gitcode.com/gh_mirrors/it/iTop 还在为混乱的IT服务管理而烦恼吗?iTop作为一款完…

作者头像 李华
网站建设 2026/6/6 23:50:04

MySQL(二):MySQL架构与存储引擎

目录 一、从 SQL 语句谈起 二、MySQL 整体架构 1. 架构总览 三、SQL 分类 1. DDL 数据定义语言 2. DML 数据操作语言 3. DQL 数据查询语言 4. DCL 数据控制语言 四、存储引擎 1. 存储引擎概念 2. 查看存储引擎 探测全局 查看具体某张表 五、常见存储引擎 1. Inn…

作者头像 李华
网站建设 2026/6/6 23:45:37

第2章:能力自测——找到最适合你的副业方向

本章你将收获 一份40题的副业自测问卷(可直接复制到Excel打分) 基于问卷得分的匹配算法(Python代码,我自己用的) 4种“副业人格类型”及对应的3条最优路径 CSDN热门方向数据(2024-2025真实订阅数据) 两个我踩过的方向选择坑(以及怎么避开) 今天就能完成的完整自测流程…

作者头像 李华