在这里插入图片描述

在这里插入图片描述

一、前置思考

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,后台可杀

六、高阶总结与最佳实践

  1. 两级分配:大块走伙伴系统(页)、小块走 slab(对象缓存)——快慢分明。
  2. 页表保隔离:MMU + 页表是进程隔离的基石,权限位(RWX)是安全底线。
  3. 回收分优先级:先页缓存、再匿名页、最后 OOM——内存不够时回收顺序有讲究。
  4. OOM 要保护前台:badness 评分保护交互进程,杀后台大头。
  5. 碎片靠合并:伙伴系统"合并伙伴"是抗碎片的核心,释放及时才能合并。

一句话记住:内存管理四板斧——伙伴系统管大块(2^n 页)、slab 管小块(对象缓存)、页表管隔离(MMU 权限)、LRU/OOM 管兜底(先回收后杀人)——让每一字节都有归属、有边界、有退路。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐