Skip to content

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 防御标志。