计算机组成 -- 异常
异常
异常是一个硬件和软件组合在一起的处理过程
异常的发生和捕捉,是在硬件层面完成的
异常的处理,是在软件层面完成的
计算机会为每一种可能发生的异常,分配一个异常代码(Exception Number),别称中断向量(Interrupt Vector)
异常发生的时候,通常是CPU检测到一个特殊的信号
在组成原理里面,一般叫作发生了一个事件(Event),CPU在检测到事件的时候,就已经拿到了对应的异常代码
异常代码
IO发出的信号的异常代码,是由操作系统来分配,即由软件来设定
像加法溢出这样的异常代码,是由CPU预分配的,即由硬件来设定
拿到异常代码后,CPU会触发异常处理流程
计算机在内存里,会保留一个异常表(Exception Table),别称中断向量表(Interrupt Vector Table)
存放的是不同的异常代码对应的异常处理程序所在的地址
CPU拿到异常代码后,会先把当前程序的执行现场(CPU当前运行程序用到的所有寄存器),保存到程序栈里面
然后根据异常代码查询,找到对应的异常处理程序,最后把后续指令执行的指挥权,交给这个异常处理程序
异常可以由硬件触发,也可以由软件触发 ...
计算机组成 -- 超线程 + SIMD
超线程 – 线程级并行Pentium 4
Pentium 4失败的原因:CPU的流水线级数太深
超长的流水线,使得之前很多解决冒险、提升并发的方案都用不上
解决冒险、提升并发的方案,本质上是一种指令级并行的技术方案,即CPU希望在同一个时间,去并行执行两条指令
但这两条指令,原本在代码里是有先后顺序的
无论是流水线架构、分支预测以及乱序执行,还是超标量和超长指令字
都是想通过在同一时间执行两条指令,来提升CPU的吞吐率
但在Pentium 4上,上面这些方法都可能因为流水线太深,而起不到效果
更深的流水线意味着同时在流水线里面的指令就很多,相互的依赖关系就多
因此,很多时候不得不把流水线停顿下来,插入很多NOP操作,来解决这些依赖带来的冒险问题
超线程
无论是多个CPU核心运行不同的程序,还是单个CPU核心里切换运行不同线程的任务
在同一时间点上,一个物理的CPU核心只会运行一个线程的指令,其实并没有做到真正的指令级并行
超线程的CPU,把一个物理层面的CPU核心,伪装成两个逻辑层面的CPU核心
这个CPU会在硬件层面增加很多电路,使得可以在一个CPU核心内部,维护两个不同线程的指令的状 ...
计算机组成 -- Superscalar + VLIW
吞吐率
程序的CPU执行时间 = 指令数 × CPI × Clock Cycle Time
CPI = Clock Per Instruction
IPC = 1/CPI = Instruction Per Clock
一个时钟周期内能够执行的指令数,代表了CPU的吞吐率
最佳情况下,IPC只能到1
无论做了哪些流水线层面的优化,即使做到了指令执行层面的乱序执行
CPU仍然只能在一个时钟周期内取一条指令!!
无论指令后续无论优化得多好,一个时钟周期也只能执行一条指令,IPC只能是1
但Intel CPU或者ARM CPU,一般IPC能做到2以上
多发射 + 超标量
整数计算过程和浮点数的计算过程差异比较大
整数计算和浮点数计算的电路,在CPU层面是分开的
一直到80386,CPU都是没有专门的浮点数计算的电路的,当时的浮点数计算,都是通过软件进行模拟的
在80386时代,Intel给386配了单独的387芯片,专门用来做浮点数运算
386dx:带387浮点数计算芯片
386sx:不带387浮点数计算芯片
CPU会有多个ALU,在指令执 ...
计算机组成 -- 冒险
冒险
流水线架构的CPU,是主动进行的冒险选择,期望通过冒险带来更高的回报
对于各种冒险可能造成的问题,都准备好了应对方案
分类
结构冒险(Structural Hazard)
数据冒险(Data Hazard)
控制冒险(Control Hazard)
结构冒险
结构冒险,本质上是一个硬件层面的资源竞争问题
CPU在同一个时钟周期,同时在运行两条计算机指令的不同阶段,但这两个不同的阶段可能会用到同样的硬件电路
内存的数据访问
第1条指令执行到访存(MEM)阶段的时候,流水线的第4条指令,在执行取指令(Fetch)操作
访存和取指令,都是要进行内存数据的读取,而内存只有一个地址译码器,只能在一个时钟周期内读取一条数据
无法同时执行第1条指令的读取内存数据和第4条指令的读取指令代码
解决方案
解决方案:增加资源
哈佛架构
把内存分成两部分,它们有各自的地址译码器,这两部分分别是存放指令的程序内存和存放数据的数据内存
缺点:无法根据实际情况去动态调整
普林斯顿架构 – 冯.诺依曼体系架构
今天使用的CPU,仍然是冯.诺依曼体系架构,并没有把内存拆成程序内存和数据内存两部分
混合架 ...
计算机组成 -- 指令流水线
单指令周期处理器
一条CPU指令的执行:Fetch -> Decode -> Execute
这个执行过程,最少需要花费一个时钟周期,因为在取指令的时候,需要通过时钟周期的信号,来决定计数器的自增
单指令周期处理器(Single Cycle Processor):在一个时钟周期内,处理器正好能处理一条指令,即CPI为1
时钟周期是固定的,但指令的电路复杂程度是不同的,因此一条指令的实际执行时间是不同的
随着门电路层数的增加,由于门延迟的存在,位数多、计算复杂的指令需要的执行时间会更长
不同指令的执行时间不同,但需要让所有指令都在一个时钟周期内完成,只能把时钟周期和执行时间最长的指令设成一样
快速执行完成的指令,需要等待满一个时钟周期,才能执行下一条指令
CPI能够保持在1,但时钟频率没办法设置太高,因为有些复杂指令是没办法在一个时钟周期内运行完成的
在下一个时钟周期到来,开始执行下一条指令的时候,前一条指令的执行结果可能还没有写入到寄存器里
那么下一条指令读取的数据就是不准确的,会出现错误
无论是PC上使用的Intel CPU,还是手机上使用的ARM CPU,都不是单指令周期处理器 ...
计算机组成 -- 建立数据通路
三种周期指令周期
执行一条指令的过程
Fetch(取得指令)
从PC寄存器里面找到对应的指令地址,根据指令地址从内存里把具体的指令,加载到指令寄存器中
然后把PC寄存器自增,便于未来执行下一条指令
Decode(指令译码)
根据指令寄存器里面的指令,解析成要进行什么样的操作,是R、I、J中的哪一种指令
具体要操作哪些寄存器、数据或者内存地址
Execute(执行指令)
实际运行对应的R、I、J这些特定的指令,进行算术逻辑操作、数据传输或者直接的地址跳转
重复上面步骤
指令周期(Instruction Cycle):Fetch -> Decode -> Execute
涉及的组件
取指令的阶段,指令是放在存储器里的
通过PC寄存器和指令寄存器取出指令的过程,是由控制器(Control Unit)操作的
指令的解码过程,也是由控制器进行的
一旦到了执行指令阶段,R、I型指令都是由算术逻辑单元(ALU)操作
进行算术操作、逻辑操作的R型指令
进行数据传输、条件分支的I型指令
如果是简单的无条件地址跳转,可以直接在控制器里面完成,不需要用到运算器
机器周期
Machin ...
计算机组成 -- 定点数 + 浮点数
浮点数的不精确性12>>> 0.3 + 0.60.8999999999999999
32bit是无法表达所有实数的,只能表达2^32次方不同的数字,差不多40亿
40亿个数字比起无限多的实数集合只是沧海一粟,应该让这40亿个数映射到实数集合上的哪些数字,在实际应用中才最划算
定点数
用4个bit表示0~9的整数,那么32个bit就可以表示8个这样的整数
然后把最右边的2个0~9的整数,当成小数部分;把最左边6个0~9的整数,当成整数部分
这样用32bit可以表示0.00~999999.99这1亿个实数
这种用二进制表示十进制的编码方式,称为BCD编码(Binary-Coded Decimal)
应用非常广泛:超市、银行
缺点
浪费
本来32bit可以表示40亿个不同的数字,但在BCD编码下,只能表示1亿个数
如果精确到分,能够表达的最大金额为100W,非常有限
无法同时表示很大的数字和很小的数字
浮点数
浮点数的科学计数法的表示,有一个IEEE的标准,定义了两个基本的格式
一个是用32bit表示单精度的浮点数,即float
一个是用64bit表示双精度的浮点数,即doubl ...
计算机组成 -- 乘法器
13 × 9
顺序乘法
二进制的乘法,在单个位上,乘数只能是0或1,所以实际的乘法,就退化成位移和加法
被乘数13表示成二进制是1101,乘数9表示成二进制是1001
最后边的个位是1,个位乘以被乘数,被乘数1101得以保留
二位和四位都是0,所以乘以被乘数都是0,保留下来的都是0000
八位是1,仍然需要把被乘数1101复制下来,但需要把复制好的结果向左移动三位
然后把上面四个乘法加位移的结果再加起来
最后一步的加法可以用上一讲的加法器来实现
二进制乘法因为只有0和1两种情况,所以可以做成输入输出都是4个开关,中间有一个开关
中间的开关决定,下面的输出是完全复制输入,还是将输出全部设置为0
位移:左移一位,就错开一位接线;左移两位,就错开两位接线
节约开关(晶体管)
为了节省资源(开关,即晶体管)
类似13×9这样两个四位数的乘法,不需要把四次单位乘法的结果都用四组独立的开关单独记录然后再加起来
否则,如果计算一个32位的整数乘法,就要32组开关,非常浪费
如果顺序地计算,只需要一组开关就好
先拿乘数最右侧的个位乘以被乘数,然后把结果写入用来存放计算结果的开关里面
然后,把被乘 ...
计算机组成 -- 加法器
基本门电路
基本门电路:输入都是两个单独的bit,输出是一个单独的bit
如果要对2个8bit的数字,计算与或非的简单逻辑(无进位),只需要连续摆放8个开关,来代表一个8bit数字
这样的两组开关,从左到右,上下单个的位开关之间,都统一用『与门』或者『或门』连起来
就能实现两个8bit数的AND运算或者OR运算
异或门 + 半加器一bit加法
个位
输入的两位为00和11,对应的输出为0
输入的两位为10和01,对应的输出为1
上面两种关系都是异或门(XOR)的功能
异或门是一个最简单的整数加法,所需要使用的基本门电路
进位
输入的两位为11时,需要向更左侧的一位进行进位,对应一个与门
通过一个异或门计算出个位,通过一个与门计算出是否进位
把这两个门电路打包,叫作半加器(Half Adder)
全加器
半加器只能解决一bit加法的问题,不能解决2bit或以上的加法(因为有进位信号)
二进制加法的竖式,从右往左,第二列称为二位,第三列称为四位,第四列称为八位
全加器:两个半加器和一个或门
把两个半加器的进位输出,作为一个或门的输入
只要两次加法中任何一次需要进位,那么在二位上,就 ...
计算机组成 -- 电路
电报机
电报机的本质:蜂鸣器 + 电线 + 按钮开关
蜂鸣器装在接收方,开关留在发送方,双方通过电线连在一起
继电器
电线的线路越长,电线的电阻越大,当电阻很大,而电压不够时,即使按下开关,蜂鸣器也不会响的
继电器(Relay):为了实现接力传输信号
中继:不断地通过新的电源重新放大已经开始衰减的原有信号
中间所有小电报站都使用『螺旋线圈+磁性开关』的方式,来替代蜂鸣器+普通开关
只在电报的始发和终点用普通开关和蜂鸣器
这样就可以将长距离的电报线路,拆成一个个小的电报线路,接力传输电报信号
继电器别名:电驿,驿站的驿
二进制
有了继电器后,输入端通过开关的『开』和『关』表示1和0,输出端也能表示1和0
输出端的作用,不仅仅是通过一个蜂鸣器或者灯泡,提供一个供人观察的输出信号
还可以通过『螺旋线圈+磁性开关』,使得输出也有『开』和『关』两种状态,表示1和0,作为后续线路的输入信号
与(AND)
在输入端的电路上,提供串联的两个开关,只有两个开关都打开,电路才接通,输出的开关才能接通
或(OR)
在输入端的电路上,提供两条独立的线路到输出端
两条线路上各有一个开关,任意一个开关打开了, ...