# CPU
## CPU的功能和基本结构
## 指令执行过程
## 数据通路的功能和基本结构
## 控制器的功能和工作原理
## 异常和中断机制
- 基本概念
- 分类
- 检测和响应
## 指令流水线
- 基本概念
- 基本实现
- 结构冒险、数据冒险和控制冒险
- 超标量和动态流水线的基本概念
## 多处理器基本概念
- SISD、SIMD、MIMD、向量处理器的基本概念
- 硬件多线程的概念
- 多核处理器的基本概念
- 共享内存多处理器的概念
## 总线和输入/输出系统
### 总线
- 基本概念
- 组成及性能指标
- 事务和定时
### I/O接口
- 功能和基本结构
- 端口及其编址
### I/O方式
- 程序查询方式
- 程序中断方式
- DMA方式
中央处理器
1 - 功能和结构
CPU 基本结构
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 从存储器取出一个指令后,控制器负责解码这个指令,以确定要执行的操作和涉及的操作数。
- 生成控制信号:基于解码的指令,控制器生成一系列的控制信号,这些信号会驱动其他计算机部分(如算术逻辑单元、寄存器和存储器)按预期执行相应的操作。
- 指令执行的顺序和时序:通过先后发出不同的控制信号,确保指令的逻辑正确被执行,
根据控制器产生微操作控制信号方式的不同,控制器可以分为硬布线控制器和微程序控制器。
控制信号
控制信号是由控制单元(Control Unit)生成和发出的电信号,这些信号用于指挥 CPU 内部的各种操作。例如,控制信号可以指示算术逻辑单元(ALU)执行加法还是减法,或者指示寄存器进行读写操作。
类型
其实控制信号的种类很多,但就目前阶段而言,会考察的可以被总结为三类控制信号:
- 读写信号:对内存或 IO 设备进行读写,比如
MemR
和MemW
- 寄存器选择信号:选择特定的寄存器进行读写操作,比如
PCin
和PCout
- 操作码信号:示算术逻辑单元(ALU)执行哪种运算,如加法、减法、与、或等。
指令执行的控制信号
指令的执行包含取指、译码、执行、写回阶段,在这四个阶段中控制单元会发出不同的控制信号,以实现指令的执行。
以指令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 - 多核处理器
SISD, SIMD, MIMD, 向量处理机
- SISD (Single Instruction stream, Single Data stream)
- 定义:一个指令流和一个数据流在单一处理器上顺序执行。这种架构中的每个操作指令都在一个数据上执行。
- 特点:简单、易于管理,但处理能力有限。
- SIMD (Single Instruction stream, Multiple Data streams)
- 定义:一个指令在多个数据上并行执行。这种架构用于执行重复的数据操作,特别适用于图形处理和科学计算。
- 特点:能高效处理大量数据,尤其在图像、音频和视频处理中表现出色。
- MIMD (Multiple Instruction streams, Multiple Data streams)
- 定义:多个处理器或多核处理器并行执行不同的指令序列,每个指令序列作用在不同的数据流上。这种架构用于多任务和并行处理环境。
- 特点:灵活、强大,能处理多任务和复杂的并行处理问题。
- 向量处理机 (Vector Processor)
- 定义:使用向量寄存器来存储数据,并能在一个指令中处理整个向量数据。适用于执行大量重复和并行操作的科学和工程计算。
- 特点:高性能,尤其在科学、工程和图形处理任务中表现出色。
多核处理器
现代一个CPU可以多个物理核心,一个物理核心可以使用超线程技术来实现多个逻辑核心。比如4核8线程的处理器,就是有4个物理核心,每个物理核心对应两个逻辑核心。
物理核心(Physical Cores):物理核心是处理器芯片上的实际硬件核心,它们能够独立执行指令和处理任务。一个物理核心可以看作是一个完整的处理单元,具有自己的寄存器、执行单元以及缓存。在4核心的处理器中,有四个物理核心。
硬件多线程
超线程技术的核心思想是将一个物理核心模拟成多个逻辑核心(线程),从而在同一时间内执行多个线程。每个逻辑核心都拥有自己的寄存器集合和执行单元,这些逻辑核心之间共享物理核心的资源,如缓存和执行单元。
共享内存多处理机
共享内存多处理机是一种计算机架构,其中多个处理器可以访问同一块物理内存。这样的架构允许处理器之间通过内存共享数据,从而实现并行处理和任务协同。
共享内存多处理机系统提供了一种有效的并行处理和数据共享机制,但也带来了同步、数据一致性和扩展性等方面的挑战。需要根据特定的应用和性能需求来选择和优化共享内存多处理机系统。