第 34~40 集 核心知识整合(运维视角)
这 7 集核心围绕Linux 内存管理机制展开,中间穿插了嵌入式多媒体项目的架构设计。以下剔除了纯源码实现、汇编级操作、硬件驱动开发、逆向工程等运维无用内容,去重后按「基础概念→地址转换→核心优化机制→内存管理进阶→嵌入式架构提炼」的逻辑梳理,同时补充日常运维对应的现象、工具、排障知识,帮你从原理落地到实际工作。
先说明时代局限性:课程基于 Linux 0.11 早期内核讲解,部分实现细节(如 64MB 进程逻辑地址、两级页表、段寄存器操作)属于早期设计;现代 Linux 已经演进为平坦虚拟地址 + 三级 / 四级页表,但虚拟内存、分页映射、缺页中断、写时复制这些核心原理完全通用,也是理解日常内存问题的基础。
模块一:内存管理基础认知
一、物理内存布局
早期 Linux 物理内存被划分为三大区域,现代 Linux 思路一致但更动态:
- 内核内存区:存放内核代码、内核数据结构,常驻内存,用户进程不能直接访问。
- 高速缓冲区 :介于内核和用户内存之间,用于缓存磁盘块数据,提升 IO 效率。 补充:现代 Linux 对应的是页缓存(Page Cache) ,大小动态调整,会自动占用空闲内存,也是
free命令里buff/cache的主要组成部分。 - 用户主内存区:供所有用户进程分配使用,由内存管理模块统一管理、动态分配回收。
二、三种地址概念
表格
| 地址类型 | 定义 | 运维对应 |
|---|---|---|
| 逻辑地址(虚拟地址) | 进程视角看到的独立、连续地址空间,每个进程都认为自己独占整个内存 | 对应top里的VIRT(虚拟内存大小),数值可以远大于物理内存 |
| 线性地址 | 早期分段机制的中间地址,逻辑地址 + 段基址得到 | 现代 Linux 基本放弃分段,直接用分页映射,无需关注 |
| 物理地址 | CPU 内存总线实际访问的硬件地址,是真实的内存单元 | 对应top里的RSS(物理内存占用),是进程实际占用的物理内存大小 |
三、虚拟内存的核心价值
虚拟内存是操作系统内存管理的基石,核心价值体现在:
- 进程隔离:每个进程独立地址空间,一个进程崩溃、内存越界,不会影响其他进程和内核,是系统稳定性的基础。
- 空间扩展:可以提供比物理内存更大的地址空间,不足部分用磁盘交换(swap)兜底。
- 碎片整理:零散的物理内存页,可以映射成连续的虚拟地址空间,避免物理碎片问题。
- 动态分配:按需分配物理页,不用的地址不占实际内存,提升资源利用率。
模块二:地址转换机制
一、分段机制(早期设计,了解即可)
早期 x86 架构通过分段实现地址隔离:
- GDT(全局描述符表):全局唯一,存放内核段、各进程的 TSS 和 LDT 描述符。
- LDT(局部描述符表):每个进程一份,存放自己的代码段、数据段基址。
- 转换逻辑:逻辑地址 + 段基址 = 线性地址。不同进程段基址不同,即使逻辑地址一样,最终也映射到不同物理内存。
补充:现代 Linux 基本放弃了分段机制,采用平坦地址模型,只保留少数必要的段,核心内存管理靠分页机制实现。
二、分页机制(核心重点)
分页是虚拟内存落地的核心机制,由 MMU 硬件自动完成地址转换,对上层程序透明。
1. 基本单位
内存以页 为最小单位管理,标准页大小是4KB。
补充:现代 Linux 支持大页(HugePage),常见 2MB、1GB。大页可以减少页表数量、降低 TLB miss,数据库、大内存应用常用,能显著提升性能。
2. 页表映射
早期是两级页表结构,现代扩展为三级 / 四级,核心逻辑一致:
- 虚拟地址拆分为:页目录索引 + 页表索引 + 页内偏移
- CR3 寄存器指向当前进程的页目录表基址,CPU 通过两次查表,找到对应物理页基址,加上页内偏移,得到最终物理地址。
- 每个进程有自己独立的页表,这是进程内存隔离的底层实现。
3. TLB 转译后备缓冲器
- 作用:缓存虚拟地址→物理地址的映射结果,加速地址转换,避免每次都去查内存里的页表。
- 特性:页表映射修改后,必须刷新 TLB,否则会出现地址不一致。
- 运维对应:TLB miss 过多会导致 CPU 性能下降;使用大页可以大幅减少 TLB miss,提升大内存场景的性能。
模块三:两大核心优化机制(运维必懂)
缺页中断和写时复制,是操作系统内存高效运行的两个核心设计,对应日常大量的现象和问题。
一、缺页中断(Page Fault)
1. 触发逻辑
当程序访问的虚拟地址,对应的页面不在物理内存中(页表项 P 位为 0),就会触发缺页异常,陷入内核处理。
2. 处理流程
- 内核分配一个空闲物理页
- 根据地址定位到磁盘上的对应位置,把数据读取到物理页
- 建立页表映射,设置 P 位为有效
- 恢复程序执行,程序感知不到中断发生
3. 核心价值
按需加载:程序启动不需要把全部代码、数据读进内存,用到哪页才加载哪页。
- 程序启动速度快,不用等完整文件读取
- 节省内存,不会用到的代码(比如异常处理分支)永远不加载
- 动态库、函数也是按需调用,调用到才加载对应页
4. 运维对应
- 缺页分两种:
- 次缺页(min_flt):数据已经在内存中,只是没建立映射,开销小
- 主缺页(maj_flt):需要从磁盘读取数据,开销大,严重影响性能
- 查看命令:
ps -o min_flt,maj_flt 进程号 - 排障:程序卡顿、启动慢,先看主缺页是不是很高,大概率是磁盘 IO 瓶颈导致加载慢。
二、写时复制(Copy On Write, COW)
1. 触发逻辑
fork创建子进程时,不直接复制物理内存:
- 复制父进程的页表,父子进程的页表指向同一份物理内存
- 把所有共享页面设置为只读
- 当任意一方尝试写入页面时,触发写保护异常
- 内核为写入方分配新的物理页,拷贝原页数据,更新页表,解除共享
- 之后双方各自拥有独立的页面,互不影响
2. 核心价值
- fork 速度极快:只复制页表,不复制物理内存,毫秒级完成
- 极致节省内存:只读数据(比如代码段、常量)全程共享,只有修改的部分才复制
- 多实例程序内存利用率极高:比如 Nginx 多个 worker 进程、运行多个相同程序,代码段完全共享,只私有数据段
3. 运维对应
- 为什么父进程占 100M,fork 出的子进程 VIRT 是 100M 但 RSS 很小?就是因为共享物理内存。
- 为什么大量相同程序同时运行,内存没有线性增长?核心就是代码段共享机制。
top里的SHARE列,就是进程共享的物理内存大小。
模块四:内存管理进阶
一、页表项的关键标志位
每个页表项除了存物理页基址,还有几个关键标志位,控制内存的权限、状态:
表格
| 标志位 | 作用 |
|---|---|
| P(存在位) | 1 = 页面在物理内存中;0 = 不在,访问触发缺页中断 |
| RW(读写位) | 1 = 可写;0 = 只读,写入触发写保护异常 |
| U/S(用户 / 内核位) | 区分用户态还是内核态访问权限,保护内核内存不被用户态越权访问 |
| A(访问位) | 记录页面是否被访问过,内存回收时用于判断冷热页面 |
| D(脏位) | 记录页面是否被修改过;回收时脏页需要先写回磁盘 /swap,干净页直接释放 |
补充:脏位不只是文件缓存有,匿名内存页也有脏位,回收时需要写回交换分区。
二、内存回收与 OOM
1. 回收时机
物理内存不足时,内核后台线程(kswapd)会启动内存回收,腾出空闲页。
2. 回收优先级
- 优先回收干净的冷页面:没有被修改、长时间没访问的页面,直接释放,开销最小。
- 其次回收文件映射脏页:把数据写回磁盘,再释放页面。
- 最后回收匿名脏页:写入 swap 交换分区,再释放页面。
3. OOM 内存溢出
当物理内存 + swap 都耗尽,无法再分配内存时,触发OOM Killer,根据评分选择进程杀掉,释放内存。
- 运维对应:
dmesg里出现Out of memory: Kill process,就是 OOM Killer 触发了。 - 可以调整
/proc/进程号/oom_score_adj,保护关键进程不被优先杀掉。
三、进程退出的内存释放
进程退出时:
- 释放自己的页表映射
- 对应物理页的引用计数减 1
- 引用计数到 0 的页面,标记为空闲,进入回收池
经典问题:为什么进程退出了,系统可用内存没明显增加? 大概率是进程的数据变成了系统缓存,或者有共享内存还被其他进程引用,不是内存泄漏。
模块五:嵌入式项目架构提炼(第 38 集)
剔除硬件选型、驱动开发、编码实现等内容,提炼通用的系统设计思想,对定制轻量系统、裁剪镜像有参考价值:
- 完整系统栈:Uboot 引导 → Linux 内核 → 根文件系统 → 应用层,和之前讲的启动流程完全对应。
- 文件系统构建:裁剪依赖库、创建设备节点、配置开机脚本,核心思路和最小根文件系统一致 ------ 只保留必要组件,最小化体积。
- 应用层设计:多进程 + 多线程分层架构,模块化拆分(编码、字体、显示、输入独立模块),高内聚低耦合,便于维护和扩展。
- 字符编码:ASCII/Unicode/UTF-8 的存储差异,中文乱码本质是编码和解码格式不匹配。
运维常用内存工具补充
课程没讲但日常排障必备:
表格
| 工具 | 作用 | 典型场景 |
|---|---|---|
free -h |
查看整体内存使用(used、free、buff/cache、available) | 快速判断内存是否紧张 |
top / htop |
查看进程 VIRT、RSS、SHARE、% MEM | 定位哪个进程占内存多 |
ps -o min_flt,maj_flt |
查看进程缺页次数 | 排查程序卡顿是不是缺页导致 |
vmstat 1 |
查看内存、swap、缺页、IO 整体情况 | 持续监控内存压力 |
pmap 进程号 |
查看进程详细内存映射分布 | 排查内存泄漏、内存占用异常 |
slabtop |
查看内核 slab 缓存占用 | 排查内核内存占用异常 |
valgrind |
检测内存泄漏、越界访问 | 定位程序内存问题 |
整体学习路径
内存部分的学习逻辑建议: 基础概念 → 地址转换 → 两大核心机制(缺页、COW) → 回收与保护 → 工具与排障 先理解「虚拟内存为什么存在」,再懂「地址怎么转换」,再吃透缺页和写时复制这两个最核心的优化,最后结合工具落地到日常排障,就能解释绝大多数内存相关的现象和问题。