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的内存。这能忍?

我刚开始做系统编程时,觉得「保护」这个概念有点多余。直到有一次,我写了一个内存测试程序,不小心把操作系统的内核数据覆盖了。屏幕一黑,我整个人都傻了。从那以后,我再也不敢轻视内存保护。

保护模式要解决三个核心问题:

  1. 隔离性:程序之间不能互相干扰
  2. 权限控制:内核和用户程序要有不同权限
  3. 更大的地址空间: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。这样分段就「透明化」了,所有地址转换都交给分页。这叫「平坦内存模型」。

我在项目中调试过一个性能问题:某次内核升级后,内存访问变慢了。排查了半天,发现是段描述符的配置有问题,导致每次内存访问都多了一次权限检查。嗯,这种坑不踩一次真的记不住。

我曾经踩过的坑:在写内核模块时,直接操作物理地址。但忘了分页机制下,内核看到的也是虚拟地址。结果写错了地方,把别的进程数据覆盖了。记住:在内核态,也要用kmapioremap来获取正确的虚拟地址。

1.6 知识体系总览

下面这张图,是我自己总结的内存寻址演变脉络。建议你保存下来,后面学内存管理时经常回来看看。

内存寻址演变脉络图 1 实模式 8086时代 段:偏移 = 物理地址 最大1MB 2 保护模式 80286+ 段描述符 + GDT/LDT 权限检查 3 分段机制 逻辑地址 → 线性地址 段基址 + 段界限 DPL权限控制 4 分页机制 线性地址 → 物理地址 页表 + 页目录 虚拟内存支持 核心演变逻辑 1. 实模式:直接拼地址,无保护,1MB限制 2. 保护模式:引入段描述符,增加权限检查 3. 分段机制:逻辑地址→线性地址,解决隔离问题 4. 分页机制:线性地址→物理地址,解决碎片问题 现代Linux内核: 平坦内存模型(段基址=0,段界限=4GB) 主要依赖分页机制管理内存

1.7 小结

好了,这一章的内容就到这里。总结一下:

  • 实模式是起点,简单但危险
  • 保护模式引入分段,解决了权限和隔离问题
  • 分页机制进一步解决了碎片问题,并支持虚拟内存
  • 现代Linux以分页为主,分段基本透明化

下一章,咱们深入看看分段机制的具体实现——GDT、LDT、段描述符到底是怎么工作的。到时候我会分享一些调试段错误的实战经验,保证让你少走弯路。

专注资料整理