计组与微机控制
5.9 当前段、逻辑地址与物理地址
5.9 当前段、逻辑地址与物理地址
当前段、逻辑地址与物理地址
前半部分继续讲 8086/8088 的分段管理。
CS:代码段DS:数据段SS:堆栈段ES:附加段
这 4 个段寄存器保存的是对应段的 段基值,也就是段起始地址的高 16 位表示。
1. 当前段最多能容纳多少内容?
书上说:当前段最多可容纳:
64KB 的代码 64KB 的堆栈 128KB 的数据
为什么是这样?因为:
CS 对应一个代码段,最大 64KBSS 对应一个堆栈段,最大 64KBDS 和 ES 可以分别指向两个数据段,所以合起来最多 128KB 数据
注意,DS 和 ES 不一定连续,它们只是两个当前可用的数据段。
所以你可以理解成:CPU 在某一时刻最多直接“拿在手边”的段有 4 个。
代码一个,栈一个,数据可以有两个。
2. 程序超过当前段怎么办?
书上说,如果应用程序比较长,超过了这些容量限制,可以通过修改段寄存器来访问其他段。

图 2.11 里,右边画了很多逻辑段:
A、B、C、D、E、F、G、H、I、J、K ...
但左边 4 个段寄存器当前只指向:
CS → B DS → F SS → I ES → K
所以当前段是 B、F、I、K。
如果程序现在想访问 J 段的数据,而 J 段目前不在 DS 或 ES 中,那怎么办?
就可以把 DS 或 ES 的内容改成 J 段的段基值。
也就是:原来 DS 指向 F,现在让 DS 指向 J
这样 J 就变成当前数据段了。
3. 但段寄存器不能随便乱改
这里要稍微注意:DS、ES 一般比较容易改,因为它们主要指向数据段。
SS 和 CS 就不能随便动。
SS 指向堆栈段,改 SS 时必须马上配合改 SP,否则栈顶就乱了。
CS 指向代码段,普通 MOV CS, AX 这种操作是不允许的。通常要通过远跳转、远调用、中断返回等方式改变 CS。
所以课本这里讲的是原理:如果当前段不够用,就通过改变段寄存器来切换当前段。
二、物理地址和逻辑地址
这一页进入非常重要的一节:逻辑地址与物理地址。
物理地址就是 CPU 真正送到地址总线上的地址。
8086/8088 有 20 根地址线,所以物理地址是 20 位。
范围是:00000H ~ FFFFFH也就是 1MB 空间。
书上说:CPU 和存储器之间的任何信息交换,都使用物理地址。
这句话的意思是:内存芯片本身不认识什么 DS:1000H、CS:IP。
它只认识真正的 20 位地址,比如:
12345H 2D3H FFFFFH
2. 逻辑地址是什么?
逻辑地址是程序员使用的地址形式。8086/8088 里,逻辑地址由两部分组成:段基值 : 偏移量
常写成:段地址:偏移地址
比如:0915H:003AH
其中:0915H 是段地址,003AH 是偏移量
注意,逻辑地址不是最终送到内存的地址。
CPU 的 BIU 会把逻辑地址转换成物理地址。
3. 逻辑地址转物理地址
转换公式是:
物理地址 = 段地址 × 10H + 偏移地址
也就是:物理地址 = 段地址左移 4 位 + 偏移地址
段地址 = 0915H偏移量 = 003AH
先把段地址左移 4 位:0915H → 09150H
再加偏移量:09150H + 003AH = 0918AH
所以:0915H:003AH 对应物理地址 0918AH
以 BP 为基址:默认 SS
如果有效地址里用了 BP,比如:
MOV AX, [BP] MOV AX, [BP+4] MOV AX, [BP+SI]
默认段寄存器是:SS
因为 BP 通常用于访问栈中的参数或局部数据。
所以:MOV AX, [BP]
默认不是 DS:BP,而是:SS:BP
一般变量:默认 DS
除了源串、目的串、BP 寻址这些特殊情况,大多数普通变量访问默认使用:DS
例如:
MOV AX, [BX] MOV AX, [SI] MOV AX, [2000H] MOV AX, [BX+SI+10H]
默认段基址一般来自 DS。
所以普通数据访问通常是:DS:有效地址

有效地址 EA?表 2.3 里有一个词:
有效地址 EA,Effective Address
EA 本质上就是 偏移地址。它不是完整物理地址。
它是 CPU 根据寻址方式算出来的段内偏移量。
例如:MOV AX, [BX+SI+10H]
如果:BX = 1000H SI = 0020H位移量 = 0010H
那么:
EA = BX + SI + 10HEA = 1000H + 0020H + 0010HEA = 1030H
如果这条指令默认用 DS,那么物理地址就是:
DS × 10H + EA
所以要区分:EA:段内偏移量,物理地址:段基址 + EA
接下来进入:
Intel 8086/8088 CPU 对堆栈的设置与操作。
这部分非常重要,后面学 CALL、RET、中断都会用到。
一、堆栈是干什么的?(前面讲过)
书上说堆栈主要用于:
暂存数据,现场保护过程调用时保存返回地址,中断处理时保存断点信息
可以把堆栈理解成程序运行时的一块临时存储区。
比如调用子程序时,CPU 要记住:
“我执行完子程序以后要回到哪里?”
这个返回地址就会暂存在栈里。
发生中断时,也要保存当前执行位置和标志状态,这样中断处理完才能回到原来的程序继续执行。
SS 和 SP 如何指定堆栈?
8086/8088 中,堆栈由:
SS:堆栈段寄存器SP:堆栈指针
共同指定。
SS 指出堆栈段在哪里SP 指向当前栈顶的偏移地址
所以当前栈顶物理地址是:SS × 10H + SP
写成逻辑地址就是:SS:SP
空栈时 SP 指向哪里?
书上说,SP 初始化时,它的值就是堆栈深度,这时它指向栈底 + 1 单元。
这句话看起来有点绕,我们用例子理解。
假设给堆栈分配 100H 个字节,偏移范围大概是:
0000H ~ 00FFH
那么栈底在最高地址附近:00FFH
空栈时还没有数据,SP 可以初始化为:0100H
也就是栈底再往下一格的位置。
第一次 PUSH 时,8086 会先让 SP 减 2:
SP = 0100H - 2 = 00FEH
然后把一个字存入:SS:00FEH 和 SS:00FFH
所以第一次压入的数据正好放在栈底附近。
8086/8088 堆栈按“字”组织
8086/8088 的堆栈是按字组织的。
意思是:每次 PUSH/POP 的基本单位是 1 个字1 个字 = 2 个字节所以压栈一次,SP 变化 2。
出栈一次,SP 也变化 2。
压入 1234H 怎么存?

如果把一个字:1234H
压入堆栈,内存中存放方式仍然是小端:
低地址:34H高地址:12H
图上类似:
09154H:34H 数据低 8 位09155H:12H 数据高 8 位

所以栈里虽然按字操作,但实际内存仍然按字节存放。
PUSH 操作到底做了什么?
假设:
SS = 1000HSP = 0100HAX = 1234H
执行:PUSH AX
8086 的动作是:
第一步,SP 减 2:
SP = 00FEH
第二步,把 AX 写入 SS:SP 开始的两个字节:
SS:00FEH 放低字节 34HSS:00FFH 放高字节 12H
物理地址分别是:
1000H × 10H + 00FEH = 100FEH1000H × 10H + 00FFH = 100FFH
所以 PUSH 的规律是:
先 SP = SP - 2再把数据写入 SS:SP
八POP 操作到底做了什么?
继续上面的例子。
如果执行:
POP BX
CPU 会:
第一步,从 SS:SP 指向的位置取一个字:
低字节来自 SS:00FEH = 34H高字节来自 SS:00FFH = 12H
组合成:1234H
送入 BX:BX = 1234H
第二步,SP 加 2:SP = 0100H
所以 POP 的规律是:先从 SS:SP 取数据,再 SP = SP + 2
PUSH 和 POP 总结
PUSH:SP 先减 2,再存入一个字POP:先取出一个字,再 SP 加 2
栈增长方向:
压栈:向低地址增长出栈:向高地址回退
这个是后面学 CALL、RET、中断时的基础。
修改 SS 时为什么必须紧接着修改 SP?
第 26 页还说,如果要更换堆栈区,可以重新设置 SS,但每次更换 SS 后必须紧接着给 SP 赋新值。
原因是:SS 和 SP 是一对。
SS 决定栈在哪个段SP 决定栈顶在这个段内的位置
如果你只改 SS,不改 SP,那么 CPU 会把旧的 SP 当成新栈段里的偏移地址。
这可能会导致栈顶指到错误位置,后面的 PUSH、POP、CALL、RET 都会乱。
所以正确思路是:
MOV AX, 新堆栈段MOV SS, AX MOV SP, 新栈顶偏移
核心原则:换栈时,SS 和 SP 必须配套设置。
