第十二章 处理器组织、寄存器组织、间址周期和流水线

 教材参考计算机组织与结构——性能设计(第九版)第14章

12.1 Processor Organization处理器组织

为了理解处理器的组织,我们先来看一看处理器需要做的事情有哪些?

  • 取指

  • 译码

  • 取操作数

  • 处理数据

  • 写回执行的结果

为了做到上面的这些事情,处理器必然需要保存一些数据,比如在取指时,处理器需要知道到哪里取指令,那么就需要保存下一条指令的地址;再比如,处理器执行时,比如处理器在运算,也需要暂存一些数据。

那么这些数据又存储在哪里呢,处理器内部有一些小的存储单元——寄存器,这些处理器需要暂存的数据或者指令就存储在寄存器内。

回忆一下,在之前的章节中说过,CPU内部的组成部分有4个,分别为:CU(控制单元Control Unit)ALU(算数逻辑单元Arithmetic Logic Unit)Registers(寄存器)内部总线。下图展示了CPU和系统总线的关系:

image-20220109215128689

将上图中的CPU细化一下,CPU的内部结构如下图所示:

image-20220109215403344

从上图中可以看到,寄存器和ALU通过内部总线进行数据的传输,这是因为ALU只能够从处理器内部的存储获取数据进行运算。

12.2 寄存器组织

寄存器大致可以分为两类,当然,实际上这两类并没有清晰的界限。

  • 用户可见寄存器

  • 控制和状态寄存器

首先,我们来讲讲用户可见寄存器,用户可见寄存器可以分成下面这4种,分别为通用寄存器数据寄存器地址寄存器条件码寄存器。

用户可见寄存器

通用寄存器(general purpose):通用寄存器可以包含任意指令的操作数。对比专用的(dedicated)来说,专用寄存器有只用来保存浮点数的,也有专门用于栈操作的寄存器。有些时候,通用寄存器会被用在寄存器间接寻址和偏移寻址(详见寻址方式一节)。

数据寄存器:只能用来存储数据,不能用来进行操作数地址的计算,如累加寄存器(Accumulator)。

地址寄存器:可能在某些寻址模式中被使用,比如索引寻址,还有可能被用在支持分段的机器上被用作段基址寄存器(见操作系统中关于内存管理的部分),或者,也可以被用在栈指针(用来进行栈的操作,如ARM中的R13也叫SP)。

条件码寄存器:可以被用来存储最近一次运算结果的信息,比如进位(carry)、溢出(overflow)、负数(negative)、零(zero)。例如,当进行了减法运算,结果如果是负数,那么条件码寄存器中表示负数的一位就会被置位。在进行跳转的时候经常会使用到该寄存器,当然,是隐式的使用,比如当结果为零时跳转,就会读条件码寄存器看是否为零。

条件码寄存器很多时候不能被程序设置,而且是对于程序员部分可见的。

讲完了用户可见寄存器,我们再来看看另一类——控制状态寄存器(Control and Status Register),显然,这些寄存器是用户不可见的(不然为什么不也叫用户可见寄存器呢)。

对于不同的系统有不同的控制状态寄存器,但是基本上都会包含这么几个对于程序执行至关重要的寄存器(在不同的系统中可能名字不同):

  • 程序计数器PC(Program Counter):存储了下一条要执行的指令的地址

  • 指令寄存器IR(Instruction Register):存储了最近一次取的指令

  • 内存地址寄存器MAR(Memory Address Register):存储了一个内存地址(用来读内存)

  • 内存缓冲寄存器MBR(Memory Buffer Register):存储了最近一次从内存中取到的数据或者是要写入内存的数据

不是所有的处理器都会有MAR和MBR,但是会有类似的缓存机制。

常见的取指过程是:首先将PC的值传给MAR,然后然后MAR将内存地址通过地址总线发给内存,同时CPU发出存储器读命令,在内存中找到要取得指令,然后内存将指令放到数据总线上传给MBR,接着MBR的值会被传送给IR。

除了以上的这4个寄存器,通常还会有一个或多个寄存器被称为程序状态字PSW(Program Status Word),操作系统中也会提到这个寄存器,因为PSW中包含了CPU正处于的模态(管态/用户态),一般还会包含比如进位、溢出、是否允许中断的信息等。

只有在管态才能执行特权指令。

到这里我们就讲完了寄存器的组织,在进行控制状态寄存器设计的时候,一个十分重要的因素就是操作系统的支持,设计者对于操作系统有着怎样的理解,那么设计寄存器的时候就会去满足操作系统的需求。还有一个因素就是关于控制的信息放多少在寄存器中,多少在内存中,这是一个关于速度和代价的权衡问题。

12.3 指令周期——间址周期

之前,我们说指令的周期包括了3个:

  • 取指周期

  • 执行周期

  • 中断周期

但是实际情况下,我们如果使用了间接寻址,即数据的地址是存储在内存某个单元中的,我们需要先获得内存中数据的地址,然后再根据该地址到内存中找到我们需要的操作数。于是就引出了另一个指令周期——间址周期,加上该周期后,四个指令周期之间的转换如下图所示。

image-20220109225452539

指令周期转换的主线是取指和执行的交替,加上间址周期后,在取指完成之后,会检查是否需要间址周期(判断是否用了间接寻址),如果需要的话则转入间址周期,反之则直接转到执行周期。

更加细化的指令周期图如下:

image-20220109225854535

先进行取指,然后进行指令的译码,再计算操作数的地址,取操作数,如果有需要则进入间址周期(当然也有可能有多个操作数,需要取多次),然后进行数据的运算,运算完成后进行地址的计算,将结果存入该地址(也有可能进入间址周期,因为可能存入内存地址中的地址),后面就进入了中断周期,判断是否有中断,如果有则进行中断的处理。

间址周期CPU和内存的交互如下图:

image-20220109230511693

如果已经取完指令,那么IR和MBR中保存的都是取到的指令,先检查IR看是否是间接寻址,如果是,则将MBR中的指令地址传给MAR,然后读内存,获得操作数真正的内存地址。

12.4 流水线

在补充了指令周期之后,我们来到重点——流水线(Pipeline)。

首先,什么是流水线? 流水线取自工厂中的流水线作业,想象一下,把一个作业分成多个互不干扰的作业,然后每一个人只负责其中一段,结束之后交给下一个人,流水线技术就是基于这样的思想。将指令的执行分成若干流水段,比如有取指、译码、执行,一个指令在译码的时候,上一个可能在执行,而下一个可能在取指,这样就增加了并行性。

举个例子,我们可以将指令的执行分为这样几个段:

  • 取值 Fetch instruction (FI)

  • 译码 Decode instruction (DI)

  • 计算操作数地址 Calculate operands address (CO)

  • 取操作数 Fetch operands (FO)

  • 执行 Execute instructions (EI)

  • 写回结果 Write operand (WO)

那么流水线在理想情况下会这样工作:

image-20220109231543700

比如在第4个时间段中,第一条指令在取操作数,第二条指令在计算操作数的地址,第三条指令在译码,第四条指令在取值,这样看上去就是4条指令并行执行。

当然,上面这6段流水线是理想情况下的,实际情况会有下面这几个因素使得流水线的效率没有理想的这么高。

  • 有的指令可能没有其中的一些段,比如load指令并不需要写回结果,那么就没有了WO这一段。

  • 如果有冲突和依赖关系,就不会这么顺利地流水,比如存在先写后读的相关,前一条指令对数据进行写,后一条指令读该数据,那么只有在前一条指令WO完成后,后一条指令才能取操作数FO。

  • 每一个阶段的长度是不同的,流水段设计的长度是根据最长的一个阶段来的,比如CO是最长的,那么流水段的长度就会是CO的时间。

  • 跳转和中断会破坏流水线

跳转破坏流水线的例子如下:

image-20220109232651670

指令3是跳转指令,那么指令3执行完后,需要取指令15,而指令4到指令7的这些流水段都需要被清空,就没有用了。

当然了,虽然存在这些问题,流水线还是被证实能够大大缩短程序执行的时间的。那么如何来衡量流水线的性能呢?

我们引入一个加速比(Speeup)的概念来衡量流水线的性能:

$$
S_k=\frac{没有使用流水线的时间T_{1,n}}{使用了流水线的时间T_{k,n}},其中k表示流水段数量
$$

假设是理想的流水段,定义每一段的时间为:

$$
\tau=max[\tau_i]+d,其中1\le i\le k,d为流水段之间转换的延迟
$$

那么加速比为:

$$
S_k=\frac{T_{1,n}}{T_{k,n}}=\frac{nk\tau}{[k+(n-1)]\tau}=\frac{nk}{k+(n-1)\tau}
$$

上式中当n趋于无穷的时候,加速比可以趋于k,那么就有人想着,那不如直接让流水段的段数无限增大,岂不是就能够提高性能?

实际上并不是的。有这么几点原因:

  • 增多流水线段数会让冲突的概率变大

  • 控制流水线的逻辑会变得很复杂

  • 流水线之间如果要传递数据,那么就需要锁存,锁存的延迟也是一个问题

上面我们提到了“冲突”,那么到底什么是流水线冲突呢?

冲突就是一些使得流水线不能够按理想情况执行的情况,可以分为如下几种:

  • 资源冲突:假设内存只有一个端口,每次只允许一个读操作,而取操作数和取指令都要读内存,这时候就会产生资源的冲突。

  • 数据冲突:两条指令对同一寄存器或内存单元进行操作,且两条指令有严格的先后顺序。

    • 先写后读(RAW)比如你先把1写成2然后读出来,和先读了1再写成2

    • 先读后写(WAR)比如你先把1读了然后再写为2,和先写为2再读

    • 写写(WAW)比如你先赋值为1,再赋值为2,这能反过来么

那么问题来了,如何解决呢?

大致有这么5种方式:

  • 多条流水线

  • 预取转移目标

  • 循环缓冲

  • 分支预测

  • 分支延迟

具体内容可以自行查阅,这一章就讲这么多啦~

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值