2

中央处理器CPU

  • RAM + 寄存器 + ALU 做个CPU
  • 解释“指令 -> 解释 -> 执行”这个循环
  • 时钟是什么,时钟速度和赫兹
  • 超频提升性能,降频省电

重点:

  1. 拼个CPU出来。
  2. CPU怎么执行命令?

ALU:
作用:输出二进制,它会执行计算。

clipboard.png

两种内存:
寄存器: 很小的一块内存,能存一个值。
RAM: 是一大块内存,能在不同地址存大量数字。 (寄存器增大后改造成RAM)

RAM, 寄存器, ALU放在一起,组件计算机的心脏CPU(中央处理单元)。

拼个CPU

CPU: 四个寄存器 + 2个寄存器(指令寄存器和指令地址寄存器) + RAM + ALU(指令需要其功能才使用) + “时钟”

CPU作用: 负责执行程序。
程序由一个个操作组成,这些操作叫做“指令”,因为它们“指示”计算机要做什么。如果是数学指令,比如加/减,CPU会让ALU进行数学计算。也可能是内存指令,CPU会和内存通信,然后读/写值。
CPU里有很多组件。重点放在功能,而不是一根根线具体怎么连。当用一根线连接两个组件时,这条线只是所有必须线路的一个抽象。这种高层次视角叫“微体系架构”。

首先要内存,使用RAM,为了保持简单,假设它只有16个位置,每个位置存8位。再来四个8位存储器,叫A, B, C, D。寄存器用来 临时存数据 和 操作数据。

clipboard.png

数据是以二进制存在内存里。
程序也可以存在内存里。

可以给CPU支持的所有指令,分配一个ID:

clipboard.png

在这个假设的例子,用前四位存“操作代码”(operation code),简称“操作码”(opcode),后四位代表数据来自哪里,可以是寄存器或内存地址。

还需要两个寄存器,来完成CPU

  1. 一个寄存器追踪程序运行到哪里了,叫它“指令地址寄存器”。(存当前指令的内存地址)
  2. 另外一个寄存器存当前指令, 叫“指令寄存器”。

clipboard.png

当启动计算机时,所有寄存器从0开始。

clipboard.png

CPU的第一个阶段叫“取指令阶段”(FETCH PHASE),负责拿到指令。

首先,将“指令地址寄存器”连到RAM,寄存器的值为0,因此RAM返回地址0的值,0010 1110会复制到“指令寄存器”里。

clipboard.png

现在指令拿到了,要弄清楚是什么指令,才能执行(execute),而不是杀死(kill)它,这是“解码阶段”。
前4位0010LODA A指令,意思是,把RAM的值放入寄存器A。
后4位1110RAM的地址,转成十进制是14。

clipboard.png

接下来,指令由“控制单元”进行解码,就像之前的所有东西,“控制单元”也是逻辑门组成的。

clipboard.png

clipboard.png

比如:为了识别“LOAD A”指令,需要一个电路,检查操作码是不是0010,可以用很少的逻辑门来实现。

clipboard.png

指令:

clipboard.png

知道了什么是指令,就可以开始执行了,开始“执行阶段”。

  1. 用“检查是否LOAD_A指令的电路”,可以打开RAM的“允许读取线”,把地址14传过去,RAM拿到值,0000 0011,十进制的3。
  2. 因为是LOAD_A指令,想把这个值只放到寄存器A,其它寄存器不受影响,所以需要一根线,把RAM连接到4个寄存器,用“检查是否LOAD_A指令的电路”启用寄存器A的“允许写入线”。

成功把RAM地址14的值,是十进制的3,放到了寄存器A:

clipboard.png

CPU怎么执行命令?
“取指令 -> 解码 -> 执行”

指令完成了,可以关掉所有线路:

clipboard.png

去拿下一条指令,“指令地址寄存器”+1,“执行阶段”结束。

LOAD_A只是CPU可以执行的各种指令之一,不同指令由不同逻辑电路解码,这些逻辑电路会配置CPU内的组件来执行对应操作。
现在计算机支持更多指令,直接操作内存,但原理上是如此。

复杂的逻辑电路解码,把它封装成一个整体的“控制单元”。

clipboard.png

汇编指令与机器指令就是一一对应。

控制单元就像管弦乐队的指挥,“指挥”CPU的所有组件。“取指令 -> 解码 -> 执行”完成。
后续可以再从“取指令”开始:

  1. “指令地址寄存器”现在值是1,所以RAM返回地址1里的值:0001 1111
  2. 到了“解码”阶段,0001LOAD_B指令,从RAM里把一个值复制到寄存器B,这次内存地址是1111,十进制的15。
  3. 现在到“执行阶段”,“控制单元”叫RAM读地址15,并配置寄存器B接受数据。
  4. 完成,把值0001 1110也就是十进制的14存到了寄存器B。
  5. 最后,再把“指令地址寄存器”+1。
  6. “执行阶段”结束。

clipboard.png

clipboard.png

后续可以再从“取指令”开始:

  1. “指令地址寄存器”现在值是2,所以RAM返回地址1里的值:1000 0100
  2. 1000是ADD指令,这次后面4位不是RAM地址,而是2位,2位,分别代表2个寄存器,2位可以表示4个值(00, 10, 01, 11),所以足够表示4个寄存器。
  3. 第一个地址是01,代表寄存器B,第二个地址是00,代表寄存器A。因此,1000 0100,代表把寄存器B,加到寄存器A里。
  4. 为了执行这个指令,要整合ALU。“控制单元”负责选择正确的寄存器作为输入,并配置ALU执行正确的操作。
  5. 对于ADD指令,“控制单元”会,启用寄存器B,作为ALU的第一个输入,还启用寄存器A,作为ALU的第二个输入。ALU可以执行不同操作,所以控制单元必须传递ADD操作码告知ALU要做什么。
  6. 最后,结果应该存到寄存器A。但不能直接写入寄存器A,这样新值会进入ALU,不断和自己相加。因此,控制单元用自己的一个寄存器暂时保存结果,关闭ALU,然后把值写入正确的寄存器。这里3 + 14 = 17,二进制是0001 0001
  7. 最后,再把“指令地址寄存器”+1。
  8. ADD结束。

clipboard.png

clipboard.png

clipboard.png

后续可以再从“取指令”开始:

  1. “指令地址寄存器”现在值是3,所以RAM返回地址3里的值:0100 1101
  2. 解码得知是STORE A指令(把寄存器A的值放入到内存)RAM地址13中。
  3. 接下来,把地址传给RAM,但这次不是“允许读取”而是“允许写入”;同时,打开寄存器A的“允许读取”,这样就可以把寄存器A里的值,传给RAM

clipboard.png

clipboard.png

运行第一个电脑程序:
它从内存中加载两个值,相加,然后把它结果放回到内存中。

人工切换CPU的状态:“取指令 -> 解码 -> 执行”。

最后:
STORE_A 13指令执行完之后,再执行HALT指令。否则CPU会不停运行下去,处理后面的0。因为0不是操作码,所以电脑会崩掉。
一般来说RAM没写的话,里面会储存乱码而不是0(不HALT就会崩掉)。

时钟

是“时钟”来负责管理CPU的节奏,时钟以精确的间隔,触发电信号,“控制单元”会用这个信号,推进CPU的内部操作。就像罗马帆船的船头,有一个人负责按节奏的击鼓,让所有划船的人同步。就像节拍器一样。

clipboard.png

节奏不能太快,因为就算是电也要一定时间来传输,CPU“取指令 -> 解码 -> 执行”的速度叫“时钟速度”。单位是赫兹,赫兹是用来表示频率的单位。1赫兹代表一秒1个周期。

前面的步骤:
读取 -> 读取 -> 相加 -> 存储时钟速度大概是0.03赫兹,(1/360 = 0.03Hz)。

第一个单芯片CPU是“英特尔 4004”1971年发布的4位CPU。
它的微架构:

clipboard.png

虽然是第一个单芯片的处理器,但它的时钟速度达到了740千赫兹 - 每秒74万次。

一兆赫兹是1秒1百万个时钟周期,现在的电脑或手机,肯定有几千兆赫兹。1秒10亿次时钟周期。

计算机超频,意思是修改时钟速度,加快CPU的速度。芯片制造商经常会给CPU留一点余地,可以接受一点超频。但超频太多会让CPU过热,或产生乱码,因为信号跟不上时钟(超频一般不会让计算机起火)。
降频,有时候没有必要让处理器全速运行,可能用户走开了,或者在跑一个性能要求较低的程序,把CPU的速度降下来,可以省很多电。省电对用户电池的设备很重要,比如笔记本和手机。
为了尽可能省电,很多现代处理器可以按需求,加快或减慢时钟速度。这叫“动态调整频率”。加上时钟后,CPU才是完整的。

寄存器ALU时钟封装在一起:

clipboard.png

RAM,是在CPU外边的独立组件。
CPURAM之间用“地址线”“数据线”“允许读/写线”进行通信。

指令和程序

重点:运行一遍程序。

  • 介绍“指令集”。LOAD_A, LOAD_B, SUB, JUMP, ADD, HALT等指令。
  • 带条件跳转,JUMP NEGATIVE是负数才跳转,还有其他类型的JUMP
  • 真正现在CPU用更多指令集。位数更长。
  • 1971年的英特尔4004处理器,有46个指令。
  • 如今英特尔酷睿i7,有上千条指令。

ALU, 控制单元, RAM, 时钟结合在一起,做了一个基本,但可用的“中央处理单元”,简称CPU。它是计算机的核心。CPU之所以强大,是因为它是可编程的,如果写入不同指令,就会执行不同任务。CPU是一块硬件,可以被软件控制。

介绍指令集
  • LOAD_A寄存器A
  • LOAD_B寄存器B
  • ADD相加。
  • STORE_A存储寄存器A,并写入RAM中。
  • SUB减法,和ADD一样也要2个寄存器来操作。
  • JUMP(跳转),让程序跳转到新位置。如果想改变指令顺序,或跳过一些指令,这个就很实用。例如:JUMP 0,可以调回开头。JUMP在底层的实现方式是:把指令后4位代表的内存地址的值覆盖掉“指令地址寄存器”里的值。
  • JUMP_NEGATIVE,它只在ALU的“负数标志”为真时,进行JUMP。算数结果为负,“负数标志”才是真,结果不是负数时,“负数标志”为假,如果是假,JUMP_NEGATIVE就不会执行程序照常运行。
  • HALT(停止),计算机还需要知道什么时候该停下来。
条件跳转

clipboard.png

指令和数据都是存在同一个内存里的。它们在根本层面上毫无区别,都是二进制数HALT很重要,能区分指令和数据。

JUMP让程序更完整一些:
现在从CPU的视角走一遍程序。

clipboard.png

  • 首先LOAD_A 14,把1存入寄存器A(地址14里值是1)。
  • 然后LOAD_B 15,把1存入寄存器B(地址15里值是1)。
  • 然后ADD B A把寄存器B和A相加,结果放到寄存器A里。现在寄存器A的值是2,(以二进制存储的)
  • 然后STORE_A 13指令,把寄存器A的值存入内存地址13。
  • JUMP 2指令,CPU会把“指令寄存器”的值,现在的“4”,改成“2”。因此下一步不是HALT
  • 再读内存地址2里的指令,也就是ADD B A,寄存器A里的2,寄存器B里的1。1+2=3,寄存器A变成3。存入内存。又碰到JUMP 2,回到ADD B A,1+3=4。每次循环都+1,不断增多。如果按照这样执行下去,永远不会碰到HALT,总是会碰到JUMP 2。这个叫无限循环(INFINITE LOOP)∞

clipboard.png

JUMP 2指令:
clipboard.png

为了停止下来,需要有条件的JUMP,只有特定条件满足了,才执行JUMP
比如:JUMP NEGATIVE就是条件跳转的一个例子。
还有其它类型的条件跳转,比如,JUMP IF EQUAL(如果相等)JUMP IF GREATER(如果更大)

for循环是有个数限制,while和python里的repeat是无限循环。

执行下图程序:

clipboard.png

  • 程序先把内存值放入寄存器A和B。寄存器A是11,寄存器B是5。
  • SUB B A,用A减B,11-5=6。现在寄存器A的值是6
  • JUMP_NEG 5,ALU上次计算的是6,是正数,所以“负数标志”是假。因此处理器不会执行JUMP,继续下一条指令。
  • JUMP 2JUMP 2没有条件,直接执行SUB B A, 6-5=1。现在寄存器A的值是1。
  • JUMP_NEG 5,因为1还是正数,因此JUMP NEGATIVE不会执行,来到下一条指令。
  • JUMP 2,直接执行SUB B A, 1-5=-4。现在寄存器A的值是-4。这次ALU的“负数标志”是真。现在寄存器A的值是-4。
  • JUMP_NEG 5, CPU的执行跳转到内存地址5的指令ADD B A。跳出了无限循环。
  • ADD B A,-4+5=1,存入寄存器A,现在寄存器A的值是1。
  • STORE_A 13,存入内存地址13
  • HALT指令,停止程序。

clipboard.png

指令顺序:

  1. LOAD_A 14 值是11
  2. LOAD_B 15 值是5
  3. SUB B A 寄存器A值6(11-5)
  4. JUMP_NEG 5 "负数标志"false
  5. JUMP 2 跳转会地址2再次依次执行
  6. SUB B A 寄存器A值1(6-5)
  7. JUMP_NEG 5 "负数标志"false
  8. JUMP 2 跳转会地址2再次依次执行
  9. SUB B A 寄存器A值-4(1-5)
  10. JUMP_NEG 5 "负数标志"true
  11. ADD B A 寄存器A值1(-4+5)
  12. STORE_A 13 存入内存地址13
  13. HALT指令,程序结束

这段程序只有7个指令,但CPU执行了13个指令。因为在内部循环了2次。
这段代码是算余数,11除5余1 -> 11-5-5-5+5 = 1

软件可以做到硬件做不到的事。
ALU可没有除法功能,是程序给了除法这个功能。

假设的CPU很基础,所有指令都是8位,操作码只占了前4位,即便用尽4位,也只能代表16个指令。而且有几条指令,是用后4位来指定内存地址。只能表示16个指令,并不多,甚至不用使用JUMP 17,因为4位二进制无法表示数字17,因此,真正的现代CPU用两种策略。

  1. 最直接的方法是用更多位来代表指令,比如32位或64位。这叫 “指令长度”
  2. “可变指令长度”,例如:某个CPU用8位长度的操作码,如果看到HALT指令,HALT不需要额外数据,那么会马上执行。如果看到JUMP,它得知道位置值,这个值在JUMP的后边。这个叫“立即值”,这样设计,指令可以是任意长度。但会让读取阶段复杂一些。

某些指令不需要操作内存所以可以省下内存那四位。
但是JUMP也需要4位操作码,这样还是只有4位来表示内存地址。
一般来说 (指令=操作码+操作值地址)。当(指令=操作码+操作值)时,这个操作值就是立即值。
这样大于8位的地址,可以通过多次重复读取的方式获得,这也是读取阶段相对麻烦的地方。
立即值是指令+位置

英特尔4004处理器

1971年,英特尔发布了4004处理器。这是第一次把CPU做成一个芯片,给后来的英特尔处理器打下基础。

  • 支持46个指令。足够做一台能够用的电脑。
  • 使用了前面使用过的指令。比如JUMP, ADD, SUB, LOAD
  • 也用8位的“立即值”来执行JUMP,以表示更多内存地址。

处理器从1971年到现在发展巨大,现代CPU,比如英特尔酷睿i7,有上千个指令和指令变种。长度从1到15个字节。
例如:光ADD指令就很多变种。
指令越来越多,是因为给CPU设计了越来越多功能。

高级CPU设计

  • 早期是加快晶体管切换速度,来提升CPU速度。
  • 给CPU专门的除法电路+其它电路来做复杂操作,比如游戏,视频解码。
  • 给CPU加缓存,提高数据存取速度。
  • 脏位(Dirty bit)
  • 流水线设计
  • 并行处理(parallelize)
  • 乱序执行(out-of-order execuition)
  • 推测执行(speculative execution)
  • 分支预测(branch prediction)
  • 多个ALU
  • 多核(Code)
  • 多个独立CPU
  • 超级计算机,中国的“神威 太湖之光”

从1秒1次运算,到现在千兆赫甚至兆赫的CPU。现在看视频的设备也有GHz速度。
1秒10亿条指令,这是很大的计算量。

减少晶体管切换时间

早期计算机的提速方式是,减少晶体管的切换时间。
晶体管组成了逻辑门,ALU以及其它计算机组件。

这种提速方法最终会碰到瓶颈,所以处理器厂商,发明了各种新技术来提升性能,不但让简单指令运行更快,也让它能进行更复杂的运算。

除法的程序,给CPU执行,方法是做一连串减法。比如16除4会变成。16-4-4-4-4,碰到0或负数才停下来。这种方法要多个时钟周期,很低效。所以现代CPU直接在硬件层面设计了除法,可以直接给ALU除法指令。

除法电路

拥有除法的ALU更大也更复杂一些。但也更厉害,复杂度vs速度 的平衡在计算机发展史上经常出现。
例如:现代处理器有专门电路来处理图形操作,解码压缩视频,加密文档等等。如果用标准操作来实现,要很多个时钟周期。可能听说过处理器MMX, 3DNOW, SEE。它们有额外电路做更复杂的操作。用于游戏和加密等场景。

指令不断增加,一旦习惯了它的便利就很难删掉,所以为了兼容旧指令集,指令数量越来越多。

英特尔4004,第一个集成CPU,有46条指令,足够做一台能用的计算机。但现代处理器有上千条指令,有各种巧妙复杂的电路。

超高的时钟速度带来另外一个问题: 如何快速传递数据给CPU。就像有强大的蒸汽机,但无法快速加煤。RAM成了瓶颈。

RAM是CPU之外的独立组件,意味着数据要用线来传递,叫“总线”。
总线可能只有几厘米,别忘了电信号的传输接近光速。但CPU每秒可以处理上亿条指令。很小的延迟也会造成问题。

clipboard.png

给CPU加缓存

RAM还需要时间找地址,取数据,配置,输出数据。一条“从内存读数据”的指令可能要多个时钟周期,CPU空等数据。解决延迟的方法之一是:给CPU加一点RAM,叫“缓存”
因为处理器里空间不大,所以缓存一般只有KBMB,而RAM都是GB起步。
缓存提高了速度,CPU从RAM拿数据时,RAM不用传一个,可以传一批。虽然花的时间久一点,但数据可以存在缓存。

clipboard.png

这很实用,因为数据常常是一个个按顺序处理。
例如:算餐厅的当日收入。先取RAM地址100的交易额,RAM与其只给1个值,直接给一批值。把地址100到200都复制到缓存,当处理器要下一个交易额时,地址101,缓存会说:“我有下一个交易额的值”,而不用向RAM取数据。

clipboard.png

因为缓存离CPU近,一个时钟周期就能给数据,CPU不用空等
比反复去RAM拿数据快得多,如果想要的数据已经在缓存,叫“缓存命中”。如果想要的数据不在缓存,叫“缓存未命中”。
缓存也可以当临时空间,存一些中间值,适合长/复杂的运算。

如果计算完餐厅一天销售额,想把结果存到地址150,就像之前,数据不是直接存到RAM,而是存在缓存,这样不但存起来快一些,如果还要接着算,取值也快一些。
但这样带来一个问题,缓存和RAM不一致了,这种不一致必须记录下来,之后要同步。因此缓存里每块空间,有一个特殊标记,叫“脏位(Dirty bit)”。

同步一般发生在,当缓存满了而CPU又要缓存时,在清理缓存腾出空间之前,会先检查“脏位”,如果是“脏”的,在加载新内容之前,会把数据写会到RAM

clipboard.png

流水线设计

提升性能方法叫“指令流水线”。
例如:洗一整个酒店的床单,但只有1个洗衣机, 1个干燥机。

选择1: 按顺序来,放洗衣机等30分钟洗完,然后拿出湿床单,放进干燥机等30分钟烘干。这样1小时洗一批。

需要用“并行处理”进一步提高效率,先放一批床单到洗衣机,等30分钟洗完,然后湿床单放进干燥机,但这次,与其干等30分钟烘干,可以放另一批进洗衣机。让两台机器同时工作,30分钟后,一批床单完成,另一批完成一半,另一批准备开始,效率是翻倍的。

clipboard.png

处理器也可以这样子设计,CPU是按序处理,取指令(fetch) -> 解码(decode) -> 执行(execute),不断重复。这种设计,三个时钟周期执行1条指令。

clipboard.png

并行处理

但因为每个阶段用的是CPU的不同部分,意味着可以并行处理,“执行”一个指令时,同时可以“解码”下一个指令,“读取”下下个指令。不同任务重叠进行,同时用上CPU里所有部分。这样的流水线,每个时钟周期执行1个指令,吞吐量*3。

clipboard.png

和缓存一样,这也会带来一些问题:

  • 指令之间的依赖关系。例如:在读某个数据,而正在执行的指令会改这个数据。也就是说拿是旧数据,因此流水线处理器,要先弄清数据依赖性,必要时停止流水线,避免出问题。
    高端CPU,比如笔记本和手机里的,会更进一步,动态排序 有依赖关系的指令。 最小化流水线的停工时间。这叫“乱序执行(out-of-order execuition)”。
    这种电路非常复杂,但因为非常高效,几乎所有现代处理器都有流水线。
  • “条件跳转”,比如“JUMP NEGATIVE”,这些指令会改变程序的执行流,简单的流水线处理器,看到JUMP指令会停一会儿等待条件值确定下来,一旦JUMP的结果出来了,处理器就继续流水线。但因为空等会造成延迟,所以高端处理器会用一些技巧(调度算法,冒险分支)。
    可以把JUMP想象成是“岔路口”,高端CPU会猜哪条路的可能性大一些,然后提前把指令放进流水线,这叫“推测执行(speculative execution)”。当JUMP的结果出了,如果CPU猜对了,流水线已经塞满正确可以执行的指令,可以马上运行。如果CPU猜错了,就要清空流水线,就像走错路掉头。
    为了尽可能减少清空流水线的次数,CPU厂商开发了复杂的方法,来猜测哪条分支更有可能,叫做“分支预测(branch prediction)”。现代CPU的正确率超过90%。
多个ALU

理想情况下,流水线一个时钟周期完成1个指令,然后“超标量处理器”出现了,一个时钟周期完成多个指令。即便有“流水线设计”,在指令执行阶段,处理器里有些区域还是可能会空闲,比如,执行一个“从内存取值”指令期间ALU会闲置。所以一次性处理多条指令(取指令+解码)会更好。

可以再进一步,加多几个相同的电路,执行出现频次很高的指令。(一核有难,多核围观)
例如:很多CPU有四个,八个甚至更多完全相同的ALU。可以同时执行多个数学运算。

clipboard.png

目前说过的方法:“缓存”,“指令流水线”,“多个ALU”,都是优化1个指令流的吞吐量。

多核(Code)

另外一个提升性能的方法是:同时运行多个指令流,用多核处理器。(七核看戏)

双核或四核处理器,意思是一个CPU芯片里,有多个独立处理单元。很像是有多个独立CPU,但因为它们整合紧密,可以共享一些资源。比如缓存,使得多核可以合作运算。但多核不够时,可以用多个CPU。

clipboard.png

高端计算机,视频观看的计算机,需要更多的马力,让上百人以上能够同时流畅的观看,2个或4个CPU是最常见的,但有时有更高的性能需求,所以造了超级计算机。
比如模拟宇宙的形成,需要强大的计算能力。给普通的台式机加几个CPU没什么用,需要更多的处理器。目前为止,最快的计算机在中国无锡的“神威 太湖之光”,有40960个CPU,每个CPU有256和核心,总共超过1千万个核心,每个核心的频率是1.45GHz,每秒可以进行9.3亿亿次浮点运算。

现代的处理器不但大大的提高了速度,而且也变得更复杂,用各种技巧,榨干每个时钟周期,做尽可能多的运算。


alogy
1.3k 声望121 粉丝

// Designer and Developer