流水线处理
流水线处理时一种提高CPU的处理性能的技术。所谓流水线处理,是将吃cpu操作分为多个阶段,让那后像流水线作业一样执行。CPU中的各种硬件资源,只在处理的相应阶段使用,其他时间大多处于空闲状态,比如,运算器在指令执行时使用,在指令读取、解码时空闲。因此,为了提高使用效率,引入了流水线处理技术。
流水线处理的情况下,读取某条指令后,该指令解码的同时读取下一条指令。通过使各个阶段的动作重叠,可以让硬件资源有效使用,同时提高处理速度。流水线处理就像是将之前一个人完成的操作,分成N个相连的步骤进行处理,以此提高处理效率。
流水线处理中,处理的各个阶段被称为流水线级。各个流水线级的处理时间应该尽量相等,最慢的处理级的时间就是系统的时钟周期。
典型的5阶段流水级
流水级之间通常设置流水线寄存器,用来保存状态并传递给下一个操作阶段。
1、IF(Instruction Fetch)阶段
将PC的值发送到内存,读取指令
2、ID(Instruction Decode)阶段
将读取的指令解码并决定将要进行的操作,从寄存器读取数据
3、EX(Execution)阶段
使用运算器执行操作,可以执行算术运算和逻辑运算的可以并称为ALU(Arithmetic and Logic Unit)
4、MEM(Memory Access)阶段
进行内存访问
5、WB(Write Back)阶段
将结果写回寄存器堆
时刻 | IF阶段 | ID阶段 | EX阶段 | MEM阶段 | WB阶段 |
---|---|---|---|---|---|
时刻1 | 读取指令1 | 等待状态 | 等待状态 | 等待状态 | 等待状态 |
时刻2 | 读取指令2 | 解码指令1 | 等待状态 | 等待状态 | 等待状态 |
时刻3 | 读取指令3 | 解码指令2 | 执行指令1 | 等待状态 | 等待状态 |
时刻4 | 读取指令4 | 解码指令3 | 执行指令2 | 内存访问指令1 | 等待状态 |
时刻5 | 读取指令5 | 解码指令4 | 执行指令3 | 内存访问指令2 | 回写指令1 |
时刻6 | 读取指令6 | 解码指令5 | 执行指令4 | 内存访问指令3 | 回写指令2 |
时刻7 | 读取指令7 | 解码指令6 | 执行指令5 | 内存访问指令4 | 回写指令3 |
时刻8 | 读取指令8 | 解码指令7 | 执行指令6 | 内存访问指令5 | 回写指令4 |
流水级冒险
流水级处理中,由于各个阶段的依赖关系,硬件资源的竞争等原因,会出现操作无法执行的情况。造成流水线故障的原因成为冒险,冒险分为构造、数据冒险和控制冒险。
构造冒险时由于硬件资源的竞争,操作无法同时执行的情况。比如IF阶段和MEM阶段都要访问内存,由于访问内存使用的总线是共享资源,无法同时进行操作,因此,如果发生IF阶段和MEM阶段同时访问内存的情况,一方需要等待另一方完成才能继续执行。这种指令和数据使用同一通道的构造成为冯诺依曼架构。
如果导致冒险产生的硬件资源数量足够的多,也可以依靠硬件避免冒险的发生,只需要保证程序运行过程中,总是有空闲的硬件资源可供指令级流水级使用即可。
如果指令和数据访问内存的通道分开,则被称为是哈佛构造,哈佛构造的优点是针对数据面和控制面这两个最广泛、普遍的数据分类,提供了一个基本的分割。这样直接的保护了数据面的问题和业务面的问题分开。
数据冒险,由于指令执行所需要的数据还未准备好所引起的冒险情况。但即将执行的指令依赖于还未完成处理的数据时,会导致指令无法立刻开始执行,引发数据冒险。为了回避数据冒险,使用直通(Forwarding)方法,原本回写运算的结果是在WB阶段走内存的,现在在明确代码之间的依赖关系后,第二条指令等待第一条指令结果时,直接在第一条指令的EX阶段(同时刻即为第二条指令的最早为ID的阶段)直接传递数据给ID。数据冒险的解决依赖于代码将依赖关系的判断。
控制冒险,无法确定下一跳指令而引发的冒险行为,这本质上也时数据冒险的一种,只不过是冒险发生的实时机不一样,对数据冒险来说,只需要保证取数据的ID阶段来临时,数据可以就位就可以了,控制冒险需要数据就位的时间将会更长一些,需要在IF阶段就要求数据直接就位,因此解决控制冒险最少需要间隔一个周期才能将EX阶段的信息传递给新指令的IF阶段。
这些冒险的概念本身并不一定依赖于5级流水的这一特定的架构。如何通过编译、硬件资源调用的方式规避上述的冒险行为,就成了编译器所需要工作的核心。目前进行冒险规避的方法,大多数都是使用编译器做语义分析后,以软件方式规避实现的。