1. 内存寻址的演变:从实模式到保护模式
大家好,我是你们的讲师。今天咱们聊聊内存寻址的演变史。说实话,我刚开始学内核时,这块内容绕得我头晕。后来在项目中踩过几次坑,才真正理解——为什么要有分段?为什么又搞出分页?
说白了,内存寻址的演变,就是计算机从「能用」到「好用」再到「安全」的过程。咱们一步步来看。
1.1 实模式:最原始的寻址方式
8086处理器时代,内存只有1MB。那时候的寻址方式叫「实模式」。怎么寻址呢?
CPU用段寄存器 + 偏移地址,拼出物理地址。公式很简单:
物理地址 = 段基址 × 16 + 偏移地址
举个例子,段基址是0x1000,偏移是0x2000,那物理地址就是:
0x1000 × 16 + 0x2000 = 0x10000 + 0x2000 = 0x12000
嗯,这里要注意:段基址是16位的,但左移4位后变成20位。所以最大寻址范围是1MB(2^20)。
实模式的特点:
- 程序可以直接访问物理内存
- 没有保护机制,任何程序都能读写任意地址
- 段最大64KB(因为偏移地址是16位)
- 地址空间只有1MB
我在项目中遇到过一个问题:一个老旧的嵌入式系统,用的还是实模式。某个驱动写错了地址,直接把系统关键数据覆盖了。结果呢?系统直接崩溃,连日志都没来得及写。这就是实模式的致命伤——没有保护。
1.2 为什么需要保护模式?
你想想看,随着软件越来越复杂,多个程序同时运行成了刚需。但实模式下,程序A可以随意读写程序B的内存。这能忍?
我刚开始做系统编程时,觉得「保护」这个概念有点多余。直到有一次,我写了一个内存测试程序,不小心把操作系统的内核数据覆盖了。屏幕一黑,我整个人都傻了。从那以后,我再也不敢轻视内存保护。
保护模式要解决三个核心问题:
- 隔离性:程序之间不能互相干扰
- 权限控制:内核和用户程序要有不同权限
- 更大的地址空间:1MB显然不够用了
1.3 分段机制的引入
保护模式引入了分段机制。但和实模式的分段完全不同。
实模式的分段,说白了就是拼地址。保护模式的分段,核心是「描述符」和「权限检查」。
每个段由一个段描述符定义,包含:
- 段基址(32位)
- 段界限(20位)
- 权限级别(DPL,0~3)
- 类型(代码段、数据段、系统段等)
段描述符存放在全局描述符表(GDT)或局部描述符表(LDT)中。段寄存器里存的不是段基址,而是段选择子——指向GDT或LDT中的某个描述符。
我个人习惯:在调试内核时,经常用gdb查看GDT的内容。你可以用info registers看段寄存器的值,再用p/x *(struct desc_struct *)gdt_addr解析描述符。这招排查段错误特别好用。
分段机制带来的好处很明显:
- 每个段可以独立设置权限
- 段内偏移可以到4GB(32位)
- 非法访问会被CPU捕获,触发异常
1.4 分页机制的引入
分段解决了权限和隔离问题,但还有一个痛点:内存碎片。
你想想看,程序加载、卸载、再加载,内存会变得支离破碎。分段机制下,段的大小不固定,时间长了,空闲内存虽然总量够,但都是小碎片,大程序加载不了。
分页机制就是来解决这个问题的。
分页把内存分成固定大小的页(通常是4KB)。程序看到的是连续的虚拟地址,但物理内存可以是分散的页。这就是虚拟内存的核心思想。
我举个例子:
虚拟地址:0x00001000 ~ 0x00001FFF
物理地址:可能分布在 0x1000、0x5000、0xA000 等不同位置
分页机制通过页表来完成地址转换。每个进程有自己的页表,互不干扰。
分页的优势:
- 消除外部碎片
- 支持虚拟内存(可以比物理内存大)
- 按需加载(只加载用到的页)
- 页级权限控制(比段级更精细)
1.5 分段 vs 分页:谁更重要?
这个问题我经常被问到。说实话,在x86架构下,分段和分页是共存的。但实际使用中,分页的地位远高于分段。
为什么?
Linux内核的做法是:把段基址设为0,段界限设为4GB。这样分段就「透明化」了,所有地址转换都交给分页。这叫「平坦内存模型」。
我在项目中调试过一个性能问题:某次内核升级后,内存访问变慢了。排查了半天,发现是段描述符的配置有问题,导致每次内存访问都多了一次权限检查。嗯,这种坑不踩一次真的记不住。
我曾经踩过的坑:在写内核模块时,直接操作物理地址。但忘了分页机制下,内核看到的也是虚拟地址。结果写错了地方,把别的进程数据覆盖了。记住:在内核态,也要用kmap或ioremap来获取正确的虚拟地址。
1.6 知识体系总览
下面这张图,是我自己总结的内存寻址演变脉络。建议你保存下来,后面学内存管理时经常回来看看。
1.7 小结
好了,这一章的内容就到这里。总结一下:
- 实模式是起点,简单但危险
- 保护模式引入分段,解决了权限和隔离问题
- 分页机制进一步解决了碎片问题,并支持虚拟内存
- 现代Linux以分页为主,分段基本透明化
下一章,咱们深入看看分段机制的具体实现——GDT、LDT、段描述符到底是怎么工作的。到时候我会分享一些调试段错误的实战经验,保证让你少走弯路。