1. 内存管理全景:物理内存与虚拟内存、MMU与页表、内核空间与用户空间划分
各位同学,咱们今天聊点硬核的。内存管理,说白了就是操作系统怎么管好这块“地皮”。你写代码时觉得内存是连续的、无限的,其实都是假象。真实情况是——物理内存就那么点,还到处是碎片。
我刚开始做嵌入式Linux驱动时,就吃过这个亏。一个DMA缓冲区分配下去,死活不连续,硬件直接罢工。嗯,从那以后我才真正开始研究内存管理全景图。
1.1 物理内存:硬件给你的“硬通货”
物理内存就是板上焊的那几颗DDR颗粒。CPU通过地址总线直接访问它。但有个问题——物理内存是有限的,而且多个进程同时跑,谁用哪块?怎么隔离?
举个例子,一个32位系统,物理内存最多4GB。但你的程序可能跑着跑着就需要2GB,另一个程序也要2GB。如果直接操作物理内存,冲突是必然的。
核心要点:物理内存是资源,但不是直接给进程用的。进程看到的“内存”是虚拟的。
1.2 虚拟内存:给每个进程画的大饼
虚拟内存是什么?说白了,就是给每个进程一个独立的地址空间。32位系统下,每个进程看到4GB的连续空间。64位就更大了,理论上16EB。
进程以为自己独占内存,实际上它访问的地址是“假的”。这个假地址需要翻译成真地址,才能找到物理内存上的数据。
我遇到过一个问题:一个嵌入式设备只有256MB物理内存,但跑了个需要1GB虚拟空间的应用程序。怎么办?靠虚拟内存的“按需分配”和“换入换出”。进程以为自己有1GB,实际上只有256MB物理页在支撑,其他页在磁盘上睡着。
避坑指南:我曾经在调试一个视频编解码驱动时,发现虚拟地址连续,但物理地址不连续。DMA传输直接崩了。记住:DMA用的是物理地址,不是虚拟地址!
1.3 MMU:地址翻译的“交警”
MMU(内存管理单元)是CPU内部的一个硬件模块。它的工作就是:把虚拟地址翻译成物理地址。
怎么翻译?靠页表。
页表是一张映射表。虚拟地址被分成页号(VPN)和页内偏移(offset)。MMU拿着VPN去查页表,找到对应的物理页框号(PFN),然后加上偏移,就得到了物理地址。
这个过程很快,因为TLB(快表)会缓存最近用过的映射。但TLB miss时,就要走慢速路径,查内存里的页表。
// 虚拟地址翻译物理地址的简化流程
虚拟地址 = 0x00401000
页大小 = 4KB (0x1000)
VPN = 0x00401000 >> 12 = 0x00401
offset = 0x00401000 & 0xFFF = 0x000
// 查页表得到 PFN = 0x000A
物理地址 = (0x000A << 12) | 0x000 = 0x000A000
注意:MMU不是所有场景都开。在裸机或bootloader阶段,MMU通常是关闭的。Linux内核启动早期也会有一段“无MMU”的汇编代码。我调试过一个问题:MMU开启后,中断向量表地址变了,导致中断处理程序跑飞。嗯,这个坑踩得值。
1.4 页表:多级映射的艺术
页表不是一张大表。如果32位系统用4KB页,页表项4字节,那张表就有2^20个条目,占4MB内存。每个进程一张?太浪费了。
所以现代CPU都用多级页表。ARM64用4级页表,x86_64用4级或5级。好处是:只映射实际使用的虚拟地址范围,不用的页表级可以“空洞”掉,不占内存。
| 层级 | 名称 | 作用 |
|---|---|---|
| L0 | PGD(页全局目录) | 顶级目录,每个进程一个 |
| L1 | P4D(页四级目录) | 64位系统特有 |
| L2 | PUD(页上级目录) | 管理大块区域 |
| L3 | PMD(页中间目录) | 管理中等区域 |
| L4 | PTE(页表项) | 直接映射物理页 |
你想想看,一个进程只用了少量内存,多级页表可能只分配了PGD和几项PTE。内存占用从4MB降到几十KB。这就是设计的精妙之处。
1.5 内核空间与用户空间:楚河汉界
Linux把虚拟地址空间一分为二。32位系统下,通常3GB用户空间 + 1GB内核空间。64位系统下,比例更灵活,但原则不变:用户态不能直接访问内核空间。
为什么?安全。用户程序如果能看到内核数据,那系统就乱套了。我见过一个新手写的驱动,直接把用户态指针传给内核用,结果内核崩溃。嗯,用户态指针必须用copy_from_user/copy_to_user来安全访问。
关键区别:
- 用户空间:每个进程独立,切换进程时CR3寄存器(页表基址)会变。进程崩溃不影响其他进程。
- 内核空间:所有进程共享同一份内核页表。内核代码和数据对所有进程可见(但用户态不能访问)。
内核空间里还有更细的划分:直接映射区(低端内存)、vmalloc区、固定映射区、高端内存映射区等。这些在后续章节会详细讲。
1.6 全景图:一张SVG说清楚
下面这张图,是我自己画的。它把物理内存、虚拟内存、MMU、页表、内核/用户空间的关系串起来了。你盯着看三分钟,应该能理解整个流程。
1.7 总结:记住这三句话
好了,这一章的内容就这些。我帮你总结三句话,记住它们,后面的课程就好理解了:
- 物理内存是硬件资源,虚拟内存是软件抽象。进程看到的是虚拟地址,MMU负责翻译。
- 页表是多级映射表,节省内存,支持按需分配。TLB加速翻译,但要注意TLB miss的开销。
- 内核空间和用户空间是隔离的。用户态不能直接访问内核,必须通过系统调用。DMA操作必须用物理地址。
个人经验:我调试过一个网络驱动,数据包在用户态和内核态之间拷贝了4次,性能惨不忍睹。后来用了mmap和DMA直接传输,速度提升了10倍。嗯,理解内存管理,才能写出高性能驱动。
下一章,我们会深入物理内存的组织结构,讲一讲NUMA、ZONE_DMA、ZONE_NORMAL这些概念。到时候见。
公众号:蓝海资料掘金营,微信deep3321