← 返回 计组与微机控制

计组与微机控制

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. 程序超过当前段怎么办?

书上说,如果应用程序比较长,超过了这些容量限制,可以通过修改段寄存器来访问其他段。

5.9 当前段、逻辑地址与物理地址 图 1

图 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:有效地址

5.9 当前段、逻辑地址与物理地址 图 2

有效地址 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 怎么存?

5.9 当前段、逻辑地址与物理地址 图 3

如果把一个字:1234H

压入堆栈,内存中存放方式仍然是小端:

低地址:34H高地址:12H

图上类似:

09154H:34H 数据低 8 位09155H:12H 数据高 8 位

5.9 当前段、逻辑地址与物理地址 图 4

所以栈里虽然按字操作,但实际内存仍然按字节存放。

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 必须配套设置。