mm/ 内存管理机制与原理详解

mm/ 内存管理机制与原理详解

源码路径rk3588/kernel-6.1/mm/
内核版本:Linux 6.1(RK3588 平台)
平台:RK3588(ARM64,典型配置含 CMA、DMA32、无 highmem)
文档目录linuxDoc/mm/

该目录实现 Linux 6.1 的 内存管理(Memory Management, MM)子系统,涵盖从引导期物理内存描述、伙伴页分配、SLUB 小对象分配、虚拟地址空间与缺页、页缓存与写回、反向映射与回收,到 OOM、CMA、cgroup 限流等完整链路。目录内 无 Rockchip 专有补丁;RK3588 相关内存布局与预留通常在设备树、arch/arm64/ 及驱动中配置。


目录

延伸阅读


一、子系统职责与架构

1.1 分层模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────────┐
│ 用户态:mmap / brk / madvise / mincore / userfaultfd │
├─────────────────────────────────────────────────────────────┤
│ 虚拟内存:VMA、页表、缺页、COW、mprotect、mremap │
│ memory.c, mmap.c, mprotect.c, mremap.c │
├─────────────────────────────────────────────────────────────┤
│ 页缓存 / 写回:filemap、readahead、writeback │
│ filemap.c, readahead.c, page-writeback.c │
├─────────────────────────────────────────────────────────────┤
│ 反向映射 + 回收:rmap、LRU、kswapd、compaction │
│ rmap.c, vmscan.c, compaction.c, workingset.c │
├─────────────────────────────────────────────────────────────┤
│ 物理页:伙伴分配器、zone、水位线、per-CPU 页列表 │
│ page_alloc.c, mmzone.c, vmstat.c │
├─────────────────────────────────────────────────────────────┤
│ 小对象:SLUB / SLAB / kmalloc │
│ slub.c, slab_common.c │
├─────────────────────────────────────────────────────────────┤
│ 引导期:memblock → sparse → buddy │
│ memblock.c, sparse.c, page_alloc.c │
└─────────────────────────────────────────────────────────────┘

1.2 核心抽象(Linux 6.1)

抽象 说明
struct page / folio 物理页描述;6.1 逐步以 folio 作为页缓存与部分 MM 路径的中心
pg_data_t / node NUMA 节点;UMA 系统通常仅 node 0
struct zone 每个 node 内按寻址/用途划分的内存池(Normal、DMA32、CMA 等)
struct mm_struct 进程地址空间
struct vm_area_struct 虚拟内存区域(VMA)
struct address_space 文件/inode 的页缓存索引
gfp_t 分配标志(原子、IO、FS、高阶、NUMA 等)

二、源码目录与编译结构

2.1 规模

mm/176 个文件(含 kasan/kfence/damon/ 等子目录),是内核中最大子系统之一。

2.2 Makefile 核心划分

始终编入内核(obj-y) 包括:page_alloc.cmemblock.cfilemap.cvmscan.cslub 公共部分等。

MMU 相关(CONFIG_MMU) 在 RK3588 ARM64 上必开,额外链接:

  • memory.c, mmap.c, rmap.c, vmalloc.c, mprotect.c, mremap.c

常见 RK 产品 defconfig 可选项

  • CONFIG_CMA → 多媒体 / GPU 连续 DMA 内存
  • CONFIG_SLUB → 默认 slab
  • CONFIG_MEMCG → cgroup 内存限制
  • CONFIG_TRANSPARENT_HUGEPAGE → 透明大页(可选)

完整文件列表见 源码文件与模块索引.md

2.3 internal.h

internal.h 定义 MM 子系统内部接口与 GFP 约束,例如:

  • GFP_RECLAIM_MASK:影响水位与回收行为的标志子集
  • GFP_BOOT_MASK:早期引导可用标志
  • folio 相关 inline、写回记账、unmap 辅助声明

驱动与通用内核代码应使用 <linux/mm.h><linux/gfp.h> 等公开头文件,而非直接依赖 internal.h


三、启动期内存初始化

3.1 流程概览

架构 setup_archmemblock 记录物理内存与保留区sparse_init 建立 pfn ↔ pagefree_area_init 初始化各 zonepage_alloc_init buddy 与 per-CPU 列表kmem_cache_init SLUBinit_mm / 用户进程可用

3.2 memblock.c

memblock 在 buddy 与 slab 就绪前管理内存:

集合类型 含义
memory 内核可见物理内存(可被 mem= 限制)
reserved 已保留区域(内核镜像、DTB、initrd、CMA 预留等)
physmem 部分架构上的实际物理内存视图

典型 API:

  • memblock_add() / memblock_add_node():登记可用内存
  • memblock_reserve():标记保留
  • 引导末期:memblock_free_all() 将可用内存交给伙伴系统

RK3588arch/arm64/ 与设备树解析在引导早期调用 memblock;reserved-memory 节点对应的区域在此阶段被 reserve。

3.3 sparse.c

CONFIG_SPARSEMEM 下,物理内存按 section 稀疏建模,避免为空洞内存分配 struct page 数组。CONFIG_SPARSEMEM_VMEMMAP 可用虚拟 vmemmap 降低元数据开销。

3.4 init-mm.c

定义全局 init_mm(内核 swapper 页表),mm_users/mm_count 初始化,供内核线程与早期映射使用。

3.5 mm_init.c

提供启动后 校验与调试(如 CONFIG_DEBUG_MEMORY_INIT 下的 zonelist 打印、pageflags 布局检查),不参与运行时分配热路径。


四、物理页分配(伙伴系统)

详细原理与实现见 02-物理页分配机制与实现详解.md

4.1 核心文件

page_alloc.c(约 9800+ 行)— 空闲页管理中枢;注释说明 kmalloc 在 slab 中,页级分配在此文件。

4.2 伙伴算法与 order

  • 物理页以 2^order 个连续页 为块管理(order 0 = 单页,order 9 = 512 页块等)。
  • 分配时从合适 order 的 free_list 取块;若无则向更高 order 分裂。
  • 释放时尝试与 buddy 合并,降低 外部碎片

4.3 Zone 与 ARM64

Zone ARM64 典型用途
ZONE_DMA32 32 位 DMA 可寻址物理内存
ZONE_NORMAL 主要可分配内存
CMA 区域 落在某 zone 内,页带 PageCMA 属性,仅供 CMA 分配

RK3588 无 ZONE_HIGHMEM(64 位线性映射足够),highmem.c 一般不参与实际路径。

4.4 关键入口

1
2
struct page *__alloc_pages(gfp_t gfp, unsigned int order,
int preferred_nid, nodemask_t *nodemask);
  • 慢路径:内存不足时触发 direct reclaim、compaction,仍失败则可能 OOM
  • per-CPU 页列表:减少锁竞争,bulk 分配/释放优化(见文件头历史注释)。

4.5 水位线(watermark)

每个 zone 有 min / low / high

  • 低于 low:唤醒 kswapd 后台回收
  • 低于 min:分配器在 __alloc_pages 中同步回收(direct reclaim)
  • min 以下仍无法满足 → 可能进入 OOM

统计项见 vmstat.c(如 NR_FREE_PAGESNR_FREE_CMA_PAGES)。

4.6 GFP 标志(常用)

标志 含义
GFP_KERNEL 可睡眠,普通内核上下文
GFP_ATOMIC 不可睡眠(中断/持有锁)
__GFP_HIGHMEM 高端 zone(ARM64 通常无关)
__GFP_DMA32 从 DMA32 zone 分配
__GFP_MOVABLE 可迁移页,利于 compaction
__GFP_NOFAIL 尽力保证成功(慎用)

五、小对象分配(SLUB)

5.1 实现

RK3588 内核通常 CONFIG_SLUB=y

  • slub.c:per-CPU slab、per-node partial 列表,减少锁与 cache line 抖动
  • slab_common.c:cache 创建、kmalloc 类型表、引导期 create_boot_cache

5.2 与页分配器关系

SLUB 向 __alloc_pages 申请 slab 页(order 由 kmem_cacheoo 决定),再在 slab 内切分对象。

5.3 启动顺序

1
2
kmem_cache_init()        // 早期,依赖 memblock 页
kmem_cache_init_late() // 更完整 cache 集

5.4 调试子系统(可选)

组件 目录/文件 作用
KASAN kasan/ 越界、use-after-free
KFENCE kfence/ 低开销采样检测
KMSAN kmsan/ 未初始化使用
kmemleak kmemleak.c 泄漏扫描

六、虚拟内存与缺页

6.1 mmap.c

处理用户 mmap / munmap / brk

  • 创建/合并/拆分 VMAvm_area_struct
  • 与文件 vm_ops、匿名映射、MAP_SHARED/MAP_PRIVATE 等语义
  • Linux 6.1 中 VMA 管理逐步使用 maple tree(见 init-mm.cmm_mt

6.2 memory.c

缺页异常与页表操作核心:

  • handle_mm_fault() → 根据 VMA 类型分发
  • 匿名页do_anonymous_page(),可能 COW(do_wp_page()
  • 文件页:常与 filemap.c 协作
  • swapdo_swap_page() 从 swap 读入
  • THP:与 huge_memory.c 协作(若开启)

6.3 相关系统调用文件

文件 功能
mprotect.c 修改 VMA 权限
mremap.c 重映射虚拟地址
mlock.c 锁定物理页
madvise.c MADV_* 建议(丢弃、大页、顺序访问等)
mincore.c 查询页是否在内存中
msync.c 映射区与存储同步

6.4 vmalloc.c

vmalloc:内核虚拟地址连续、物理页可分散,用于大块内核缓冲区、模块加载等。与 线性映射区(lowmem/direct map)分配路径不同。

6.5 init_mm 与用户进程

每个用户进程有独立 mm_struct;内核线程可借用 active_mminit_mm。上下文切换时 switch_mm() 切换页表(架构相关,在 arch/arm64/)。


七、页缓存与文件 I/O

7.1 filemap.c

实现 通用文件 mmap 语义page cache

  • filemap_fault:文件映射缺页,读盘或复用已有 folio
  • filemap_read / filemap_write:缓冲 I/O 与页缓存交互
  • address_space、inode 锁、i_mmap_rwsem 协同

7.2 readahead.c

根据访问模式 预读 相邻页,降低顺序读延迟。

7.3 page-writeback.c + backing-dev.c

  • 脏页标脏、回写线程、wb_writeback
  • backing device 控制回写并发与脏页比例,避免内存被脏页占满

7.4 truncate.c

文件截断时释放或无效化 page cache 中超出文件大小的页。


八、反向映射(rmap)

8.1 作用

rmap.c 维护 物理页 → 所有映射它的 PTE 的关系,用于:

  • 回收时 unmap 并剥离引用
  • 迁移KSM内存故障 处理
  • 判断页是否仍被引用(page_referenced

8.2 两类映射

类型 结构 场景
匿名页 anon_vma 堆、栈、匿名 mmap
文件页 address_space + i_mmap 文件映射、shmem

8.3 锁顺序

rmap.c 文件头注释了 MM 子系统 锁顺序,调试死锁时需严格遵守(mmap_lockanon_vma/mappinglru_lock 等)。


九、内存回收与压缩

9.1 vmscan.c

页回收核心:

  • kswapd:per-node 内核线程,水位低于 low 时异步扫描 LRU
  • direct reclaim:在 __alloc_pages 慢路径中同步回收
  • LRU 链表active / inactive,分 fileanon
  • swappiness:控制匿名页 vs 文件页回收倾向

9.2 workingset.c

利用 refault 等信息区分 working set 与可回收缓存,改善回收质量。

9.3 compaction.c

内存压缩:迁移可移动页,拼凑 物理连续 空闲块,服务于:

  • CMA 分配
  • 大页透明大页
  • 高 order 伙伴块分配失败后的恢复

9.4 vmpressure.c

cgroup 或用户空间报告内存压力事件(与 memcontrol.c 配合)。


十、OOM 杀手

10.1 oom_kill.c

__alloc_pages 在回收、压缩后仍无法获得足够内存时,可能调用 out_of_memory()

  • oom_score_adj、内存占用、子进程等选择 victim
  • 发送 SIGKILL 或依赖 panic_on_oom 策略
  • memcg 结合时可做 cgroup 级 OOM(仅杀组内进程)

10.2 与调度的关系

OOM 选择进程时考虑 task_struct 属性,但不经过 sched_class;与 cpufreq_schedutil 无直接调用链,但内存压力导致的回收会增加 CPU 与 I/O 负载,间接影响调度与调频观测。


十一、CMA 连续内存分配器

详细原理与实现见 09-CMA连续内存机制与实现详解.md

11.1 cma.c

CMA(Contiguous Memory Allocator) 在启动时预留一块 物理连续 内存:

  • 平时可作为 可迁移页 参与 buddy 分配(带 PageCMA
  • 驱动需要连续 DMA buffer 时,通过 cma_alloc() 分配并 pin 住连续区域
  • 分配失败时可触发 compaction 迁移周围页

11.2 与设备树

RK3588 常见配置:

1
2
3
4
reserved-memory {
linux,cma { ... };
/* 或独立 display/camera 预留 */
};

内核启动参数 cma=256M 等可调整默认 CMA 大小(具体以 defconfig 与 DT 为准)。

11.3 编解码 / 显示

VPU、RGA、DRM 等驱动常依赖 dma_alloc_from_contiguousdrm 子系统 从 CMA 池取 buffer;问题排查需同时看:

  • /proc/meminfoCmaTotal / CmaFree
  • dmesgcma: 前缀日志
  • DT reserved-memory 是否与驱动匹配

十二、交换与子系统扩展

12.1 swap(CONFIG_SWAP)

文件 功能
swapfile.c swap 设备/文件注册
swap_state.c swap cache、swap entry
page_io.c 读写 swap 槽
swap_slots.c slot 管理

12.2 zswap(CONFIG_ZSWAP)

在写 swap 前 压缩 页到内存池,减少闪存/块设备 I/O(zswap.c + zpool/zbud/zsmalloc 等后端)。

12.3 其他扩展

机制 文件 说明
KSM ksm.c 合并相同匿名页
THP huge_memory.c, khugepaged.c 透明大页
hugetlb hugetlb.c 显式大页文件系统
shmem shmem.c tmpfs、共享内存
memfd memfd.c sealing、匿名 fd
userfaultfd userfaultfd.c 用户态处理缺页
DAMON damon/ 访问监控与回收策略
memcontrol memcontrol.c cgroup v2 内存控制器

十三、RK3588 / 编解码场景关联

13.1 本目录与平台差异

  • mm/ 内无 rockchip / rk3588 字符串,为上游 Linux 6.1 MM 代码。
  • 平台差异体现在:defconfig设备树 reserved-memory/CMAIOMMU/SWIOTLBarch/drivers/iommu)、DRM/V4L2 驱动

13.2 SeagullYpcEncode 典型关注点

场景 MM 相关点
编码 buffer CMA、dma_alloc_coherent、dmabuf(drivers + gup.c
内存压力 vmscan 回收、oom_kill、可选 memcg 限制编码进程
碎片 / 大 buffer 失败 compaction、CMA 预留不足、order 过高
32 位 DMA 设备 ZONE_DMA32CONFIG_DMA_COHERENT_POOL
性能观测 /proc/meminfo/proc/vmstat/proc/pressure/memory(PSI)

13.3 ARM64 特性

  • 无 highmemhighmem.c 非关键路径
  • 4K 页(可配置 16K/64K,以实际 Image 为准)
  • MTE/KASAN HW tags:若启用,与 kasan/hw_tags.c 相关

十四、调试与观测

14.1 proc / sysfs

接口 内容
/proc/meminfo 总览:MemFree、Cached、Swap、Cma*
/proc/vmstat 详细计数(vmstat.c
/proc/buddyinfo 各 zone 各 order 空闲块
/proc/pagetypeinfo 页类型(可迁移、不可迁移、CMA 等)
/proc/zoneinfo zone 水位与统计
/sys/kernel/mm/cma/ CMA 区域信息(CONFIG_CMA_SYSFS)

14.2 内核参数(示例)

  • mem=512M:限制可见内存
  • cma=256M:CMA 默认大小
  • vm.swappiness:回收倾向(sysctl)
  • panic_on_oom:OOM 时 panic

14.3 跟踪

  • trace/events/kmem.htrace/events/cma.h
  • CONFIG_PAGE_OWNERCONFIG_DEBUG_VM_PGTABLE 等调试选项见 Kconfig.debug

十五、总结

要点 说明
定位 Linux 6.1 通用 MM 子系统,约 176 源文件
引导 memblock → sparse → buddy → SLUB
热路径 __alloc_pages、缺页、filemap_fault、kswapd
RK3588 依赖 CMA/DT/defconfig,非 mm/ 内厂商补丁
编解码 重点 CMA、DMA、回收/OOM、memcg

附录:关键函数与调用关系

A.1 页分配慢路径(简化)

1
2
3
4
5
6
7
__alloc_pages()
├─ get_page_from_freelist() // 快路径尝试
└─ __alloc_pages_slowpath()
├─ wake_all_kswapds()
├─ try_to_compact_pages() // 可选
├─ __alloc_pages_direct_reclaim()
└─ out_of_memory() // 仍失败

A.2 缺页(简化)

1
2
3
4
5
6
7
架构 data abort / page fault
└─ handle_mm_fault()
├─ __handle_mm_fault()
│ ├─ filemap_fault() // 文件映射
│ ├─ do_anonymous_page() // 匿名
│ └─ do_swap_page() // swap
└─ 返回 VM_FAULT_* 重试或 SIGSEGV

A.3 回收(简化)

1
2
3
4
5
kswapd / direct reclaim
└─ balance_pgdat() / shrink_lruvec()
└─ shrink_list() → shrink_page_list()
└─ try_to_unmap() [rmap]
└─ 脏页写回 / 释放 / swap out

A.4 关键文件行号参考(便于跳转源码)

符号 文件 约略位置
__alloc_pages page_alloc.c ~5596
page_alloc_init page_alloc.c ~8733
kmem_cache_init slub.c ~4818
anon_vma_init rmap.c ~459
init_mm init-mm.c 全局变量

文档生成自 rk3588/kernel-6.1/mm/ 目录分析,与 README.md 配套使用。

文章互动

阅读 --

留言

0 条留言

正在加载留言…