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/ 及驱动中配置。
目录
- 一、子系统职责与架构
- 二、源码目录与编译结构
- 三、启动期内存初始化
- 四、物理页分配(伙伴系统)
- 五、小对象分配(SLUB)
- 六、虚拟内存与缺页
- 七、页缓存与文件 I/O
- 八、反向映射(rmap)
- 九、内存回收与压缩
- 十、OOM 杀手
- 十一、CMA 连续内存分配器
- 十二、交换与子系统扩展
- 十三、RK3588 / 编解码场景关联
- 十四、调试与观测
- 十五、总结
- 附录:关键函数与调用关系
延伸阅读:
- README.md — 文档索引
- 源码文件与模块索引.md
- 功能组专题(原理 + 实现):01~12 号文档,见 README
一、子系统职责与架构
1.1 分层模型
1 | ┌─────────────────────────────────────────────────────────────┐ |
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.c、memblock.c、filemap.c、vmscan.c、slub 公共部分等。
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→ 默认 slabCONFIG_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 流程概览
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()将可用内存交给伙伴系统
RK3588:arch/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 | struct page *__alloc_pages(gfp_t gfp, unsigned int order, |
- 慢路径:内存不足时触发 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_PAGES、NR_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_cache 的 oo 决定),再在 slab 内切分对象。
5.3 启动顺序
1 | kmem_cache_init() // 早期,依赖 memblock 页 |
5.4 调试子系统(可选)
| 组件 | 目录/文件 | 作用 |
|---|---|---|
| KASAN | kasan/ |
越界、use-after-free |
| KFENCE | kfence/ |
低开销采样检测 |
| KMSAN | kmsan/ |
未初始化使用 |
| kmemleak | kmemleak.c |
泄漏扫描 |
六、虚拟内存与缺页
6.1 mmap.c
处理用户 mmap / munmap / brk:
- 创建/合并/拆分 VMA(
vm_area_struct) - 与文件
vm_ops、匿名映射、MAP_SHARED/MAP_PRIVATE等语义 - Linux 6.1 中 VMA 管理逐步使用 maple tree(见
init-mm.c中mm_mt)
6.2 memory.c
缺页异常与页表操作核心:
handle_mm_fault()→ 根据 VMA 类型分发- 匿名页:
do_anonymous_page(),可能 COW(do_wp_page()) - 文件页:常与
filemap.c协作 - swap:
do_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_mm 或 init_mm。上下文切换时 switch_mm() 切换页表(架构相关,在 arch/arm64/)。
七、页缓存与文件 I/O
7.1 filemap.c
实现 通用文件 mmap 语义 与 page cache:
filemap_fault:文件映射缺页,读盘或复用已有 foliofilemap_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_lock → anon_vma/mapping → lru_lock 等)。
九、内存回收与压缩
9.1 vmscan.c
页回收核心:
- kswapd:per-node 内核线程,水位低于 low 时异步扫描 LRU
- direct reclaim:在
__alloc_pages慢路径中同步回收 - LRU 链表:
active/inactive,分 file 与 anon - 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 | reserved-memory { |
内核启动参数 cma=256M 等可调整默认 CMA 大小(具体以 defconfig 与 DT 为准)。
11.3 编解码 / 显示
VPU、RGA、DRM 等驱动常依赖 dma_alloc_from_contiguous 或 drm 子系统 从 CMA 池取 buffer;问题排查需同时看:
/proc/meminfo中CmaTotal/CmaFreedmesg中cma:前缀日志- 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/CMA、IOMMU/SWIOTLB(
arch/、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_DMA32、CONFIG_DMA_COHERENT_POOL |
| 性能观测 | /proc/meminfo、/proc/vmstat、/proc/pressure/memory(PSI) |
13.3 ARM64 特性
- 无 highmem:
highmem.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.h、trace/events/cma.h等CONFIG_PAGE_OWNER、CONFIG_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 | __alloc_pages() |
A.2 缺页(简化)
1 | 架构 data abort / page fault |
A.3 回收(简化)
1 | kswapd / direct reclaim |
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 配套使用。
正在加载留言…