本节摘要:拆开 8086 的内部结构——总线接口单元与执行单元的分工、三组寄存器各自的职责、标志位如何记录运算的心跳,为后续时序与编程两节备好演员表。
本章主线的第一站。要看懂一条指令的一生,先得认识它依赖的所有道具:指令在哪里排队、操作数暂存在哪、运算结果的状态由谁登记。这一站把 8086 的内部结构铺开,重点放在"每个部件在指令执行中扮演什么角色",而不是部件参数的罗列。
8086 内部分成两个单元。**总线接口单元(BIU)**负责一切对外的总线事务:算物理地址、发起读写、维持一个六字节的指令队列;**执行单元(EU)**负责真正的指令解释与运算。两者能并行——EU 在执行当前指令时,BIU 已经在预取后面的指令填队列。这个"前台干活、后台备菜"的结构是现代流水线的雏形,也是 8086 比 8080 快的重要原因。
寄存器是 EU 手边的写字台,8086 把它们分成三组:
| 分组 | 成员 | 专用绰号与分工 |
|---|---|---|
| 通用寄存器 | AX BX CX DX | AX 算术累加器,CX 串操作计数,DX 乘除高位与端口寻址 |
| 指针与变址 | SP BP SI DI | SP 栈顶指针,BP 栈帧基址,SI DI 串操作源与目的 |
| 段寄存器 | CS DS SS ES | 代码段、数据段、栈段、附加段基址 |
通用寄存器的"专用绰号"不是装饰——不少指令只认特定寄存器:串操作必须用 CX 计数、IN/OUT 用 DX 间接寻址端口、乘除法默认夹带 AX 或 DX:AX。这是 CISC 时代的烙印,也解释了为什么编译器生成代码时寄存器分配有明显的偏好顺序。另有两个单步可见的指针:IP 指向下一条待取指令,FLAG 登记运算状态。
标志寄存器里最常用的是六个状态位与三个控制位。状态位由每次算术运算自动刷新:CF 记进位(无符号溢出)、ZF 记结果为零、SF 记符号、OF 记有符号溢出、PF 记低八位奇偶、AF 记半字节进位。控制位里 IF 的地位特殊——它决定可屏蔽中断能不能被响应,是第 5 章中断系统的总开关。
看一段汇编,观察标志位如何被连续改写:
MOV AL, 7FH ; AL = 0111 1111 ADD AL, 01H ; AL = 1000 0000 ; ZF=0 SF=1 CF=0 OF=1 ; 无符号视角 127+1=128 没超界,CF=0 ; 有符号视角 127+1=-128,正溢出,OF=1 ADD AL, 80H ; AL = 0000 0000 ; ZF=1 CF=1 OF=0
同一条 ADD 指令,CF 与 OF 却可能一清一置——因为它们服务两种数字观:CF 盯着无符号回卷,OF 盯着有符号溢出。条件跳转指令的书写者必须先想清楚自己把操作数当成什么:JA 看的是 CF 与 ZF,JG 看的是 SF 与 OF,选错就等价于拿体温计量身高。这个细节在接口程序里后果严重:比较外设状态字节时,多数场合该用无符号跳转。
BIU 与 EU 的并行不是无限的。EU 需要访存取操作数时,必须向 BIU 申请总线;若队列已满,BIU 停止预取。真正的瓶颈由此显现:EU 再快,也得等总线事务排期。这解释了 2.2 节的时序细节为何值得逐拍学习——总线周期才是指令执行的真实节拍器。现代处理器的乱序机制把这段等待藏进深队列里,但藏不等于消除,访存等待至今仍是性能账本上最大的一笔支出(呼应 1.3 节的归因结论)。
案例展开。背景:初学者写了一段循环程序处理串口数据,发现循环体逻辑无误,串口状态却时对时错。操作:单步调试,盯着寄存器与标志位走完每条指令;发现循环内用 JLE 判断"状态字节是否小于等于就绪值",而状态字节的最高位是外设自检位,可能为 1。结果:当自检位为 1 时,有符号视角下该字节是负数,JLE 恒成立,循环错误地跳过了就绪判断。解读:又是符号视角错配——状态字节是位模式,不是有符号数,应使用 JBE。变式:凡是从外设读回的字节,一律先问"这是位拼图还是数字",位拼图用逻辑指令与无符号跳转处理,永远不要让编译器或汇编器替你决定符号。
四个通用寄存器各有"拆分"形态:AX 可拆成 AH 与 AL,各管高低八位;BX、CX、DX 同理。这个设计在字长不够用的年代是省指令的妙招——一个字节的处理不必动用整个十六位寄存器。看一段连招:
MOV AX, 1234H ; AX=1234H,于是 AH=12H,AL=34H MOV AH, 0 ; 只清高八位,AL 保持 34H,AX 变 0034H ADD AL, 0CCH ; 低八位相加,34H 加 CCH 结果带进位 ; AL=00H,CF=1,AX 仍为 0000H ADC AH, 0 ; 带进位加法,把 CF 计入高位,AH=1 ; AX=0100H,等价于 16 位加法 34H+CCH
四条指令演完了一场"八位运算拼出十六位结果"的戏——低位加法的进位由 CF 保管,高位用带进位加法接住。这也是为什么循环累加多字节大数时,汇编比 C 更能精确控制每一步的进位去向。动手在纸上或调试器里把每条指令后的 AX、AH、AL、CF 写出来,比读十遍文字都管用。
段寄存器也值得手算一遍。CS 等于 0B34CH、IP 等于 0100H 时,下一条指令的物理地址=0B34C0H 加 0100H=0B35C0H;换一组逻辑地址 0B350H:00C0H,物理地址同样是 0B35C0H——同一个地址的两副面孔又一次应验。考试与调试都爱出这类题,因为手算不过关的人,读实模式程序时永远在迷路。
问:标志位这么多,全部要背吗? 按用途分组记:CF、OF 管溢出(无符号与有符号各一个),ZF、SF 管比较结果,PF、AF 平时几乎不出场。控制位里 IF 与 DF 常用,TF 只在调试器里露脸。用场景记而不是用名单记——写条件跳转时自然会想起该看哪几位。
问:现代处理器的寄存器多了几十倍,这套十六位的故事还有用吗? 有用,而且是理解的前置:现代架构的寄存器约定、标志位语义、现场保护思想,与 8086 一脉相承,只是位宽加宽、数量扩容。看懂了小写字表,大字典只是查得快一点。
寄存器和队列都备好了,下一站把指令放上示波器,看总线周期如何逐拍推进。