变了什么
你在 8086 学的是实模式:程序直接跑在硬件上,独享 1MB 物理内存,想访问哪个地址就访问哪个。而 x86-32 引入了保护模式,带来一系列根本变化:
| 对比项 | 8086 实模式(你已会) | x86-32 保护模式(新) |
|---|---|---|
| 位数 / 寄存器 | 16 位 · AX BX SP IP | 32 位 · EAX EBX ESP EIP(AX 是 EAX 的低 16 位) |
| 地址空间 | 1MB(20 位地址) | 4GB(32 位地址) |
| 寻址方式 | 段:偏移 → 物理地址 | 平坦模型 + 分页 → 虚拟地址 |
| 看到的内存 | 直接使用物理内存,大家共用 | 每个进程独立的虚拟内存 |
| 内存保护 | 无,可碰任何内存 / 硬件 | 有,特权级 + 进程隔离 |
| 典型环境 | DOS 单任务 | Windows / Linux 多任务 |
因为计算机进入了多任务时代——一台机器要同时跑很多程序,就必须解决三件事:① 互相隔离(一个程序崩溃别拖累别人);② 物理内存不够时能"变出"内存来用;③ 防止越权(普通程序不许乱碰系统和他人的内存)。保护模式 + 虚拟内存 + 分页,就是为解决这三件事而生的。
分段 → 平坦
8086 之所以用"段:偏移",是因为 16 位寄存器最大只能表示 64KB,凑不出 1MB,只好用"段 × 16 + 偏移"拼出 20 位地址。到了 32 位,一个偏移寄存器就能表示 4GB,这套"拼图"就没必要了。先动手回顾一下你已经会的:
- 段寄存器还在(CS / DS / SS…),但操作系统把它们的基址全设为 0、界限设为 4GB
- 于是:线性地址 = 0 + 偏移 = 偏移本身
- 分段"名存实亡",你只需面对一个连续、平坦的 32 位地址空间
- 不用再算"段 × 16"了
8086 是"两个 16 位数拼出一个 20 位地址";x86-32 是"基址固定为 0,一个 32 位偏移就是地址"。你写的地址(如 0x00401000)直接就是线性地址——但它还不是物理地址,下一章揭晓。
虚拟内存与进程
8086 时代,所有程序和 DOS 共用同一块 1MB 物理内存,谁都能看谁都能改——所以一个程序写错地址,常常整机死机。x86-32 的做法是:给每个进程发一个独立的、私有的 4GB"虚拟地址空间",彼此隔离。
看下图:进程 A 和进程 B 都使用虚拟地址 0x00400000,但经过各自的页表,映射到了物理内存的不同位置——所以它们互不影响:
你在 32 位程序里写下的地址(如 0x00400000)是虚拟地址,不是物理地址。真正的物理位置由操作系统和 CPU 在幕后决定。这样,A 程序崩溃不会踩到 B,也不用担心地址撞车。
每个进程的 4GB 虚拟空间里,低 2GB(0x0 ~ 0x7FFFFFFF)给用户程序用,高 2GB(0x80000000+)是内核区,用户程序碰不到——这就是"保护"。
分页机制
既然程序用的是虚拟地址,CPU 怎么找到真正的物理内存?靠分页和页表。这是从 8086"地址即物理"到 x86"地址要翻译"的最大转变。先建立三个概念:
把虚拟空间和物理内存都切成固定大小(4KB = 0x1000)的小块。虚拟的叫"页",物理的叫"页帧"。
每个进程一张"对照表",记录 虚拟页号 → 物理页帧(或"不在内存")。
虚拟地址 = [虚拟页号 | 页内偏移]。低 12 位是页内偏移,其余是页号。
页表可标"该页不在内存"。访问到就触发缺页中断,OS 从磁盘把它读进 RAM 再继续。所以内存只装"正在用的页"。
以虚拟地址 0x00401002 为例,看 CPU 和操作系统如何一步步把它翻译成物理地址:
和 8086 对比着记
8086 是"查一次加法"(段×16+偏移);x86-32 是"查一次表"(页号→页帧)再加偏移。多出来的这张"页表",正是虚拟内存、进程隔离、按需调页的总开关。
内存映射
有了"虚拟内存 + 分页 + 按需调页",你就能理解 EXE 和 DLL 到底是怎么加载的了——靠的是内存映射文件。
"EXE 加载到 0x00400000、DLL 加载到 0x7C800000",说的都是虚拟地址;它们的物理页是按需调入的,而且同一份系统 DLL 的物理页还能在多个进程间共享——省内存。
这正是《DLL 与 EXE 的关系》里"映射 DLL 到进程地址空间"的底层机制。概念已经补齐,可以去那里看 EXE 与 DLL 如何协同工作了。
自测一下
这 6 道题检验你是否完成了从 8086 到 x86-32 的概念跨越。 尚未作答