CPU 的全称是 Central Processing Unit,即中央处理单元,是计算机的核心部件 - 计算部件。CPU 的核心是从程序或应用程序获取指令并执行计算。此过程可以分为三个关键阶段: 提取,解码和执行 。CPU 从系统的主存中提取指令,然后解码该指令的实际内容,然后再由 CPU 的相关部分执行该指令。

计算机内部处理过程

以 C 语言为例来说明程序运行流程:

运行流程

在这个流程中,CPU 负责的就是解释和运行最终转换成机器语言的内容。
CPU 主要由两部分构成:控制单元 和 算术逻辑单元(ALU)

控制单元:从内存中提取指令并解码执行
算数逻辑单元(ALU):处理算数和逻辑运算

CPU 是计算机的心脏和大脑,它和内存都是由许多晶体管组成的电子部件。它接收数据输入,执行指令并处理信息。它与输入 / 输出(I / O)设备进行通信,这些设备向 CPU 发送数据和从 CPU 接收数据。
从功能来看,CPU 的内部由寄存器、控制器、运算器和时钟四部分组成,各部分之间通过电信号连通。

cpu 部件

寄存器 是中央处理器内的组成部分。它们可以用来暂存指令、数据和地址。可以将其看作是内存的一种。根据种类的不同,一个 CPU 内部会有 20 - 100 个寄存器。

控制器 负责把内存上的指令、数据读入寄存器,并根据指令的结果控制计算机

运算器 负责运算从内存中读入寄存器的数据

时钟 负责发出 CPU 开始计时的时钟信号

CPU 是一系列寄存器的集合体

控制器、运算器、时钟都是基于数字电路原理,在计算机科学中主要关注寄存器。

种类 功能
累加寄存器 存储运行的数据和运算后的数据。
标志寄存器 用于反应处理器的状态和运算结果的某些特征以及控制指令的执行。
程序计数器 程序计数器是用于存放下一条指令所在单元的地址的地方。
基址寄存器 存储数据内存的起始位置
变址寄存器 存储基址寄存器的相对地址
通用寄存器 存储任意数据
指令寄存器 储存正在被运行的指令,CPU 内部使用,程序员无法对该寄存器进行读写
栈寄存器 存储栈区域的起始位置

其中 程序计数器、累加寄存器、标志寄存器、指令寄存器和栈寄存器 都只有一个,其他寄存器一般有多个。

CPU 寄存器

程序计数器

程序计数器 (Program Counter) 是用来存储下一条指令所在单元的地址。

程序执行时,PC 的初值为程序第一条指令的地址,在顺序执行程序时,控制器 首先按程序计数器所指出的指令地址从内存中取出一条指令,然后分析和执行该指令,同时将 PC 的值加 1 指向下一条要执行的指令。

我们还是以一个事例为准来详细的看一下程序计数器的执行过程

程序计数器

这是一段进行相加的操作,程序启动,在经过编译解析后会由操作系统把硬盘中的程序复制到内存中,示例中的程序是将 123 和 456 执行相加操作,并将结果输出到显示器上。

地址 0100 是程序运行的起始位置。Windows 等操作系统把程序从硬盘复制到内存后,会将程序计数器作为设定为起始位置 0100,然后执行程序,每执行一条指令后,程序计数器的数值会增加 1(或者直接指向下一条指令的地址),然后,CPU 就会根据程序计数器的数值,从内存中读取命令并执行,也就是说, 程序计数器控制着程序的流程

条件分支和循环机制

高级语言中的条件控制流程主要分为三种:顺序执行、条件分支、循环判断 三种,顺序执行是按照地址的内容顺序的执行指令。条件分支是根据条件执行任意地址的指令。循环是重复执行同一地址的指令。

  • 顺序执行的情况比较简单,每执行一条指令程序计数器的值就是 + 1。
  • 条件和循环分支会使程序计数器的值指向任意的地址,这样一来,程序便可以返回到上一个地址来重复执行同一个指令,或者跳转到任意指令。

下面以条件分支为例来说明程序的执行过程(循环也很相似)

条件分支和循环机制

程序的开始过程和顺序流程是一样的,CPU 从 0100 处开始执行命令,在 0100 和 0101 都是顺序执行,PC 的值顺序 + 1,执行到 0102 地址的指令时,判断 0106 寄存器的数值大于 0,跳转(jump)到 0104 地址的指令,将数值输出到显示器中,然后结束程序,0103 的指令被跳过了,这就和我们程序中的 if () 判断是一样的,在不满足条件的情况下,指令会直接跳过。所以 PC 的执行过程也就没有直接 + 1,而是下一条指令的地址。

标志寄存器

条件和循环分支会使用到 jump(跳转指令),会根据当前的指令来判断是否跳转,上面我们提到了 标志寄存器,无论当前累加寄存器的运算结果是正数、负数还是零,标志寄存器都会将其保存

CPU 在进行运算时,标志寄存器的数值会根据当前运算的结果自动设定,运算结果的正、负和零三种状态由标志寄存器的三个位表示。标志寄存器的第一个字节位、第二个字节位、第三个字节位各自的结果都为 1 时,分别代表着正数、零和负数。

CPU 的执行机制比较有意思,假设累加寄存器中存储的 XXX 和通用寄存器中存储的 YYY 做比较,执行比较的背后,CPU 的运算机制就会做减法运算。而无论减法运算的结果是正数、零还是负数,都会保存到标志寄存器中。结果为正表示 XXX 比 YYY 大,结果为零表示 XXX 和 YYY 相等,结果为负表示 XXX 比 YYY 小。 程序比较的指令,实际上是在 CPU 内部做减法运算。

标志寄存器

函数调用机制

接下来,我们继续介绍函数调用机制,哪怕是高级语言编写的程序,函数调用处理也是通过把程序计数器的值设定成函数的存储地址来实现的。函数执行跳转指令后,必须进行返回处理,单纯的指令跳转没有意义,下面是一个实现函数跳转的例子

程序调用

图中将变量 a 和 b 分别赋值为 123 和 456 ,调用 MyFun (a,b) 方法,进行指令跳转。图中的地址是将 C 语言编译成机器语言后运行时的地址,由于 1 行 C 程序在编译后通常会变为多行机器语言,所以图中的地址是分散的。在执行完 MyFun (a,b) 指令后,程序会返回到 MyFun (a,b) 的下一条指令,CPU 继续执行下面的指令。

函数的调用和返回很重要的两个指令是 callreturn 指令,再将函数的入口地址设定到程序计数器之前,call 指令会把调用函数后要执行的指令地址存储在名为栈的主存内。函数处理完毕后,再通过函数的出口来执行 return 指令。return 指令的功能是把保存在栈中的地址设定到程序计数器。MyFun 函数在被调用之前,0154 地址保存在栈中,MyFun 函数处理完成后,会把 0154 的地址保存在程序计数器中。这个调用过程如下

call 与 return

在一些高级语言的条件或者循环语句中,函数调用的处理会转换成 call 指令,函数结束后的处理则会转换成 return 指令。

通过地址和索引实现数组

接下来我们看一下基址寄存器和变址寄存器,通过这两个寄存器,我们可以对主存上的特定区域进行划分,来实现类似数组的操作,首先,我们用十六进制数将计算机内存上的 00000000 - FFFFFFFF 的地址划分出来。那么,凡是该范围的内存地址,只要有一个 32 位的寄存器,便可查看全部地址。但如果想要想数组那样分割特定的内存区域以达到连续查看的目的的话,使用两个寄存器会更加方便。

例如,我们用两个寄存器(基址寄存器和变址寄存器)来表示内存的值

数组

这种表示方式很类似数组的构造,数组 是指同样长度的数据在内存中进行连续排列的数据构造。用数组名表示数组全部的值,通过索引来区分数组的各个数据元素,例如: a [0] - a [4],[] 内的 0 - 4 就是数组的下标。

CPU 指令执行过程

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

  • 取指令 阶段是将内存中的指令读取到 CPU 中寄存器的过程,程序寄存器用于存储下一条指令所在的地址
  • 指令译码 阶段,在取指令完成后,立马进入指令译码阶段,在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分出不同的指令类别以及各种获取操作数的方法。
  • 执行指令 阶段,译码完成后,就需要执行这一条指令了,此阶段的任务是完成指令所规定的各种操作,具体实现指令的功能。
  • 访问取数 阶段,根据指令的需要,有可能需要从内存中提取数据,此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算。
  • 结果写回 阶段,作为最后一个阶段,结果写回(Write Back,WB)阶段把执行指令阶段的运行结果数据 “写回” 到某种存储形式:结果数据经常被写到 CPU 的内部寄存器中,以便被后续的指令快速地存取;