功能和结构
CPU
组成结构
CPU 包含 数据通路 和 控制器。
数据通路(Datapath)是指令执行过程中,数据所经过的路径,包括路径中的部件。它是指令的 执行部件。
控制器(Control)对指令进行解码,生成对应的控制信号,控制数据通路的动作。能够执行指令发出控制信号,是指令的 控制部件。
数据通路
CPU的数据通路是指CPU内部数据流动的路径,它决定了数据在CPU的各个功能部件之间如何传输和处理。简单来说,数据通路就是CPU中数据“流动”的“高速公路”。
数据通路由以下几个部分组成:
- 寄存器:临时存储数据和指令。
- 算数逻辑单元(ALU):执行算术和逻辑运算。
- 多路选择器(MUX):选择数据源,将特定的数据传输到目的地。
- 总线:连接各个功能部件,传输数据的通道。
控制器
控制器是计算机的指挥中心,它负责协调和控制计算机的所有操作。 控制器的功能和组成详见 该节。
8086
CPU 的内部结构由 BIU 和 EU 两部分构成,简单而言,BIU 负责读指令,EU 负责执行指令。两个组成部分的具体功能如下:
- BIU(Bus Interface Unit,总线接口单元):
- 主要功能
- 从内存中取指令,并将其存储在指令队列中。
- 计算存储器地址,并控制总线进行数据传输。
- 组成:
- 指令队列:用于存储从内存中预取的指令。
- 特殊寄存器
- 指令指针寄存器(IP, Instruction Pointer):存储下一条要执行的指令地址。
- 指令寄存器(IR, Instruction Register),存储当前正在执行的指令
- 内存地址寄存器(MAR, Memory Address Register),存储内存需要读取的地址
- 内存数据寄存器(MDR, Memory Data Register),存储内存读取的数据单元
- 段寄存器(Segment Registers)
- 主要功能
- EU(Execution Unit,执行单元):
- 主要功能:
- 从指令队列取出指令,进行译码。
- 执行指令,包含算术运算、数据传输等。
- 组成:
- 算术逻辑单元(ALU),用于执行算术和逻辑运算,例如加法、减法、逻辑与、逻辑或等。
- 通用寄存器: 包括 AX、BX、CX、DX、SI、DI、BP 和 SP 等寄存器,用于存储数据和地址。
- 控制单元(CU, Control Unit),负责取指令和解码指令、生成控制信号、协调 CPU 中指令的执行过程。
- 主要功能:
BIU 和 EU 并行工作,以此提高 CPU 的效率。例如,当 EU 执行一条指令时,BIU 可以同时从内存中预取下一条指令,并将其存储在指令队列中。
寄存器
寄存器类型
这里为了方便大家对寄存器从概念进行分类,下面使用 8086 的寄存器进行说明,总的来说,寄存器可以为分为 通用寄存器、段寄存器、指针寄存器 以及 附加寄存器:
- 通用寄存器:用于存储任意的地址或者数据。
AX
寄存器:累加器(Accumulator),用于执行算术和逻辑运算。BX
寄存器:基址寄存器(Base Register),通常用于存储内存地址。CX
寄存器:计数寄存器(Counter Register),用于循环计数和移位操作。DX
寄存器:数据寄存器(Data Register),用于输入/输出操作和大整数运算。
- 段寄存器:用于存储内存段的起始地址。
CS (Code Segment)
寄存器:代码段寄存器,存储指向代码段的地址。DS (Data Segment)
寄存器:数据段寄存器,存储指向数据段的地址。ES (Extra Segment)
寄存器:附加数据段寄存器,通常用于数据访问。SS (Stack Segment)
寄存器:堆栈段寄存器,存储指向堆栈段的地址。
- 变址寄存器:用于支持变址寻址模式,和数组和指针操作相关。
SI
寄存器:源变址寄存器,通常用于数据传送操作。DI
寄存器:目的变址寄存器,也通常用于数据传送操作。
- 指针寄存器:
SP (Stack Pointer)
寄存器:堆栈指针寄存器,指向函数栈的顶部。BP (Base Pointer)
寄存器:堆栈基址寄存器,指向函数栈的底部。IP
寄存器:指令指针寄存器,存储当前执行指令的偏移地址。
- 标志寄存器:
FLAGS
寄存器:标志寄存器,存储有关条件和状态的信息,例如进位、零标志、溢出等。
注意
哪些寄存器是程序员可见的:
有在 8086 仿真器上编写过汇编程序的人会知道,可见的寄存器就是你编写汇编程序时可以直接通过指令进行控制的寄存器。
可见的寄存器包括:通用寄存器、段寄存器、标志寄存器(Flags)和指令指针寄存器(IP)。
当然更为方便地是记住哪些寄存器是不可见的:MAR、MDR、IR。
段寄存器
段寄存器指向 程序调用时的内存结构中不同段的起始位置:
- CS 指向代码段(.text)的起始位置
- SS 指向栈段(User Stack)的起始位置
- DS 指向数据段(.data)的起始位置
- ES 指向附加段(Extra Segment)的起始位置
下图包含程序内存结构中所包含的不同段,
当然,上图是现代 linux 程序的内存结构,对于运行在 8086 上的程序而言,其内存结构更加简单,但其中的逻辑结构是类似的:
从逻辑上来说,程序的执行需要四个段:
- 代码段:存储编译后程序指令的地方
- 数据段:全局数据
- 栈段:函数嵌套调用的发生场所
- 额外段:提供一些灵活性,供程序员发挥
不同的段寄存器与不同的段相关联,指向相关段的起始地址。
指针寄存器
对于有些段来说,仅仅知道其起始地址是不足够的,在程序执行过程中,需要一些额外的寄存器来实现我们需要的操作,指针寄存器主要操作 栈段 和 代码段。
SP,BP 栈指针寄存器
函数在执行过程中需要保存的数据与栈类似,具有先进后出的特点。
函数栈从高地址向低地址增长,嵌套调用的函数所对应的函数栈 在栈段上不断堆叠。如果我们在 main()
主函数中嵌套调用 f(g(h(1)))
,那么该程序对应的栈段对应的逻辑结构如下所示:
----------------- ← SS
main 的函数栈
-----------------
f 的函数栈
-----------------
g 的函数栈
----------------- ← BP (指向函数 h 的栈底)
上一个函数的BP
函数参数
局部变量 ← SP(指向函数 h 的栈顶)
其中 BP 指向最后一个调用的函数栈的底部,SP 指向最后一个调用的函数栈的顶部,通过 BP 和 SP 我们可以对最后一个调用的函数进行操作,具体可以参考 函数调用时的内存结构。
IP 指令指针寄存器
指令在代码段(.text 段)中是从低地址向高地址增长的,这种增长方式也符合 程序计数器(PC 或 IP)的增长逻辑:在 指令执行的取指阶段,控制单元在完成取指后会控制 IP = IP + 指令长度,这样 IP 就指向了下一条指令的地址。
-----------------
代码段高地址
-----------------
后续指令 ← 取指后 IP 指向该位置
-----------------
当前指令 ← IP 当前位置
-----------------
前一指令
-----------------
代码段低地址
变址寄存器
变址寄存器主要用于实现变址寻址模式,方便对数组、字符串等数据结构中的 第i个元素 进行操作。8086 中的主要变址寄存器是 SI(源变址寄存器)和DI(目的变址寄存器),下文分别从 数组和字符串操作 两个例子说明一下:
数组操作
以下汇编代码段通过一个循环操作实现了对于数组中的前五个元素进行操作。注意在 8086 中,我们是在 CX 中保存 loop 的轮数,每次 LOOP 被调用后 CX 的值自动被减一。
; 假设DS指向数据段并且数组从数据段开始
MOV DS, addr ; 将数组的起始地址保存到DS中
MOV CX, 5 ; CX用作循环计数器,假设数组有5个元素
MOV SI, 0 ; SI作为索引寄存器,初始化为0
NEXT_ELEMENT:
MOV AX, [SI] ; 从数组当前元素读取到AX
; 对AX中的数据进行处理
ADD SI, 2 ; 移动到下一个元素(假设每个元素2字节)
LOOP NEXT_ELEMENT ; 循环直到CX为0
字符串操作
以下汇编代码将一个字符串的前 length 个字符复制到另一个字符串中。
; 假设DS和ES已经分别指向源和目的数据段
MOV SI, OFFSET source ; SI指向源字符串的起始位置
MOV DI, OFFSET dest ; DI指向目的字符串的起始位置
CLD ; 清除方向标志位,确保字符串操作从低地址到高地址
MOV CX, length ; CX初始化为字符串的长度
REP MOVSB ; 复制CX个字节从DS:SI到ES:DI
标志寄存器
标志寄存器(Flags)用于存储和反映处理器在执行指令过程中产生的各种状态和条件,这些状态可以进一步被 控制器 所用,控制指令执行的行为。
标志寄存器中的不同位用于标记某个特殊的状态,8086 中的标志寄存器是一个 16 位的寄存器,其中 9 个标志位被使用,其他 7 个标志位没有含义。
这些标志位在逻辑上可以被分为 条件标志(conditional flags)和 控制标志(control flags)两种:
条件标志
条件标志用于标记指令执行后的结果状态,用于影响程序的控制流,条件标志包含以下几种:
- OF (Overflow flag):溢出标志。
- 当有符号整数运算的结果太大而无法适应目标寄存器时,OF 标志会设置为 1,表示发生了溢出。
- SF (Sign flag):符号标志。
- 根据操作结果的符号位来设置,如果结果为负数,则 SF 被设置为 1,否则为 0。
- ZF (Zero flag):零标志。
- 当操作结果为零时,ZF 标志被设置为 1,否则为 0。
- AF (Auxiliary carry flag):辅助进位标志。
- 通常用于 BCD(二进制编码十进制)算术运算,指示低四位的进位。
- PF (Parityh flag):奇偶校验标志。
- 根据结果中二进制位 1 的个数是奇数还是偶数,设置 PF 标志。奇数个 1 则 PF 为 1,偶数个 1 则 PF 为 0。
- CF (Carry flag):进位标志。
- 当无符号整数运算的结果超出了目标寄存器的位数,CF 标志被设置为 1,表示发生了进位。
条件标志不需要 程序员 手动通过指令设置,当算数和逻辑指令(Add 和 AND 等) 和 比较指令(CMP)被执行时,相关的标志位会被自动设置。
注意
OF 标志位是如何被设置的?
对于两个 n 位数字的计算指令,只需要判断最高位是否有进位即可,如果有进位,OF 被设置为 1,否则被设置0。
这是一种可行的方法,但在考试中常考察的是另一种方式,假设我们进行 $A$ 和 $B$ 的计算加法计算,两个数字都包含 $n$ 位,那么其计算结果可能包含以下几种情况:
- $A$ 和 $B$ 中一个为负数,一个为正数,那么 $A+B$ 一定不会溢出。
- $A$ 和 $B$ 中任意一个为 0,那么 $A+B$ 一定不会溢出。
- $A$ 和 $B$ 都是正数
- $A+B$ 的结果为负数,加法溢出
- $A+B$ 的结果为正数,加法无溢出
- $A$ 和 $B$ 都是负数
- $A+B$ 的结果为正数,加法溢出
- $A+B$ 的结果为负数,加法无溢出
令 $C=A+B$,则可以通过如下公式计算 OF:
$$\text{OF} = A_{n-1} \cdot B_{n-1} \cdot \overline{C_{n-1}} + \overline{A_{n-1}} \cdot \overline{B_{n-1}} \cdot C_{n-1}$$
对于减法指令,可以将其转化为等价的加法指令,进行同样逻辑的判断。
控制标志
控制标志并不会被通常的运算指令自动修改,而是通过专门的指令来进行设置或清除。对于以下字段,了解即可:
- IF (Interrupt flag)
- 控制中断处理
- 当 IF 被设置为 1 时,CPU 允许中断请求。如果 IF 为 0,CPU 将禁止所有中断请求,无论是外部硬件中断还是软件中断。
- TF (Trap flag)
- 控制单步执行。
- 当 TF 被设置为 1 时,CPU 将进入单步执行模式。在单步执行模式下,每执行一条指令后,CPU 将引发一个单步中断,允许程序员逐条调试程序。
- DF (Direction flag)
- 字符串操作的标志位。
- 当 DF 被设置为 1 时,字符串操作(如 MOVS、LODS、STOS)在内存中向高地址方向移动。这通常用于从高地址向低地址扫描字符串。当 DF 被清除为 0 时,字符串操作在内存中向低地址方向移动。这通常用于从低地址向高地址扫描字符串。