计算机组成 -- 虚拟机
解释型虚拟机 要模拟一个计算机系统,最简单的办法,就是兼容这个计算机系统的指令集 开发一个应用程序,运行在操作系统上,该应用程序可以识别想要模拟的计算机系统的程序格式和指令,然后一条条去解释执行 原先的操作系统称为宿主机(Host),有能力模拟指令执行的软件称为模拟器(Emulator) 实际运行在模拟器上被虚拟出来的系统,称为客户机(Guest VM) 这种方式和运行Java程序的JVM比较类似,只不过JVM运行的是Java中间代码(字节码),而不是一个特定的计算机系统的指令 真实的应用案例:Android模拟器、游戏模拟器 优势 模拟的系统可以跨硬件 Android用的是ARM CPU,开发机用的是Intel X86 CPU,两边的CPU指令集是不一样的,但一样可以正常运行 劣势 无法做到精确模拟 很多老旧的硬件的程序运行,需要依赖特定的电路乃至电路特有的时钟频率,很难通过软件做到100%模拟 性能很差 并不是直接把指令交给CPU去执行,而是要经过各种解释和翻译的工作 编译优化:Java的JIT 把本来解释执行的指令,编译成Host可以直接运行的指令 全虚拟化 全...
计算机组成 -- CISC + RISC
历史 在早期,所有的CPU都是CISC 实际的计算机设计和制造会严格受到硬件层面的限制,当时的计算很慢,存储空间很小 为了让计算机能够尽量多地工作,每个字节乃至每个比特都特别重要 CPU指令集的设计,需要仔细考虑硬件限制,为了性能考虑,很多功能都直接通过硬件电路来完成 为了少用内存,指令长度也是可变的 常用的指令要短一些,不常用的指令要长一些 用尽量少的内存空间,存储尽量多的指令 计算机的性能越来越好,存储的空间也越来越大,70年代末,RISC出现 CPU运行程序,80%的运行代码都在使用20%的简单指令 对比 CISC RISC 以硬件为中心的指令集设计 以软件为中心的指令集设计 通过硬件实现各类程序指令 通过编译器实现简单指令组合,完成复杂功能 更高效地使用内存和寄存器 – 一开始都是CISC,硬件资源非常珍贵 需要更大的内存和寄存器,并更频繁地使用 可变的指令集,支持更复杂的指令长度 简单、定长的指令 大量指令数 少量指令数 CISC的缺点 在硬件层面,如果想要支持更多的复杂指令,CPU里面的电路就要更复杂,设计起来更困难 更复杂的电路,在散热和功...
计算机组成 -- 异常
异常 异常是一个硬件和软件组合在一起的处理过程 异常的发生和捕捉,是在硬件层面完成的 异常的处理,是在软件层面完成的 计算机会为每一种可能发生的异常,分配一个异常代码(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型指令 如果是简单的无条件地址跳转,可以直接在控制器里面完成,不需要用到运算器 机器周期 Mac...
计算机组成 -- 定点数 + 浮点数
浮点数的不精确性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表示双精度的浮点数,即do...
计算机组成 -- 乘法器
13 × 9 顺序乘法 二进制的乘法,在单个位上,乘数只能是0或1,所以实际的乘法,就退化成位移和加法 被乘数13表示成二进制是1101,乘数9表示成二进制是1001 最后边的个位是1,个位乘以被乘数,被乘数1101得以保留 二位和四位都是0,所以乘以被乘数都是0,保留下来的都是0000 八位是1,仍然需要把被乘数1101复制下来,但需要把复制好的结果向左移动三位 然后把上面四个乘法加位移的结果再加起来 最后一步的加法可以用上一讲的加法器来实现 二进制乘法因为只有0和1两种情况,所以可以做成输入输出都是4个开关,中间有一个开关 中间的开关决定,下面的输出是完全复制输入,还是将输出全部设置为0 位移:左移一位,就错开一位接线;左移两位,就错开两位接线 节约开关(晶体管) 为了节省资源(开关,即晶体管) 类似13×9这样两个四位数的乘法,不需要把四次单位乘法的结果都用四组独立的开关单独记录然后再加起来 否则,如果计算一个32位的整数乘法,就要32组开关,非常浪费 如果顺序地计算,只需要一组开关就好 先拿乘数最右侧的个位乘以被乘数,然后把结果写入用来存放计算结果的开关里面 然后,...















