内存管理是内核中最复杂的活动。
虚拟内存
虚拟内存是一种逻辑抽象,进程的虚拟地址空间包括了进程所有可用的虚拟内存地址。
在虚拟内存中,CPU访问任何内存都是通过虚拟内存地址来访问的,但是实际上最终访问内存还是得用物理内存地址。所以在CPU中存在一个MMU,负责把虚拟地址转化为物理地址,然后再去访问内存。而MMU把虚拟地址转化为物理的过程需要页表的支持,页表是由内核负责创建和维护的。一套页表可以用来表达一个虚拟内存空间,不同的进程可以用不同的页表集,页表集是可以不停地切换的,哪个进程正在运行就切换到哪个进程的页表集。于是一个进程就只能访问自己的虚拟内存空间,而访问不了别人的虚拟内存空间,这样就实现了进程之间的隔离。一个虚拟内存空间又分为两部分,内核空间和用户空间,内核空间只有一个,用户空间有N个,所有的虚拟内存空间都共享同一个内核空间。内核运行在内核空间,进程运行在用户空间,内核空间有特权,用户空间无特权,用户空间不能随意访问内核空间。这样进程和内核之间的隔离就形成了。内核空间的代码运行的时候,CPU会把自己设置为特权模式,可以执行所有的指令。用户空间运行的时候,CPU会把自己设置为用户模式,只能执行普通指令,不能执行敏感指令。
下面就是虚拟内存如何解决物理内存不足的问题了。系统刚启动的时候还是运行在物理内存上的,内核也被全部加载到了物理内存。然后内核建立页表体系并开启分页机制,内核的物理内存和虚拟内存就建立映射了,整个系统就运行在虚拟内存上了。后面运行进程的时候就不是这样了,内核会记录进程的虚拟内存分配情况,但是并不会马上分配物理内存建立页表映射,而是让进程先运行着。进程运行的时候,CPU都是通过MMU访问虚拟内存地址的,MMU会用页表去解析虚拟内存,如果找到了其对应的物理地址就直接访问,如果页表项是空的,就会触发缺页异常,在缺页异常中会去分配物理内存并建立页表映射。然后再重新执行刚才的那条指令,然后CPU还是通过MMU访问内存,由于页表建立好了,这下就可以访问到物理内存了。当物理内存不足的时候,内核还会把一部分物理内存解除映射,把其内容存放到外存中,等其再次需要的时候再加载回来。这样,一个进程运行的时候并不需要立马加载其全部内容到物理内存,进程只需要少量的物理内存就能顺利地运行,于是系统运行进程的吞吐量就大大提高了。
- 内存管理的目标
内存管理的目标除了前面所说的进程之间的隔离、进程与内核之间的隔离、减少物理内存并发使用的数量之外,还有以下几个目标。
- 减少内存碎片,包括外部碎片和内部碎片。外部碎片是指还在内存分配器中的内存,但是由于比较分散,无法满足用户大块连续内存分配的申请。内部碎片是指你申请了5个字节的内存,分配器给你分配了8个字节的内存,其中3个字节的内存是内部碎片。内存管理要尽量同时减少外部碎片和内部碎片。
- 内存分配接口要灵活多样,同时满足多种不同的内存分配需求。既要满足大块连续内存分配的需求,又能满足小块零碎内存分配的需求。
- 内存分配效率要高。内存分配要尽量快地完成,比如说你设计了一种算法,能完全解决内存碎片问题,但是内存算法实现得特别复杂,每次分配都需要1毫秒的时间,这就不可取了。
- 提高物理内存的利用率。比如及时回收物理内存、对内存进行压缩。
虚拟内存的底层实现机制
包括:
🌐 虚拟内存到物理内存的映射过程(地址转换)
🔄 页表(Page Table)结构与分层机制
⚙️ MMU(内存管理单元)的作用
🧩 页面置换与 Swap 的触发时机
✅ 1. 虚拟地址到物理地址的映射过程(地址转换)
在现代 Linux 系统中,进程访问的地址其实是虚拟地址,在 CPU 访问内存时,需要转换成物理地址,过程如下:
css
复制编辑
用户态进程:
使用虚拟地址(Virtual Address)
↓↓↓↓
CPU/MMU + 页表:
将虚拟地址 → 物理地址(Physical Address)
↓↓↓↓
最终访问物理内存 RAM(或 swap)
✅ 2. 页表(Page Table)结构:多级映射机制
虚拟地址转换依靠页表完成,但由于虚拟地址空间很大(如 64 位系统为 2⁶⁴),单级页表太大,内存装不下,因此引入多级页表。
✳️ 示例:64位系统的四级页表结构(x86_64)
页表层级
位数
说明
PML4(第4级)
9位
一级页表索引(最多 512 项)
PDPT(第3级)
9位
二级页表索引
PD(第2级)
9位
三级页表索引
PT(第1级)
9位
最底层页表,指向物理页框号
页内偏移
12位
通常页面大小为 4KB
一个虚拟地址 64 位,一般只用到高 48 位:
复制编辑
| 9 | 9 | 9 | 9 | 12 |
每级页表项会索引到下一层页表的地址,最终到达底层的页表项,指向实际物理页框地址。
✅ 3. MMU(内存管理单元)
MMU(Memory Management Unit) 是 CPU 中用于地址转换的硬件模块,它:
根据当前进程的页表,把虚拟地址 ➝ 物理地址
检查访问权限(只读、可写、用户态、内核态)
发生缺页(Page Fault)时触发中断,交由内核处理
🧠 Linux 中每个进程都有自己的 mm_struct,保存页表根目录地址。
✅ 4. 缺页中断 & 页面置换(Page Fault & Swapping)
🌟 缺页(Page Fault)
当进程访问一个虚拟地址,但该页尚未映射到物理内存(可能还在磁盘中),就会触发缺页异常:
c
复制编辑
int p = (int)malloc(1000000 * sizeof(int));
p[999999] = 42; // 只有访问到这里,内核才分配物理页
Linux 采用“按需分配”策略,不访问不会真正分配物理内存。
♻️ 页面置换(Page Replacement)
当物理内存不足时,Linux 内核根据一定的算法(如 LRU)将某些页换出到 Swap 区。
常用的页面置换算法:
LRU(Least Recently Used)
CLOCK(近似 LRU)
📌 可以通过 vmstat, top, free, htop 等命令查看内存状态与页面换入换出。
🧠 实用命令 & 文件路径
功能
命令或路径
查看内存映射
cat /proc/
查看页表项(高级)
cat /proc/
查看内存使用
free -h、top、vmstat
查看 swap 使用
swapon -s、free -m
🧪 实例:用户进程访问虚拟地址过程
`c
'''
int *a = malloc(4096); // 分配一个页
a[0] = 123; // 第一次访问,触发 page fault
'''
过程如下:
用户进程通过 malloc 申请内存,但尚未映射物理页。
写入 a[0],触发 page fault。
内核分配一页物理内存,并更新页表。
下次访问同一页时,MMU 能直接完成地址转换
浙公网安备 33010602011771号