鸿蒙系统内存管理高级源码解析:伙伴系统/Buddy Allocator/slab分配器/页表管理/内存回收/OOM机制
·


一、前置思考
1.1 内存是系统的"棋盘"
内核管理内存的核心挑战:谁在用、谁释放、碎片怎么办、不够了怎么办。
内存管理的四大问题:
① 分配: 进程申请 4KB → 内核怎么快速给
② 隔离: 进程 A 不能访问进程 B 的内存(MMU/页表)
③ 回收: 内存不够时先回收谁(LRU 页缓存/匿名页)
④ 兜底: 回收还不够 → OOM Killer 杀谁
1.2 内存管理失败的灾难
❌ 内存碎片化: 总内存还有 1GB,但申请 8MB 连续内存失败
❌ 内存泄漏: 泄漏的进程吃光内存,系统卡死
❌ OOM 误杀: 杀掉了重要的前台进程(用户正在用的)
❌ 页表膨胀: 大内存进程页表占用过多
1.3 本文价值
剖析伙伴系统(Buddy Allocator)、slab 分配器、页表管理(MMU)、内存回收(LRU/kswapd)、OOM 机制的底层原理与源码级解析。
二、核心原理
2.1 内存分配两级架构
┌─────────────────────────────────────────┐
│ 进程内存 (用户态) │
│ malloc → 用户堆 (虚拟内存) │
├─────────────────────────────────────────┤
│ 内核内存分配器 │
│ ┌───────────────────────────────────┐ │
│ │ slab 分配器 (小块内存, 缓存复用) │ │
│ │ 对象缓存: task_struct/inode/文件 │ │
│ ├───────────────────────────────────┤ │
│ │ 伙伴系统 (大块内存, 页为单位) │ │
│ │ 按 2^n 页分配: 4KB/8KB/.../1MB │ │
│ └───────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ 页表/MMU (虚拟→物理映射 + 权限隔离) │
└─────────────────────────────────────────┘
2.2 伙伴系统原理
伙伴系统 (Buddy Allocator):
内存按 2 的幂次分成块(order 0~10)
分配: 申请 12KB → 找 order 4 (16KB) 块
→ 找不到就向上拆: order 5 拆成两个 order 4
→ 一个给用户,一个记为"伙伴"
释放: 释放 12KB → 找它的伙伴
→ 伙伴也空闲 → 合并成 order 5
→ 继续向上合并(减少碎片)
核心数据结构: free_area[order] 链表数组
每个 order 维护空闲块链表
2.3 slab 分配器原理
问题: 内核频繁创建/销毁对象(进程、文件、socket)
每次都从伙伴系统分配 4KB → 太慢 + 浪费
slab 方案: 为每种对象维护专属缓存
创建: 从缓存池取(预分配好的对象)
销毁: 归还缓存池(不还给伙伴系统)
slab 结构:
cache → slab (一组连续页) → object (对象)
每个 cache 管理一种对象类型
常见缓存: task_struct_cache / inode_cache / dentry_cache
三、源码/API 深度解析
3.1 伙伴系统源码(alloc_pages)
// Linux 伙伴系统核心: 页面分配
#include <linux/gfp.h>
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
{
// order: 2^order 页 (order=0 → 1页=4KB, order=3 → 8页=32KB)
struct page *page;
// 1. 从伙伴系统取块
page = __rmqueue(zone, order, gfp_mask);
// 2. 不够则尝试回收内存 (kswapd/直接回收)
if (!page) {
page = __alloc_pages_slowpath(gfp_mask, order);
}
return page;
}
// 释放: 伙伴合并
void __free_pages(struct page *page, unsigned int order)
{
// 归还伙伴系统,尝试与伙伴合并成更大块
free_pages_prepare(page, order);
// 若伙伴空闲 → 合并 (order+1)
// 递归向上合并,减少碎片
}
3.2 slab 分配器源码
// slab 缓存创建与使用
#include <linux/slab.h>
// 创建对象缓存(驱动/子系统专用)
static struct kmem_cache *sensor_cache;
static int __init sensor_init(void)
{
// 为 sensor 对象创建专属缓存
sensor_cache = kmem_cache_create(
"sensor_obj", // 缓存名
sizeof(struct sensor_obj), // 对象大小
0, // 对齐
0, // 标志
NULL); // 构造函数
return 0;
}
// 分配/释放对象(从缓存池)
struct sensor_obj *s = kmem_cache_alloc(sensor_cache, GFP_KERNEL);
// ... 使用 ...
kmem_cache_free(sensor_cache, s);
3.3 页表管理与 MMU
// 虚拟地址 → 物理地址映射 (页表)
// 页表层级: PGD → PUD → PMD → PTE (4级页表, ARM64)
// 页表项 (PTE) 属性:
// bit0: 有效位 (Present)
// bit1: 可读
// bit2: 可写 (RW)
// bit7: 用户/内核 (U/S)
// bit10: 不可执行 (NX, 防注入)
// 内核源码: 设置页表映射
static void set_pte_at(pte_t *ptep, pte_t pte)
{
// 写入页表项 + 屏障保证可见性
*ptep = pte;
// TLB 失效(内存屏障): 让新映射立即生效
flush_tlb_page(NULL, 0);
}
3.4 内存回收与 OOM
// 内存回收: kswapd 后台守护
// 内存压力 watermark 分级:
// HIGH > LOW > MIN
// kswapd 在内存低于 LOW 时启动回收
// 回收优先级: 页缓存 > 匿名页(LRU) > 不可回收
// OOM Killer: 内存彻底不够时的兜底
// 选择被杀进程的评分 (badness):
// ① 占用内存大 → 分数高(优先杀)
// ② 前台进程 → 分数低(保护)
// ③ 根进程/关键系统进程 → 保护
// 源码: oom_badness 评分
unsigned long oom_badness(struct task_struct *p)
{
unsigned long points;
// 基础分 = 进程内存占用 (RSS)
points = get_mm_rss(p->mm);
// 加分: 进程 CPU 时间多 (可能继续膨胀)
points += p->signal->oom_score_adj;
// 保护: 前台交互进程 adj 为负 → 分数低
// 关键进程 adj = -1000 → 永不杀
return points;
}
四、企业级实战落地
4.1 内存管理清单
| 环节 | 动作 | 说明 |
|---|---|---|
| 分配 | 大块用伙伴系统 | 页为单位 |
| 分配 | 小块用 slab 缓存 | 对象复用 |
| 映射 | 页表 + MMU | 进程隔离 |
| 回收 | LRU + kswapd | 水位触发 |
| 兜底 | OOM Killer | 评分选择 |
4.2 完整示例:内存管理机制演示
@Entry
@ComponentV2
struct KernelMemoryDemo {
@Local logs: string[] = [];
@Local order: number = 3;
private runBuddy(): void {
this.logs = [];
this.log('📊 伙伴系统分配演示');
this.log('申请: 20KB 内存');
this.log('→ 取 order 5 (32KB) 块');
this.log('→ 用户用 20KB,剩余作为伙伴');
this.log('释放: 伙伴合并 → 减少碎片');
this.log('核心: free_area[order] 链表按 2^n 管理');
}
private runSlab(): void {
this.logs = [];
this.log('🔩 slab 分配器演示');
this.log('进程对象缓存: task_struct_cache');
this.log('创建进程 → 从缓存池取对象 (us级)');
this.log('销毁进程 → 归还缓存池 (不还给伙伴系统)');
this.log('收益: 频繁创建/销毁对象提速 10x');
}
private runOom(): void {
this.logs = [];
this.log('💥 OOM 机制演示');
this.log('内存耗尽 → kswapd 回收 → 仍不够');
this.log('OOM Killer 评分 (badness):');
this.log(' 进程A (后台, RSS 2GB): 分数 90 → 🎯被杀');
this.log(' 进程B (前台, RSS 500MB): 分数 30 → 保护');
this.log(' 系统进程: adj=-1000 → 永不杀');
this.log('原则: 保护前台交互,杀后台大头');
}
build() {
Column({ space: 12 }) {
Text('🧠 内核内存管理').fontSize(20).fontWeight(FontWeight.Bold)
Row({ space: 8 }) {
Button('伙伴系统').layoutWeight(1).height(40).fontSize(12)
.onClick(() => this.runBuddy())
Button('slab缓存').layoutWeight(1).height(40).fontSize(12)
.onClick(() => this.runSlab())
Button('OOM机制').layoutWeight(1).height(40).fontSize(12)
.onClick(() => this.runOom())
}
.width('100%')
Scroll() {
Column() {
ForEach(this.logs, (l: string) => {
Text(l).fontSize(11).lineHeight(18).fontColor('rgba(255,255,255,0.8)').width('100%')
}, (l: string, i: number) => l + i)
}.width('100%')
}
.layoutWeight(1).width('100%').scrollBar(BarState.Off)
}
.width('100%').height('100%').padding(16)
.backgroundColor('#0D1B2A')
}
}
4.3 内存管理对比表
| 机制 | 单位 | 场景 | 特点 |
|---|---|---|---|
| 伙伴系统 | 2^n 页 | 大块内存 | 合并减碎片 |
| slab | 对象 | 内核对象复用 | 快 + 省 |
| 页表 | 页 | 虚拟内存映射 | 隔离 + 权限 |
| LRU 回收 | 页 | 内存压力 | 先回收缓存 |
| OOM | 进程 | 彻底不够 | 评分杀进程 |
五、问题排查与性能优化
| 问题 | 原因 | 解决 |
|---|---|---|
| 申请大块失败 | 内存碎片化 | 伙伴系统合并 + 大页 |
| 内核对象慢 | 每次新建不缓存 | slab 缓存复用 |
| 内存泄漏 | 对象未归还 | kmemleak 排查 |
| OOM 误杀前台 | 评分策略差 | adj 保护前台 |
| 页表膨胀 | 大内存映射多 | 透明大页 THP |
| 回收卡顿 | 同步回收 | 提前 kswapd 异步 |
5.1 内存优化要点
1. 应用层: 及时释放不用的对象(避免 RSS 膨胀)
2. 内核层: 高频对象用 slab 缓存,别每次都分配
3. 大块分配用大页(THP): 减少页表开销 + TLB 命中
4. 监控: 碎片率/内存水位/LRU 命中率持续观察
5. OOM 保护: 前台进程设负 adj,后台可杀
六、高阶总结与最佳实践
- 两级分配:大块走伙伴系统(页)、小块走 slab(对象缓存)——快慢分明。
- 页表保隔离:MMU + 页表是进程隔离的基石,权限位(RWX)是安全底线。
- 回收分优先级:先页缓存、再匿名页、最后 OOM——内存不够时回收顺序有讲究。
- OOM 要保护前台:badness 评分保护交互进程,杀后台大头。
- 碎片靠合并:伙伴系统"合并伙伴"是抗碎片的核心,释放及时才能合并。
一句话记住:内存管理四板斧——伙伴系统管大块(2^n 页)、slab 管小块(对象缓存)、页表管隔离(MMU 权限)、LRU/OOM 管兜底(先回收后杀人)——让每一字节都有归属、有边界、有退路。
更多推荐




所有评论(0)