文章

Linux 内核学习笔记:内存管理

Linux 内核学习笔记:内存管理

这一篇记录内存管理的几个基础问题:虚拟内存、分段、分页、多级页表、TLB、段页式内存管理、Linux 内存管理。基础概念和常见问法先整理清楚,再补 OSTEP 的机制抽象,以及 Linux 里的真实对象、源码入口和可观察现象。

问题索引

主题本篇记录
虚拟内存虚拟地址、物理地址、MMU、地址空间
内存分段代码段、数据段、堆、栈;外部碎片和交换成本
内存分页page、page frame、page table、PTE
多级页表稀疏地址空间下如何减少页表内存
TLB地址翻译缓存、TLB miss、TLB shootdown
段页式内存管理分段表达逻辑区域,分页负责实际映射
Linux 内存管理mm_struct、VMA、page fault、COW、page cache、mmap

1. 虚拟内存:进程看到的地址空间

程序里看到的地址通常是虚拟地址。CPU 执行 load/store 时,硬件 MMU 根据页表把虚拟地址翻译成物理地址,再访问真实内存。

名词记录
虚拟地址进程指令里使用的地址,由进程地址空间定义
物理地址内存硬件上的真实地址
地址空间一个进程可见的虚拟地址范围,以及各段区域的权限和映射关系
MMUCPU 内的内存管理单元,负责地址翻译和权限检查
页表虚拟页到物理页框的映射表,通常由内核维护、硬件读取

虚拟内存提供三类基础能力。

能力具体含义
隔离每个进程拥有自己的虚拟地址空间,普通用户态代码不能直接访问其他进程内存
权限页表项可以标记可读、可写、可执行、用户态可访问等属性
按需分配虚拟地址范围可以先建立,物理页等到真正访问时再分配

OSTEP 里通常把虚拟化看成 OS 的核心抽象之一:CPU 被抽象成进程,内存被抽象成地址空间,磁盘被抽象成文件。内存虚拟化的关键点在于,程序使用连续、私有的地址空间,OS 和硬件共同完成映射、保护和异常处理。

Linux 落点:

Linux 对象作用
task_struct->mm指向当前进程的用户地址空间描述
struct mm_struct记录页表根、VMA 树、用户地址空间统计、锁等信息
struct vm_area_struct描述一段连续虚拟地址区域及其权限、来源和操作
page table架构相关页表,x86-64 常见为多级页表
page fault handler处理缺页、权限错误、写时复制、按需加载等情况

源码入口可以先记这些路径:include/linux/mm_types.h 看 mm_struct 和 vm_area_struct,mm/mmap.c 看 VMA 管理,mm/memory.c 看缺页和页表操作主线,arch/x86/mm/fault.c 看 x86 page fault 入口。

2. 分段:按逻辑区域管理内存

分段把程序内存按逻辑含义切分,比如代码段、数据段、堆、栈。每个段有基址、长度和权限。

段常见内容权限倾向
text指令代码只读、可执行
rodata常量数据只读
data已初始化全局变量可读写
bss未初始化全局变量可读写,初始为 0
heap动态分配内存可读写,向高地址扩展较常见
stack函数调用栈、局部变量、返回地址可读写,向低地址扩展较常见

分段模型的主要问题是外部碎片。段大小不固定,内存分配和释放后会留下大小不一的空洞。交换整个段时,搬运成本也可能很高。

Linux 落点:现代 x86-64 Linux 对普通用户进程主要使用平坦分段模型。分段机制仍有架构历史和少量用途,但内存隔离、权限和映射主要依赖分页。学习 Linux 内存管理时,重点放在 VMA 和页表。

3. 分页:固定大小的映射单位

分页把虚拟地址空间切成固定大小的虚拟页,把物理内存切成同样大小的页框。Linux 常见基础页大小是 4KB,实际机器也可能使用 huge page。

名词记录
page虚拟地址空间中的固定大小页
page frame物理内存中的固定大小页框
PTEpage table entry,页表项
page offset页内偏移,翻译前后不变

一个虚拟地址可以拆成“虚拟页号 + 页内偏移”。MMU 用虚拟页号查页表得到物理页框号,再拼上页内偏移形成物理地址。

页表项信息作用
present该虚拟页当前是否有有效物理映射
writable是否允许写
user用户态是否可访问
executable / NX是否允许取指执行
dirty页是否被写过
accessed页是否被访问过

分页减少了外部碎片,物理页可以分散放置。内部碎片仍然存在:一个分配不到整页的数据,也会占用页内剩余空间。

4. 多级页表:为稀疏地址空间省内存

单级页表需要覆盖整个虚拟地址空间。地址空间很大、实际使用区域很少时,单级页表会浪费大量内存。多级页表把页表拆成树状结构,只为实际使用的地址范围分配下级页表页。

模型特点
单级页表查询简单,但需要为整个地址空间保留页表
多级页表只给已使用范围分配页表页,查询需要逐级走表
TLB缓存翻译结果,降低多级页表遍历成本

x86-64 上常见页表层级可以记成 PGD、P4D、PUD、PMD、PTE。具体层级数量会随架构配置变化,Linux 源码通过通用宏和架构代码屏蔽一部分差异。

Linux 落点:

入口记录
Documentation/mm/page_tables.rstLinux 页表层级说明
include/linux/pgtable.h通用页表操作接口
arch/x86/include/asm/pgtable*.hx86 页表结构和宏
mm/memory.c建立、复制、释放、修改页表的主路径

5. TLB:地址翻译缓存

TLB 是 CPU 缓存的页表翻译结果。没有 TLB 时,每次访存都可能伴随多级页表遍历;命中 TLB 后,虚拟页到物理页框的翻译可以快速完成。

现象记录
TLB hit翻译结果在 TLB 中,访存路径短
TLB miss需要走页表,成本更高
context switch切换地址空间时,旧进程的 TLB 项可能不能继续使用
TLB shootdown多核上修改页表后,需要让其他 CPU 上的旧 TLB 项失效

TLB 把内存管理和调度联系起来。进程切换不只保存寄存器,还可能影响地址空间、页表根和 TLB 热度。同一个进程的线程共享地址空间,线程切换通常不需要切换 mm_struct;不同进程切换通常涉及不同地址空间。

6. Linux 地址空间:VMA 和 mmap 区域

Linux 用 VMA 描述一段连续虚拟地址区域。/proc/<pid>/maps 里看到的每一行,大体可以理解为一个 VMA 或 VMA 的用户态展示。

区域来源
可执行文件 text/dataexecve() 装载 ELF 时建立
heapbrk() / sbrk() 扩展传统堆区域
mmap 区域mmap() 建立的文件映射、匿名映射、共享库映射
stack线程栈
vvar / vdso内核提供给用户态的特殊映射

mmap 区域是一段由 mmap() 创建的虚拟地址范围。它可以映射文件,也可以映射匿名内存。

mmap 类型含义常见场景
文件映射虚拟页关联到文件内容共享库、内存映射文件、数据库文件
匿名映射虚拟页没有文件来源malloc 大块内存、线程栈、JIT 内存
私有映射写入时触发 COW,不直接修改原文件或原共享页进程私有数据、共享库私有重定位
共享映射多个进程共享修改,文件映射可写回文件共享内存、mmap 文件通信

mmap() 成功后,通常只是建立虚拟地址区域和权限记录。物理页可以等第一次访问时通过 page fault 分配或装入。

7. page fault:异常、分配和权限检查

page fault 是当前指令访问虚拟地址时触发的异常。它不等于程序错误。缺页、按需分配、COW 都依赖 page fault。

场景Linux 处理
访问尚未分配物理页的匿名映射分配物理页,清零,建立 PTE
访问文件映射但页不在内存从 page cache 或文件系统路径装入
fork() 后写私有页触发 COW,复制物理页,更新当前进程 PTE
访问无权限地址发送 SIGSEGV 或返回错误
内核访问坏用户指针syscall 返回 -EFAULT 等错误

简化路径可以记成:CPU 触发 page fault,架构入口保存异常信息,内核定位当前 mm_struct 和 VMA,检查访问权限,再根据 VMA 类型进入匿名页、文件页、COW 或错误处理。

源码入口记录
arch/x86/mm/fault.cx86 page fault 入口
mm/memory.chandle_mm_fault() 等主线
mm/filemap.c文件页和 page cache 相关路径
mm/mmap.cVMA 查找、插入、拆分、合并

8. page cache:文件 I/O 和内存管理的交界

page cache 是内核用内存缓存文件内容的机制。普通 read() 读文件时,数据常常先进入 page cache,再拷贝到用户缓冲区。文件 mmap() 后,用户态访问映射区域也可能直接命中 page cache 中的页。

路径和 page cache 的关系
read()从 page cache 拷贝到用户缓冲区;miss 时触发文件系统读盘
write()写入 page cache,把页标记为 dirty,之后 writeback
file-backed mmap()用户虚拟页映射到文件 page cache 页
reclaim内存压力下回收干净文件页,脏页需要先写回

这个点很容易把文件系统和内存管理串起来:文件页既是文件系统缓存,也是进程地址空间里可能映射的物理页。

9. 观测入口

入口看什么
/proc/<pid>/mapsVMA 地址范围、权限、文件来源
/proc/<pid>/smaps每个 VMA 的 RSS、PSS、匿名页、文件页等统计
/proc/<pid>/pagemap虚拟页到物理页框信息,读取受权限限制
/proc/meminfo系统内存、page cache、slab、swap 等统计
pmap -x <pid>进程地址空间汇总
vmstat 1page in/out、swap、内存压力
perf stat -e dTLB-load-missesTLB miss 相关事件,事件名依硬件而异
perf trace -e page-faults观察缺页事件

最小观察顺序:

步骤命令
看进程映射cat /proc/<pid>/maps
看每段实际占用cat /proc/<pid>/smaps
看系统内存cat /proc/meminfo
看缺页统计ps -o pid,min_flt,maj_flt,comm -p <pid>

压缩表

概念基础概念 / 八股OSTEP 模型Linux 落点
虚拟内存虚拟地址通过 MMU 转物理地址地址空间抽象mm_struct、页表
分段按逻辑区域管理,存在碎片问题base + bounds 的扩展模型现代 Linux 普通路径主要依赖分页
分页固定大小页,页表映射page table + protectionPTE、page fault、page frame
多级页表节省页表内存稀疏地址空间优化PGD/PUD/PMD/PTE
TLB页表翻译缓存加速地址翻译shootdown、context switch 成本
mmap建立一段虚拟地址映射地址空间区域管理VMA、匿名页、文件页
page cache文件内容的内存缓存I/O 缓存file-backed page、writeback、reclaim

检查点与参考回答

问题参考回答
虚拟地址和物理地址怎么区分?虚拟地址是进程指令使用的地址,物理地址是内存硬件上的真实地址。CPU 访存时由 MMU 根据页表把虚拟地址翻译成物理地址,并检查权限。
为什么需要虚拟内存?虚拟内存提供进程隔离、权限控制和按需分配。每个进程看到自己的地址空间,内核通过页表控制哪些页可读、可写、可执行。
分段和分页的核心区别是什么?分段按程序逻辑区域划分,段大小可变;分页按固定大小页划分,页和页框通过页表映射。分段容易产生外部碎片,分页主要面对页表开销和内部碎片。
多级页表为什么省内存?多级页表只为实际使用的虚拟地址范围分配下级页表页。大片未使用地址空间不需要完整页表。
TLB 解决什么问题?TLB 缓存虚拟页到物理页框的翻译结果,减少每次访存都走多级页表的成本。
mmap 区域是什么?mmap 区域是通过 mmap() 创建的一段虚拟地址范围,可以映射文件,也可以映射匿名内存。它对应 Linux 里的 VMA,物理页通常按需建立。
page fault 一定是错误吗?page fault 是一种异常入口。缺页加载、匿名页按需分配、COW 都会触发 page fault;访问非法地址或权限不匹配时才会变成 SIGSEGV 或 -EFAULT。
mm_struct 和 VMA 分别记录什么?mm_struct 描述一个进程的用户地址空间整体,包括页表根和 VMA 集合;VMA 描述其中一段连续虚拟地址区域,包括起止地址、权限和映射来源。
page cache 和文件 I/O 有什么关系?page cache 用内存缓存文件内容。read()、write() 和 file-backed mmap() 都可能经过 page cache,内存压力下干净文件页可回收,脏页需要写回。

本篇参考

  • Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces
  • Michael Kerrisk, The Linux Programming Interface
  • Robert Love, Linux Kernel Development
  • Linux Kernel Documentation: Memory Management
  • Linux Kernel Documentation: Page Tables
本文由作者按照 CC BY 4.0 进行授权