考研冲刺前的总结 —— 计算机组成原理

30066 字
150 分钟
考研冲刺前的总结 —— 计算机组成原理
考研冲刺前的总结 —— 计算机组成原理

前言#

关于计组、操作系统和计网,我认为学习方法和数据结构有差别。

对于这三门课,主要是心里要有一个整体的模型,基本上是一章一个模型。每道题目只要套到这个模型分析,那么题目就很容易能做出来。

需要注意的就是很多时候不要想当然分析,而是必须要套到某个模型上。

学习这门课,模型是重中之重的,当然其他操作系统和计网也需要非常重视模型。

计算机系统概述#

考纲 - 计算机系统概述#

  • 计算机系统层次结构

  • 计算机系统的基本组成

  • 计算机硬件的基本组成

  • 计算机软件和硬件的关系

  • 计算机系统的工作原理:“存储程序”的方式;高级语言程序与机器语言程序的转换;程序和指令的执行过程

  • 计算机性能指标

  • 吞吐量;响应时间;CPU 时钟周期;主频;CPI;CPU 执行时间;MIPS;MFLOPS;GFLOPS; TFLOPS;PFLOPS;EFLOPS;ZFLOPS

计算机系统层次结构#

这个考点比较绕,主要可以从下面走:

高级语言 -> 汇编语言 -> (操作系统) -> 机器语言 -> 体系结构 ISA -> 微程序控制器。

可以理解为谁为谁提供服务。这里的操作系统派在这里不是很准确,但是也没什么大问题。

可以区分语言层和硬件接口:高级语言通常经编译器转换为汇编语言, 再由汇编器生成遵循 ISA 的机器代码。ISA 规定软件可见的指令、编码和行为, 微体系结构则负责实现 ISA。操作系统属于系统软件层,为应用程序提供硬件抽象和资源管理。

还需要辨析编译、汇编、翻译、链接的区别。

编译通常将高级语言转换成汇编语言或目标代码;汇编器将汇编语言转换成机器代码。 “翻译”是把一种语言转换成另一种语言的总称,其中逐条转换且不生成目标文件的过程也是翻译。 链接器将多个目标文件和库连接成可执行文件,是操作系统那块的概念。

此外,还有 ISA。ISA 是指令集体系结构,相同的 ISA 可以对应不同的机器实现。 例如,x86 ISA 可以经历多代硬件迭代,而 ISA 本身保持兼容。

计算机硬件与软件#

冯·诺依曼结构以“存储程序”为基础,硬件由运算器、控制器、存储器、输入设备和输出设备五大部分组成。

早期的机器以运算器为中心,输入输出都要经过运算器;现代计算机以存储器为中心,输入输出和 CPU 可以并行工作,DMA 就是在这个基础上出现的。

冯·诺依曼机的特点:

  • 指令和数据都以二进制形式存放在同一个存储器中,按地址访问,存储位置不区分指令和数据;
  • 指令由操作码和地址码组成;
  • 指令顺序执行,由 PC 控制,遇到转移指令才改变顺序;
  • 早期以运算器为中心,现代机器更接近以存储器为中心。

需要注意,区分指令和数据靠的是指令周期的阶段,取指周期取出的按指令解释,执行周期取出的按数据解释,而不是看它存在哪里。

指令和数据分开存储的结构叫哈佛结构,可以同时取指和取数,现代 CPU 的 L1 Cache 一般就是指令 Cache 和数据 Cache 分离,本质上是哈佛结构。

软件分为系统软件和应用软件。系统软件包括操作系统、编译程序、数据库管理系统、网络软件等,应用软件是面向具体问题的程序。

硬件和软件在逻辑功能上是等价的,同一个功能既可以用硬件实现,也可以用软件实现,区别在速度和成本。例如乘法既可以用乘法器一个时钟周期算完,也可以用移位加法用一段程序模拟。

翻译程序分为编译程序和解释程序,两者的区别在是否生成目标程序:

  • 编译程序把源程序整体翻译成目标程序,之后目标程序独立运行,运行时不需要编译程序;
  • 解释程序不生成目标程序,逐条翻译并立即执行,所以运行时需要解释器在场,速度慢。

需要注意的是,编译和解释不是按语言分的,同一个语言既可以有编译器也可以有解释器。

存储程序的执行流#

这块放在 CPU 那一章细讲,主要是理解控制驱动和数据驱动。 控制驱动由控制流决定计算顺序,数据驱动则在输入数据就绪时触发计算。

性能指标#

主要是记忆性的内容,常考 CPI、IPS 及其转换关系。

CPI 通常表示平均每条指令所需的时钟周期数,主频 ff 表示 CPU 每秒的时钟周期数。 一个时钟周期为 Tcycle=1/fT_{\text{cycle}}=1/f,一条指令的平均执行时间为 Tinstr=CPI/fT_{\text{instr}}=\mathrm{CPI}/f;在单一指令流、以平均 CPI 估算时,指令吞吐率为 IPS=f/CPI\mathrm{IPS}=f/\mathrm{CPI}。

对于这个考点,需要注意题目描述:有时给出执行 xx 条指令, 有时给出执行 yy 个时钟周期。

此外,机器 A 的主频高、机器 B 的主频低,并不意味着某程序在 A 上的运行时间 一定比在 B 上短。

做这类题可以设总指令数为 NN。

把几个量写成公式会更好用。设平均 CPI 为 CPI\mathrm{CPI},主频为 ff:

TCPU=N×CPI×Tcycle=N×CPIfT_{\text{CPU}}=N\times\mathrm{CPI}\times T_{\text{cycle}}=\frac{N\times\mathrm{CPI}}{f}MIPS=NTCPU×106=fCPI×106\mathrm{MIPS}=\frac{N}{T_{\text{CPU}}\times10^6}=\frac{f}{\mathrm{CPI}\times10^6}MFLOPS=浮点运算次数TCPU×106\mathrm{MFLOPS}=\frac{\text{浮点运算次数}}{T_{\text{CPU}}\times10^6}

需要注意,MIPS 只有在指令集相同、指令功能相近的机器之间比较才有意义,因为不同 ISA 的一条指令做的事差别很大,甚至同一台机器跑不同程序 MIPS 也不一样。

响应时间和吞吐量是两个方向:响应时间是单个任务从提交到完成的时间,越短越好;吞吐量是单位时间内完成的任务数,越大越好。流水线、多核主要提升吞吐量,缩短关键路径、减少分支预测失败主要改善响应时间。

基准程序(Benchmark)是一组有代表性的程序,用它们的运行时间评价机器,SPEC 是最常见的一套。需要注意,基准程序只对它覆盖的那类负载有代表性。

还有一点,CPU 时间只是程序运行时间的一部分,完整的运行时间还要加上 I/O 等待、操作系统开销、缺页处理等。

数据表示和运算#

考纲 - 数据表示和运算#

  • 数制和编码:进位计数制及其相互转换;定点数的编码表示
  • 运算方法和运算电路:
    • 基本运算部件:加法器;算术逻辑单元
    • 加减运算:补码加减运算器;标志位的生成
    • 乘除运算:乘除运算的基本原理;乘法电路和除法电路的基本结构
  • 整数的表示和运算:无符号数的表示和运算;有符号数的表示和运算
  • 浮点数的表示和运算:IEEE 754 标准;浮点数的加减运算

字,字节,字长,位#

什么是位,什么是字节,什么是字,什么是字长?

位是最小的单位,一个二进制位只能表示 0 或 1。

字节是 8 位;按无符号数解释时,一个字节可以表示 00 到 255255。 字节通常是存储和编址的基本单位。

而什么是字,什么是字长,需要根据计算机本身的系统结构来确定。

例如,对于某些早期机器,可以用 2 字节表示一个字,即字长为 16 位; 但字长和编址粒度是两个独立概念,不能由前者推出按字编址。

现代计算机通常按字节编址,但 1 个字通常包含多个字节,具体取决于 ISA(常见为 2、4 或 8 字节)。

数制和编码#

这个考点说难也难,说简单也简单,主要是细节问题。

2 进制、8 进制、10 进制、16 进制的相互转换。

对于十进制转二进制的整数部分,可以采用短除法:除以 22 并取余数, 直到商为 00,再从下到上把余数拼接起来。

对于十进制转二进制的小数部分,可以反复乘以 22 并取整数部分, 直到小数部分为 00,或达到所需精度,再从上到下把整数部分拼接起来。

当然,熟练后更快的做法是拼凑,也就是利用 2x2^x 的幂次展开转换为对应的二进制数。

这里可以注意,不是所有的十进制数都可以精确转换为二进制数,例如 0.10.1 的二进制展开是无限循环小数。

对于二进制转十进制,可以采用按权展开法,把各位按权展开后相加。

对于八进制转二进制,可以采用拆分法,把每个八进制数字拆成 3 个二进制位。

对于二进制转八进制,可以采用合并法,每 3 个二进制位合并为 1 个八进制数字。

对于十六进制转二进制,可以采用拆分法,把每个十六进制数字拆成 4 个二进制位。

对于二进制转十六进制,可以采用合并法,每 4 个二进制位合并为 1 个十六进制数字。

还可以注意表示,二进制数可以表示为 B 的形式,八进制数可以表示为 O 的形式,十六进制数可以表示为 H 的形式。

例如 10102=1010B1010_2=1010\mathrm{B}、138=13O13_8=13\mathrm{O}、1516=15H15_{16}=15\mathrm{H}。

真值,机器数,无符号数与有符号数,编码#

真值指的是机器数在指定编码下对应的实际值。例如,按 4 位原码解释时, 10102=−2101010_2=-2_{10};换用无符号数或补码解释时,结果会不同。

机器数指的是计算机中实际存储的值,一般都是以二进制的方式存储。

需要注意的是,计算机存储的是位串;编码规则决定如何解释这些位串,同一个数在不同编码下的位串也可能不同。

在讨论编码之前,需要捋清楚无符号数和有符号数的区别。

无符号数与有符号数#

无符号数指的是机器数中不表示负数,即表示非负数(包括 0)。 所有位都用于表示数值,不需要单独的符号位。

例如,8 位无符号数可以表示的范围为 00 到 255255,即 00000000200000000_2 到 11111111211111111_2。

而有符号数的第一位一定是符号位,0 表示正数,1 表示负数。需要注意的是,原码、反码、补码都是基于 有符号数 去讨论的,所以即便原码看着和无符号数很像,但是其本质是不一样的。

原码#

原码的第一位是符号位,0 表示正数,1 表示负数,后续的位数表示数值的绝对值。

例如对于 15 和 -15,其 8 位原码分别为 00001111200001111_2 和 10001111210001111_2。

对于 8 位原码,其表示范围为 [−127,127][-127,127];负数和正数的最大编码分别为 11111111211111111_2 和 01111111201111111_2。

从而产生了一个问题,即 0 的表示是不唯一的,即 00000000200000000_2 和 10000000210000000_2 都表示 0。

原码很直观,但是这里先不讲为什么原码的计算很不方便。

由于原码的计算很不方便,所以很少在计算机中使用。

反码#

反码的第一位是符号位,0 表示正数,1 表示负数,后续的位数是数值的绝对值的反码。

这里先不讨论反码的来源,先说反码的计算方法。

对于正数,其反码和原码相同,因为正数的反码就是其本身。

对于负数,其反码是原码除了符号位的每一位取反,即 0 变 1,1 变 0。

例如对于 15 和 -15,其 8 位反码分别为 00001111200001111_2 和 11110000211110000_2。

对于 8 位反码,其表示范围为 [−127,127][-127,127];负数和正数的最大编码分别为 11111111211111111_2 和 01111111201111111_2。

从而也有一个问题,即 0 的表示是不唯一的,即 00000000200000000_2 和 11111111211111111_2 都表示 0。

反码在现代整数编码中已经少用,主要作为补码的过渡概念。

加法器、补数与补码的来源#

加法器是计算机中用于执行二进制加法运算的核心逻辑电路,主要包括半加器、全加器以及多位并行加法器。

半加器(Half Adder)#

半加器只负责计算本位两个一位二进制数的加法,不考虑来自低位的进位。

输入:被加数 AA、加数 BB。

输出:本位和 SS,向高位的进位 CC。

逻辑表达式:

S=A⊕B,C=A∧BS=A\oplus B,\qquad C=A\land B

示例:计算 1+11+1 时,S=1⊕1=0S=1\oplus1=0,C=1∧1=1C=1\land1=1, 合起来输出二进制 10210_2。

半加器无法接收低位产生的进位输入,因此不能直接用于多位数的进位加法。

全加器(Full Adder)#

全加器在半加器的基础上,增加了接收低位进位的功能。

输入:被加数 AA、加数 BB,来自低位的进位 CinC_{\mathrm{in}}。

输出:本位和 SS,向高位的进位 CoutC_{\mathrm{out}}。

逻辑表达式:

S=A⊕B⊕Cin,Cout=(A∧B)∨(Cin∧(A⊕B))=(A∧B)∨(A∧Cin)∨(B∧Cin)\begin{aligned} S&=A\oplus B\oplus C_{\mathrm{in}},\\ C_{\mathrm{out}}&=(A\land B)\lor\bigl(C_{\mathrm{in}}\land(A\oplus B)\bigr)\\ &=(A\land B)\lor(A\land C_{\mathrm{in}})\lor(B\land C_{\mathrm{in}}) \end{aligned}

示例:计算 1+1+11+1+1(包含低位进位 11)时,S=1S=1, Cout=1C_{\mathrm{out}}=1,合起来输出二进制 11211_2。

并行加法器(Parallel Adder)#

为了实现多位二进制数的快速相加,通常将多个全加器组合起来使用:

串行进位加法器将多个全加器首尾相连,进位从低位向高位逐级传递,延迟较高。

超前进位加法器通过进位生成和传递的布尔逻辑提前计算各位进位, 不必等待低位逐级传递,延迟较低。实际工程中常以 4 位加法器作为基本模块, 再通过进位链级联。

算术逻辑单元(ALU)#

ALU 是组合逻辑电路,没有记忆功能。输入是操作数 AA、BB 和功能选择信号,输出是运算结果 FF 以及若干标志位。

核心是一个加法器,再加移位电路、逻辑运算电路(与、或、异或、非)和一个多路选择器,由操作码决定最终输出哪一路。

ALU 的位数一般就是机器字长,因为 ALU 要能一次处理一个字。

需要注意,ALU 只负责算,不负责存。结果要打入寄存器(累加器、通用寄存器或标志寄存器)才算完成一次运算,这也是数据通路里“组合逻辑 + 寄存器”的基本套路。

加法器是 ALU 的子集,ALU 在加法器外面套了一层功能选择,所以支持减法、逻辑运算、比较(做减法看标志位)。

补码的好处在这里体现得很明显:减法直接用加法器加补数实现,ALU 不需要单独的减法电路,只需要把 BB 取反再把最低位进位设为 1。

在这里讲加法器,是因为可以理解补码的来源。

暂时不考虑溢出的情况,仅考虑下面的情况。

对于 8 位无符号数,1313 和 1212 的二进制表示分别为 00001101200001101_2 和 00001100200001100_2,相加可得 00011001200011001_2,即 2525。

对于 8 位无符号数,13−1213-12 可写为 000011012−000011002=00000001200001101_2-00001100_2=00000001_2,结果为 11。

对于 8 位原码,13+1213+12 的编码为 000011012+000011002=00011001200001101_2+00001100_2=00011001_2,结果为 2525。

对于 8 位原码,13−1213-12 的编码为 000011012−000011002=00000001200001101_2-00001100_2=00000001_2,结果为 11。

这看起来对人类而言很直观,但是对于计算机来说,相减如何实现?

如果直接按原码的“绝对值相减、再确定符号”规则实现, 需要比较绝对值并单独处理符号;如果为无符号减法设计专用路径, 还要处理借位。

这些额外逻辑会增加硬件复杂度,因此通常复用加法器和补码来实现减法。

硬件资源是稀缺的,所以需要一种更简单的方法来实现减法。

对模 nn 的无符号数 xx,其补数定义为 (−x) mod n=(n−x) mod n(-x)\bmod n=(n-x)\bmod n。

对于 8 位无符号数,模为 28=2562^8=256。 因此,1212 的补数为 256−12=244256-12=244,对应的位串为 11110100211110100_2。

考虑硬件实现,计算 13−1213-12 等同于计算 13+(256−12)=13+244=25713+(256-12)=13+244=257。在 8 位寄存器中,最高进位被舍弃, 结果为 257 mod 256=1257\bmod256=1。

在固定 nn 位二进制、模为 2n2^n 的情况下,补数可以通过对 xx 的每一位取反再加 11 得到。

所以可以得出下面的结论:

计算机的寄存器位数是固定的。对于 8 位无符号寄存器,其模为 28=2562^8=256。

固定宽度运算只保留低 8 位,结果等价于按模 256256 截取;是否发生“溢出”要看数值解释: 无符号数看进位标志 CF,补码有符号数看溢出标志 OF。

在模 256 的系统中,减去一个数相当于加上这个数的补数。

例如:

13−12≡13+(256−12)(mod256)13-12\equiv13+(256-12)\pmod{256}

那么对于有符号数呢?

考虑原码 13−1213-12:其位串为 000011012−00001100200001101_2-00001100_2。如果采用与无符号数补数求法类似的思路,能否得到正确结果?

补码#

补码的第一位是符号位,0 表示非负数,1 表示负数。对于非负数,其补码和原码相同。 对于负数,其补码是原码除符号位外的每一位取反再加 11。

对于 8 位补码,其表示范围为 [−128,127][-128,127],即从 10000000210000000_2 到 01111111201111111_2;没有正 128128,且只有一个 00。

例如,−12-12 的原码为 10001100210001100_2,其补码为 11110100211110100_2。

在 8 位补码中,13−1213-12 等同于 13+[−12]补13+[-12]_{\text{补}},即 000011012+111101002=00000001200001101_2+11110100_2=00000001_2;最高进位因位数限制被舍弃。

对于补码,可以把第一位(符号位)理解为负位权。 对 8 位补码,−12-12 的位串可展开为 −27+26+25+24+22=−12-2^7+2^6+2^5+2^4+2^2=-12。

移码#

对 nn 位整数,常规定义的移码(偏置码)偏置为 2n−12^{n-1}, 编码值为真值加该偏置。

对于 −128-128,其补码为 10000000210000000_2,其 8 位移码为 00000000200000000_2。

对于 00,其补码为 00000000200000000_2,其 8 位移码为 10000000210000000_2。

对采用 nn 位补码、真值范围为 [−2n−1,2n−1−1][-2^{n-1},2^{n-1}-1] 的编码, 移码的位串等价于将补码的符号位取反。

移码在计算机中主要用于表示浮点数的阶码。IEEE 754 单精度的偏置为 127127, 双精度的偏置为 10231023。

四种编码的对照#

设字长为 nn 位,把前面几种编码放在一起看:

编码正数负数真值范围0 的个数
原码符号位 0 + 绝对值符号位 1 + 绝对值[−(2n−1−1), 2n−1−1][-(2^{n-1}-1),\,2^{n-1}-1]2
反码同原码原码数值位逐位取反[−(2n−1−1), 2n−1−1][-(2^{n-1}-1),\,2^{n-1}-1]2
补码同原码反码加 1[−2n−1, 2n−1−1][-2^{n-1},\,2^{n-1}-1]1
移码补码符号位取反同左[−2n−1, 2n−1−1][-2^{n-1},\,2^{n-1}-1],编码值是 [0, 2n−1][0,\,2^n-1]1

从表中可以看出,补码比原码、反码多表示一个负数,这就是 0 唯一带来的好处。

移码的编码值随真值单调递增,所以比较浮点数阶码大小时可以直接按无符号数比较,这也是阶码用移码的原因。

原码和反码的对称性在计算时要多写判断逻辑,补码则把符号位也当成数的一部分参与运算,硬件最省事。

C 语言整数类型及转换#

C 语言整数类型的精确位宽由实现和 ABI 决定。但考研用的是 long 为 32 位。

以 CHAR_BIT=8、ILP32 环境举例:

  • char:1 字节,通常为 8 位;
  • short:2 字节,16 位;
  • int:4 字节,32 位;
  • long:4 字节,32 位;
  • long long:8 字节,64 位。

在 LP64 等其他 ABI 中,long 可能是 8 字节;C 标准只规定各类型的最小范围。

对于整数类型和转换,需要注意:

转换到无符号目标类型时,结果按目标类型的模保留低位; 转换到有符号目标类型且值无法表示时,结果由实现定义,不能一概而论为截断。

在常见二补码实现中,如果是短转长,整型提升或转换通常会进行扩展:无符号数高位补 00, 有符号数按符号位扩展;具体结果仍以目标类型和实现规则为准。

unsigned short a = 65530;
int b = a;

分析可知,对于无符号数 aa,其 16 位十六进制表示为 FFFAH。 整型提升时零扩展为 32 位 0000FFFAH,再解释为 int,结果为 6553065530。

short a = -1;
unsigned int b = a;

分析可知,对于 16 位有符号数 aa,其位串为 FFFFH。 整型提升先符号扩展为 32 位 FFFFFFFFH,再转换为 unsigned int, 结果为 232−12^{32}-1。

基本的计算#

移位运算#

移位运算分为逻辑移位和算术移位。

逻辑移位是直接将二进制位向左或向右移动,移动后空位补 0。

算术右移时,高位补入原符号位;算术左移时,低位始终补 00。

例如,对于 8 位补码 10101010,逻辑左移一位和算术左移一位均为 01010100;逻辑右移一位为 01010101,算术右移一位为 11010101。

由于二进制的表达,移位运算天然地和乘除法有关系。

在不发生溢出的前提下,算术左移通常相当于乘以 22; 对非负数,算术右移相当于整除 22,对负数则通常向负无穷取整。

例如,8 位补码 11001010 的真值为 −54-54。 算术左移一位得到 10010100,真值为 −108-108; 算术右移一位得到 11100101,真值为 −27-27。

需要注意的是,左移时若有效位被丢弃或符号位发生改变,可能发生溢出。 例如,8 位补码 10000000 的真值为 −128-128。算术左移一位得到 00000000(按 8 位补码解释为 00),但数学结果应为 −256-256, 超出了 8 位补码的可表示范围。

对加法器的补充#

对于固定为 nn 位的加法器,实现减法通常将 BB 按位取反,并把最低位进位输入设为 11, 最后只保留低 nn 位:

A−B≡A+¬B+1(mod2n)A-B\equiv A+\lnot B+1\pmod{2^n}

从上面可以看出,反码类似加法器的一种状态。对于加法器而言,不是直接把 BB 取补,而是取反再 +1+1。

补码加减法运算#

本质上,补码加减法运算就是将减法转换为加法。

对 nn 位补码运算,结果按模 2n2^n 截取:

[A+B]补=[A]补+[B]补(mod2n),[A+B]_{\text{补}}=[A]_{\text{补}}+[B]_{\text{补}}\pmod{2^n},[A−B]补=[A]补+[−B]补(mod2n)[A-B]_{\text{补}}=[A]_{\text{补}}+[-B]_{\text{补}}\pmod{2^n}

判定溢出与标志位#

判断补码溢出常用以下三种方法。对加法而言,只有同号数相加可能溢出: 两个正数相加得到负数,或两个负数相加得到正数时发生溢出。 对减法而言,只有异号数相减可能溢出:被减数与减数符号不同,且结果符号 与被减数符号不同时发生溢出。

也可以比较进入符号位和从符号位产生的进位:二者不同时发生溢出。

第三种方法是采用双符号位,即 0000 表示正数,1111 表示负数, 0101 表示正数溢出,1010 表示负数溢出。

需要注意的是,双符号位只在运算过程中使用;存储一个 nn 位补码数时仍只保留 1 位符号位。

4 个标志位

  • OF:Overflow Flag,代表有符号数运算是否发生溢出。
  • SF:Sign Flag,代表运算结果的符号位。
  • ZF:Zero Flag,代表运算结果是否为 0。
  • CF:Carry Flag,代表无符号运算产生的进位或借位(具体语义取决于指令集)。
    • 对采用“减数取反并加 11”的加法器,硬件常用 CF=Cout⊕Sub\text{CF}=C_{\text{out}}\oplus\text{Sub},其中 Sub=0\text{Sub}=0 表示加法、 Sub=1\text{Sub}=1 表示减法。
    • 加法运算(A+BA+B):CF=Cout\text{CF}=C_{\text{out}}。若最高位产生进位, 则表示无符号加法溢出。
    • 减法运算(A−BA-B):在 x86 等“CF 表示借位”的约定下, CF=¬Cout\text{CF}=\lnot C_{\text{out}};若 A<BA<B,则发生借位。

需要注意的是,对于无符号数的加减法,OF 通常不用于判断无符号溢出; 对于有符号数运算,CF 通常不用于判断有符号溢出,但处理器仍可能同时更新这两个标志位。

乘法#

常见实现有移位加法迭代、顺序乘法器和阵列乘法器。

对于这三者,可以在 CPU 那一章有一个经典的分析,即为什么从时间上来讲,模拟耗时 > 乘法器 > 乘法阵列器。

即大体上来分析,模拟需要消耗多个指令周期,乘法器只需要消耗一个指令周期,而乘法阵列器只需要消耗一个时钟周期。

无符号数乘法(原码一位乘)。思路和手算一样,只是把“错位相加”换成了“部分积右移”。设被乘数 XX、乘数 YY 都是 nn 位:

  1. 看乘数最低位 y0y_0,为 1 则部分积加 XX,为 0 则加 0;
  2. 部分积和乘数一起逻辑右移一位;
  3. 重复 nn 次,最后得到 2n2n 位的积。

所以顺序乘法器需要 nn 次加法和 nn 次移位,一个加法器加几个寄存器就能实现,代价是慢。

阵列乘法器把 n×nn\times n 个全加器排成阵列,进位在阵列内部横向传递,一次就能算出 2n2n 位结果,代价是硬件量大。

有符号数乘法可以直接按原码做:符号位单独异或,数值部分当无符号数乘。也可以用补码直接乘,就是下面的 Booth 算法。

Booth 算法(补码一位乘)。在乘数最低位右边虚设一位 y−1y_{-1},初始为 0,每次看 yiyi−1y_i y_{i-1} 两位:

yiyi−1y_i y_{i-1}操作
00部分积加 0
01部分积加 [X]补[X]_{\text{补}}
10部分积减 [X]补[X]_{\text{补}},即加 [−X]补[-X]_{\text{补}}
11部分积加 0

然后部分积和乘数一起算术右移一位,重复 nn 次(nn 为乘数位数,最后一步是否移位看具体教材的约定)。

Booth 的好处是不用管符号,正负数走同一套流程,而且乘数里连续的 0 和连续的 1 都只对应很少的加减操作。

除法器的两种方式#

除法的本质是“左移、试减、看余数符号定商”。

恢复余数法:

  1. 余数左移一位,减去除数;
  2. 若余数为负,说明不够减,商上 0,再把除数加回来恢复余数;
  3. 若余数为正,商上 1;
  4. 重复 nn 次。

恢复余数法的问题是多了一次“加回来”的操作,速度慢。

不恢复余数法(加减交替法)就不恢复了:

  1. 若余数为正,商上 1,左移后减除数;
  2. 若余数为负,商上 0,左移后加除数;
  3. 重复 nn 次。

最后一步的规则要注意:如果第 nn 步余数为负,需要再做一次加法把它恢复成正确的余数。

需要注意,原码除法是符号单独处理,数值部分按无符号数除;补码除法可以直接做,但商的修正规则(末位恒置 1 还是加 1)要按教材来,不要自己推。

IEEE 浮点数#

浮点数的表示#

N=(−1)S×1.M×2E−biasN=(-1)^S\times 1.M\times 2^{E-\text{bias}}

其中 SS 是符号位,1.M1.M 是隐含最高位为 1 的规格化尾数(原码小数),EE 是把真实阶码加上偏置后得到的无符号阶码字段,bias\text{bias} 是偏置值。

需要注意,尾数用原码表示,所以不能像补码那样直接按位串比大小;浮点数比较要按符号、阶码、尾数依次比。

  • float 型浮点数
    • 符号位:1 位
    • 阶码:8 位
    • 尾数:23 位
  • double 型浮点数
    • 符号位:1 位
    • 阶码:11 位
    • 尾数:52 位

做这类题的时候,主要是要小心翼翼,不要脑子抽了。

IEEE 浮点数的表示范围与特殊浮点数#

类型符号位 (S)阶码位 (E)尾数位 (M)阶码偏置值 (Bias)最小正非规范化数 (绝对值极小)最小正规范化数最大正规范化数 (绝对值极大)十进制有效数字位数
单精度 (float, 32 位)1 位8 位23 位1272−149≈1.40×10−452^{-149} \approx 1.40 \times 10^{-45}2−126≈1.18×10−382^{-126} \approx 1.18 \times 10^{-38}(2−2−23)×2127≈3.40×1038(2 - 2^{-23}) \times 2^{127} \approx 3.40 \times 10^{38}约 6 ~ 7 位
双精度 (double, 64 位)1 位11 位52 位10232−1074≈4.94×10−3242^{-1074} \approx 4.94 \times 10^{-324}2−1022≈2.23×10−3082^{-1022} \approx 2.23 \times 10^{-308}(2−2−52)×21023≈1.80×10308(2 - 2^{-52}) \times 2^{1023} \approx 1.80 \times 10^{308}约 15 ~ 17 位
特殊数值名称阶码字段 (E)尾数字段 (M)含义与说明
正零 / 负零 (±0\pm 0)全 0全 0符号位 S=0S=0 表示 +0+0,S=1S=1 表示 −0-0。在数值比较时 +0=−0+0=-0 成立。
正无穷 / 负无穷 (±∞\pm \infty)全 1 (单精度为 255,双精度为 2047)全 0符号位 S=0S=0 表示 +∞+\infty,S=1S=1 表示 −∞-\infty。通常出现在溢出或除以 0 的情况。
非数 (NaN, Not a Number)全 1 (单精度为 255,双精度为 2047)不全为 0表示无效的未定义操作(例如 0/00/0、∞−∞\infty-\infty 或对负数开平方时产生)。分为静默 NaN (qNaN) 和发信号 NaN (sNaN)。
非规范化数 (Subnormal)全 0不全为 0用于实现“渐进下溢”(Gradual Underflow),隐藏位变为 00(即 0.M×21−Bias0.M \times 2^{1-\text{Bias}}),填补 00 到最小规范化数之间的空隙。

浮点数的相关计算#

  • 对阶:小阶向大阶看齐。阶差为 dd 时,小阶的尾数右移 dd 位,阶码加 dd。右移移出的位不能直接扔掉,要保留下来参加后面的舍入
  • 尾数加减:对阶之后按定点小数做加减,可能进位,也可能出现前导 0
  • 尾数规格化
    • 右规:尾数用双符号位时出现 01.xxx01.xxx 或 10.xxx10.xxx(加减时进位了),尾数右移 1 位,阶码加 1
    • 左规:出现 00.0xx⋯00.0xx\cdots 或 11.1xx⋯11.1xx\cdots(对阶时丢了有效位),尾数每左移 1 位,阶码减 1,直到变成 00.1xx⋯00.1xx\cdots 或 11.0xx⋯11.0xx\cdots
    • 规格化的目的只有一个,让尾数最高位固定为 1,表示唯一,精度最大
  • 舍入
    • 就近舍入(0 舍 1 入):把保留位之后的部分看成 0.xxx0.xxx,小于 100100 舍去,大于 100100 进 1,恰好等于 100100 时向偶数舍入,也就是看保留的末位,末位是 0 就舍,末位是 1 就进 1
    • 直接进位:只要后面还有非 0 位就进 1,误差朝大方向
    • 直接舍去:多余位直接扔掉,误差朝 0
    • 向无穷进位 / 舍去:正数进 1,负数舍去
    • 向 0 进位 / 舍去
  • 溢出判断
    • 上溢:阶码超过最大阶码,按机器约定产生异常或置为 ±∞\pm\infty
    • 下溢:下溢指的是要下溢到非规格化浮点数都表示不了的程度,此时按机器零处理,不再当异常

浮点数与整型转换相关#

int 转 float,不会溢出,但可能会发生精度丢失。因为 float 的尾数只有 24 位有效(含隐藏位),而 int 有 31 位数值。

int 或 float 转 double,精确原值。double 的阶码和尾数都更长,能完整容纳。

double 转 float,可能溢出 / 精度丢失。阶码范围变小了,数值太大就变成 ±∞\pm\infty。

float 或 double 转 int,小数部分直接丢弃(向 0 取整),超出 int 的范围则结果未定义,一般表现为溢出。

需要注意,转换的顺序会影响结果:先把 float 转成 double 再转 int,和直接转 int 在边界上可能不同,做题时按题目的类型逐次转换。

大端法与小端法#

大端法:高位字节放在低地址,低位字节放在高地址,和人的书写顺序一致。

小端法:低位字节放在低地址,高位字节放在高地址,x86 用的是小端。

例如 32 位整数 0x12345678,从地址 0x1000 开始存放:

地址大端法小端法
0x100012H78H
0x100134H56H
0x100256H34H
0x100378H12H

需要注意,大小端只影响多字节数据在内存中的字节顺序,不影响单字节的内部位序,也不影响寄存器内部的位序。

做题时判断是哪种方式,就看题目给出的内存内容里,低地址放的是高位字节还是低位字节。

数据对齐#

数据对齐指的是数据的起始地址必须是其自身大小的整数倍。不对齐时可能需要多访存一次,某些平台甚至直接异常。

对齐规则一般是:

  • 每个成员的偏移量是其自身大小的整数倍,编译器在成员之间插入填充字节;
  • 结构体总大小是其最大成员大小的整数倍,末尾也要填充。
struct Test{
char a;
short b;
int c;
};

假设首地址是 0x1000H,那么 a 的地址是 0x1000H,b 的地址是 0x1002H(因为边界对齐,所以空 1 位),c 的地址是 0x1004H,结构体总大小为 8 字节。

如果把成员按“从大到小”排列,写成 int c; short b; char a;,那么 c 在 0x1000、b 在 0x1004、a 在 0x1006,总大小还是 8 字节,但没有浪费中间的空隙。

反过来写成 char a; int c; short b;,总大小会变成 12 字节。这就是调整成员顺序可以省空间的原因。

存储系统#

考纲 - 存储系统#

  • 存储器的分类
  • 层次化存储器的基本结构
  • 半导体随机存储器(RAM):SRAM,DRAM,Flash 存储器
  • 主存储器:DRAM 芯片和内存条、多模块存储器、主存储器和 CPU 之间的连接
  • 外部存储器:磁盘存储器、固态硬盘
  • 高速缓冲存储器
    • Cache 的基本原理:Cache 和主存之间的映射方式
    • Cache 中的主存替换算法;cache 写策略
  • 虚拟存储器
    • 虚拟存储器的基本概念
    • 页式虚拟存储器:基本原理、页表、地址转换、TLB(快表)
    • 段式存储器的基本原理
    • 段页式存储器的基本原理

存储器分类#

  • 按存储元件分类

    • 半导体材质:SRAM,DRAM
    • 磁性材料:磁盘
    • 光介质:光盘(需要注意可以随机寻址,但是只能顺序读)
  • 存取方式

    • 随机存取:RAM
    • 顺序存取:磁带
    • 直接存取:磁盘
    • 相联存储:TLB,路由表,按内容访问的存储器
  • 信息可更改性

    • 只读:ROM,注意 flash 属于只读
    • 可读可写:RAM
  • 可保存性

    • 易失性:RAM
    • 非易失性:ROM,磁盘,光盘

存储器的组成与层次化#

层次化#

CPU - Cache - 主存 - 磁盘 - 磁带/光盘

越往上速度越快、容量越小、单位成本越高;越往下容量越大、速度越慢、单位成本越低。寄存器也在最上层,容量最小、速度最快。

层次化能成立的前提是局部性原理:程序在一段时间内只访问一小部分地址。Cache 同时利用时间局部性和空间局部性,虚拟存储器主要利用时间局部性。

需要注意,各层之间的信息调度主体不同:Cache 与主存之间由硬件自动完成,主存与外存之间由操作系统的缺页处理完成。

辨析存储周期与存取时间#

存储周期 > 存取时间,存储周期 = 存取时间 + 复原时间。

主存#

最先需要辨析的,是主存和内存。考研范围里,主存和内存是一个意思,而内存和生活中的常用语是不同的。

SRAM#

本质为双稳态触发器,无须刷新。静态 RAM 存取速度快,但是集成度低,功耗大,成本高一般用于 cache 上。

DRAM#

本质为电容,需要定时刷新。存取速度较慢,容易漏电丢失信息,读出过程为破坏性读出。

DRAM 需要地址线复用,并且需要注意的是,看清楚题目要求你计算什么。是地址线多少,还是几个 DRAM 组合起来的地址线多少。

例如,对于一个容量为 16 KB16\text{ KB}、按字节编址的主存系统,CPU 访问该主存所需的系统地址线总共为 1414 根,因为 16 KB=214 B16\text{ KB}=2^{14}\text{ B}。

这 1414 根地址线(A13∼A0A_{13}\sim A_0)的功能划分为:

  • 高 4 位地址线(A13∼A10A_{13}\sim A_{10}):用于片选译码。整个 16 KB16\text{ KB} 空间按 1 KB1\text{ KB} 划分为 16 个模块,通过这 4 根线译码产生片选信号,分别选中 2 KB2\text{ KB} 的 ROM 模块以及 14 片 1 K×8 位1\text{ K}\times 8\text{ 位} 的 DRAM 芯片;
  • 低 10 位地址线(A9∼A0A_9\sim A_0):用于片内寻址。由于单片 DRAM 采用地址线复用技术,该 10 位片内地址在进入 DRAM 芯片前会被分时复用,拆分为 5 位行地址和 5 位列地址,因此所有 14 片 DRAM 的片内地址引脚全部并联接到这组复用线路上。

注意,DRAM 内部是 5 根地址线,然后整个系统的地址总线就是 14。

内存条就是把多片 DRAM 芯片装在一块 PCB 上,再加一个 SPD 芯片(一般是 EEPROM,存容量、时序等参数,开机时由 BIOS 读取)。

需要注意,内存条的数据位宽要匹配内存总线宽度,例如 64 位总线就用 8 片 8 位的芯片并联;带 ECC 的内存条还要额外加校验芯片。

有三种刷新(复原)方式:

  • 集中刷新:在规定的最大复原周期内,前面的时间专门划给 CPU 时候,然后最后的时间专门划给刷新,缺点是有死区
  • 分散刷新:把每一个系统工作周期分为两半,前半部分用来供 CPU 进行正常的读写操作,后半部分固定用来复原刷新一行存储单元,复原操作完全分散在每一个存取周期内部,系统响应极度平滑,随时可访存;但由于每次存取都要强行带上复原操作,导致整个存取周期直接翻倍拉长,大大降低了存储器的实际工作效率
  • 异步刷新:集中与分散的折中方案。假设刷新周期为 2ms,存储阵列有 128128 行,就把 2ms 均分为 128128 段,每隔约 15.6μs15.6\mu s 发出一次单行的复原请求。每一行有各自短暂的“死区”(仅占用一个读写周期的时间),死区被完全打散,宏观上几乎感觉不到停顿

对于集中刷新,集中刷新被粗暴分成两个部分,一部分是给 CPU 使用,一部分是给刷新使用。

对于分散刷新,是每一个工作周期都刷新,也就是执行一下,刷新一下。

对于异步刷新,是执行一行刷新一次。

一般采用异步刷新,从而引出了如何设计几行几列:设存储阵列为 mm 行 nn 列,要求 m≥nm\ge n,且 nn 尽可能接近 mm。

需要注意,行数越多,单次刷新涉及的单元越少,但刷新次数越多;行列接近正方形时,地址复用最省引脚,芯片内部连线也最短。

对于行缓冲器,一般是 SRAM 实现,用于缓存行数据。

ROM#

断电后不会丢失数据,可靠。

  • ROM
    • MROM,掩膜 ROM,不可修改
    • PROM,一次编程 ROM
    • EPROM,可擦除可编程 ROM,使用紫外线擦除且擦除次数有限
    • EEPROM,电擦除可编程 ROM
  • Flash 闪存,读写性能不对称
  • SSD 固态硬盘

可以注意,主存里一般是有 ROM 的,因为要存储 BIOS。

SRAM、DRAM 与 Flash 的对比#

对比项SRAMDRAMFlash
存储元双稳态触发器(6 管)电容(1 管 1 容)浮栅晶体管
是否需要刷新不需要需要,定时刷新不需要
读出是否破坏非破坏性破坏性,读出后要重写非破坏性
地址线不需要复用行地址、列地址复用按页、块寻址
速度快慢介于两者之间
集成度低高最高
功耗大小小
成本高低低
易失性易失易失非易失
用途Cache主存U 盘、SSD、BIOS

需要注意,Flash 的读和写粒度不同:读以页为单位,写(编程)也是页,但擦除必须以块为单位,所以随机写要先读出整块、改完再写回,这也是 SSD 随机写慢的根本原因。

NOR Flash 可以按字节随机读,支持片内执行,常用来存 BIOS;NAND Flash 按页读写、成本低、容量大,用来做 SSD 和 U 盘。

多模块存储器#

  • 连续编址,高位地址作为模块号,低位地址为模块内地址,各个模块不能并行访问,无法提高存储器的吞吐率。

  • 交叉编址(低位编址),低位地址用作模块号,高位地址作为模块内地址。

    • 轮流启动:按模块号轮流启动,每个模块的存取周期为 TT,总线传送周期为 rr,则连续读取 mm 个字所需的时间为 t=T+(m−1)rt=T+(m-1)r
    • 同时启动:一次性取 nn 个模块,即连续几个字节一起读,可以理解为类似超字长
    • 需要注意,交叉编址能提高带宽的前提是 m≥Trm\ge \frac{T}{r},否则模块还没复原完,下一轮又轮到它了

需要注意,多模块交叉存储提高的是带宽,不是单个字的访问延迟。单个字的延迟还是由模块本身决定。

主存与 CPU 的连接#

这块知识并不难,主要是要把 16 进制的地址转换成 2 进制理解,并且不要想当然地去计算(比如说本来只有位扩展,但是写的字位扩展)。

还有就是一定要捋清楚 A0 - A15 谁是高位,谁是低位,这个题目里会讲。

位扩展#

芯片的字数够、位数不够。所有芯片的地址线、片选线并联,数据线分别接到不同的位,几片一起构成一个更宽的字。

例如用 1 K×41\text{ K}\times 4 位的芯片组成 1 K×81\text{ K}\times 8 位的存储器,需要 2 片,两片的地址线全部并联,一片接 D0∼D3D_0\sim D_3,另一片接 D4∼D7D_4\sim D_7,读写同时进行。

字扩展#

位数够、字数不够。数据线并联,地址线的低位并联,高位用来译码产生片选信号。

例如用 1 K×81\text{ K}\times 8 位的芯片组成 4 K×84\text{ K}\times 8 位的存储器,需要 4 片,地址线 A9∼A0A_9\sim A_0 并联,A11A10A_{11}A_{10} 经 2-4 译码器产生 4 个片选信号。

字位扩展#

两者都需要,先位扩展成一组,再把多组字扩展。

需要注意,片选信号的产生有两种方式:

  • 线选法:高位地址线直接作为片选,简单,但地址空间有空洞,不能充分利用;
  • 译码片选法:高位地址经译码器产生片选,地址连续,用得多,但要考虑译码器的使能端接法。

计算地址范围时,先算出每片的容量,再乘以片数,然后根据题目给的起始地址写出十六进制范围,不要凭印象写。

磁盘与 SSD#

  • 磁盘:直接存取存储器,扇区是最小的单位。

    • 磁道(柱面) -> 盘面(磁头) -> 扇区(这个是地址划分)
    • 非格式化容量 > 格式化容量
    • 格式化容量 == 记录面数 ×\times 每面磁道数 ×\times 每道扇区数 ×\times 扇区容量
    • 数据传输率 == 每道扇区数 ×\times 扇区容量 ×\times 转速(转速的单位要换成转每秒)
    • 平均旋转等待时间 == 磁盘转一圈时间的一半,即 12×60转速(r/min)\frac{1}{2}\times\frac{60}{\text{转速(r/min)}}
    • 响应时间 = 排队时延 + 控制时间 + 寻道时间 + 旋转等待时间 + 数据传输时间(具体题目会给条件,给的条件的都要算进去)
    • 需要注意,磁盘地址按柱面、磁头、扇区排列,先同柱面再换磁头,就是为了减少寻道
    • RAID 独立冗余磁盘阵列。将多个独立的物理磁盘合成一个逻辑磁盘
      • RAID0:条带化,没有冗余,容量利用率 100%100\%,坏一块盘数据全丢
      • RAID1:镜像,容量利用率 50%50\%,可靠性最高
      • RAID2:海明码校验,已经淘汰
      • RAID3:位交叉奇偶校验,校验信息集中在一个盘上,容量利用率 (n−1)/n(n-1)/n
      • RAID4:块交叉奇偶校验,校验盘成为写瓶颈
      • RAID5:无独立校验盘分布式奇偶校验磁盘阵列,容量利用率 (n−1)/n(n-1)/n,允许坏 1 块盘
  • SSD

    • 基于闪存的存储设备
    • 读/写以页为单位,擦除以块为单位
    • 随机写慢,是因为擦除时间耗时长,并且如果修改的话,需要先复制到另一个页,写入后然后再写回。
    • 磨损均衡
      • 动态磨损均衡:写入数据的时候优先写入到空闲的页
      • 静态磨损均衡:在后台自动迁移到高磨损块中(把基本只读的东西搬运)

cache#

cache 的基本工作原理#

主存和 Cache 都按块划分,块大小相同,块内地址一一对应。

CPU 访存时,先用地址中的某几位判断它落在 Cache 的哪一行,再比较标记,标记一致且有效位为 1 就是命中,直接读 Cache;否则就是不命中,需要从主存把整个块调入,再按替换算法决定换出哪一块。

所以一次访存的时间取决于命中率,而命中率取决于程序局部性和映射、替换策略。

需要注意,Cache 与主存之间以块为单位交换,CPU 与 Cache 之间以字为单位访问,块大小的选择是空间局部性和缺失代价的折中。

局部性原理#

  • 时间局部性:访问我之后,在短时间内还会再访问自己
  • 空间局部性:访问我之后,在短时间内会访问我身边的人

cache 命中率#

  • 耗时
    • 在访问 cache 时同时读主存
    • 在访问 cache 后发现未命中,再读主存
    • 这两种方案看具体题目怎么讲

设命中率为 hh,Cache 访问时间为 tct_c,主存访问时间为 tmt_m。

同时访问时,未命中也不用再等一次 Cache,所以

tavg=h tc+(1−h) tmt_{\text{avg}}=h\,t_c+(1-h)\,t_m

先查 Cache、未命中再查主存时,未命中的那一次要先把 Cache 查完,所以

tavg=tc+(1−h) tmt_{\text{avg}}=t_c+(1-h)\,t_m

效率是

e=tctavge=\frac{t_c}{t_{\text{avg}}}

需要注意,题目说“Cache 与主存同时访问”时,主存里也要有这一块的数据,命中时主存的读出结果被丢弃,这是用硬件换时间。

多级 Cache 要分清局部缺失率和全局缺失率:局部缺失率是相对于本级的访问次数算的,全局缺失率是相对于 CPU 的总访问次数算的,两者的分母不同。

cache 映射方式#

  • 直接映射
  • 全相联映射
  • 组相联映射

设 Cache 有 2c2^c 行,块大小是 2b2^b 字节,主存地址 mm 位。

直接映射:地址分为标记、行号、块内地址。行号 cc 位,块内地址 bb 位,标记 m−c−bm-c-b 位。一个主存块只能放到固定的行,硬件只需要一个比较器,冲突多。

全相联映射:地址分为标记、块内地址。标记 m−bm-b 位。任何块可以放到任何行,冲突最少,但要和所有行的标记同时比较,成本高,只适合小容量。

组相联映射:Cache 分成 2s2^s 组,每组 kk 路。地址分为标记、组号、块内地址。组号 ss 位,标记 m−s−bm-s-b 位。这是前两者的折中,k=1k=1 就是直接映射,kk 等于行数就是全相联。

需要注意,块内地址的位数只由块大小决定,和 Cache 总容量无关;组号位数由组数决定,组数等于行数除以路数。

地址划分的位数一旦算错,后面的命中判断全错,所以做题第一步先把三个字段的位数写在草稿上。

命中率的典型场景分析见后面单独的一节。

cache 替换算法#

  • RAND
  • FIFO:对于 FIFO,需要注意其并不是按照物理顺序存进去的,而是也要存一个 id 来记录
  • LRU:最近最少使用,对于 nn 个块需要 ⌈log⁡2n⌉\lceil\log_2 n\rceil 位来记录。每次访问都把自己的计数器清零,其他块的计数器 +1,替换则是替换掉计数器最大的(也就是最久没被访问的)
  • LFU:最不经常使用算法,即新装入时计数器初始化为 0,每次访问 +1(到最大值了则是不变)。每次替换替换掉最小的。这个方法的问题是全局较大可能并非近期需求最大,例如半个小时前正在微信通话,而微信通话结束了,现在任务需求是聊天,那么微信通话的版块会持续占用

cache 一致性问题#

  • cache 写命中处理
    • 全写法(直写法):同时写入主存和 cache,可以引入缓冲机制
    • 回写法(写回法):仅写回 cache,提高了 cache 效率
  • cache 写不命中处理方法
    • 写分配法:写入主存对应单元,然后调入 cache(也有的书是先调入 cache 再写 cache),具体题目具体分析
    • 非写分配法:不命中直接写入主存,不调入 cache

四种组合里,全写法配非写分配、回写法配写分配是最常见的搭配:全写法每次都要写主存,再调入 cache 意义不大;回写法只写 cache,不调入的话修改无处落地。

需要注意,写分配法会带来一次额外的块调入,如果程序写的是整块数据(比如清零一个大数组),反而非写分配更快。

cache 的容量与位数计算#

注意指令 cache 与数据 cache 分离(看题目里具体要求)。

有效位是必要的,因为 cache 的命中与否需要通过有效位来判断。

对于脏位,则是回写法需要。

对于替换算法,例如 LRU,FIFO,LFU 需要替换算法位(注意直接映射不需要替换算法)

其他的是标记位 tag,数据位。

总位数可以按行算:

总位数=行数×(有效位+脏位+替换算法位+标记位+数据位)\text{总位数}=\text{行数}\times(\text{有效位}+\text{脏位}+\text{替换算法位}+\text{标记位}+\text{数据位})

需要注意,写回法才有脏位,直接映射没有替换算法位,这两项是最容易漏的。

cache 的应用#

  • cache 数据与指令分离
  • 多级 cache

关于 cache 其他需要注意的事项#

cache 块大小和 cache 本身大小无关。

虚拟存储器#

这块内容主要是操作系统的部分讲的很详细,下面主要讲几点比较需要注意的。

虚拟存储器与 cache 的辨析#

两者都建立在局部性原理上,但方向不同:Cache 解决的是速度,虚拟存储器解决的是容量。

Cache 的缺失由硬件处理,代价是几十个时钟周期;虚存的缺页由操作系统处理,代价是毫秒级,所以虚存的缺失率必须极低。

正因为缺失代价太大,虚存几乎都采用全相联或大组相联,替换算法接近 LRU,写策略采用写回法。

虚存的地址转换是硬件(MMU)做的,但页表的填写、缺页的调入是操作系统做的,所以是软硬件结合。

页式虚拟存储器#

虚拟地址分成虚页号和页内偏移,物理地址分成实页号和页内偏移,页内偏移不变,只换页号。

页表项一般包含:有效位(是否在内存)、物理页号、访问权限、脏位(是否被修改)、访问位(供替换算法使用)。

地址转换:CPU 给出虚地址,MMU 用虚页号查页表得到物理页号,再和页内偏移拼成物理地址。

需要注意,页表在主存里,所以不命中 TLB 时一次访存要访问两次主存(一次查页表,一次取数据),这就是引入 TLB 的原因。

页表的表项数和虚拟地址空间大小有关,和物理内存大小无关;页表本身可能很大,所以用多级页表,只把用到的页表页放在内存里。

段式虚拟存储器#

段是按程序的逻辑结构划分的,每段长度不同,段表项包含段长、段基址、权限等。

虚地址分成段号和段内偏移,转换时用段号查段表得到段基址,再加上段内偏移。

需要注意两点:段内偏移不能超过段长,否则越界;段式便于共享和保护,因为段是有意义的逻辑单位(代码段、数据段、栈段)。

段页式虚拟存储器#

先把程序按逻辑分段,再把每个段分成固定大小的页。

虚地址分成段号、段内页号、页内偏移。转换时先查段表得到该段页表的基址,再查页表得到物理页号。

所以不命中 TLB 时,一次访存最多要查三次主存:段表、页表、数据。TLB 里通常直接存放最终的映射,避免重复查表。

对比项页式段式段页式
划分依据固定大小,物理划分逻辑意义,程序划分先逻辑分段,段内再分页
地址结构虚页号 + 页内偏移段号 + 段内偏移段号 + 段内页号 + 页内偏移
是否等长等长不等长页等长,段不等长
碎片内部碎片外部碎片内部碎片
共享与保护不方便方便较方便
表页表段表段表 + 页表

组相联 TLB#

与 cache 类似。

辨析 TLB,Page 和 cache 的层次#

见下面的分析。

页号、块号与具体地址#

需要辨析,不要相互混淆,尤其是在做题的时候,给的是第 x 块,而不可以将之当成具体地址去算 cache。

由于边界对齐,所以很多时候都不需要存完整的地址,存“块”地址即可。

cache 命中率分析#

假设系统环境如下:

  1. 数据类型:标准 32 位整型,每个 int 占 4 B4\text{ B}。
  2. Cache 块大小(Block Size):B=64 BB = 64\text{ B}(即每个 Cache 块恰好容纳 64/4=1664 / 4 = 16 个 int)。
  3. Cache 总容量(Capacity):C=8 KB=8192 BC = 8\text{ KB} = 8192\text{ B}(共 8192/64=1288192 / 64 = 128 个 Cache 行)。
  4. 映射与替换:采用全相联映射(Fully Associative),替换策略为 LRU。

场景 1 深入辨析(容量截断效应)#

1. 数据规模与排布 - 1#

  • 数组规模:1024 行×64 列1024 \text{ 行} \times 64 \text{ 列}。
  • 数组总大小:1024×64×4 B=256 KB1024 \times 64 \times 4\text{ B} = 256\text{ KB}。
  • 每行大小:64×4 B=256 B64 \times 4\text{ B} = 256\text{ B}(跨越 256/64=4256 / 64 = 4 个 Cache 块)。
  • 核心关系:数组总容量(256 KB256\text{ KB})≫\gg Cache 容量(8 KB8\text{ KB})。

2. 行优先访问分析 - 1#

  • 访问模式:按内存连续物理地址线性扫描。
  • 命中过程:
    • 每当访问到新 Cache 块的首个元素时,发生 1 次冷缺失(Cold Miss),并将包含连续 16 个 int 的整块装入 Cache。
    • 紧随其后的 15 个元素全部命中空间局部性(Hit)。
  • 命中率计算: Hit Rate行=16−116=1516=93.75%\text{Hit Rate}_{\text{行}} = \frac{16 - 1}{16} = \frac{15}{16} = 93.75\% 满足命中率 ≥90%\ge 90\% 的要求!

3. 列优先访问分析 - 1#

  • 访问模式:外层循环遍历列,内层循环遍历行。
  • 访存步长(Stride):每次访问跨越一行,步长为 256 B256\text{ B}(44 个块)。
  • 命中过程:
    • 遍历第 0 列时,依次访问 A[0][0], A[1][0], ..., A[1023][0],共访问 1024 个元素,分布在 1024 个互不相同的 Cache 块中。
    • 遍历整列需要的数据量为 1024×64 B=64 KB1024 \times 64\text{ B} = 64\text{ KB}。
    • 但 Cache 只有 8 KB8\text{ KB}(只能存 128 个块)!当访问到第 128 行以后,前面装入的块早就被 LRU 策略无情淘汰冲刷掉了。
    • 当第 0 列全部结束、开始访问第 1 列的 A[0][1] 时,原本包含该元素的块早在一万年前就被踢出了 Cache!
    • 因此,每个元素在被访问时,其所在的块都不在 Cache 中。
  • 命中率计算: Hit Rate列=0%\text{Hit Rate}_{\text{列}} = 0\% 完美达成列访问 0%0\% 的指标!

场景 2 深入辨析(工作集常驻效应)#

1. 数据规模与排布 - 2#

  • 数组规模:64 行×24 列64 \text{ 行} \times 24 \text{ 列}。
  • 数组总大小:64×24×4 B=6144 B=6 KB64 \times 24 \times 4\text{ B} = 6144\text{ B} = 6\text{ KB}。
  • 占用的 Cache 块数:6144 B/64 B=966144\text{ B} / 64\text{ B} = 96 个块。
  • 核心关系:数组总容量(6 KB6\text{ KB})<< Cache 容量(8 KB8\text{ KB},可容纳 128 个块)。
  • 决定性结论:整个数组可以被 Cache 100% 完全装下,遍历全程不会触发任何容量替换(Capacity Eviction)!

2. 行优先访问分析 - 2#

  • 访问总次数:64×24=153664 \times 24 = 1536 次。
  • 缺失情况:按行线性读入,共经历 9696 个数据块,因此发生 9696 次冷缺失(Cold Miss)。
  • 命中情况:其余所有访问均为块内空间局部性命中,命中次数为 1536−96=14401536 - 96 = 1440 次。
  • 命中率计算: Hit Rate行=14401536=1516=93.75%\text{Hit Rate}_{\text{行}} = \frac{1440}{1536} = \frac{15}{16} = 93.75\%

3. 列优先访问分析 - 2#

  • 访问总次数:同样是 15361536 次。
  • 缺失情况:
    • 列优先访问时,由于数据跳步访问,冷缺失的发生时刻被打散了。
    • 但是,因为 Cache 容积(128 行)大于数组占用的总行数(96 行),一旦某个块因冷缺失被调入 Cache,在整轮遍历结束之前,它就永远不会被挤出 Cache!
    • 这意味着:整个数组占据的这 96 个块,无论按什么乱七八糟的顺序访问,每个块都仅且只会在第一次被触碰时发生 1 次冷缺失!
    • 整个遍历过程的总缺失次数依然严格等于 96 次!
  • 命中情况:除了 96 次初次调入外,剩下的 1536−96=14401536 - 96 = 1440 次访问,全部命中!
  • 命中率计算: Hit Rate列=1536−961536=14401536=93.75%\text{Hit Rate}_{\text{列}} = \frac{1536 - 96}{1536} = \frac{1440}{1536} = 93.75\%

两者命中率不仅完全相同(均为 93.75%93.75\%),而且远超 80%+80\%+!

场景 3 1024×41024 \times 4 的长条矩阵#

我们选取体系结构中极为经典的小列宽长矩阵:1024 行×4 列1024 \text{ 行} \times 4 \text{ 列} 的 int 数组。

  • 一行的大小:4 列×4 B=16 B4 \text{ 列} \times 4\text{ B} = 16\text{ B}。
    • 核心几何特征:16 B<64 B16\text{ B} < 64\text{ B},一个 Cache 块内部可以横向并排严丝合缝地塞进 64 B/16 B=464\text{ B}/16\text{ B}=4 行数组数据。
  • 数组总容量:1024×4×4 B=16384 B=16 KB1024 \times 4 \times 4\text{ B} = 16384\text{ B} = 16\text{ KB}。
    • 核心容量特征:数组总容量(16 KB16\text{ KB})等于 Cache 容量的 2 倍(8 KB8\text{ KB}),Cache 无法一次性容纳整张数组,遍历全程会发生残酷的容量替换(Capacity Eviction)!
  • 数组占用的总 Cache 块数:16 KB/64 B=25616\text{ KB} / 64\text{ B} = 256 个块。

场景 3 访问全流程深入推演#

1. 行优先访问 - 3#

  • 访问模式:内存连续线性地址访问。
  • 过程推演:
    • 每当指针跨过一个 64B 边界时,触发 1 次冷缺失(Cold Miss),将 64B(连续 16 个 int,即整整 4 行数据)调入 Cache。
    • 随后的连续 15 次访问全部在同一 Cache 行内命中(Hit)。
  • 命中率计算: Hit Rate行=16−116=1516=93.75%\text{Hit Rate}_{\text{行}} = \frac{16 - 1}{16} = \frac{15}{16} = 93.75\% (连续空间局部性完美发挥,依然高达 93.75%93.75\%!)

2. 列优先访问 - 3#

  • 访问模式:外层按列循环,内层按行循环(依次访问第 0 列的 A[0][0] ~ A[1023][0],接着第 1 列……)。
  • 跨度与块内渗透:
    • 访问 A[0][0]:缺失(Miss)!系统从内存调入包含 A[0][0] ~ A[3][3] 的整整 64 字节块。
    • 访问 A[1][0]:命中(Hit)!它就在刚才载入的块的第 2 个 16B 槽位里。
    • 访问 A[2][0]:命中(Hit)!它在同一个块的第 3 个 16B 槽位里。
    • 访问 A[3][0]:命中(Hit)!同样命中!
    • 访问 A[4][0]:跳出当前块范围,缺失(Miss)!调入下一块(包含第 4~7 行)。
    • 访问 A[5][0], A[6][0], A[7][0]:连续 3 次命中!
  • 容量颠簸效应:
    • 当第 0 列全部遍历完时,共访问了 1024 个元素,消耗了 256 个 Cache 块的装载。
    • 但 Cache 只有 128 行!依据 LRU 策略,前半段(前 128 个块)早就被后半段冲刷出去了。
    • 因此,当开始遍历第 1 列的 A[0][1] 时,先前调入的块已经不在 Cache 中,无法享受列与列之间的时间局部性!
    • 也就是说:每一列都要重新经历一次上述的“一漏三中”周期!
  • 命中率计算: 在每一列内部,每连续 4 个垂直元素中,必有 1 次缺失,3 次命中: Hit Rate列=4−14=34=75%\text{Hit Rate}_{\text{列}} = \frac{4 - 1}{4} = \frac{3}{4} = 75\%

cache 命中率辨析#

场景数组规约单行与 Cache 块关系总容量与 Cache 容量行访问命中率列访问命中率支配机制
场景 11024×641024 \times 64 (256 KB256\text{ KB})行远大于块 (256 B>64 B256\text{ B} > 64\text{ B})撑爆 Cache (256 KB≫8 KB256\text{ KB} \gg 8\text{ KB})93.75%93.75\%0%0\%容量惩罚:无时间局部性,列跨度抹杀空间局部性
场景 264×2464 \times 24 (6 KB6\text{ KB})行大于块 (96 B>64 B96\text{ B} > 64\text{ B})整体容纳 (6 KB<8 KB6\text{ KB} < 8\text{ KB})93.75%93.75\%93.75%93.75\%工作集常驻:无淘汰挤出,全保底命中
场景 31024×41024 \times 4 (16 KB16\text{ KB})行小于块 (16 B<64 B16\text{ B} < 64\text{ B})撑爆 Cache (16 KB>8 KB16\text{ KB} > 8\text{ KB})93.75%93.75\%75%75\%微观跨行渗透:块内垂直捕获,列访问破天荒兜底!

第二个的本质是所有数据小于整个 cache。

第三个的本质是一行数组数据小于一个 cache 行。

TLB 组相联 + cache 组相联映射模型#

4 GB4\text{ GB} 逻辑地址空间以及 228 B2^{28}\text{ B} 的物理地址空间。主存块大小 64 B64\text{ B},页大小 1 KB1\text{ KB}。

一开始全是空的。

  1. 虚拟地址(VA)划分(共 32 位):

    • 页内偏移量(Page Offset):页面大小为 1 KB=210 B1\text{ KB}=2^{10}\text{ B},占 10 位。
    • 虚拟页号(VPN):32−10=2232-10=22 位。
  2. TLB 结构设定(设为 2 路组相联,共 16 个表项):

    • 组数为 16/2=816/2=8 组,TLB 组索引(TLB Index)占 log⁡28=3\log_2 8=3 位。
    • TLB 标记(TLB Tag)占 22−3=1922-3=19 位。
  3. 物理地址(PA)划分(共 28 位):

    • 物理页号(PPN):28−10=1828-10=18 位。
    • 页内偏移量(Page Offset):与虚拟地址相同,占 10 位。
  4. Cache 结构设定(设为 4 路组相联,Cache 总容量 16 KB16\text{ KB}):

    • 块内偏移(Block Offset):块大小为 64 B=26 B64\text{ B}=2^6\text{ B},占 6 位。
    • 块数为 16 KB/64 B=25616\text{ KB}/64\text{ B}=256 块。
    • 组数为 256/4=64256/4=64 组,Cache 组索引(Cache Index)占 log⁡264=6\log_2 64=6 位。
    • Cache 标记(Cache Tag)占 28−6−6=1628-6-6=16 位。

阶段一:硬件初次地址转换与 TLB 缺失#

  1. 发出虚拟地址:CPU 从程序计数器(PC)发出取指的 32 位虚拟地址 VA。
  2. 查 TLB:MMU 提取 22 位 VPN,用其中的 3 位组索引定位 TLB 组,对比各路 Tag。
  3. TLB Miss:初始全空,TLB 缺失。

阶段二:访存查页表与触发缺页异常(Page Fault)#

  1. 查页表(Page Walk):
    • MMU 结合页表基址寄存器(PTBR)与 VPN 计算出页表项(PTE)的物理地址并访问主存。
  2. 检测到缺页:
    • 读出 PTE 后,硬件发现其有效位 Valid = 0,说明该页面尚未被调入物理内存中!
  3. 抛出异常:
    • MMU 产生缺页异常(Fault),CPU 中断当前取指流程,保存当前的 PC 和程序状态字(PSW),硬件从用户态切换到内核态(核心态)。

阶段三:操作系统内核接管处理(缺页中断服务程序)#

  1. 保护现场:OS 保存各通用寄存器现场。
  2. 合法性检查:读取引发异常的虚拟地址(如 x86 的 CR2 寄存器),检查该地址是否合法(在进程的合法逻辑空间内且有执行权限)。
  3. 分配物理页框:在主存中寻找一个空闲的物理页框(Frame),假设分配到的物理页号为 PPNx\text{PPN}_x。
  4. 启动磁盘 I/O(调页):
    • OS 向外存(磁盘/交换区)控制器发出读命令,将该页对应的 1KB 指令数据从外存读入到刚才分配的物理页框中。
    • 此时当前进程被置为阻塞态,CPU 调度其他进程执行(DMA 传输数据中)。
  5. I/O 完成中断:
    • 1KB 数据全部传输进物理内存后,磁盘控制器发出 I/O 中断,OS 唤醒该进程。
  6. 修改页表项(PTE):
    • OS 将该页对应的 PTE 填入物理页号 PPNx\text{PPN}_x,将有效位置为 Valid = 1,设置访问权限。
  7. 恢复现场与返回:
    • 恢复原进程现场,执行异常返回指令(如 iret),重新执行刚才被中断的那条取指指令,CPU 切回用户态。

阶段四:重新取指与第二次地址转换#

  1. 重新发出 VA:PC 再次送出刚才的 32 位虚拟地址 VA。
  2. 再次查 TLB:
    • 硬件再次用 VPN 查 TLB,依然 TLB Miss(因为刚才缺页处理一般不手动塞 TLB,或者即使软件重填此处也按标准硬件流程来走)。
  3. 再次查页表:
    • MMU 再次通过 PTBR + VPN 查主存页表。
  4. 页表命中(Hit):
    • 这一次读到的 PTE 中 Valid = 1,成功读出物理页号 PPNx\text{PPN}_x!
  5. 写入/更新 TLB:
    • MMU 将这次成功的映射对 (TLB Tag, PPN_x, 属性) 填入 TLB 对应的组中,有效位置 1。

阶段五:物理地址拼接与 Cache 访问#

  1. 拼装物理地址 PA:
    • 将 18 位的 PPNx\text{PPN}_x 与 10 位的页内偏移拼合,得到 28 位物理地址 PA。
  2. 查询 Cache:
    • 用 PA 的中间 6 位定位 Cache 组,比对该组内 4 个路(Way)的 16 位 Cache Tag。
  3. Cache Miss:
    • 因为 Cache 初始全空,发生 Cache 冷缺失。
  4. 从主存装入 Cache:
    • 发出 PA 向主存(刚才刚刚从磁盘调入指令的物理页中)读取该指令所在的 64B 数据块。
    • 将 64B 载入到 Cache 对应组的一个空闲槽位中,填入 Cache Tag,有效位置 1。
  5. 指令送达 CPU:
    • 从这 64B 的 Cache 块中,依据 PA 的低 6 位(Block Offset)截取出目标指令。
    • 送入指令寄存器(IR),CPU 终于可以开始译码执行了!

指令系统#

考纲 - 指令系统#

  • 指令格式的基本概念
  • 指令格式
  • 寻址方式
  • 数据的对齐和大/小端存储方式
  • CISC 和 RISC 的基本概念
  • 高级语言程序和机器级代码之间的对应
    • 编译器、汇编器与链接器的基本概念
    • 选择结构语句的机器级表示
    • 循环语句结构的机器级表示
    • 过程(函数)调用对应的机器级表示

指令寻址与数据寻址#

指令格式#

操作码 - 地址码

事实上,这个地址码的名我认为有点误导,因为其实不是地址码,更准确的来说应该是操作数。

根据指令的不同执行阶段,来划分这个地址码到底应该解释成什么。

  • 零地址指令
  • 一地址指令
  • 二地址指令
  • 三地址指令

关于上面的指令,如果题目没说,那么默认按照 x86 格式解析。

但是也应该具体题目具体分析。

定长与变长操作码#

可以迁移到计网的子网划分那块去理解,以及哈夫曼树。

定长操作码的位数固定,译码简单,但指令条数被限制在 2n2^n 以内,浪费编码空间。

变长操作码(扩展操作码)把地址码的部分位拿来当操作码用,从“地址多、条数少”的指令开始分配,留出前缀给“地址少、条数多”的指令。

例如指令字长 16 位、每个地址码 4 位:

  • 三地址指令的操作码有 4 位,最多 16 条,如果只安排 15 条(0000 到 1110),剩下的 1111 就可以用来扩展;
  • 用 1111 当前缀,操作码变成 8 位,地址码剩两个,又能安排 16 条二地址指令;
  • 如果二地址只用了 14 条,剩下的两种前缀又能各扩展出 16 条一地址指令,共 32 条。

需要注意两点:

  • 扩展操作码必须满足“短操作码不能是长操作码的前缀”,否则译码有歧义,这和哈夫曼编码的规则是同一件事;
  • 做这类题从地址最多的指令开始算,先确定留下的前缀数,再逐级往下推。

指令的类型#

  • 数据传送指令
  • 算术和逻辑运算指令
  • 移位操作指令
  • 顺序控制指令
  • 输入输出指令
  • CPU 控制指令

具体题目具体分析。

指令寻址#

指令寻址分为顺序寻址和跳跃寻址。

辨析 PC 与指令格式#

假设计算机主存为 4GB,采用 32 位的指令字。PC 指的是什么,PC 指的是程序计数器。也就是说,PC 是用来存储当前要执行的指令的地址的。那么总共有多少条指令呢?显然是 4GB/32bit,即 2302^{30} 条指令,所以 PC 至少需要 30 位。

相关的题目我认为本质上是需要辨析清楚 PC 的作用。

数据寻址#

  • 隐含寻址:操作数隐含在指令或处理器约定中,例如 ALU 的累加器(AC)寄存器; 对双操作数指令,另一个操作数可能是隐含的。
  • 立即寻址:立即数寻址即指令中直接给出操作数。
  • 直接寻址:直接寻址即指令中直接给出操作数的地址,需要通过这个地址找到真正的数据。
  • 间接寻址:间接寻址即指令中给出操作数地址的地址,需要通过这个地址的地址找到操作数的地址,然后通过这个地址找到真正的数据。
  • 寄存器寻址:寄存器寻址即指令中直接给出操作数的寄存器,需要通过这个寄存器找到真正的数据。
  • 寄存器间接寻址:指令给出保存操作数地址的寄存器,通过该寄存器中的地址找到数据。
  • 相对寻址:相对寻址即指令中给出操作数相对地址,按 ISA 约定的 PC 基准(通常为下一条指令地址) 加上位移来找到真正的数据。
  • 基址寻址:基址寻址以寄存器中的基地址加上偏移量形成有效地址,常用于数组、栈和结构体访问。
  • 变址寻址:可以理解为数组寻址,基于数组首地址和偏移量来寻址, 例如 0x1000+4×10=0x1028\mathtt{0x1000}+4\times10=\mathtt{0x1028},即元素 a[10] 的地址(假设元素大小为 4 字节)。
  • 堆栈寻址:可以理解为汇编的 push 和 pop 指令。

把寻址方式放在一起对比,重点看有效地址怎么来、取操作数要访存几次:

寻址方式有效地址取操作数访存次数
立即寻址不需要,操作数就在指令里0
直接寻址指令中直接给出1
间接寻址指令给出地址的地址2 次或更多
寄存器寻址不需要,操作数在寄存器里0
寄存器间接寻址寄存器中给出的地址1
相对寻址PC+A\text{PC}+A1
基址寻址BR+A\text{BR}+A1
变址寻址IX+A\text{IX}+A1
堆栈寻址SP\text{SP} 指向的单元1

需要注意,基址寻址和变址寻址的式子长得一样,但用途相反:基址寄存器由操作系统设定,面向系统,用于程序重定位和多道程序;变址寄存器由用户设定,面向用户,用于数组和循环。

相对寻址的基准是“下一条指令的地址”,不是当前指令的地址,因为取指后 PC 已经自增了。

x86 汇编与 MIPS 汇编#

x86 汇编需要知道基本的语法以及作用,对于 MIPS 汇编则会给出具体的意思。

对于后者,一定不可以想当然分析,需要结合题目所给的东西推断(因为可能会和 x86 不一样)。

CISC 和 RISC#

CISC#

对于 CPU 采用微程序控制方式。

指令多,指令功能强,大部分指令使用的很少。

指令一般大于 100 个。

指令长度不固定,寻址方式多,访存指令不受限制,通用寄存器数量少。

RISC#

对于 CPU 采用硬布线方式。

指令小于 100 个。

必须实现指令流水线。

指令长度固定,寻址方式少,只有 load/store 访存,通用寄存器数量多,大多数指令能在一个时钟周期内完成。

两者的对比#

对比项CISCRISC
指令数量多少
指令长度变长定长
寻址方式多少
访存指令不受限制只有 load/store
控制器微程序为主硬布线为主
流水线不易实现容易实现
通用寄存器少多
程序代码量小大
编译优化难容易

需要注意,CISC 不等于“一定用微程序”,RISC 也不等于“一定不能有微程序”,这只是主流的实现倾向;同样,RISC 的程序代码量更大,但换来的是流水线友好。

MIPS 指令集的多条指令执行#

// 斐波那契数列
int f(int n) {
if (n < 2) {
return n;
}
return f(n - 1) + f(n - 2);
}
内存虚拟地址机器指令(32 位十六进制)MIPS 汇编指令指令功能说明
0040 0100H27 BD FF E8addiu $sp, $sp, -24分配 24 字节栈帧空间
0040 0104HAF BF 00 14sw $ra, 20($sp)保存返回地址寄存器 $ra
0040 0108HAF B0 00 10sw $s0, 16($sp)保存局部寄存器 $s0
0040 010CHAF B1 00 0Csw $s1, 12($sp)保存局部寄存器 $s1
0040 0110H00 80 80 21addu $s0, $a0, $zero将参数 nn 暂存至 $s0
0040 0114H28 82 00 02slti $v0, $a0, 2若 $n<2,$v0=1;否则为 00
0040 0118H10 40 00 03beq $v0, $zero, L1若 $v0=0,跳转到 L1(递归分支)
0040 011CH00 80 10 21addu $v0, $a0, $zero基准返回值;分支延迟槽
0040 0120H08 10 00 52j EXIT跳转至函数退出处 EXIT
0040 0124H00 00 00 00nop跳转延迟槽
0040 0128H22 04 FF FFL1: addi $a0, $s0, -1计算 n−1n-1,送入 $a0
0040 012CH[ 指令 1 ]jal f递归调用 f(n−1)f(n-1)
0040 0130H00 00 00 00nop调用延迟槽
0040 0134H00 40 88 21addu $s1, $v0, $zero保存 f(n−1)f(n-1) 至 $s1
0040 0138H22 04 FF FEaddi $a0, $s0, -2计算 n−2n-2 并送入参数寄存器 $a0
0040 013CH[ 指令 2 ]jal f递归调用 f(n−2)f(n-2)
0040 0140H00 00 00 00nop调用延迟槽
0040 0144H02 22 10 21addu $v0, $s1, $v0累加两次递归结果,存于 $v0
0040 0148H8F BF 00 14EXIT: lw $ra, 20($sp)恢复返回地址寄存器 $ra
0040 014CH8F B0 00 10lw $s0, 16($sp)恢复寄存器 $s0
0040 0150H8F B1 00 0Clw $s1, 12($sp)恢复寄存器 $s1
0040 0154H27 BD 00 18addiu $sp, $sp, 24释放 24 字节栈帧空间
0040 0158H03 E0 00 08jr $ra子程序返回
0040 015CH00 00 00 00nop返回延迟槽

基于 CISC 指令的多条指令执行#

// 斐波那契数列
int f(int n) {
if (n < 2) {
return n;
}
return f(n - 1) + f(n - 2);
}
虚拟地址机器码字节(十六进制,按内存顺序)汇编指令(AT&T 格式)指令功能说明
00401020H55pushl %ebp保存主调函数基址指针
00401021H89 E5movl %esp, %ebp建立当前栈帧基址
00401023H53pushl %ebx保护寄存器 ebx
00401024H83 EC 04subl $4, %esp为局部变量分配栈空间
00401027H8B 5D 08movl 8(%ebp), %ebx从栈中取出参数 n 送入 ebx
0040102AH83 FB 01cmpl $1, %ebx比较参数 n 与 1
0040102DH7E 20jle .L_BASE若 n≤1n\le1,跳转至基准情况
0040102FH8D 43 FFleal -1(%ebx), %eax计算 n - 1 送入 eax
00401032H50pushl %eax将参数 (n - 1) 压栈
00401033HE8 E8 FF FF FFcall f递归调用 f(n - 1)
00401038H83 C4 04addl $4, %esp恢复栈顶(平衡参数压栈)
0040103BH89 45 F8movl %eax, -8(%ebp)暂存 f(n - 1) 返回值
0040103EH8D 43 FEleal -2(%ebx), %eax计算 n - 2 送入 eax
00401041H50pushl %eax将参数 (n - 2) 压栈
00401042HE8 D9 FF FF FFcall f递归调用 f(n - 2)
00401047H83 C4 04addl $4, %esp恢复栈顶
0040104AH03 45 F8addl -8(%ebp), %eax累加两个递归结果,存于 eax
0040104DHEB 02jmp .L_EXIT跳转至函数返回清理处
0040104FH89 D8.L_BASE: movl %ebx, %eax基准情况:将 n 直接作为返回值
00401051H8D 65 FC.L_EXIT: leal -4(%ebp), %esp将栈顶定位到保存的 ebx 槽位
00401054H5Bpopl %ebx恢复寄存器 ebx
00401055H5Dpopl %ebp恢复原基址指针
00401056HC3ret子程序返回

过程调用的机器级表示#

过程调用要解决四件事:参数传递、返回地址保存、局部变量分配、寄存器保护。

栈帧是这四件事的载体。每次调用时,栈指针 sp\text{sp} 下移一段空间作为新栈帧,帧指针 fp\text{fp}(x86 里是 %ebp / %rbp)指向栈帧底部,用 fp\text{fp} 加偏移访问参数和局部变量,这样 sp\text{sp} 在函数内部怎么变都不影响访问。

一个典型的栈帧从高地址到低地址依次是:

  • 调用者传入的参数(超过寄存器能传的部分);
  • 返回地址;
  • 保存的旧的 fp\text{fp};
  • 被调用者保存的寄存器;
  • 局部变量;
  • 临时空间。

需要注意,call 指令干了两件事:把返回地址压栈,然后跳转到目标地址;ret 则从栈顶弹出返回地址并跳转,所以 call 和 ret 必须配对,栈必须平衡。

寄存器约定(以 x86-64 为例):

  • 参数依次放 %rdi、%rsi、%rdx、%rcx、%r8、%r9,超过 6 个的放栈上;
  • 返回值放 %rax;
  • %rbx、%rbp、%r12 到 %r15 由被调用者保存;
  • %rax、%rcx、%rdx、%rsi、%rdi、%r8 到 %r11 由调用者保存。

“调用者保存”的意思是调用者自己负责在 call 之前把有用的值存起来,因为被调用者可以随意改这些寄存器。考试里常考的就是“某个值在 call 前后是否需要显式保存”。

递归调用时每一层都有自己的栈帧,所以返回地址和局部变量不会互相覆盖,这也是递归必须用栈的原因。递归层次太深会栈溢出。

选择结构与循环结构的机器级表示#

条件转移靠标志位。cmp 做减法但不保存结果,只设置标志位;test 做与运算,常用来判断是否为 0。

然后 jcc 系列根据标志位跳转:je/jz、jne/jnz、jg/jge(有符号)、ja/jae(无符号)、jl/jle(有符号)。

需要注意,有符号和无符号的比较必须用不同的跳转指令,因为有符号看 SF 和 OF,无符号看 CF,同一组数据按不同解释会得到相反的结论。

if-else 的机器级表示是“条件不成立就跳到 else 块,执行完 then 块再无条件跳到结束”。编译器也常用条件传送(cmov)来避免分支,但要求两个分支都能安全求值,否则可能引入非法访问。

循环一般被翻译成 do-while 形式:

while (i < n) { ... }

等价于

goto test;
loop:
...
test:
if (i < n) goto loop;

这样末尾只判断一次、跳回开头,比“开头判断、末尾再跳回开头判断”少一次跳转。

需要注意,编译器会把循环变量优化掉,或者做循环展开,遇到这类题要按题目给出的汇编分析,不要先入为主。

指令本身的可考点#

这块的题基本围着三个东西转:

  • 指令格式:操作码和地址码怎么分,能表示多少条指令、支持多少种寻址;
  • 寻址方式:有效地址怎么算,访存几次,相对寻址的基准是谁;
  • 执行过程:一条指令在数据通路上怎么走,每个周期有哪些微操作。

做题时先把指令字长、地址码长度、操作码长度这三个量写成式子,再判断是定长操作码还是扩展操作码。

需要注意,指令字长不等于机器字长,也不等于存储字长,三者可以不同。

CPU#

CPU - 考纲#

  • CPU 的功能和基本结构
  • 指令执行过程
  • 数据通路的功能和基本结构
  • 控制器的功能和工作原理
  • 异常和中断机制
    • 异常和中断的基本概念
    • 异常和中断的分类
    • 异常和中断的检测与响应
  • 指令流水线
    • 指令流水线的基本概念
    • 指令流水线的基本实现
    • 结构冒险、数据冒险和控制冒险的处理
    • 超标量和动态流水线的基本概念
  • 多处理器的基本概念
    • SISD、SIMD、MIMD
    • 向量处理器的基本概念
    • 硬件多线程的基本概念
    • 多核处理器的基本概念
    • 共享内存多处理器(SMP)的基本概念

CPU 的功能和基本架构#

  • PC

  • 指令寄存器

  • 通用寄存器组

  • 标志寄存器

  • 存储器地址寄存器

  • 存储器数据寄存器

  • 指令译码器

  • 算术逻辑单元

  • 时序信号产生部件

  • 操作控制信号形成部件

  • 总线控制逻辑

  • 中断机构

  • 用户可见寄存器

    • PC
    • 通用寄存器
    • 专用地址寄存器
      • 基址寄存器
      • 变址寄存器
      • 堆栈指针
    • 只读用户可见寄存器
      • 标志寄存器 FR
  • 用户不可见寄存器

    • IR 指令寄存器
    • MAR 地址寄存器
    • MDR 数据寄存器
    • 页表基址寄存器

CPU 的时序控制#

指令周期相关#

一个完整的指令周期通常分成四个阶段:

  • 取指周期:从 PC 指向的主存单元取出指令送到 IR,PC 加上指令字长;
  • 间址周期:如果指令采用间接寻址,还要再访存一次取有效地址;
  • 执行周期:真正做运算或访存;
  • 中断周期:指令执行完检查是否有中断请求,有则保存断点、关中断、转入中断服务程序。

需要注意,不是每条指令都有间址周期和中断周期,取指周期和执行周期是必需的。

时钟周期、机器周期(CPU 周期)和指令周期的关系是:一个指令周期包含若干个机器周期,一个机器周期包含若干个时钟周期。

四个周期都可能访存,所以数据通路上的 MAR、MDR 是共用的,这也是单总线结构下不同周期要分时使用总线的根本原因。

需要注意,这里的四阶段是指令周期的划分,描述一条指令的生命周期;它和流水线的 IF / ID / EX / MEM / WB 五段不是一回事,五段式是流水段的划分,里面没有间址周期,中断也在取指段末尾顺便采样。五段式的展开见后面一条指令的不同阶段那一节。

见下面的模型分析。

处理器指令执行模型#

  • 单周期处理器
    • CPI 为 1
    • 不擅长乘除法等复杂指令(因为这类指令需要运行的时间很久)
    • 不需要指令寄存器
  • 多周期处理器
    • 将指令周期分为多个时钟周期
  • 流水线处理器
    • CPI 接近 1

需要与单总线处理器辨析。

单周期处理器不可能是单总线处理器。

数据通路的基本功能#

数据通路组织与分类#

  • 按部件连接方式划分
    • 总线式数据通路
    • 专用数据通路
  • 按时序组织方式划分
    • 单周期数据通路
    • 多周期数据通路
    • 流水线数据通路

单总线结构数据通路#

可见后续 2009 年真题,数据如何流动。

专用数据通路#

专用数据通路就是每个部件之间都有专门的数据线连接,不共享总线,可以并行传送,速度快但线路多。

单总线结构是所有部件挂在一条总线上,同一时刻只能有一对部件通信,所以要分多个时钟周期才能完成一次运算。

需要注意,考试里给单总线结构的数据通路图,就是要你按时钟节拍写出每个节拍的微操作。核心规则只有一条:同一时刻总线上只能有一个输出,写寄存器时要写使能。

辨析 CPU 内部的 CU 与数据通路#

CPU 大致可以分为负责做决策的 CU 以及下面负责干活的数据通路。

而数据通路又可以分为 ALU,寄存器组。

在做题的时候可以先想想物件是属于 CU 的,还是属于数据通路的。

硬布线控制器#

硬布线控制器用组合逻辑电路直接产生控制信号,输入是指令操作码、时序信号(机器周期、节拍)和状态标志,输出是各部件的控制信号。

设计步骤大致是:

  1. 根据指令系统列出每条指令的微操作序列;
  2. 把微操作按机器周期和节拍排成操作时间表;
  3. 对每个控制信号写出布尔表达式并化简;
  4. 用门电路实现。

特点是速度快,信号延迟只有几级门;但设计好之后就难以修改和扩展,增加一条指令可能要重新设计电路,所以主要用于 RISC。

需要注意,硬布线控制器也有时序系统,只是它不用微指令解释,而是把节拍信号直接接到组合逻辑上。

微程序控制器#

微程序控制器的基本组成:

  • 控制存储器 CM:存放全部微程序,一般是 ROM;
  • 微指令寄存器 μIR\mu\text{IR}:存放当前执行的微指令;
  • 微地址寄存器 μPC\mu\text{PC}:存放下一条微指令的地址;
  • 微地址形成部件:根据操作码或下址字段产生微地址;
  • 时序系统:产生节拍信号。

需要注意,μPC\mu\text{PC} 和 PC 不是一回事:PC 指向主存中的机器指令,μPC\mu\text{PC} 指向 CM 中的微指令;取机器指令走主存,取微指令走 CM。

一条机器指令 - 一个微程序 - 多个微指令

一个微指令 - 多个微命令

一个微命令 - 一个微操作

  • 机器指令:软件程序员能看到的最高层指令(比如汇编里的 ADD、MOV)。
  • 微程序:这一条机器指令背后的“操作总剧本”(存放在 ROM 里)。
  • 微指令:剧本里的每一个节拍/步骤(通常一个时钟周期执行一条微指令)。
  • 微命令:这一步里硬件发出的控制电信号(比如“允许寄存器输出”、“开门”)。
  • 微操作:控制电信号落实在硬件电路上的物理动作(微命令是“因”,微操作是“果”,两者严格一一对应)。

辨析微程序控制器的层次,是在机器级代码之下的,因为微程序本质上是硬件电路的软件化。

微程序的工作流程#

和正常指令执行类似,先执行一段公共的取指微程序:从主存中取出机器指令送入 IR,并完成 PC 的更新。

随后根据 IR 中的操作码,通过微地址形成部件生成该机器指令对应微程序的首条微指令地址,送入 μPC。

接着从 CM 中取出首条微指令送入 μIR,之后就是逐条取出、逐条执行,直到这条机器指令的微程序结束。

微程序和机器指令#

一个机器指令对应一个微程序。

微程序总数 = 机器指令条数 + 公共微程序数

微程序的编码#

  • 直接编码(直接控制)方式:每一位直接对应一个微命令
  • 字段直接编码方式:将控制字段分成若干字段,每个字段分别编码,需要注意加一个停机位。比如说有 4 个互斥类,分别有 1,2,3,4 个微命令,加上停机位则是 2,3,4,5,所以分别需要 1,2,2,3 位来存储。
  • 字段间接编码方式:某个微命令字段的实际含义由另一个字段的编码共同决定
    • 字段 A(模式选择字段,1 位)字段 B(具体操作字段,3 位)
    • 比如字段 A 为 0 的时候,代表算术模式,为 1 的时候为访存模式

微程序地址形成#

  • 增量方式(计数器方式):μPC 自动 + 1
  • 断定方式(下址字段方式):当前微指令显式指定下条微地址

实际运行时取决于以下三种情况

  • 微程序入口地址形成:一条机器指令从主存中取出并送入 IR 后,其操作码经微地址形成部件生成对应微程序的首条微指令地址,送入 μPC
  • 顺序执行
  • 条件分支

微程序指令格式#

  • 水平型指令:操作控制(n 位),顺序控制(判断测试字段,后继地址字段)

  • 垂直型指令:微操作码,目的地址,源地址

    • 指令短,编写简单,但是微程序长,执行速度慢
  • 水平并行能力强

  • 一条水平可以完成更多工作

  • 水平指令字长但微程序短

  • 水平编写难度大,垂直类似机器指令好编写

中断与异常#

中断又称外中断,异常又称内中断。默认中断指的是外中断。

异常#

异常是 CPU 在执行指令的过程中由内部检测到的意外事件,和当前指令有关。

  • 故障(fault):缺页异常,除 0 异常,处理完回到本条指令重新执行
  • 自陷(trap):由陷入指令主动触发,例如系统调用、断点,处理完执行下一条
  • 终止(abort):硬件故障等致命错误,处理程序一般直接终止进程

中断#

中断是外部设备发出的请求,和当前指令无关,响应在一条指令执行完之后。

  • 可屏蔽中断:受中断屏蔽字和 EINT 控制,键盘、磁盘等普通外设
  • 不可屏蔽中断:不受 EINT 影响,用于电源掉电等紧急情况

异常和中断的检测与响应#

检测的时机不同:中断请求在每个指令周期的末尾统一采样,异常在指令执行过程中由内部逻辑检测。

响应需要满足条件:有请求、CPU 处于开中断状态(对可屏蔽中断)、当前指令执行完(对中断)。

响应的过程由中断隐指令完成,全部由硬件自动实现:

  • 关中断,防止响应过程被打断;
  • 保存断点,把 PC 和 PSW 压栈或存入指定单元;
  • 识别中断源并转入中断服务程序,可屏蔽中断一般通过中断向量表。

需要注意,隐指令不是指令,它没有操作码,也不在指令系统中,是硬件在响应阶段自动做的一串动作。

中断处理程序的完整流程在 I/O 那一章展开,这里只要记住“硬件做前一半,软件做后一半”。

指令流水线#

指令流水线操作#

见本节模型。

先把性能指标写清楚。设流水线有 kk 段,每段时间为 Δt\Delta t(取最慢的那一段),连续执行 nn 条指令,则总时间为

Tk=(k+n−1)ΔtT_k=(k+n-1)\Delta t

吞吐率、加速比和效率分别是

TP=nTk=n(k+n−1)ΔtTP=\frac{n}{T_k}=\frac{n}{(k+n-1)\Delta t}S=T顺序T流水=nkΔt(k+n−1)Δt=nkk+n−1S=\frac{T_{\text{顺序}}}{T_{\text{流水}}}=\frac{nk\Delta t}{(k+n-1)\Delta t}=\frac{nk}{k+n-1}E=Sk=nk+n−1E=\frac{S}{k}=\frac{n}{k+n-1}

当 n→∞n\to\infty 时,TP→1/ΔtTP\to 1/\Delta t,S→kS\to k,E→1E\to 1。

需要注意,段数不是越多越好:段数增加后,每段的时间受流水寄存器开销限制,而且冒险带来的停顿会变多,实际加速比远小于 kk。

流水线时空图就是把每条指令画成一行、横轴是时间,斜着看就是流水,有停顿的地方画一段空白。

MIPS 指令#

见上一节模型。

结构冒险#

即共同争夺某种资源,又称资源冲突。例如取指和访存同时要用存储器,或者两条指令同时要用同一个功能部件。

处理办法:

  • 资源重复:把指令存储器和数据存储器分开(哈佛结构),或者增加功能部件、增加端口;
  • 分时使用:同一部件在不同流水段轮流使用,代价是插入停顿。

数据冒险#

例如写后读:后一条指令要读的寄存器,正好是前一条指令还没写回的结果。

数据冒险有三种:写后读(RAW)、读后写(WAR)、写后写(WAW)。按序流水线只可能出现 RAW,乱序流水线三种都要考虑。

处理办法:

  • 延迟指令:插入气泡,等结果写回再继续;
  • 旁路转发:把 ALU 的结果直接送给下一条指令的输入端,不用等写回寄存器,能解决大多数 RAW;
  • load-use 冒险:load 的数据要到 MEM 末尾才出来,转发也来不及,必须停一个周期,然后再转发。

这三类处理见后续模型。

控制冒险#

对于 if / loop 相关指令,需要等待条件跳转判断是否成立。

主要有几种解决方案:

  • 延迟分支处理:把一条一定执行的指令放进延迟槽,或者插入气泡
  • 分支预测技术:尽早生成目标地址并预测方向
    • 静态预测:每次都选择成立 / 不成立
    • 动态预测:根据程序运行时的历史信息来预测,常用两位饱和计数器和分支历史表
    • 预测错误:需要清除流水线,惩罚是已经取入的指令条数

需要注意,分支预测只解决“猜方向”,目标地址的计算还要在 ID 段尽早完成,否则就算方向猜对了也要等地址算出来。

高级流水线技术#

  • 超标量流水线:增加功能部件(硬件),结合动态分支预测以及乱序执行
  • 超长指令字技术:多条并行的指令合成一个长指令字
  • 超流水线技术:把一个指令进一步细分成更多的流水段,理想情况下 CPI < 1

需要注意,超标量的并行是硬件在运行时找出来的(动态),超长指令字的并行是编译器在编译时打包好的(静态),后者对编译器要求高,但硬件简单。

动态流水线指的是流水段可以按需重组,允许指令乱序执行、乱序完成,但必须顺序提交,以保持程序语义。

多处理器#

SISD#

单指令单数据流。

SIMD#

单指令多数据流,代表为向量处理器。

向量处理器的一条向量指令处理一整个向量,内部用流水线的方式逐元素计算,元素之间没有数据相关性,所以能流水起来,还能用链接技术把多条向量指令串起来。

需要注意,SIMD 的并行度受向量长度限制,遇到条件分支不好处理,所以后来又有带掩码的向量指令。

MISD#

多指令单数据流(无应用)

MIMD#

多指令多数据流,应用有多计算机系统 / 多处理器系统。

硬件多线程#

  • 细颗粒度多线程:每个时钟周期轮流切换不同线程的指令执行,可隐藏短流水线停顿,但单线程执行被拉长。
  • 粗颗粒度多线程:平时专注跑单一线程,仅在发生重大停顿(如 Cache 缺失)时才切换到其他线程,切换有充填开销。
  • 同时多线程:基于多发射超标量流水线,允许在同一个时钟周期内同时发射和执行多个不同线程的指令,硬件利用率最高。

多核处理器#

一个 CPU 有多个核,实现真正的并行。

但是需要注意的是,多任务操作系统并发就足够了。

共享内存多处理器#

多个处理器核心围着“同一口超级大饭锅”吃饭!

  1. 共享单一地址空间: 系统里虽然有多个 CPU(或处理器核心),但它们看到并使用的是同一个统一编址的物理内存空间。任何一个 CPU 都可以直接通过访存指令(Load/Store)去读写整个内存里的任意一个地址。

  2. 通信方式超简单(靠内存通信): 处理器之间不用像发微信、寄快递(消息传递)那样麻烦,CPU-A 想给 CPU-B 传数据,只需要写到某块内存变量里,CPU-B 直接去读这个变量就拿到了。

  3. 关键难点:同步与一致性: 大家都在读写同一个地方,就容易“抢饭吃”发生打架(数据冲突/读写冲突)!所以硬件必须提供 Cache 一致性协议(确保各 CPU 的私有 Cache 里数据是一致的),软件上也需要加锁/互斥机制来保证同步。

  4. 常见两大家族:

    • UMA(均匀存储访问 / 经典 SMP):每个 CPU 访问内存中任何区域的时间都是一模一样的(平权模式)。
    • NUMA(非均匀存储访问):内存物理上分块挂在不同 CPU 旁边,访问自己身边的“本地内存”超快,访问挂在别人那里的“远程内存”稍慢,但逻辑上依然是一个统一的大内存。

数据是如何在硬件里流动的#

以 2009 年真题为例,解释数据流动。

解释的内容是:ADD (R1), R0

2009-44
2009-44

时钟节拍功能(微操作)有效控制信号所属阶段与总线行为机制
C1MAR←(PC)\text{MAR} \leftarrow (\text{PC})PCout, MARin【取指】PC 地址送内部总线打入 MAR
C2MDR←M(MAR)\text{MDR} \leftarrow M(\text{MAR}), PC←(PC)+1\text{PC} \leftarrow (\text{PC}) + 1MemR, MDRinE, PC+1【取指】启动读主存(走外总线),PC 硬件自增
C3IR←(MDR)\text{IR} \leftarrow (\text{MDR})MDRout, IRin【取指】指令码经内部总线送入指令寄存器
C4指令译码无【译码】控制器硬件组合逻辑/微码解析操作码
C5A←(R0)\text{A} \leftarrow (\text{R0})R0out, Ain【执行】源操作数 R0 送入 ALU 暂存器 A 锁存
C6MAR←(R1)\text{MAR} \leftarrow (\text{R1})R1out, MARin【执行】目的操作数内存地址(R1)送入 MAR
C7MDR←M(MAR)\text{MDR} \leftarrow M(\text{MAR})MemR, MDRinE【执行】启动读主存,操作数从外总线读入 MDR
C8AC←(A)+(MDR)\text{AC} \leftarrow (\text{A}) + (\text{MDR})MDRout, ADD, ACin【执行】MDR 送内总线,ALU 加法,结果进 AC
C9MDR←(AC)\text{MDR} \leftarrow (\text{AC})ACout, MDRin【写回】运算结果从 AC 经内总线送入 MDR
C10M(MAR)←(MDR)M(\text{MAR}) \leftarrow (\text{MDR})MemW, MDRoutE【写回】启动写主存,MDR 数据经外总线写入内存

注意到 C7 时,CPU 内部总线是空闲的,所以可以重排如下:

时钟节拍功能(微操作)有效控制信号总线并行与硬件动作机制说明
C1MAR←(PC)\text{MAR} \leftarrow (\text{PC})PCout, MARin【取指】PC 地址送入内部总线,打入 MAR
C2MDR←M(MAR)\text{MDR} \leftarrow M(\text{MAR}),PC←(PC)+1\text{PC} \leftarrow (\text{PC}) + 1MemR, MDRinE, PC+1【取指】启动读主存(走外总线),PC 硬件自增
C3IR←(MDR)\text{IR} \leftarrow (\text{MDR})MDRout, IRin【取指】指令机器码经内部总线打入指令寄存器 IR
C4指令译码无【译码】控制器逻辑对操作码进行译码,内部无总线流动
C5MAR←(R1)\text{MAR} \leftarrow (\text{R1})R1out, MARin【重排前置】优先将目标内存地址(R1)经内总线打入 MAR
C6MDR←M(MAR)\text{MDR} \leftarrow M(\text{MAR}),A←(R0)\text{A} \leftarrow (\text{R0})MemR, MDRinE, R0out, Ain【双总线并行·核心优化】① 外总线:启动主存读,数据由外总线打入 MDR (MemR, MDRinE);② 内总线:利用内总线空闲,将 R0 数据打入暂存器 A (R0out, Ain)。👉 两组操作互不抢占总线,单周期同时完成,净省 1 拍!
C7AC←(A)+(MDR)\text{AC} \leftarrow (\text{A}) + (\text{MDR})MDRout, ADD, ACin【执行加法】MDR 读出的操作数送内总线,与暂存器 A 相加,结果存入累加器 AC
C8MDR←(AC)\text{MDR} \leftarrow (\text{AC})ACout, MDRin【写回准备】运算结果从 AC 经内部总线打入 MDR 缓冲锁存
C9M(MAR)←(MDR)M(\text{MAR}) \leftarrow (\text{MDR})MemW, MDRoutE【写回主存】启动主存写,MDR 数据经外部数据总线写入 R1 所指主存单元

一条指令的不同阶段#

取指令 - 分析指令 - 执行指令 - 访存 - 写回

一般来讲,这么划分的是 RISC 指令,所以 RISC 更经常考一条指令的执行。

指令一:RISC 纯算术指令 ADD R1, R2, R3#

语义:R1←R2+R3R1 \leftarrow R2 + R3(寄存器之间运算,完全不碰内存)

[IF] 取指 -> [ID] 译码 + 读 R2/R3 -> [EX] ALU 算加法 -> [MEM] 旁路空闲 -> [WB] 写回 R1
  1. 取指令阶段(IF):
    • PC 送出地址,从 Instruction Cache 中读取 32 位定长指令字到 IR。
    • PC 自动递增:PC←PC+4\text{PC} \leftarrow \text{PC} + 4。
  2. 分析指令 / 译码阶段(ID):
    • 译码:硬件识别出操作码为 ADD。
    • 读寄存器:由于 RISC 指令格式规整,硬件直接并行读取源寄存器堆中的 R2R2 和 R3R3 的内容,存入流水线寄存器(ID/EX 寄存器)。
  3. 执行阶段(EX):
    • 真正做加法!ALU 将输入的 R2R2 和 R3R3 直接相加: ALU_Out=R2+R3\text{ALU\_Out} = R2 + R3
    • 将计算结果锁存进流水线寄存器(EX/MEM 寄存器)。
  4. 访存阶段(MEM):
    • 空操作(NOP / 旁路透传)!
    • 因为这是纯算术指令,不访问任何数据 Cache,该阶段完全不消耗内存总线,ALU 的运算结果原封不动地直接传到下一个流水线寄存器(MEM/WB 寄存器)。
  5. 写回阶段(WB):
    • 控制信号打开通用寄存器堆的写使能。
    • 将经过 MEM 阶段传递过来的运算结果写入目的寄存器 R1R1。指令顺利结束!

指令二:RISC 纯存储指令 SW R1, 0(R2)(Store Word)#

语义:Mem[R2+0]←R1\text{Mem}[R2 + 0] \leftarrow R1(纯地址计算并写入内存,不修改通用寄存器)

[IF] 取指 -> [ID] 译码 + 读基址 R2/数据 R1+ 符号扩展 -> [EX] ALU 算地址 -> [MEM] 写内存 -> [WB] 空操作
  1. 取指令阶段(IF):
    • PC 发出地址,从 Instruction Cache 读出该 SW 指令。
    • PC 自动自增 4。
  2. 分析指令 / 译码阶段(ID):
    • 译码:识别出是 Store 指令。
    • 读寄存器:同时读出基址寄存器 R2R2(用于算物理位置)和源数据寄存器 R1R1(待写入内存的内容)。
    • 立即数符号扩展:将指令里的 16 位偏移量 0 进行符号扩展生成 32 位立即数。
  3. 执行阶段(EX):
    • 计算有效访存地址(EA)!
    • ALU 此时不是算加法业务,而是在算地址: EA=R2+SignExtend(offset)\text{EA} = R2 + \text{SignExtend}(\text{offset})
    • 同时将要写入的数据(R1R1 的值)继续沿流水线送入下一级缓冲。
  4. 访存阶段(MEM):
    • 真正的内存写操作!
    • 将 EX 阶段算出的有效地址 EA\text{EA} 送往 Data Cache。
    • 发出写控制使能信号,把寄存器 R1R1 的数据正式写入该内存单元中。
  5. 写回阶段(WB):
    • 空操作(NOP)!
    • 存储指令的目的地是内存,在 MEM 阶段使命就已经彻底达成了,它没有任何通用寄存器需要更新,寄存器堆写使能关闭,直接提交(Commit)!

RISC 指令对比#

阶段纯算术指令 ADD R1, R2, R3纯存储指令 SW R1, 0(R2)
IF(取指)取指令,PC←PC+4\text{PC} \leftarrow \text{PC} + 4取指令,PC←PC+4\text{PC} \leftarrow \text{PC} + 4
ID(译码)译码,读源寄存器 R2,R3R2, R3译码,读基址 R2R2 与写入数据 R1R1,扩展偏移量
EX(执行)ALU 算算术结果(R2+R3R2 + R3)ALU 算内存有效地址(R2+offsetR2 + \text{offset})
MEM(访存)空操作(NOP),数据直接穿透传递内存写入:把 R1R1 数据写入目标地址
WB(写回)寄存器写回:结果写入目标寄存器 R1R1空操作(NOP),无需更新任何寄存器

指令流水线的真题辨析#

先过。

数据冲突的三种方案辨析和时空图#

先过。

总线#

  • 总线的基本概念
  • 总线的组成及性能指标
  • 总线事务与定时

总线概述#

总线分类#

  • 内部总线:CPU 芯片连接 ALU,寄存器等
  • 系统总线
    • 数据总线
    • 地址总线
    • 控制总线
  • IO 总线:与内部 IO 设备通信
  • 通信总线:与外部 IO 设备或者不同计算机通信

总线性能指标#

  • 总线传输周期:一次总线操作需要的时间,包括地址、数据和应答等阶段
  • 总线时钟频率:早期与 CPU 同频,现在有独立的总线时钟
  • 总线工作频率:一个总线周期里能完成几次数据传输,突发传输时工作频率可以高于时钟频率
  • 总线宽度:数据线的根数,单位是位
  • 总线带宽:单位时间内传输的字节数
  • 总线复用:同一组线分时传送不同的信息
  • 总线寻址能力:由地址位数决定,nn 位地址最多寻址 2n2^n 个单元

带宽的计算是常考点:

带宽=总线宽度(字节)×总线工作频率\text{带宽}=\text{总线宽度(字节)}\times\text{总线工作频率}

需要注意,如果采用突发传输,一次地址传送后连续传多个数据,那么有效带宽要按“一个总线周期传几个数据”来算,不能只算一次。

总线复用常见的是地址线和数据线复用,好处是减少引脚数,代价是要加锁存器把地址锁住,而且同一组线不能同时传地址和数据,速度会降低。

需要注意,总线宽度增加会让并行传输的线间干扰变严重,这也是高速接口转向串行的原因。

总线结构#

  • 单总线结构
  • 双总线结构(分成系统总线和 IO 总线)
  • 三总线结构(双总线 + DMA)
  • 南北桥结构
    • 北桥,即 MCH,作为高速枢纽连接 CPU
    • 南桥,即 ICH,作为 IO 控制器集线器
  • 现代架构
    • 内存访问路径缩短
    • 处理期间高速互连
    • IO 通路直连

总线事务#

  • 地址传送阶段
  • 设备应答阶段(数据准备)
  • 数据传输阶段
    • 非突发式传输
    • 突发式传输:传输一个地址,后续都不需要传输地址。

真题有一年专门考了这块内容,需要注意把每个时间点都算上。

  • 串行传输
    • 一帧数据以起始位(逻辑 0)开始,以停止位(逻辑 1)结束,接收方靠这个规律同步
    • 奇偶校验:在数据后加一位校验位,使 1 的个数为奇数或偶数,只能检错不能纠错
  • 并行:多条线同时传输多个位

由于电气特性,并行传输的工作频率难以提高,线间干扰和时序偏移会随频率恶化,所以现代高速接口一般是串行的。

总线定时#

  • 同步定时:采用统一的时钟信号协调发送方和接受方,间隔为总线周期
  • 异步定时:通过主从设备握手信号
    • 不互锁
    • 半互锁:发,读发进行确认,对确认进行确认
    • 全互锁
  • 半同步:增设 wait 信号线,主设备检测 wait 状态,直到低电平。
  • 分离式定时方式:
    • 上述都是独占式
    • 请求阶段:主设备获得总线使用权,发送地址和命令
    • 应答阶段:从设备 B 准备好数据后,主动申请总线

辨析系统总线#

总线类型传输内容方向性
地址总线 (AB)内存单元或 I/O 端口的物理地址单向(通常由 CPU/主设备发往从设备,DMA期间由DMA控制器发出)
数据总线 (DB)真正的内容信息(数据、指令码、控制字/状态字)双向
控制总线 (CB)协调操作的控制脉冲/握手信号(读/写选通、中断请求、就绪信号等)单根线单向,总线整体双向

输入 / 输出系统#

输入 / 输出系统 - 考纲#

  • IO 接口(IO 控制器)
    • IO 接口的功能和基本结构
    • IO 端口及其编址
  • IO 方式
    • 程序查询方式
    • 程序中断方式
      • 中断的基本概念
      • 中断响应过程
      • 中断处理过程
      • 多重中断和屏蔽的概念
    • DMA 方式
      • DMA 控制器的组成
      • DMA 传送过程

IO 接口与 IO 端口#

IO 接口 = 设备

IO 端口是寄存器

IO 接口的功能与基本结构#

I/O 接口是设备和总线之间的桥梁,主要功能有:

  • 地址译码与设备选择;
  • 数据缓冲,解决 CPU 和设备之间的速度差异;
  • 记录并报告设备状态(忙、就绪、错误);
  • 接收 CPU 的控制命令并转换成设备能懂的操作;
  • 数据格式转换,比如串并转换、电平转换。

基本结构包括数据缓冲寄存器、状态寄存器、控制寄存器、地址译码电路和控制逻辑,以及和设备之间的信号线。

需要注意,I/O 端口是接口里 CPU 可以直接访问的寄存器,接口还包括端口之外的逻辑,两者不是一回事。

IO 端口的编址#

  • 独立编址(x86):I/O 空间和内存空间分开,用专门的 IN/OUT 指令访问,控制逻辑复杂,但内存空间完整;
  • 统一编址:I/O 端口占用内存地址的一部分,用普通的访存指令就能访问,无需专门 IO 指令,但译码电路复杂,还要占用内存空间。

需要注意,统一编址下普通的 load/store 指令就能访问外设,所以操作系统做权限检查时要把这块地址一起管起来。

程序查询方式#

软件介入轮询。

独占查询。

忙等待。

流程是:CPU 先向设备发命令,然后反复读设备状态寄存器,直到“就绪”位为 1,才用一条输入指令把数据读进 CPU,再写到内存。

问题是 CPU 和外设完全串行工作,设备慢,CPU 就一直空转,效率极低,只适合简单、低速的场合。

需要注意,程序查询方式下 CPU 并没有“阻塞”,它是在原地死循环,操作系统也无法调度别的进程,这和中断方式把进程挂到阻塞队列是两回事。

程序中断方式#

中断屏蔽字#

中断屏蔽字是确认多个中断同时发生时,谁可以把谁打断。

对于五个任务 m0, m1, m2, m3, m4,假设处理顺序要 m3,m1,m4,m2,m0。那么对 m4 而言,其不能被 0,2 以及自己 4 打断,所以,应该设置为 10101。

只有优先级比自己高的中断源,对应的屏蔽位才设为 0(允许打断我)。

中断流程#

[外设/内部异常触发]
│
======【第一阶段:硬件主场(中断隐指令完成)】======
├─ 1. 关中断(硬件置锁)
├─ 2. 保存断点(PC 和 PSW 压栈)
└─ 3. 引出中断服务程序(根据中断向量查表并跳到 ISR 入口)
│
======【第二阶段:软件主场(操作系统中断服务程序 ISR 执行)】======
├─ 4. 保存现场与屏蔽字(通用寄存器压栈、设置屏蔽字)
├─ 5. 执行具体的中断处理例程(干具体脏活累活)
└─ 6. 恢复现场并中断返回(出栈通用寄存器,开中断并返回原程序)
│
[回到原断点继续执行原程序]

在 408 真题中,CPU 在中断响应阶段需要干的事情表述为:关中断,保存断点和程序状态,识别中断源。

需要注意,CPU 是硬件(而不是软件),下面的是操作系统(也就是软件)需要做的事情。

中断向量与中断向量表#

中断向量本质是中断服务程序的入口地址

中断向量表本质指的是存放中断服务程序入口地址的表

中断类型号指的是中断号,例如键盘敲击中断号为 0x21,系统调用为 0x80,只是一个编号。

中断向量地址是存放该中断向量的存储单元地址

所以,流程是硬件识别中断源(找出中断类型号),硬件查中断向量表得到中断向量地址,然后根据中断向量地址得到中断向量…

中断相关辨析#

“数据搬运”由软件(CPU 执行中断服务程序的代码指令)来完成的。

而处理中断的起步阶段(中断响应),是由硬件(CPU 内部的硬件控制逻辑)来完成的。

多重嵌套中断#

DMA 优先级会比中断高。

硬件响应优先级是一回事,可以通过中断优先级调整为另一种。

辨析硬件和软件的中断响应#

对比维度硬件响应优先级软件处理优先级
决定主体硬件(排队判优逻辑电路)软件(操作系统的中断屏蔽字)
能否修改通常固定,不可动态修改可由程序随时动态修改
解决的问题多个中断同时到来,先响应谁?中断已经在跑,谁能抢占/打断?
关乎指标响应时间的前后实际完成处理的前后、中断嵌套层级

DMA 方式#

DMA 传送方式#

  • 停止 CPU 访存
  • 周期挪用
  • DMA 与 CPU 交替访问

DMA 控制器的组成#

  • 主存地址寄存器:存放要读写的内存地址,每传一个字自动加 1;
  • 字计数器:记录还要传多少个字,每传一个减 1,减到 0 发中断;
  • 数据缓冲寄存器:暂存一次传送的数据;
  • DMA 请求触发器和屏蔽触发器:控制请求的发出与屏蔽;
  • 控制/状态逻辑:产生 DMA 请求和总线请求,接收 CPU 的响应;
  • 中断机构:传送结束后向 CPU 发中断。

需要注意,DMA 控制器和 CPU 争的是总线使用权,不是内存本身。DMA 占用总线时 CPU 不能访存,但仍可以执行不需要访存的指令,有 Cache 时影响更小。

DMA 传送过程#

  • 预处理:CPU 给 DMA 控制器写地址、计数值、控制字,启动设备
  • 数据传送:DMA 控制器申请总线、挪用周期、直接在主存和设备之间搬数据,CPU 不参与
  • 后处理:传送结束后 DMA 发中断,CPU 校验数据、唤醒进程

DMA 与中断的区别#

  • 中断是程序切换,靠 CPU 执行指令搬数据;DMA 是硬件直接搬,CPU 只在开始和结束时参与;
  • 中断以字为单位,DMA 以数据块为单位;
  • 中断的响应发生在指令执行完之后,DMA 的请求可以在一个总线周期结束时就被响应;
  • DMA 的优先级高于中断,因为它面对的是高速设备,等待会丢数据。

四种 I/O 方式的对比:

对比项程序查询程序中断DMA
CPU 参与程度全程忙等传送时执行中断服务程序只在开始和结束参与
数据传送者CPUCPUDMA 控制器
传送单位字字块
CPU 与设备串行部分并行高度并行
适用场景简单、低速低速、随机高速、成块

需要注意,DMA 也要用中断来通知结束,所以“DMA 方式不需要中断”是错的,它只是不需要 CPU 一条条搬数据。

DMA 方式全流程辨析#

第一阶段:系统调用与准备(用户态 →\rightarrow 内核态,进程阻塞)#

  1. 进程发起请求:运行在用户态的应用进程执行到了读取磁盘文件的代码(如调用 read()),执行一条陷入指令(Trap / 访管指令)。
  2. 状态转换(变态!):CPU 响应异常,由用户态切换到内核态(变态成功!),保存当前进程的上下文,转入操作系统的系统调用处理程序。
  3. 设备排队与进程阻塞:操作系统检查发现数据在慢吞吞的磁盘上,把该进程的状态由运行态改为阻塞态(等待 I/O),挂入等待该设备的阻塞队列中。此时 CPU 调度器切出,选择另一个就绪态进程上 CPU 运行。

第二阶段:CPU 初始化 DMA 控制器(内核态)#

  1. 写入控制参数:在内核态下,CPU(通过设备驱动程序)向 DMA 控制器的寄存器写入初始化信息:
    • 内存起始地址(数据放到内存的哪里)
    • 传送字节数 / 计数器(要读多少数据)
    • 磁盘设备地址(要读磁盘的哪个扇区)
    • 控制字/命令字(指定为“读”操作,启动传数)
  2. CPU 脱手解放:DMA 启动完毕,CPU 彻底甩手不管,继续跑别的用户进程(CPU 又变回用户态去跑别的程序了)。

第三阶段:DMA 控制数据传输(纯硬件自主传输,无 CPU 参与数据搬运)#

  1. 设备准备数据:DMA 控制器向磁盘控制器发出读请求,磁盘把数据读入自身的硬件内部缓冲区。
  2. 申请总线 / 窃取周期:缓冲区攒够一个字(或一个块)后,DMA 控制器向 CPU 发出总线使用权请求(HRQ)。
  3. 总线周期挪用(Cycle Stealing):CPU 放弃一个存取周期的总线控制权(若发生冲突,DMA 优先于 CPU 访存)。
  4. 直通内存:DMA 控制器直接把数据写入内存指定的缓冲区中,不需要经过 CPU 内部寄存器!每传一个字,传输计数器减 1,内存地址加 1。
  5. 循环直至完成:重复上述 2~4 步,直到计数器减为 0(一连串数据全部传进内存)。

第四阶段:传输结束与中断唤醒(内核态 →\rightarrow 用户态)#

  1. DMA 发出中断请求:整块数据传完后,DMA 控制器向 CPU 发出硬件中断请求信号(INTR),汇报工作完成。
  2. CPU 响应中断(变态!):CPU 在当前指令执行周期的末尾检测到中断,立刻暂停手头的工作,再次由用户态切换到内核态(又变态啦!),保护现场,转入 DMA 中断服务程序。
  3. 唤醒原进程:操作系统在内核态下处理后续工作(校验数据、修改文件元数据等),并将原来处于阻塞态的应用进程唤醒,转入就绪态,挂入就绪队列。
  4. 返回用户态:中断服务程序执行完毕,恢复现场,执行中断返回指令(iret),CPU 由内核态切换回用户态。
  5. 进程继续运行:一旦该应用进程再次被 CPU 调度选中,就从阻塞前断开的地方继续执行,直接在内存里愉快地享用读进来的数据啦!

以 scanf 为例,讲解输入 / 输出系统(结合操作系统)#

当程序执行到 scanf 时,库函数先检查 stdio 缓冲区;只有缓冲区中没有满足格式要求的数据时,才会通过 read 等系统调用进入内核。若输入尚未就绪,该系统调用会使进程阻塞。

键盘输入通常由设备中断交给驱动和终端行规程处理,再进入内核缓冲区。当缓冲区中已有满足本次读取的数据时,内核将数据复制到用户缓冲区并唤醒等待进程;具体的缓冲、回显、行结束和调度时机取决于操作系统与终端配置。

系统调用返回后,scanf 在用户态缓冲区中解析数据并返回。

简单来讲,就是执行 scanf 后,如果没有数据,那么使进程阻塞,等待用户输入数据。当用户输入数据的时候,数据首先进入键盘缓冲区,然后中断服务条例结束。

随后数据进入内核缓冲区,复制到用户缓冲区,然后使进程变为就绪态,等待进程唤醒。

进程被唤醒后,完成该函数,然后进入下一条函数(所以函数返回是最后做的)。

低级错误#

辨析 A 与 B 的关系#

判断正误,需要考虑 A 推导 B,也要考虑 B 的发生会不会影响 A。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

考研冲刺前的总结 —— 计算机组成原理
https://blog.attilio.cc/posts/npee/co/
作者
ShaddockNH3
发布于
2026-09-11
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
ShaddockNH3
Just a humble and obscure soul, dreaming of ascending to prominence while journeying along the path.
公告
欢迎来到我的博客!
分类
标签
最新动态
站点统计
文章
17
分类
6
标签
32
总字数
689,780
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
1
前言
2
计算机系统概述
考纲 - 计算机系统概述
计算机系统层次结构
计算机硬件与软件
存储程序的执行流
性能指标
3
数据表示和运算
考纲 - 数据表示和运算
字,字节,字长,位
数制和编码
真值,机器数,无符号数与有符号数,编码
无符号数与有符号数
原码
反码
加法器、补数与补码的来源
半加器(Half Adder)
全加器(Full Adder)
并行加法器(Parallel Adder)
算术逻辑单元(ALU)
补码
移码
四种编码的对照
C 语言整数类型及转换
基本的计算
移位运算
对加法器的补充
补码加减法运算
判定溢出与标志位
乘法
除法器的两种方式
IEEE 浮点数
浮点数的表示
IEEE 浮点数的表示范围与特殊浮点数
浮点数的相关计算
浮点数与整型转换相关
大端法与小端法
数据对齐
4
存储系统
考纲 - 存储系统
存储器分类
存储器的组成与层次化
层次化
辨析存储周期与存取时间
主存
SRAM
DRAM
ROM
SRAM、DRAM 与 Flash 的对比
多模块存储器
主存与 CPU 的连接
位扩展
字扩展
字位扩展
磁盘与 SSD
cache
cache 的基本工作原理
局部性原理
cache 命中率
cache 映射方式
cache 替换算法
cache 一致性问题
cache 的容量与位数计算
cache 的应用
关于 cache 其他需要注意的事项
虚拟存储器
虚拟存储器与 cache 的辨析
页式虚拟存储器
段式虚拟存储器
段页式虚拟存储器
组相联 TLB
辨析 TLB,Page 和 cache 的层次
页号、块号与具体地址
cache 命中率分析
场景 1 深入辨析(容量截断效应)
1. 数据规模与排布 - 1
2. 行优先访问分析 - 1
3. 列优先访问分析 - 1
场景 2 深入辨析(工作集常驻效应)
1. 数据规模与排布 - 2
2. 行优先访问分析 - 2
3. 列优先访问分析 - 2
场景 3 1024×41024 \times 41024×4 的长条矩阵
场景 3 访问全流程深入推演
1. 行优先访问 - 3
2. 列优先访问 - 3
cache 命中率辨析
TLB 组相联 + cache 组相联映射模型
阶段一:硬件初次地址转换与 TLB 缺失
阶段二:访存查页表与触发缺页异常(Page Fault)
阶段三:操作系统内核接管处理(缺页中断服务程序)
阶段四:重新取指与第二次地址转换
阶段五:物理地址拼接与 Cache 访问
5
指令系统
考纲 - 指令系统
指令寻址与数据寻址
指令格式
定长与变长操作码
指令的类型
指令寻址
辨析 PC 与指令格式
数据寻址
x86 汇编与 MIPS 汇编
CISC 和 RISC
CISC
RISC
两者的对比
MIPS 指令集的多条指令执行
基于 CISC 指令的多条指令执行
过程调用的机器级表示
选择结构与循环结构的机器级表示
指令本身的可考点
6
CPU
CPU - 考纲
CPU 的功能和基本架构
CPU 的时序控制
指令周期相关
处理器指令执行模型
数据通路的基本功能
数据通路组织与分类
单总线结构数据通路
专用数据通路
辨析 CPU 内部的 CU 与数据通路
硬布线控制器
微程序控制器
微程序的工作流程
微程序和机器指令
微程序的编码
微程序地址形成
微程序指令格式
中断与异常
异常
中断
异常和中断的检测与响应
指令流水线
指令流水线操作
MIPS 指令
结构冒险
数据冒险
控制冒险
高级流水线技术
多处理器
SISD
SIMD
MISD
MIMD
硬件多线程
多核处理器
共享内存多处理器
数据是如何在硬件里流动的
一条指令的不同阶段
指令一:RISC 纯算术指令 ADD R1, R2, R3
指令二:RISC 纯存储指令 SW R1, 0(R2)(Store Word)
RISC 指令对比
指令流水线的真题辨析
数据冲突的三种方案辨析和时空图
7
总线
总线概述
总线分类
总线性能指标
总线结构
总线事务
总线定时
辨析系统总线
8
输入 / 输出系统
输入 / 输出系统 - 考纲
IO 接口与 IO 端口
IO 接口的功能与基本结构
IO 端口的编址
程序查询方式
程序中断方式
中断屏蔽字
中断流程
中断向量与中断向量表
中断相关辨析
多重嵌套中断
辨析硬件和软件的中断响应
DMA 方式
DMA 传送方式
DMA 控制器的组成
DMA 传送过程
DMA 与中断的区别
DMA 方式全流程辨析
第一阶段:系统调用与准备(用户态 →\rightarrow→ 内核态,进程阻塞)
第二阶段:CPU 初始化 DMA 控制器(内核态)
第三阶段:DMA 控制数据传输(纯硬件自主传输,无 CPU 参与数据搬运)
第四阶段:传输结束与中断唤醒(内核态 →\rightarrow→ 用户态)
以 scanf 为例,讲解输入 / 输出系统(结合操作系统)
9
低级错误
辨析 A 与 B 的关系
文章目录
1
前言
2
计算机系统概述
考纲 - 计算机系统概述
计算机系统层次结构
计算机硬件与软件
存储程序的执行流
性能指标
3
数据表示和运算
考纲 - 数据表示和运算
字,字节,字长,位
数制和编码
真值,机器数,无符号数与有符号数,编码
无符号数与有符号数
原码
反码
加法器、补数与补码的来源
半加器(Half Adder)
全加器(Full Adder)
并行加法器(Parallel Adder)
算术逻辑单元(ALU)
补码
移码
四种编码的对照
C 语言整数类型及转换
基本的计算
移位运算
对加法器的补充
补码加减法运算
判定溢出与标志位
乘法
除法器的两种方式
IEEE 浮点数
浮点数的表示
IEEE 浮点数的表示范围与特殊浮点数
浮点数的相关计算
浮点数与整型转换相关
大端法与小端法
数据对齐
4
存储系统
考纲 - 存储系统
存储器分类
存储器的组成与层次化
层次化
辨析存储周期与存取时间
主存
SRAM
DRAM
ROM
SRAM、DRAM 与 Flash 的对比
多模块存储器
主存与 CPU 的连接
位扩展
字扩展
字位扩展
磁盘与 SSD
cache
cache 的基本工作原理
局部性原理
cache 命中率
cache 映射方式
cache 替换算法
cache 一致性问题
cache 的容量与位数计算
cache 的应用
关于 cache 其他需要注意的事项
虚拟存储器
虚拟存储器与 cache 的辨析
页式虚拟存储器
段式虚拟存储器
段页式虚拟存储器
组相联 TLB
辨析 TLB,Page 和 cache 的层次
页号、块号与具体地址
cache 命中率分析
场景 1 深入辨析(容量截断效应)
1. 数据规模与排布 - 1
2. 行优先访问分析 - 1
3. 列优先访问分析 - 1
场景 2 深入辨析(工作集常驻效应)
1. 数据规模与排布 - 2
2. 行优先访问分析 - 2
3. 列优先访问分析 - 2
场景 3 1024×41024 \times 41024×4 的长条矩阵
场景 3 访问全流程深入推演
1. 行优先访问 - 3
2. 列优先访问 - 3
cache 命中率辨析
TLB 组相联 + cache 组相联映射模型
阶段一:硬件初次地址转换与 TLB 缺失
阶段二:访存查页表与触发缺页异常(Page Fault)
阶段三:操作系统内核接管处理(缺页中断服务程序)
阶段四:重新取指与第二次地址转换
阶段五:物理地址拼接与 Cache 访问
5
指令系统
考纲 - 指令系统
指令寻址与数据寻址
指令格式
定长与变长操作码
指令的类型
指令寻址
辨析 PC 与指令格式
数据寻址
x86 汇编与 MIPS 汇编
CISC 和 RISC
CISC
RISC
两者的对比
MIPS 指令集的多条指令执行
基于 CISC 指令的多条指令执行
过程调用的机器级表示
选择结构与循环结构的机器级表示
指令本身的可考点
6
CPU
CPU - 考纲
CPU 的功能和基本架构
CPU 的时序控制
指令周期相关
处理器指令执行模型
数据通路的基本功能
数据通路组织与分类
单总线结构数据通路
专用数据通路
辨析 CPU 内部的 CU 与数据通路
硬布线控制器
微程序控制器
微程序的工作流程
微程序和机器指令
微程序的编码
微程序地址形成
微程序指令格式
中断与异常
异常
中断
异常和中断的检测与响应
指令流水线
指令流水线操作
MIPS 指令
结构冒险
数据冒险
控制冒险
高级流水线技术
多处理器
SISD
SIMD
MISD
MIMD
硬件多线程
多核处理器
共享内存多处理器
数据是如何在硬件里流动的
一条指令的不同阶段
指令一:RISC 纯算术指令 ADD R1, R2, R3
指令二:RISC 纯存储指令 SW R1, 0(R2)(Store Word)
RISC 指令对比
指令流水线的真题辨析
数据冲突的三种方案辨析和时空图
7
总线
总线概述
总线分类
总线性能指标
总线结构
总线事务
总线定时
辨析系统总线
8
输入 / 输出系统
输入 / 输出系统 - 考纲
IO 接口与 IO 端口
IO 接口的功能与基本结构
IO 端口的编址
程序查询方式
程序中断方式
中断屏蔽字
中断流程
中断向量与中断向量表
中断相关辨析
多重嵌套中断
辨析硬件和软件的中断响应
DMA 方式
DMA 传送方式
DMA 控制器的组成
DMA 传送过程
DMA 与中断的区别
DMA 方式全流程辨析
第一阶段:系统调用与准备(用户态 →\rightarrow→ 内核态,进程阻塞)
第二阶段:CPU 初始化 DMA 控制器(内核态)
第三阶段:DMA 控制数据传输(纯硬件自主传输,无 CPU 参与数据搬运)
第四阶段:传输结束与中断唤醒(内核态 →\rightarrow→ 用户态)
以 scanf 为例,讲解输入 / 输出系统(结合操作系统)
9
低级错误
辨析 A 与 B 的关系