# CPU
## CPU的功能和基本结构
## 指令执行过程
## 数据通路的功能和基本结构
## 控制器的功能和工作原理
## 异常和中断机制
- 基本概念
- 分类
- 检测和响应
## 指令流水线
- 基本概念
- 基本实现
- 结构冒险、数据冒险和控制冒险
- 超标量和动态流水线的基本概念
## 多处理器基本概念
- SISD、SIMD、MIMD、向量处理器的基本概念
- 硬件多线程的概念
- 多核处理器的基本概念
- 共享内存多处理器的概念
## 总线和输入/输出系统
### 总线
- 基本概念
- 组成及性能指标
- 事务和定时
### I/O接口
- 功能和基本结构
- 端口及其编址
### I/O方式
- 程序查询方式
- 程序中断方式
- DMA方式
中央处理器
1 - 功能和结构
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 时,字符串操作在内存中向低地址方向移动。这通常用于从低地址向高地址扫描字符串。
2 - 控制器
主要功能
控制器是计算机系统的指挥中心,控制器的主要功能有:
- 指令解码:CPU 从存储器取出一个指令后,控制器负责解码这个指令,以确定要执行的操作和涉及的操作数。
- 生成控制信号:基于解码的指令,控制器生成一系列的控制信号,这些信号会驱动其他计算机部分(如算术逻辑单元、寄存器和存储器)按预期执行相应的操作。
- 时序控制:通过先后发出不同的控制信号,确保指令的逻辑正确被执行,
根据控制器产生微操作控制信号方式的不同,控制器可以分为硬布线控制器和微程序控制器。
组成
要理解控制器的组成,就要从其功能出发,大体来说,控制器的组成也可以分为三个组件:
- 指令控制器:负责取指和译码,并且形成下一条指令的地址。
- 时序控制器:产生计算机运行所需的各种时序信号。控制指令的执行节奏,确保各个部件协调工作。
- 控制信号发生器:根据指令译码器的输出,产生具体的控制信号。这些控制信号用于控制计算机各个部件的微操作。
指令控制器
指令控制器由 CS:IP 获取当前要执行的指令地址。 其中 CS 为程序段寄存器,标记了进程代码段(.code)的开始,IP 为指令指针寄存器,标记了当前指令在代码段内的偏移。
获取当前指令地址后,输入输出控制电路从内存中读取指令放入指令寄存器中。 然后修改 IP 寄存器的值,使 CS:IP 指向下一条指令的地址。
时序控制器
时序控制器的作用是为每条指令按时间顺序提供控制信号。时序控制器包括时钟发生器和倍频定义单元,其中时钟发生器由石英晶体振荡器发出非常稳定的脉冲信号,就是 CPU 的主频;而倍频定义单元则定义了 CPU 主频是存储器频率(总线频率)的几倍。
晶体振荡器利用石英晶体的压电效应,当施加电压时,晶体在特定频率下可以产生稳定的振荡。 晶体振荡器产生精确的频率信号,作为电子设备的时钟源。这个时钟信号就像一个“节拍器”,控制着设备内部各个部件的协调运作。
晶体振荡器产生的频率信号直接决定了电子设备的时钟周期。一个指令周期中包含 取指令 和 执行指令 这两个 CPU 周期,而 CPU 周期又由多个时钟周期组成。
假设当前要执行 MOV R1, [addr]
这条指令,其功能就是找到内存中 addr 地址的数据,并且将其传输到寄存器 R1 中,这条指令的执行包含以下几个步骤:
- 将地址传入 MAR 中。
- 存储器从 MAR 中读取地址,并且读取数据,存入 MDR。
- 从 MDR 中取出数据,通过 CPU 内的数据总线传输到寄存器 R1 中。
上述过程的执行需要保证严格的时序关系,先后关系不可颠倒,否则会导致指令执行的出错。
通过这个简单的例子是为了说明这样一个原理:在 CPU 内部,一条指令的执行也会被拆分为若干的子过程,这些子过程需要满足严格的时序关系,而这种时序关系就是由时序控制器进行控制的。
控制信号发生器
控制信号发生器负责接收指令译码器的结果,并且在每个时钟周期内产生 控制信号。
其中控制信号可以是 CPU 内的控制信号,通过 CPU 内总线传输至 CPU 内其他部件。 也可以是 CPU 外的控制信号,发送至系统的控制总线,用于协调计算机其他设备与 CPU 的交互。
控制信号
控制信号是由控制信号发生器生成和发出的电信号,这些信号用于指挥 CPU 内部的各种操作。例如,控制信号可以指示算术逻辑单元(ALU)执行加法还是减法,或者指示寄存器进行读写操作。
类型
其实控制信号的种类很多,但就目前阶段而言,会考察的可以被总结为三类控制信号:
- 读写信号:对内存或 IO 设备进行读写,比如
MemR
和MemW
- 寄存器选择信号:选择特定的寄存器进行读写操作,比如
PCin
和PCout
- 操作码信号:示算术逻辑单元(ALU)执行哪种运算,如加法、减法、与、或等。
寄存器传送语言
控制信号决定了计算机指令的执行顺序和数据流动。计算机内部组件根据控制信号执行相应操作,数据流动通常用寄存器传输语言(RTL)描述。
除了控制信号外,常常也需要我们用 RTL 描述数据流动,试题中的 RTL 一般需符合如下规范:
- 用 R 表示寄存器 R 的内容;
- 用 M[addr] 表示读取主存单元 addr 的内容;
- 传送方向用 “←” 表示,传送源在右,传送目的在左;
- 读取主存中的内容
指令执行的控制信号
指令的执行包含取指、译码、执行、写回阶段,在这四个阶段中控制单元会发出不同的控制信号,以实现指令的执行。
以指令ADD R0, (R1)
为例,说明一下指令执行阶段四个阶段的控制信号。
取指和译码阶段
在该阶段中:PC 提供了当前需要读取指令的地址,从相应地址读取指令后,增加 PC 的值,使其指向下一条指令的地址,接着通过译码部件完成译码。
该阶段主要包含如下表所示的控制信号:
时钟 | 功能 | 控制信号 | 解释 |
---|---|---|---|
C1 | MAR ← PC | PCout , MARin | 从 PC 中读取指令地址至 MAR 中 |
C2 | MDR ← M(MAR) | MemR , MDRin | 存储器从 MAR 地址所在的内存单元读取数据 并加载到 MDR 中 |
C3 | MUXop ← PCIncr | PCIncr | 在二路选择器中生成值 1 添加入 ALU 的一端 |
C4 | T2 ← PC + 1 | MARout , T2in , Add | ALU 计算下一条指令的地址 |
C5 | PC ← T2 | T2out , PCin | 将计算得到的地址加载进 PC 中 |
C6 | 指令译码 | 无 | 由指令译码器件完成 |
执行和写回阶段
通过译码得到的控制信号,可以控制指令的执行和写回,对于 ADD R0, (R1)
为例,该阶段包含如下控制信号:
时钟 | 功能 | 控制信号 | 解释 |
---|---|---|---|
C7 | MAR ← R1 | R1out ,MARin | 将 R1 中的内容加载进 MAR |
C8 | MDR ← M(MAR) | MemR , MDRin | 存储器从 MAR 地址所在的内存单元读取数据 并加载到 MDR 中 |
C9 | T1 ← R0 | R0out , T1in | 将 R0 的内容存储在暂存器 T1 |
C10 | T2 ← MDR + T1 | MDRout , MUXop ,Add , T2in | 将MDR 的内容存储进入ALU 另一个入口执行加法操作 并将结果存储进入 T2 |
C11 | R0 ← T2 | T2out , R0in | 将计算结果写回 R0 |
注意
为什么需要暂存器 T1?
采用了单数据总线的系统,无法在一个时钟周期同时将两个不同的数字同时添加到 ALU 的两端,在一个时钟周期内 ALU 两端会接受到相同的数据,所以必须要在当前时钟周期暂存一个数据,在下一个时钟周期再向 ALU 添加另一个数据。
ALU 的输出端也可能有一个暂存器,也是为了避免数据传输时在单总线结构上出现的数据冲突。
种类
控制器的种类分为硬布线控制器和微程序控制器,这里了解即可。简而言之,硬布线控制器就是通过硬件的方式实现控制信号的输出,微程序控制器对指令的 OP 字段进行进一步编码,通过软件的方式实现控制信号的输出。
硬布线控制器
定义:硬布线控制器是通过组合逻辑电路来实现的,通常使用逻辑门、多路复用器、解码器等组合电路元件。
特点:
- 性能:因为是硬件实现,所以通常速度较快。
- 固定功能:一旦设计和实现完成,修改它就比较困难,需要改变物理电路。
- 设计复杂性:对于复杂的控制逻辑,硬布线控制器可能会变得非常复杂,难以设计和验证。
微程序控制器
定义:微程序控制器基于存储的微指令集来实现控制逻辑。它使用一块称为“控制存储器”或“微指令存储器”的特殊存储器来存储微指令。每一个微指令定义了一系列的控制信号。
特点:
- 灵活性:由于控制逻辑是存储在存储器中的,所以更改控制逻辑只需要更改存储的微指令,而无需更改硬件。
- 简化设计:对于复杂的控制逻辑,使用微指令可能会简化设计和验证过程。
- 性能:通常比硬布线控制器慢,因为它需要从控制存储器中读取微指令。
- 易于修改和扩展:添加新的指令或修改现有的指令相对容易。
指令、微指令、微命令
微命令是计算机硬件控制的基础指令,用于控制某些硬件单元完成某种操作。
一条机器指令对应一个微程序,一个微程序由数条微指令构成,每个微指令可以包含数个微命令。
微指令编码方式
- 直接编码方式
微指令中的微命令字段中每位都代表一个微命令。
- 字段直接编码方式
将微指令的微命令字段分为若干小段,把互斥行微命令组合在同一字段中,把相容性微命令组合在不同字段中,每个字段独立编码,每种编码代表一个微命令且各字段编码含义单独定义。
3 - 异常与中断
异常
在CPU中,异常是指在执行程序时遇到的非常规或意外情况,需要操作系统介入以调整、中断或改变程序的正常执行流程。CPU会使用异常处理机制来处理这些情况。
类型
- 除法错误(Division Error)
- 触发条件:程序尝试除以零或执行其他非法的除法操作时触发。
- 处理:通常会中断程序运行,并可能通过操作系统给出错误信息。
- 缺页异常(Page Fault)
- 触发条件:程序尝试访问的内存页不在物理内存中时触发。
- 处理:操作系统将相应的内存页从磁盘加载到物理内存中,并更新页表。
- 无效指令(Invalid Instruction)
- 触发条件:CPU遇到未被定义或不可执行的指令时触发。
- 处理:通常会中断程序的执行,并可能给出错误信息。
- 保护错误(Protection Fault)
- 触发条件:程序尝试执行一些不被允许的操作,如访问受保护的内存区域或执行特权指令时触发。
- 处理:通常会中断程序的执行,并可能给出错误信息。
- 机器检查(Machine Check)
- 触发条件:硬件错误或故障,如内存错误、总线错误等。
- 处理:取决于具体硬件和配置,可能包括中断程序、记录错误信息、尝试纠正错误等。
- 浮点异常(Floating-Point Exception)
- 触发条件:浮点运算错误,如溢出、下溢、除以零、无效操作等。
- 处理:可能包括提供一个近似结果、设置状态标志、中断程序等。
自陷
在CPU和操作系统的上下文中,“自陷”(也称为陷阱,trap)是一种机制,其中程序执行中的某些条件会导致处理器自动执行一个异常或中断的响应例程。自陷通常是由以下原因引起的:
- 异常:程序执行中出现了错误条件,例如除以零、访问非法内存地址、执行非法指令等。
- 系统调用:程序请求操作系统服务,例如文件操作、进程创建、网络通信等。在大多数系统中,这通过执行一个特定的指令(如syscall在x86架构上)来完成,它会触发自陷并进入内核模式。
- 断点调试:在软件开发的调试过程中,可以设置断点以在特定的程序点停止执行,允许开发者检查程序状态。当程序达到断点时,会触发自陷。
自陷流程与外中断处理流程类似:
- 处理器检测到一个自陷条件。
- 当前的程序执行被中断,处理器状态(如程序计数器、寄存器等)被保存。
- 处理器切换到内核模式(如果它之前在用户模式下运行),这是一种更高的特权级别,允许执行操作系统代码。
- 控制权转移到预定的自陷处理程序或中断处理程序。这通常是操作系统内核的一部分,能够处理异常或执行系统调用。
- 自陷处理程序执行必要的服务或处理异常。
- 一旦自陷处理完成,程序可以返回到自陷发生前的状态并继续执行,或者如果出现了无法恢复的错误,程序可能会被终止。
陷阱指令(trap instruction)是一种特殊的指令,用于故意中断当前的程序流,并将控制权转移给操作系统。这是一种由程序员显式请求的自陷(trap),通常用于执行系统调用、启动调试操作或其他由操作系统内核提供的服务。
中断
中断概念
- 中断请求(IRQ,Interrupt ReQuest):
- 外部设备通过中断请求线向CPU发送中断请求,每个设备通常有一个特定的IRQ号码。
- 中断控制器(PIC, Prgrammable Interrupt Controller):
- 包含一个中断控制器芯片用于集中处理和管理中断请求。这个控制器接收来自多个设备的中断请求,并将它们传递给 CPU。
- 中断向量:
- 中断服务程序的入口地址,是中断向量表中的一个表项。
- 中断服务程序(ISR,Interrupt Service Routine):
- 一旦中断向量被确定,处理器会跳转到相应的中断服务例程,即中断处理程序。
- 中断向量表:
- 一个数组或表格,包含了各种中断类型的中断处理程序的起始地址,每个中断向量的值对应于相应中断处理程序的地址。
分类
按中断类型
- 外部中断(External Interrupt):
- 触发来源:外部设备或外部事件触发,如输入设备、时钟、外部信号等。
- 响应:CPU响应外部事件,执行相应的中断处理程序。
- 内部中断(Internal Interrupt):
- 触发来源:程序或CPU内部状态触发,如异常、错误等。
- 响应:CPU根据内部条件触发中断,执行特定的中断处理程序。
- 软件中断(Software Interrupt):
- 触发来源:软件或操作系统指令触发,常用于实现系统调用。
- 响应:CPU执行一个特定的中断处理程序来响应软件的请求。
按是否可屏蔽
- 可屏蔽中断:可屏蔽中断是可以被禁用或屏蔽的中断。处理器可以通过设置特定的标志或寄存器来忽略这类中断。
- 不可屏蔽中断:不可屏蔽中断是不能被禁用或屏蔽的中断。这类中断通常与系统的关键和紧急事件相关。
中断和异常的区别:
异常发生在CPU内部,在一般的分类方法中可以被看为中断类型的一种(内中断),但是在有的书上将中断和异常这两个概念区分了开来:
- 异常代表CPU执行指令时的意外事件
- 中断代表来自CPU外部、与CPU执行指令无关的事件引起的中断
在这里注意一下即可,面对题目可以灵活分辨即可
中断处理流程
4 - 指令流水线
如果不对指令的执行过程进行拆分的话,那么指令的执行的粒度则是指令本身,如下图所示:
这样会导致 CPU 的执行效率很低,这节谈到的流水线方案就是对以上方式的优化:在 CPU 中将指令的执行过程拆分为多个阶段,每个阶段由不同的部件执行。
如上图所示,指令执行的五个阶段由 CPU 中不同的部件处理,下一阶段的执行部件的执行结果依赖上一个阶段的输入,不同阶段的部件可以并行工作。 这样 CPU 中不同部件的利用率就得到了提高,CPU 执行指令的吞吐也会因此提高。
指令执行阶段
一般而言,指令执行包含五个阶段:
- 取指(IF,Instruction Fetch): 从内存中取出指令。
- 从 PC 中获取下一条指令的地址 addr
- 从 addr 读取该指令,传输到 IR 中存储指令
- 将 PC 值增加,指向下一条指令的地址
- 译码(ID,Instruction Decode):
- 将 IR 中的指令解码,以确定操作码和操作数
- 寄存器读取:根据指令中的寄存器字段,读取相关寄存器的值。
- 执行(EX,Execute):
- 算数逻辑运算:ALU 执行算术或逻辑运算,操作数来自寄存器文件或立即数
- 分支/跳转计算: 对于分支指令,计算分支目标地址
- 访存(MEM,Memory Access):
- 数据加载:对于加载指令,从计算得出的内存地址读取数据,并将数据暂存
- 数据存储:对于存储指令,将寄存器中的数据写入计算得出的内存地址
- 写回(WB,Write Back):
- 结果写回寄存器
- 更新标志寄存器
不过内存访问(MEM)的操作常常也被包含在执行(EX)和写回(WB)阶段中,所以有的时候也将指令的 MEM 阶段忽略,在这种情况下,指令执行只包含四个阶段:取指、译码、执行、写回。
指令流水线的基本概念
若将指令执行的多个阶段由不同的硬件单独执行,那么各个阶段可以并行执行。 这样,虽然每条指令的执行时间没有缩短,但是CPU的吞吐量得到了提升,也就是说,每单位时间内,CPU可以执行更多的指令。
上图包含四条指令执行的流水线,在 CPU 的每个时钟周期内,不同的器件可以并行运行。如果不同的指令之间不存在任何冲突,那么 CPU 执行 N 条指令总共需要 5 + (N - 1) 个时钟周期。若不采用流水线结构,执行 N 条指令共需要 5N 个时钟周期。
流水线的冒险和处理
上图中的指令流水线是一种理想情况,然后在实际情况中,情况不会这么简单。 指令的流水线执行必须满足两个前提:
第一个前提是指令重叠执行时不会存在任何流水线资源冲突问题,即流水线的各段在同一个时钟周期内不会使用相同的数据通路资源。
第二个前提是指令通过流水线方式指令的结果与串行执行的结果应该相同。
违背以上前提的指令流水线调度方式即发生了 “冒险”,这些冒险总共可以分为三类:
- 第一种是结构冒险,是指令在重叠执行的过程中,硬件资源满足不了指令重叠执行的要求,发生硬件资源冲突而产生的冲突。
- 第二种是数据冒险,是指在同时重叠执行的几条指令中,一条指令依赖于前面指令执行结果数据,但是又得不到时发生的冲突。
- 第三种是控制冒险,它是指流水线中的分支指令或者其他需要改写PC的指令造成的冲突。
结构冒险
结构冒险是由于 CPU 的硬件资源有限而引起的。当两条或多条指令需要使用同一硬件资源时,就会发生结构冒险。
上图中画出了不同指令在每个时钟周期所需要使用到的硬件结构,其中指令 0 和指令 1 在第 4 个时钟周期分别需要读和写寄存器,但是 CPU 的架构却并不一定支持这种场景。同样,指令 0 和指令 3 在第 3 个时钟周期分别需要写和读存储器,存储器架构也不一定支持这种场景。若硬件不支持上述场景的话,指令间就发生了结构冒险。
其 处理方法 也很简单,主要分为两种:
- 资源重复:既然结构冒险是资源受限所导致的,我们就增加硬件资源的数量,这样不同的指令在同一个时钟周期就可以去访问不同的硬件资源了。
- 流水线停顿:如果指令 A 和指令 B 发生了结构冒险,那么我们就推迟指令 B 的执行,直到两者不发生结构冒险,如下图所示。
数据冒险
数据冒险是由指令之间的依赖性引起的。一条指令可能需要使用另一条指令的结果,如果这些指令过早地进入流水线,它们可能会尝试在数据准备好之前使用数据。
数据冒险可以分为三类:
- 写后读(RAW, Read After Write):下一条指令的源操作数恰好是上一条指令的目的操作数,正常的逻辑是上一条指令写完该寄存器下一条指令才能读,如果下一条指令在上一条指令写完前就读了,就发生了 RAW 数据冒险。
- 读后写(WAR, Write After Read):下一条指令的目的操作数恰好是上一条指令的源操作数,正常的逻辑是上一条指令读完下一条指令才能写,如果下一条指令在上一条指令读完前就写了,就发生了 RAW 数据冒险。
- 写后写(WAW, Write After Write):两个指令写入同一个数据项,正常的逻辑是下一条指令比上一条指令更晚写,如果出现了相反的情况,就发生了 WAW 数据冒险。
以上的中文名词由于翻译关系可能有些绕,建议大家优先记住英文表示。如果上一条指令叫做 A,下一条指令叫做 B,A 写 B 读并且发生了冒险,就叫做读后写(Read After Write),其他冒险命名以此类推。
关键点在于理解流水线调度执行的结果应该与串行执行相同,如果关于某些数据的 读/写 逻辑出现了与串行执行不一致的地方,就发生了数据冲突。
上图中给出了数据冒险的几种情况,
数据冒险的 处理方法 如下所示:
- 流水线停顿(Pipeline Stall):暂停流水线直到数据准备好。
- 重新排序指令(Instruction Reordering):编译器在编译时对指令进行重新排序,以减少数据冒险。
- 数据前推(Data Forwarding):设置相关专用通路,直接将前一条指令的结果传递给需要它的下一条指令,不等结果写回寄存器。
下面通过一个实际的例子说明在如何在题目中画出 解决了冒险的指令流水线。
假设高级语言一条赋值语句被汇编微如下四条指令:
I1 LOAD R1, [a]
I2 LOAD R2, [b]
I3 ADD R1, R2
I4 STORE R1, [x]
其中 I3
和 I1
之间存在 WAW 数据冒险,
I3
和 I2
之间存在 RAW 数据冒险,
I4
和 I3
之间存在 WAR 数据冒险。
我们可以直接通过流水线停顿解决数据冒险:假设指令 A 和 B 发生了数据冲突,指令 A 在前,指令 B 在后,那么将 B 的 ID 放在 A 的 WB 之后就可以简单粗暴地简单冲突,在考试中画流水线都应采用这种方式。
解决冲突后,四条指令对应的流水线执行如下图所示:
控制冒险
控制冒险是由分支和跳转指令引起的。因为CPU需要在执行分支和跳转指令后,才能知道下一条要执行的指令在哪里,这导致了流水线的暂停或者无效的指令进入流水线。
以下举例说明控制冒险是如何发生的:
100: ADD R1, R2, R3 ; R1 = R2 + R3
104: BEQ R1, #0, 200 ; 如果 R1 等于 0,则跳转到地址 200
108: SUB R4, R5, R6 ; R4 = R5 - R6
112: MUL R7, R8, R9 ; R7 = R8 * R9
...
200: OR R10, R11, R12 ; R10 = R11 | R12
在 BEQ 指令的 EX 阶段完成之前,流水线已经开始取下一条指令(地址 108 的 SUB 指令)。问题在于,如果 BEQ 指令的条件成立,应该跳转到地址 200,而不是继续执行地址 108 的指令。 这就产生了控制冒险。
控制冲突的 处理方法 主要包含以下几种:
- 流水线停顿(Pipeline Stall/Bubble): 在条件跳转指令之后,停止后续指令的执行,插入空操作。
- 分支预测(Branch Prediction): 预测分支的结果(跳转或不跳转),并提前取指。如果预测正确,则可以避免停顿;如果预测错误,则需要清空流水线并重新取指。
- 延迟分支(Delayed Branch):编译器或处理器对代码进行优化,将分支指令后的一些不依赖于分支结果的指令先执行,从而减少因分支预测错误造成的开销。
5 - 多核处理器
弗林分类法
弗林分类法(Flynn’s Taxonomy)是一种计算机体系结构的分类方法,由弗林(Michael J. Flynn)于 1966 年提出。它根据计算机中指令流和数据流的数量,将计算机体系结构分为 SISD、SIMD、MIMD、MISD 四类:
单一指令流 | 多指令流 | |
---|---|---|
单一数据流 | 单指令流单数据流(SISD) | 多指令流单数据流(MISD) |
多数据流 | 单指令流多数据流(SIMD) | 多指令流多数据流(MIMD) |
指令流、数据流
指令是告诉计算机执行特定操作的命令,例如“加法”、“减法”、“数据移动”等。 指令流就是程序中一系列指令的有序集合,它决定了计算机执行的步骤。 换句话说,指令流就是“计算机要做什么”。
数据可以是数字、字符、图像、视频等各种形式的信息。 数据流指的是计算机在执行指令时处理的数据序列。 换句话说,数据流就是“计算机要处理什么”。
SISD
SISD (Single Instruction Single Data) 指的是单指令流单数据流,每个指令部件每次仅译码一条指令,而且在执行时仅为操作部件提供一份数据。
如上图所示,一个处理单元(PU,Processing Unit)接收单条指令流,执行每条指令时,对单独的数据进行操作。
SIMD
SIMD(Single Instruction Multiple Data)指的是单指令流多数据流,允许对多个数据点执行相同的操作,实现数据级并行性。
如上图所示,SIMD 架构的计算机包含多个处理单元,每个处理单元在同一个时刻执行相同的指令,但是对不同的数据进行操作。
这种方式可以大幅度提升计算的并行性,比如对于一个 N x N 的矩阵加法,在 SIMD 中,需要一个处理单元连续执行 N x N 次。而在 SIMD 中,如果有 N 个处理单元的话,只需要执行 N 次即可。
以下内容了解即可:
除了 SIMD 之外,还有一个 SIMT,大家需要有所区分。 简单来说,SIMD 就是堆硬件,我多添加几个处理单元,这样就可以同时对多个数据进行操作,进而实现数据并行性。 但是这里也有一个限制,就是不同的处理单元在同一个时刻必须执行相同的指令。
SIMT(Single Instruction Multiple Thread)是一种并行处理形式,其中单个指令同时在多个线程上执行,SIMT 是 GPU 的架构方式。 与 SIMD 不同,SIMT 允许允许在 warp 内的线程在一定程度上偏离相同的执行路径,也就是说不同的线程在同一时刻不必执行相同的指令。
MISD
MISD(Multiple Instruction Multiple Data)指的是多指令流单数据流,即多个处理单元同时对同一份数据执行不同的指令。 这种架构在实际应用中非常罕见,因为它难以实现,并且适用场景有限。 一些容错系统可能采用 MISD 架构,通过多个不同的处理单元对同一份数据进行计算,然后比较结果以确保正确性。
MIMD
MIMD(Multiple Instruction Multiple Data)指的是多指令流多数据流,多个处理单元同时对不同的数据执行不同的指令。 现代计算机中的多核处理器就是 MIMD 架构的典型代表。
如上图所示,与 SIMD 不同,MIMD 中的不同处理单元可以去处理不同的指令流。
多核处理器
物理核心
物理核心是CPU芯片上实际存在的、独立的硬件处理单元。 每个物理核心都拥有独立的运算电路和缓存,能够独立执行指令。 物理核心是真实的硬件存在,是CPU进行计算的基础。
逻辑核心是通过超线程(Hyper-Threading)等技术,在一个物理核心上虚拟出的多个逻辑处理单元。 超线程技术允许一个物理核心同时执行多个线程,从而提高CPU的利用率。 逻辑核心是操作系统层面识别的虚拟处理单元,并非真实的硬件存在。
超线程
超线程技术的核心思想是将一个物理核心模拟成多个逻辑核心(线程),从而在同一时间内执行多个线程。每个逻辑核心都拥有自己的寄存器集合和执行单元,这些逻辑核心之间共享物理核心的资源,如缓存和执行单元。
超线程技术旨在提高 CPU 的利用率,特别是在多线程应用程序中。 然而,超线程并不意味着性能翻倍。由于两个逻辑核心共享相同的物理执行资源,因此性能提升通常在 20% 到 30% 之间。
简单来说,物理核心是实际存在的“房子”,逻辑核心是在“房子”里隔出来的“房间”。
共享内存多处理机
共享内存多处理机(Shared Memory Multiprocessor)是一种并行计算机体系结构,其中多个处理器共享同一个物理内存空间。这种架构允许处理器之间通过读写共享内存来进行通信和数据交换,从而实现并行计算。
共享内存多处理机有两大主要架构特点:
- 共享内存空间:
- 所有处理器都可以访问同一个物理内存空间,使得数据共享变得简单高效。
- 处理器之间通过读写共享内存中的数据来进行通信和同步。
- 处理器互连:
- 处理器通过互连网络(如总线、交叉开关等)连接到共享内存。
- 互连网络的性能对共享内存多处理机的整体性能有重要影响。