LV500-CPU与指令
一、 CPU的基本构造
程序是保存在硬盘中的,要载入内存才能运行,CPU 也被设计为只能从内存中读取数据和指令。对于 CPU 来说,内存仅仅是一个存放指令和数据的地方,并不能在内存中完成计算功能,例如要计算 a = b + c,必须将 a、b、c 都读取到 CPU 内部才能进行加法运算。为了了解具体的运算过程,我们不妨先来看一下 CPU 的结构。
CPU 是一个复杂的计算机部件,它内部又包含很多小零件,如下图所示:

运算单元是 CPU 的大脑,负责加减乘除、比较、位移等运算工作,每种运算都有对应的电路支持,速度很快。
寄存器(Register) 是 CPU 内部非常小、非常快速的存储部件,它的容量很有限,对于 32 位的 CPU,每个寄存器一般能存储 32 位(4 个字节)的数据,对于 64 位的 CPU,每个寄存器一般能存储 64 位(8 个字节)的数据。为了完成各种复杂的功能,现代 CPU 都内置了几十个甚至上百个的寄存器,嵌入式系统功能单一,寄存器数量较少。
我们经常听说多少位的 CPU,指的就是寄存器的的位数。现在个人电脑使用的 CPU 已经进入了 64 位时代,例如 Intel 的 Core i3、i5、i7 等。
寄存器在程序的执行过程中至关重要,不可或缺,它们可以用来完成数学运算、控制循环次数、控制程序的执行流程、标记 CPU 运行状态等。例如,EIP(Extern Instruction Pointer)寄存器的值是下一条指令的地址,CPU 执行完当前指令后,会根据 EIP 的值找到下一条指令,改变 EIP 的值,就会改变程序的执行流程;CR3 寄存器保存着当前进程页目录的物理地址,切换进程就会改变 CR3 的值,这将在《01-编程语言/01-C语言/30-内存管理/LV550-MMU简介.md》中学习;EBP、ESP 寄存器用来指向栈的底部和顶部,函数调用会改变 EBP 和 ESP 的值,这将在《栈(Stack)是什么?栈溢出又是怎么回事?》中讲解。
那么,在 CPU 内部为什么又要设置缓存呢?虽然内存的读取速度已经很快了,但是和 CPU 比起来,还是有很大差距的,不是一个数量级的,如果每次都从内存中读取数据,会严重拖慢 CPU 的运行速度,CPU 经常处于等待状态,无事可做。在 CPU 内部设置一个缓存,可以将使用频繁的数据暂时读取到缓存,需要同一地址上的数据时,就不用大老远地再去访问内存,直接从缓存中读取即可。
大家在购买 CPU 时,也会经常关心缓存容量,例如 Intel Core i7 3770K 的三级缓存为 8MB,二级缓存为 256KB,一级缓存为 32KB。容量越大,CPU 越强悍。
缓存的容量是有限的,CPU 只能从缓存中读取到部分数据,对于使用不是很频繁的数据,会绕过缓存,直接到内存中读取。所以不是每次都能从缓存中得到数据,这就是缓存的命中率,能够从缓存中读取就命中,否则就没命中。关于缓存的命中率又是一门学问,哪些数据保留在缓存,哪些数据不保留,都有复杂的算法。
二、 CPU 指令
要想让 CPU 工作,必须借助特定的指令,例如 add 用于加法运算,sub 用于除法运算,cmp 用于比较两个数的大小,这称为 CPU 的指令集(Instruction Set)。我们的 C 语言代码最终也会编译成一条一条的 CPU 指令。不同型号的 CPU 支持的指令集会有所差异,但绝大部分是相同的。
我们以 C 语言中的加法为例来演示 CPU 指令的使用。假设有下面的 C 语言代码:
int a = 0X14, b = 0XAE, c;
c = a + b;在 VS2010 Debug 模式下生成的 CPU 指令为:
mov ptr[a], 0X14
mov ptr[b], 0XAE
mov eax, ptr[a]
add eax, ptr[b]
mov ptr[c], eaxmov 和 add 都是 CPU 指令:
(1)mov 用来将一个数值移动到一个存储位置。这个数值可以是一个常数,也可以在内存或者寄存器上;这个存储位置可以是寄存器或者内存。
第一条指令中,ptr[a] 表示变量 a 的地址,0X14 是一个数值,mov ptr[a], 0X14 表示把数值 0X14 移动到 ptr[a] 指向的内存,也就是给变量 a 赋值。第二条指令与此类似。
第三条指令中,eax 是寄存器的名字,该寄存器常用在加法运算中,用来保存某个加数或运算结果,mov eax, ptr[a] 表示把变量 a 的值移动到寄存器 eax 中。
第五条指令表示把寄存器 eax 的值移动到变量 c 中,此时 exa 中的值为 a、b 相加的和。
(2)add 用来将两个数值相加,这两个数值可以在寄存器或者内存中,add 会将相加的结果放在第一个数所在的位置。第四条指令 add eax, ptr[b] 表示把 eax 和 ptr[b] 中的数值相加,并把结果放在 eax 中。
总起来讲:第一二条指令给变量 a、b 赋值,第三四条指令完成加法运算,第五条指令将运算结果赋值给变量 c。
实际上,上面的代码是汇编语言,不是 CPU 指令,汇编语言还要经过简单的转换才能成为 CPU 指令;为了更加容易地说明问题,这些语句也没有严格遵守汇编的语法。
三、数据处理能力
CPU 是计算机的核心,决定了计算机的数据处理能力和寻址能力,也即决定了计算机的性能。CPU 一次(一个时钟内)能处理的数据的大小由寄存器的位数和数据总线的宽度(也即有多少根数据总线)决定,我们通常所说的多少位的 CPU,除了可以理解为寄存器的位数,也可以理解数据总线的宽度,通常情况下它们是相等的。
数据总线位于主板之上,不在 CPU 中,也不由 CPU 决定,严格来讲,这里应该说 CPU 能够支持的数据总线的最大根数,也即能够支持的最大数据处理能力,为了表达方便,本文才使用"CPU 的数据总线"这一说法。
数据总线和主频都是 CPU 的重要指标:数据总线决定了 CPU 单次的数据处理能力,主频决定了 CPU 单位时间内的数据处理次数,它们的乘积就是 CPU 单位时间内的数据处理量。
我们常常听说,CPU 主频在计算机的发展过程中飞速提升,从最初的几十 KHz,到后来的几百 MHz,再到现在的 4GHz,终于因为硅晶体的物理特性很难再提升,只能向多核方向发展。在这个过程中,CPU 的数据总线宽度也在成倍增长,从早期的 8 位、16 位,到后来的 32 位,现在我们计算机大部分都在使用 64 位 CPU。
需要注意的是,数据总线和地址总线不是一回事,数据总线用于在 CPU 和内存之间传输数据,地址总线用于在内存上定位数据,它们之间没有必然的联系,宽度并不一定相等。实际情况是,地址总线的宽度往往随着数据总线的宽度而增长,以访问更大的内存。
1. 16 位 CPU
早期的 CPU 是 16 位的,一次能处理 16Bit(2 个字节)的数据。这个时候计算机产业还处在早期,个人电脑也没有进入千家万户,也没有提出虚拟地址的概念,程序还是直接运行在物理内存上,操作系统对内存的管理非常简陋,程序员轻易就能编写一个恶意程序去修改其他程序的内存。
学过汇编话应该知道,典型的 16 位处理器是 Intel 8086,它的数据总线有 16 根,地址总线有 20 根,寻址能力为 2^20 = 1MB。
2. 32 位 CPU
随着计算机产业的进步,出现了 32 位的 CPU,一次能处理 32Bit(4 个字节)的数据。这个时候就提出了虚拟地址的概念,并被应用到 CPU 和操作系统中,由它们共同完成虚拟地址和物理地址的映射,这使得程序编写更加容易,运行更加安全。
典型的 32 位处理器是 Intel 的 80386 和 Intel Pentium 4(奔腾4):80386 的数据总线和地址总线宽度都是 32 位,寻址能力达 4GB;Pentium 4 的地址总线宽度是 36 位,理论寻址能力达 64GB。
3. 64 位 CPU
现代计算机都使用 64 位的 CPU,它们一次能处理 64Bit(8 个字节)的数据。典型的 64 位处理器是 Intel 的 Core i3、i5、i7 等,它们的地址总线宽度为 40~50 位左右。64 位 CPU 的出现使个人电脑再次发生了质的飞跃。
四、小结
本节我们学习了 CPU 的简单构造以及 CPU 指令,重点是认识寄存器这个小而快速的存储部件,它在程序运行过程中起着至关重要的作用,CPU 就是用它来记录程序的运行状态,然后根据它的值再决定下一步的操作。
本文档由 markdowncli 技能辅助生成