Skip to content

Latest commit

 

History

History
199 lines (108 loc) · 23.2 KB

File metadata and controls

199 lines (108 loc) · 23.2 KB

前言

CPU(中央处理器)作为电脑最重要的一个模块,出生就自带高科技的光环,有人做过对比,制作一个CPU和制作一个操作系统哪个难度更大一些?这个暂且不论,但是于我而言,CPU本身自带一种魔力,让人情不自禁的去了解它。对于CPU与计算机,在具备了一些基本的知识之后,始终有一些问题缠绕着我。CPU是怎么执行代码的?CPU里面到底是什么样子的?CPU多核设计的核到底是什么?CPU是怎么和硬件主板打交道的?…… 恰好最近看了一些Intel Skylake CPU架构(Intel第9代处理器)的一些文章,这里把自己的理解写一下,做个记录,持续补充更新。

关于上述问题,可能需要很长的时间去一一寻找答案,本篇首先记录一下CPU core里面的架构。水平有限,如有问题,请各位批评指导。

基本知识

在开始之前,首先需要明确现代CPU里有多个核,比如我们经常听的四核八核等等。Core与CPU的关系图如下图所示。示意图中的CPU有四个Core(核),Core与Core之间通过Ring(环形总线)连接起来,每一个核就是一个独立的计算单元,Core本身自带L1 Cache与L2 Cache。L3是所有的Core共享。L1又划分为指令Cache与数据Cache两类。关于Cache的一些介绍,可以下滑至文末查看。本篇重点在于介绍CPU的Core里面的执行流程与部件。

一、CPU指令的执行过程 {#h_506663731_0}

  • 几乎所有的冯·诺伊曼型计算机的 CPU,其工作都可以分为 5 个阶段:取指令、指令译码、执行指令、访存取数、结果写回。

1.取指令阶段 {#h_506663731_1}

  • 取指令(Instruction Fetch,IF)阶段是将一条指令从主存中取到指令寄存器的过程。 程序计数器 PC 中的数值,用来指示当前指令在主存中的位置。当一条指令被取出后,PC 中的数值将根据指令字长度而自动递增:若为单字长指令,则(PC)+1->PC;若为双字长指令,则(PC)+2->PC,依此类推。

2.指令译码阶段 {#h_506663731_2}

  • 取出指令后,计算机立即进入指令译码(Instruction Decode,ID)阶段。 在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分出不同的指令类别以及各种获取操作数的方法。在组合逻辑控制的计算机中,指令译码器对不同的指令操作码产生不同的控制电位,以形成不同的微操作序列;在微程序控制的计算机中,指令译码器用指令操作码来找到执行该指令的微程序的入口,并从此入口开始执行。 在传统的设计里,CPU中负责指令译码的部分是无法改变的。不过,在众多运用微程序控制技术的新型 CPU 中,微程序有时是可重写的。

3.执行指令阶段 {#h_506663731_3}

  • 在取指令和指令译码阶段之后,接着进入执行指令(Execute,EX)阶段。 此阶段的任务是完成指令所规定的各种操作,具体实现指令的功能。为此,CPU 的不同部分被连接起来,以执行所需的操作。 例如,如果要求完成一个加法运算,算术逻辑单元 ALU 将被连接到一组输入和一组输出,输入端提供需要相加的数值,输出端将含有最后的运算结果。

4.访存取数阶段 {#h_506663731_4}

  • 根据指令需要,有可能要访问主存,读取操作数,这样就进入了访存取数(Memory,MEM)阶段。 此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算。

5.结果写回阶段 {#h_506663731_5}

  • 作为最后一个阶段,结果写回(Writeback,WB)阶段把执行指令阶段的运行结果数据“写回”到某种存储形式:结果数据经常被写到 CPU 的内部寄存器中,以便被后续的指令快速地存取;在有些情况下, 结果数据也可被写入相对较慢、但较廉价且容量较大的主存。许多指令还会改变程序状态字寄存器中标志位 的状态,这些标志位标识着不同的操作结果,可被用来影响程序的动作。
  • 在指令执行完毕、结果数据写回之后,若无意外事件(如结果溢出等)发生,计算机就接着从程序计数器 PC 中取得下一条指令地址,开始新一轮的循环,下一个指令周期将顺序取出下一条指令。许多新型 CPU 可以同时取出、译码和执行多条指令,体现并行处理的特性。

二、CPU指令流水线 {#h_506663731_6}

  • 在任一条指令的执行过程中,各个功能部件都会随着指令执行的进程而呈现出时忙时闲的现象。要加快计算机的工作速度,就应使各个功能部件并行工作,即以各自可能的高速度同时、不停地工作,使得各部件的操作在时间上重叠进行,实现流水式作业。 从原理上说,计算机的流水线(Pipeline)工作方式就是将一个计算任务细分成若干个子任务,每个子任务都由专门的功能部件进行处理,一个计算任务的各个子任务由流水线上各个功能部件轮流进行处理 (即各子任务在流水线的各个功能阶段并发执行),最终完成工作。这样,不必等到上一个计算任务完成, 就可以开始下一个计算任务的执行。 流水线的硬件基本结构如图2所示。流水线由一系列串联的功能部件(Si)组成,各个功能部件之间设有高速缓冲寄存器(L),以暂时保存上一功能部件对子任务处理的结果,同时又能够接受新的处理任务。在一个统一的时钟(C)控制下,计算任务从功能部件的一个功能段流向下一个功能段。在流水线中, 所有功能段同时对不同的数据进行不同的处理,各个处理步骤并行地操作。

  • 当任务连续不断地输入流水线时,在流水线的输出端便连续不断地输出执行结果,流水线达到不间断流水的稳定状态,从而实现了子任务级的并行。
  • 当指令流不能顺序执行时,流水过程会中断(即断流)。为了保证流水过程的工作效率,流水过程不应经常断流。在一个流水过程中,实现各个子过程的各个功能段所需要的时间应该尽可能保持相等,以避免产生瓶颈,导致流水线断流。
  • 流水线技术本质上是将一个重复的时序过程分解成若干个子过程,而每一个子过程都可有效地在其专用功能段上与其他子过程同时执行。采用流水线技术通过硬件实现并行操作后,就某一条指令而言,其执行速度并没有加快,但就程序执行过程的整体而言,程序执行速度大大加快。
  • 流水线技术适合于大量的重复性的处理。
  • 前面我提到过CPU 中一个指令周期的任务分解。假设指令周期包含取指令(IF)、指令译码(ID)、 指令执行(EX)、访存取数(MEM)、结果写回(WB)5 个子过程(过程段),流水线由这 5个串联的过程段 组成,各个过程段之间设有高速缓冲寄存器,以暂时保存上一过程段子任务处理的结果,在统一的时钟信号控制下,数据从一个过程段流向相邻的过程段。
  • 非流水计算机的时空图如下:

  • 对于非流水计算机而言,上一条指令的 5 个子过程全部执行完毕后才能开始下一条指令,每隔 5 个时 钟周期才有一个输出结果。因此,图3中用了 15 个时钟周期才完成 3 条指令,每条指令平均用时 5 个时钟周期。 非流水线工作方式的控制比较简单,但部件的利用率较低,系统工作速度较慢。

标量流水计算机工作方式

  • 标量(Scalar)流水计算机是只有一条指令流水线的计算机。图 4表示标量流水计算机的时空图。

  • 对标量流水计算机而言,上一条指令与下一条指令的 5 个子过程在时间上可以重叠执行,当流水线满 载时,每一个时钟周期就可以输出一个结果。因此,图4中仅用了 9 个时钟周期就完成了 5 条指令,每条指令平均用时 1.8 个时钟周期。
  • 采用标量流水线工作方式,虽然每条指令的执行时间并未缩短,但 CPU 运行指令的总体速度却能成倍 提高。当然,作为速度提高的代价,需要增加部分硬件才能实现标量流水。

超标量流水计算机工作方式

  • 一般的流水计算机因只有一条指令流水线,所以称为标量流水计算机。所谓超标量(Superscalar)流 水计算机,是指它具有两条以上的指令流水线。图 5表示超标量流水计算机的时空图。

  • 当流水线满载时,每一个时钟周期可以执行 2 条以上的指令。因此,图5中仅用了 9 个时钟周期就完成了 10 条指令,每条指令平均用时 0.9 个时钟周期。 超标量流水计算机是时间并行技术和空间并行技术的综合应用。

三、指令的相关性 {#h_506663731_7}

  • 指令流水线的一个特点是流水线中的各条指令之间存在一些相关性,使得指令的执行受到影响。要使流水线发挥高效率,就要使流水线连续不断地流动,尽量不出现断流情况。然而,由于流水过程中存在的相关性冲突,断流现象是不可避免的。

1.数据相关 {#h_506663731_8}

  • 在流水计算机中,指令的处理是重叠进行的,前一条指令还没有结束,第二、三条指令就陆续开始工 作。由于多条指令的重叠处理,当后继指令所需的操作数刚好是前一指令的运算结果时,便发生数据相关冲突。由于这两条指令的执行顺序直接影响到操作数读取的内容,必须等前一条指令执行完毕后才能执行后一条指令。在这种情况下,这两条指令就是数据相关的。因此,数据相关是由于指令之间存在数据依赖性而引起的。根据指令间对同一寄存器读和写操作的先后次序关系,可将数据相关性分为写后读(Read-AfterWrite,RAW)相关、读后写(Write-After-Read,WAR)相关、写后写(Write-After-Write,WAW)相关三种类型。
  • 解决数据相关冲突的办法如下:
  1. 采用编译的方法 编译程序通过在两条相关指令之间插入其他不相关的指令(或空操作指令)而推迟指令的执行,使数据相关消失,从而产生没有相关性的程序代码。这种方式简单,但降低了运行效率。
  2. 由硬件监测相关性的存在,采用数据旁路技术设法解决数据相关 当前一条指令要写入寄存器而下一条指令要读取同一个寄存器时,在前一条指令执行完毕、结果数据还未写入寄存器前,由内部数据通路把该结果数据直接传递给下一条指令,也就是说,下一条指令所需的 操作数不再通过读取寄存器获得,而是直接获取。这种方式效率较高,但控制较为复杂。

2.资源相关 {#h_506663731_9}

  • 所谓资源相关,是指多条指令进入流水线后在同一机器周期内争用同一个功能部件所发生的冲突。 例如,在图 4所示的标量流水计算机中,在第 4 个时钟周期时,第 1 条指令处于访存取数(MEM) 阶段,而第 4 条指令处于取指令(IF)阶段。如果数据和指令存放在同一存储器中,且存储器只有一个端口,这样便会发生这两条指令争用存储器的资源相关冲突。 因为每一条指令都可能需要 2 次访问存储器(读指令和读写数据),在指令流水过程中,可能会有 2 条指令同时需要访问存储器,导致资源相关冲突解决资源相关冲突的一般办法是增加资源,例如增设一个存储器,将指令和数据分别放在两个存储器中。

3.控制相关 {#h_506663731_10}

  • 控制相关冲突是由转移指令引起的。当执行转移指令时,依据转移条件的产生结果,可能顺序取下一 条指令,也可能转移到新的目标地址取指令。若转移到新的目标地址取指令,则指令流水线将被排空,并等待转移指令形成下一条指令的地址,以便读取新的指令,这就使得流水线发生断流。 为了减小转移指令对流水线性能的影响,通常采用以下两种转移处理技术:
  1. 延迟转移法 由编译程序重排指令序列来实现。其基本思想是“先执行再转移”,即发生转移时并不排空指令流水线,而是继续完成下几条指令。如果这些后继指令是与该转移指令结果无关的有用指令,那么延迟损失时间片正好得到了有效的利用。
  2. 转移预测法 用硬件方法来实现。依据指令过去的行为来预测将来的行为,即选择出现概率较高的分支进行预取。通过使用转移取和顺序取两路指令预取队列以及目标指令 Cache,可将转移预测提前到取指令阶段进行,以获得良好的效果。

四、指令的动态执行技术 {#h_506663731_11}

1.指令调度 {#h_506663731_12}

  • 为了减少指令相关性对执行速度的影响,可以在保证程序正确性的前提下,调整指令的顺序,即进行指令调度。 指令调度可以由编译程序进行,也可以由硬件在执行的时候进行,分别称为静态指令调度和动态指令调度。静态指令调度是指编译程序通过调整指令的顺序来减少流水线的停顿,提高程序的执行速度;动态 指令调度用硬件方法调度指令的执行以减少流水线停顿。
  • 流水线中一直采用的有序(in-order)指令启动是限制流水线性能的主要因素之一。如果有一条指令在流水线中停顿了,则其后的指令就都不能向前流动了,这样,如果相邻的两条指令存在相关性,流水线就将发生停顿,如果有多个功能部件,这些部件就有可能被闲置。消除这种限制流水线性能的因素从而提高指令执行速度,其基本思想就是允许指令的执行是无序的(out-of-order,也称乱序),也就是说,在保持指令间、数据间的依赖关系的前提下,允许不相关的指令的执行顺序与程序的原有顺序有所不同,这一思想是实行动态指令调度的前提。

2.乱序执行技术 {#h_506663731_13}

  • 乱序执行(Out-of-order Execution)是以乱序方式执行指令,即 CPU 允许将多条指令不按程序规定的顺序而分开发送给各相应电路单元进行处理。这样,根据各个电路单元的状态和各指令能否提前执行的具体情况分析,将能够提前执行的指令立即发送给相应电路单元予以执行,在这期间不按规定顺序执行指令;然后由重新排列单元将各执行单元结果按指令顺序重新排列。乱序执行的目的,就是为了使 CPU 内部电路满负荷运转,并相应提高 CPU 运行程序的速度。
  • 实现乱序执行的关键在于取消传统的“取指”和“执行”两个阶段之间指令需要线性排列的限制,而使用一个指令缓冲池来开辟一个较长的指令窗口,允许执行单元在一个较大的范围内调遣和执行已译码的程序指令流。

3.分支预测 {#h_506663731_14}

  • 分支预测(Branch Prediction)是对程序的流程进行预测,然后读取其中一个分支的指令。采用分支预测的主要目的是为了提高 CPU的运算速度。 分支预测的方法有静态预测和动态预测两类:静态预测方法比较简单,如预测永远不转移、预测永远转移、预测后向转移等等,它并不根据执行时的条件和历史信息来进行预测,因此预测的准确性不可能很高;动态预测方法则根据同一条转移指令过去的转移情况来预测未来的转移情况。 由于程序中的条件分支是根据程序指令在流水线处理后的结果来执行的,所以当 CPU 等待指令结果时, 流水线的前级电路也处于等待分支指令的空闲状态,这样必然出现时钟周期的浪费。如果 CPU 能在前条指令结果出来之前就预测到分支是否转移,那么就可以提前执行相应的指令,这样就避免了流水线的空闲等待,也就相应提高了 CPU 的运算速度。但另一方面,一旦前条指令结果出来后证明分支预测是错误的,那么就必须将已经装入流水线执行的指令和结果全部清除,然后再装入正确的指令重新处理,这样就比不进行分支预测而是等待结果再执行新指令还要慢了。
  • 因此,分支预测的错误并不会导致结果的错误,而只是导致流水线的停顿,如果能够保持较高的预测 准确率,分支预测就能提高流水线的性能。

Core架构图

话不多说,对于Skylake而言,CPU中的Core的架构图如下所示:

咋一看这个图,感觉超级复杂,各种连线,各种模块。其实对于core而言,主要目的就是执行各种运算,而剩下的就是就是core对执行运算之前的处理。对于上图,我们可以直接把Core划分为三个部分看,一个部分是前端(Front-end)也就是图中黄色的部分,另外一个就是(EU)执行单元了,也就是图中浅绿色的部分,第三块就是数据缓存,专门给执行单元提供执行指令期间所需要的数据.

Front-end(Core前端)

首先看Front-end,Front-end的主要目的就是从内存里提取各种各样的X86指令,然后对指令进行译码,融合优化等操作,把X86指令转化为最适合执行单元执行的微指令流传递给执行单元。Front-End的存在就是为了让执行单元时刻保持繁忙,将CPU的性能完全发挥出来。对于Core的Front-end模块,从上往下看,这部分主要包含:

  • L1指令Cache: 缓存指令用,L1指令Cache将即将执行的指令从L2Cache中存过来供后面的流水线使用。

  • Instruction Fetch,PreDecode: 指令提取单元,目的是从L1中把要执行的指令拿过来并且做预解码,预解码是将一个Cacheline(64字节)的X86指令提取出来并划分边界。

  • Instruction Queue, Macro-Fusion: 指令队列与指令融合,目的是接受来自指令提取单元的指令,并把相近的X86指令融合为一条指令。

  • 5-way Decode: 这里包含了五路解码器,目的将X86的可变长度的复杂指令转换为定长的微指令。五路解码器包含一个复杂的解码器与4路简单的解码器。简单解码器每次只能解码一个微指令而复杂解码器可以解码1~4个融合微指令。从图中可以看到,这个时候MOP转换成了uop。也就是可变长度的复杂指令变成了一个个定长的微码指令,这些微码指令直接给后面执行单元去执行。

  • MicroCode Sequencer ROM: 微指令排序器,对于有些复杂的指令,解析出的微指令超过4个,这个时候该指令Complex Decoder也无法解析,则会直接经过微指令排序器进行解析,MC每周期发出4uop。

  • Stack Engine: 堆栈引擎,对于程序中经常遇到的push, pop, call, ret等操作,这些操作是专门对堆栈进行操作的指令,Stack Engine会专门处理这些指令。如果没有Stack Engine,这些操作会占用ALU(算数逻辑单元)的资源,而ALU是专门用作运算的,将这些操作交给Stack Engine,ALU则可以专注运算,提升CPU的性能。

  • Decoded stream Buffer(DSB): 解码流缓冲区,准确的说就是X86指令在进行译码之后生成的微指令可以直接存在DSB里,DSB相当于也是一个Cache,只用于存储译码后的微指令.DSB的存在增强了指令的灵活性,微指令一样可以存在L1 指令Cache里,但可以经过DSB直接到达MUX。
  • MUX:选择器
  • Allocation Queue(IDQ): 分配队列,分配队列作为前端与执行单元的接口,是Core前端的最后的一个部件。分配队列的目的是将微指令进行重新整合与融合,发给执行单元进行乱序执行。分配队列又包含了Loop Stream Detector(LSD) 循环流检测器,对循环操作进行优化与 up-Fusion(微指令融合单元)。融合是为了让后续解码单元更有效率并且节省ROB(re-order buffer)的空间

Execution engine (执行单元)

Front-End的存在就是为了让执行单元时刻保持繁忙,那么执行单元的最主要的目的就是执行指令,运算。这也是一个core中最根本的单元。

当被AQ或者IDQ(分配单元)优化过后的微指令来到执行单元时,首先最先传输到

  • ROB(re-order buffer): 重新排序缓冲区。ROB的存在ROB的目的为存储out-of-order的处理结果,作为EU的入口兼部分出口,它是乱序执行的最基本保证。当指令被传如ROB中,微指令流会以顺序执行的方式传入到后面的RS,在经过ROB时,会占用ROB的一个位置,这个位置是存储微指令乱序执行处理完成时候的结果,之后经过整合会顺序写回到相应的寄存器。而微指令在经过ROB时候会做一些优化。(消除寄存器移动,置零指令与置一指令等)。此外对于超线程中的寄存器别名技术在此经过RAT(寄存器别名表)进行寄存器重命名。

  • RS(Scheduler unified reservation station): 统一调度保留站。指令经过前面千辛万苦来到这里,此时微指令不在融合在一起,而是被单独的分配给下面各个执行单元,从架构图中可以看到,RS下面挂载了八个端口,每个端口后面挂载不同的执行模块应对不同的指令需求.

从上图中可以看到,对于8个端口,其中Port0,Port1,Port5,Port6负责各种常见运算(整数,浮点,除法,移位,AES加密,复合整数运算……),而Port2,Port3负责从下层的指令缓存提取数据,port4负责存储数据到L1缓存。Port7挂载地址生成单元(AGU address generation unit).

数据缓存系统 (L1数据缓存)

图中紫色的模块,在EU(执行单元)在执行指令时候,L1数据Cache负责供给执行指令期间所需要的数据。从图中可以看到L1数据缓存是8-路并行数据缓存,L1数据缓存可以通过数据页表地址缓存从L2提取数据与存储数据。

至此,借着Intel第九代Skylake架构图。我们大概把CPU core里面的模块过了一遍,也对CPU core执行指令的过程有了一个大概的了解。当时留下的坑也很多,对于细节问题,后续需要逐渐了解。在了解core之后,后续还会完善core和core协同组成一个完整的CPU,以至于CPU和外设,CPU与CPU之间的协同工作。