1. Linux内存管理全景:物理内存与虚拟内存、MMU与页表、用户空间与内核空间划分
大家好,我是你们的老朋友。今天咱们来聊聊Linux内存管理这个“大块头”。说实话,我刚开始接触内核时,这块内容是最让我头疼的。但后来我发现,只要把几个核心概念理清楚,整个内存管理就像一张地图一样清晰。
嗯,咱们先从最基础的说起。
1.1 物理内存 vs 虚拟内存:一个“骗术”的艺术
物理内存,就是板子上焊的那几颗DDR颗粒。你插了4GB,它就是4GB。但问题是,你的程序可能觉得自己能用的内存远不止4GB。这是怎么回事?
说白了,这就是虚拟内存的功劳。每个进程都活在一个“假想世界”里——它以为自己独占着整个地址空间(32位下是4GB,64位下更是大到离谱)。但实际上,它看到的地址都是假的,是虚拟地址。
核心观点:虚拟内存是一种“骗术”,它让每个进程都觉得“我拥有整个内存”。而物理内存,才是那个真正干活儿的“老实人”。
我在项目中遇到过这样一个场景:一个嵌入式设备只有256MB内存,但我们要同时跑三个大型应用。每个应用都觉得自己有1GB空间可用。为什么能跑起来?因为虚拟内存让它们“共享”了那256MB物理内存,而且通过换页机制,把不常用的数据暂时挪到Flash上。
1.2 MMU与页表:虚拟到物理的“翻译官”
虚拟地址怎么变成物理地址?这就要靠MMU(内存管理单元)了。MMU是CPU内部的一个硬件模块,它负责把虚拟地址“翻译”成物理地址。
翻译的依据是什么?是页表。
页表,你可以把它理解成一张“映射表”。它记录了每个虚拟页面(通常是4KB大小)对应哪个物理页面。举个例子:
虚拟地址 0x1000 → 页表查询 → 物理地址 0x8000
虚拟地址 0x2000 → 页表查询 → 物理地址 0x9000
为什么会这样设计?你想想看,如果没有MMU,所有程序都直接操作物理地址,那一个程序写错了地址,整个系统就崩了。有了MMU,每个进程都有自己的页表,互相隔离,谁也碰不到谁的数据。
避坑指南:我曾经在调试一个DMA驱动时,发现数据总是写到错误的地方。查了两天才发现,是页表映射没更新。DMA用的是物理地址,而驱动里传的是虚拟地址。记住:DMA操作一定要用物理地址!
1.3 用户空间与内核空间:两个世界,一条界限
在Linux中,虚拟地址空间被划分为两部分:用户空间和内核空间。以32位系统为例,通常的划分是:
| 区域 | 地址范围 | 权限 | 用途 |
|---|---|---|---|
| 用户空间 | 0x00000000 - 0xBFFFFFFF | 用户态可读写 | 应用程序代码、数据、堆栈 |
| 内核空间 | 0xC0000000 - 0xFFFFFFFF | 仅内核态可访问 | 内核代码、驱动、页表 |
这个划分有什么意义?说白了,就是“隔离”和“保护”。用户程序不能直接访问内核空间,否则一个bug就能把整个系统搞死。我见过不少新手写驱动时,直接在用户态访问内核地址,结果就是段错误。
嗯,这里要注意:64位系统的划分方式不同,但原理是一样的。内核空间在高地址,用户空间在低地址,中间有一条“红线”隔开。
1.4 知识体系全景图
为了让你更直观地理解这些概念之间的关系,我画了一张图:
这张图把咱们刚才讲的内容串起来了。你看,用户空间和内核空间通过系统调用交互,而MMU和页表则负责把虚拟地址翻译成物理地址。整个体系环环相扣,缺一不可。
1.5 实际开发中的体会
我个人习惯在写驱动时,先画一张内存映射图。把虚拟地址、物理地址、页表关系都标清楚。这样调试起来会省很多时间。
我记得有一次,一个同事在调试网络驱动时,发现数据包总是丢。查了半天,原来是DMA缓冲区分配在了用户空间,而DMA控制器根本访问不到。嗯,这就是没搞清楚“用户空间 vs 内核空间”的后果。
所以,我的建议是:
- 写应用层代码:你只需要关心虚拟地址,不用管物理地址。
- 写内核驱动:你必须时刻清楚,你操作的是虚拟地址还是物理地址。
- 写DMA驱动:记住,DMA只认物理地址。用
dma_alloc_coherent或dma_map_single来获取正确的地址。
警告:千万不要在中断上下文里做复杂的内存操作!我曾经因为在内核中断里调用kmalloc,导致系统死锁。中断上下文只能用GFP_ATOMIC标志,而且分配的内存要尽量小。
好了,这一章的内容就到这里。内存管理是Linux内核的基石,理解透彻了,后面学DMA、设备驱动都会轻松很多。