03_内存体系
一、 内存架构演进与物理模型
| 时代/架构 | 运行模式 | 寻址机制与物理计算公式 | 核心特征 |
|---|---|---|---|
| 16位 (DOS/8086) | 实模式 Real Mode | 物理分段机制。物理地址 = 段寄存器 \(\times\) 16 + 偏移量 (16位) | 物理寻址上限 1MB。没有任何内存保护,程序可随意覆写系统内存。 |
| 32位 (x86) | 保护模式 Protected Mode | 平坦模型 + 分页机制。 虚拟地址 \(\rightarrow\) 页目录 \(\rightarrow\) 页表 \(\rightarrow\) 物理地址 |
引入 4GB 虚拟空间,段基址通常设为 0,段机制退化。引入 Ring 0-3 权限保护。 |
| 64位 (x64) | 长模式 Long Mode | 平坦模型 + 多级页表。CS/DS/ES/SS 段基址强制为 0。 | 支持 48位/57位 寻址。强制使用 RIP 相对寻址 (PIC)。保留 FS/GS。 |
寻址极限的物理根源:早期的 Intel 8086 处理器只有 20 根地址总线,最多只能组合出 \(2^{20}\) 种地址,即 1MB 的上限。实模式正是为了用 16 位寄存器控制 20 位地址线而设计的拼接方案。
二、 基础数据存取:大小端序与内存表现
在 x86 架构中,数据在内存中的存放严格遵循小端序 (Little-Endian):
-
核心规则:数据的低字节存放在内存的低地址,高字节存放在内存的高地址。
-
寄存器到内存的存取示例:
假设执行
MOV WORD PTR [1000h], 1234h:-
1234h的低字节是34h,高字节是12h。 -
内存布局为:地址
1000h存放34h,地址1001h存放12h。 -
读取时,CPU 会自动倒序组合这两个字节恢复为
1234h。
-
三、 16位实模式:DOS 程序的加载与内存布局
在 DOS 环境下,程序的加载、段的分配以及寄存器的初始化有一套严格的流程。
1. PSP (程序段前缀) 与 DS/ES 的初始化
当 DOS 运行一个程序时,会在程序真实的“首段”之前,强行分配一块大小为 100h (256字节) 的内存,称为 PSP (Program Segment Prefix,程序段前缀)。
-
PSP 的作用:存放与当前程序运行相关的信息。例如,命令行参数被保存在这里:
byte ptr psp:[80h]保存了命令行参数的长度,psp:81h起保存了命令行参数的内容。 -
段寄存器初始值:程序刚开始运行时,DOS 会把 DS 和 ES 赋值为首段的段地址减去
10h(即首段地址 - 10h)。这个地址其实就是 PSP 的段地址(因为10h个段落恰好是 \(16 \times 16 = 256\) 字节 =100h字节)。
2. 段的对齐、拉伸与相邻关系
-
对齐约束:段地址计算时被硬性乘以 16,因此段的物理起始地址必须是 16 的倍数(底层称为一个“节”或 Paragraph)。
-
段的拉伸 (Padding):如果源程序中定义的一个段大小不是 16 的整数倍,DOS 在加载时会向上取整拉伸到 16 的倍数,以保证下一个段的起始地址依然对齐。
3. CS:IP 与 SS:SP 的初始化
-
CS 与 IP:
cs被初始化为代码段的段地址,ip被初始化为程序入口点 (main) 的偏移地址。 -
SS 与 SP:
ss被初始化为堆栈段的段地址,sp被初始化为堆栈的长度。
四、 堆栈机制的深层剖析
堆栈是向下生长的内存区域(从高地址向低地址延伸)。
1. 堆栈的起点、终点与 SP 的关系
-
空栈状态:当堆栈为空时,
SP指向堆栈段的最高地址加 1(即栈底)。例如定义了100h大小的栈,初始SP = 100h。 -
入栈 (
PUSH):先执行SP = SP - 2,然后将字数据写入SS:SP指向的地址。此时SP指向栈顶元素。 -
SP = 0的特殊含义:如果SP初始化为 0,第一次PUSH时,SP - 2会发生 16 位整数下溢,变成FFFEh。这意味着堆栈实际上利用了整个 64KB 的段空间(从FFFFh一直向下生长到0000h)。
2. 无显式堆栈操作时堆栈段的意义
即使程序员在代码中没有任何显式的 PUSH/POP 或 CALL 操作,堆栈段也是不可或缺的。
-
始终存在的隐式压栈:计算机底层运行依赖于中断机制(如每秒 18.2 次的时钟中断、键盘中断等)。当中断发生时,CPU 必须强制将当前的
FLAGS、CS和IP压入堆栈。如果没有有效的堆栈段,一旦发生哪怕是最基础的系统时钟脉冲,程序也会立刻崩溃。 -
默认堆栈分配:若源程序中没有定义堆栈段,DOS 会给该程序默认分配一个堆栈段。此时
ss等于首段的段地址,sp = 0。 -
内存布局示例:假设
data段基址为1000h、长度10h,code段长度20h。-
1000:0000 ~ 1000:000F:数据段 -
1000:0010 ~ 1000:002F:代码段 -
1000:0030 ~ 1000:FFFF:空闲区域,被用作隐式分配的堆栈空间。 -
甚至
2000:0000 ~ 9000:FFFF也属于当前程序分配的可用范围。
-
五、 内存寻址模式及其约束
寻址机制决定了 CPU 如何通过指令定位到具体的内存字节。
1. 16位间接寻址与隐式段规则
在 16 位架构下,方括号 [] 内的间接寻址有极其严格的格式要求,基址寄存器仅限 bx 或 bp,变址寄存器仅限 si 或 di。它共有 4 种形式:单寄存器、寄存器+常数、基址+变址、基址+变址+常数。
隐式段地址规则(极度重要):
如果没有显式加段前缀,CPU 会自动决定段寄存器:
-
包含
BP-> 默认SS:当[]内有bp时,变量默认的段地址是SS(堆栈段)。这是高级语言通过BP访问栈帧内局部变量的底层逻辑。 -
不含
BP-> 默认DS:当[]内没有bp时,变量默认的段地址是DS(数据段)。
ES 的作用:
ES (Extra Segment) 是附加数据段。除用于显式段超越前缀(如 MOV AX, ES:[BX])外,它被硬件级绑定为字符串/块操作指令(如 MOVSB、STOSB 等)的强制目标段,配合 DI 寄存器使用。
2. 32/64位通用寻址:SIB 机制
进入 32 位平坦时代,指令通过 SIB (Scale-Index-Base) 字节计算有效地址:
EA = Base + (Index * Scale) + Displacement
Scale可选 1, 2, 4, 8,完美对应数组元素的字节大小。
3. 64位专属:RIP 相对寻址
64 位架构强制使用 RIP 相对寻址,指令不硬编码绝对地址,而是编码相对于当前 RIP (指令指针) 的偏移量(如 MOV RAX, [RIP + 0x2A00])。这是现代操作系统实现位置无关代码 (PIC) 与地址随机化 (ASLR) 的基石。普通段寄存器寻址彻底失效,仅保留 FS/GS 指向线程局部存储 (TLS) 或 Canary 防御标志。