计算机组成原理-笔记
今天开始我们系统学习计算机组成原理的内容,主要参考资源还是网课:王道计算机考研 计算机组成原理_哔哩哔哩_bilibili
基本概念
如何理解计算机组成原理呢?

如图所示,如果把计算机世界这样划分的话,可以清晰地看到计算机的由软件,硬件与操作系统组成,软件主要涉及的是数据结构,那么硬件主要涉及的就是操作系统。
比较常见的硬件包括:



现在我们来学习计算机硬件的基本组成:
计算机硬件组成
主流的有两种组成结构:

我们首先来学习早期的冯诺依曼结构:

这张图展示了冯・诺依曼体系计算机五大核心组成结构,包含输入设备、存储器、运算器、控制器、输出设备;其中实线代表数据线,虚线代表控制线与反馈线。工作流程如下:首先输入设备把外部的数据和程序(计算步骤)转换成计算机能够识别的二进制信息,送入存储器统一保存;控制器从存储器读取程序指令,解析后发出控制信号,指挥整机有序工作;根据指令要求,存储器将待处理的数据传输给运算器完成算术、逻辑运算,运算产生的中间结果可回存至存储器;运算完成后,最终数据再传递到输出设备,转换为人能够看懂的形式向外输出。整个过程里,控制器依靠控制线向输入设备、存储器、运算器、输出设备下发控制命令,各个部件也会通过反馈线把自身状态回传给控制器,持续协调所有硬件配合完成计算;同时遵循冯・诺依曼核心思想:数据与程序一同存放在存储器中,计算机自动逐条读取指令执行运算。
这里还要引出另一个概念:
在计算机系统中,软件和硬件在逻辑上是等效的。
一个既定的功能逻辑,既可以使用硬件电路直接实现,也可以依靠软件编程实现;从上层使用者视角看,最终对外表现的功能、行为完全一致,无法区分底层到底是硬件还是软件在干活。但二者在运行速度、成本、灵活性上存在明显差异:硬件实现速度快、功能固定;软件实现灵活性高、易于修改,但执行速度相对较慢。这一原理是计算机系统软硬件取舍、指令集设计、硬件加速、虚拟机与模拟器技术的理论基础。

冯·诺依曼计算机一共有六个特点:整机由输入设备、输出设备、存储器、运算器、控制器五大硬件部分组成;指令和数据放在同一个内存里,地位一样,依靠地址找到对应的内容;不管是指令还是数据,在电脑内部全都用0和1的二进制表示;每一条指令分成两部分,一部分说明要做什么运算,一部分说明去哪里找数据;核心思路是把提前写好的程序和数据一起放进内存,电脑就能自动一条接着一条执行,不用人工反复操作;早期的冯诺依曼机器所有数据传输都要经过运算器,是以运算器为中心,而我们现在的电脑是以存储器(内存)为中心,这点需要区分开。
然后就是我们现代计算机的结构:



早期机型以运算器为中心,所有外设和存储器之间的数据交换都必须经过运算器中转,而现代计算机转变为以存储器(内存)为中心,依靠 DMA 技术让外设能够直接和内存传输数据,不再全程占用 CPU,除此之外现代计算机将运算器与控制器整合封装为 CPU,采用统一总线连接各个硬件方便设备扩展,新增中断机制优化 CPU 与外设的通信方式,还在 CPU 和内存之间加入高速缓存 Cache,以此弥补 CPU 运算速度和内存访问速度之间巨大的性能差距,解决了原始架构 CPU 负担重、拓展性差、速度不匹配的诸多问题。
接着我们开始深入学习内部的组件结构:

我们已经知道了现代计算机的中心是存储数据的存储器,其内部结构如图所示。
我们可以结合菜鸟驿站的类比完整理解这张图,主存储器由存储体、MAR地址寄存器、MDR数据寄存器三部分构成,存储体就相当于驿站里摆放包裹的货架,每一个存放位置都拥有独一无二的地址,MAR存储地址寄存器如同店员接收的取件号,用来记录我们想要访问的存储单元编号,CPU把地址送入MAR后,硬件根据这个地址定位存储体里对应的存储单元,MDR存储数据寄存器相当于对外交互的柜台,负责临时存放即将从存储体读出、或是准备写入存储体的数据,整个存取流程就像顾客把取件号交给店员(CPU将地址送入MAR),店员依据取件号找到货架上对应的包裹(硬件根据MAR地址定位存储体单元),再把包裹放到柜台交给顾客(数据在存储体与MDR之间传输,CPU从MDR拿到数据),写入操作则反过来,顾客先把包裹放到柜台(数据送入MDR),提供取件号(地址存入MAR),店员将包裹放到货架对应位置,MAR只保存地址信息,MDR只负责暂存数据,二者分工配合完成内存的数据读写。

这张图展示了主存储器三大组成部分以及相关基础概念,存储体是存放数据的主体,内部划分出许多存储单元,每个存储单元对应唯一地址,MAR 用来存放访问目标单元的地址,它的位数决定能够寻址多少个存储单元,比如 4 位 MAR 就能寻址 2⁴共 16 个存储单元,MDR 负责临时存放读写的数据,MDR 的位数等于存储字长,也就是单个存储单元能存放的二进制位数,图中例子里 16 位 MDR 代表一个存储单元可以存放 16bit 的数据,接着区分几组易混名词:存储元是最基础硬件元件,只能保存 1bit 二进制,多个存储元组成一个存储单元,存储单元里保存的二进制信息叫做存储字,存储字所占的位数就是存储字长,还要注意字节 Byte 固定为 8bit,和存储字长没有必然相等的关系,CPU 访问内存时先把地址送入 MAR 定位存储体中的单元,读出或写入的数据都会经过 MDR 中转,以此完成内存的数据交互。

这张图展示早期经典运算器的内部结构,核心执行单元是ALU算术逻辑单元,专门依靠电路完成加减乘除算术运算以及与或非等逻辑运算,X寄存器用来存放其中一个操作数,ACC累加器存放另一个操作数,运算结束后结果也保存在ACC里,MQ乘商寄存器主要服务于乘法、除法运算,可以和ACC互相传递数据,表格清晰标明了四则运算时各个寄存器承载的数据,加法运算中ACC存被加数、X存加数,运算和放回ACC;减法ACC存被减数、X存减数,差值存回ACC;乘法里ACC最终存放乘积高位、MQ存放乘数与乘积低位、X存放被乘数;除法时ACC存放被除数和余数,MQ存放商,X存放除数,整个运算流程就是先把参与计算的数据送入对应的寄存器,交由ALU完成运算,最终结果暂存在寄存器中等待传输到存储器。

这张图展示控制器由PC程序计数器、IR指令寄存器、CU控制单元三部分构成,PC存放着下一条将要读取指令的内存地址,并且具备自动自增的能力,顺利实现程序顺序执行,CPU依靠PC里的地址从主存取出指令送到IR暂时存放当前这条正在处理的指令,IR把指令的操作码传递给CU,CU负责解析指令并向运算器、存储器、输入输出设备发送各类控制信号,完整执行一条指令分为取指令、分析指令、执行指令三个阶段,取指阶段依靠PC提供地址取出指令存入IR,分析与执行阶段依靠IR传递指令信息、由CU生成控制信号指挥计算机各个部件完成对应的操作。

这张图完整展示计算机执行程序的完整流程,我们编写的C语言这类高级语言代码需要先经过编译器翻译为二进制机器语言指令,之后连同运算所需的数据一同装入主存,主存里每一行存储单元长度为16bit也就是存储字长,每条指令分为操作码和地址码两部分,操作码代表要执行读取、乘法、加法、保存、停机这类动作,地址码标记需要访问的主存单元地址,程序计数器PC从地址0开始依次取出指令送入IR,CU解析指令后指挥硬件一步步运算:先取出地址5的数据a放入ACC,接着根据第二条指令乘以地址6的b,随后第三条指令加上地址7的c,第四条指令把最终结果存进地址8对应的y,最后执行停机指令,整个过程充分体现冯·诺依曼存储程序思想,指令与数据共同存放在主存中,控制器循环完成取指、分析指令、执行指令,依靠运算器完成计算,实现高级代码对应的数学运算。

如图所示,初始PC存放待读取指令的内存地址,先将地址送入MAR,主存根据MAR的地址把对应指令读出送入MDR,随后MDR中的指令传送至IR存放,IR将指令里的操作码发送给CU,CU识别指令类型,同时IR里的地址码再次送入MAR去寻找需要参与运算的数据地址,主存读出数据经由MDR传输到运算器的寄存器完成算术或逻辑运算,运算结束后可把结果通过MDR写回主存,完成当前指令后PC自动更新为下一条指令地址,重复这套取指、分析、执行的循环;需要注意两处细节,一是PC自增跳转发生在取指阶段,二是区分两次访问内存,第一次取指令、第二次取操作数,不要混淆两次MAR寻址的对象。
那这里不就带出一个新问题了,就是我们取指令和第二次取操作数都是去 MAR 取,如何区分呢?

MAR只是一个单纯存放地址的中转寄存器,它本身不会区分这个地址是指令地址还是数据地址,区分依靠当前处在什么工作阶段、由控制器CU掌控时序,取指阶段,PC把指令地址送入MAR,我们访问主存读取出来的内容就当成指令,送到IR;完成取指后PC完成自增,进入执行阶段,IR拿出地址码送入MAR,此时访问主存读出的内容就当作运算用的操作数送往运算器,简单来说不是靠MAR本身标记类型,而是依靠机器所处的阶段来定义本次内存访问的目的,CU把控整个时序流程,规定什么时候MAR里放指令地址、什么时候放数据地址,两套访问内存动作在时间上错开,不会发生混淆。
鉴于这部分知识点较多,我们稍微做一个总结:

计算机硬件核心包含主存、运算器、控制器三大模块,主存由存储体、MAR地址寄存器、MDR数据寄存器组成,MAR存放访问主存的单元地址,位数决定可寻址存储单元总数,MDR暂存读写的数据,位数等于存储字长;运算器依靠ALU完成算术与逻辑运算,搭配ACC累加器、MQ乘商寄存器、通用操作数寄存器X暂存运算相关数据;控制器包含PC程序计数器、IR指令寄存器与CU控制单元,PC保存下一条指令地址且具备自增能力,IR存放当前正在执行的指令,CU解析指令并向整机发送控制信号,计算机运行遵循存储程序思想,程序与数据一同存放在主存中,启动后PC持续送出指令地址至MAR,从主存取出指令经MDR送入IR完成取指阶段,PC同步自增准备读取下一条指令,IR将操作码送入CU完成指令分析,随后CU根据指令类型控制硬件,把IR地址码送入MAR再次访问主存获取操作数送入运算器运算,不同指令执行步骤存在差异,整套取指、分析、执行的流程不断循环,驱动计算机自动完成运算任务。
计算机软件

一般来说会把软件分为应用软件和系统软件,应用软件就是比如我们平时常见的APP应用,而系统软件就是诸如操作系统等可以去与硬件产生交互的软件,并为应用软件提供服务。

这里介绍了三种级别的语言,这个倒是老生常谈。机器语言是二进制代码,可直接被 CPU 识别;汇编语言使用助记符,需要汇编器翻译成机器语言;C/C++ 这类高级语言源程序存在两种处理方式,一是通过编译器一次性全部翻译为机器语言再执行,二是像 Python、JavaScript 这类语言依靠解释器逐行翻译并立刻执行;编译器、汇编器、解释器可以统称为翻译程序。

这里再次重复了软件和硬件的逻辑等效性,也就是软件可以实现的功能硬件也可以做,但是我主要想介绍一下ISA:指令集体系结构ISA是软件和硬件之间的接口规范,它定义了处理器可执行的指令种类、指令格式、寄存器、寻址方式等编程模型;基于ISA可以体现软硬件逻辑功能等价性:同一功能,如果ISA包含对应专用指令,可由硬件电路直接实现、执行效率更高,若缺少该指令,则能够利用多条基础指令通过软件模拟实现,只是性能更低,同时同一套ISA允许采用不同微架构完成硬件实现,上层软件无需感知CPU内部电路细节。

这张图展示计算机系统六层层次结构,遵循下层是上层基础、上层是下层功能扩展的原则,自上而下:M4 高级语言虚拟机面向程序员,高级语言程序依靠编译程序翻译为汇编语言,交给 M3 汇编语言虚拟机;汇编程序再经由汇编器翻译成二进制机器指令,传递给 M2 操作系统机器,操作系统通过系统调用这类广义指令对硬件资源进行管控;虚线划分软硬件边界,M1 传统机器能够执行机器语言指令,再交由最底层 M0 微程序机器,由硬件直接执行微指令来完成最终运算,每一层都屏蔽下层实现细节,向上提供更加易用的指令与服务。

这张图显示了一个程序员写的代码如何演变成最后的.exe文件,当然,这里是以C举例子。程序员编写 hello.c 源程序后,先由预处理器处理带 #的指令,得到预处理文件 hello.i;随后编译器将其翻译为汇编语言程序 hello.s;汇编器再把汇编代码转换成二进制机器语言的目标模块 hello.o;最后链接器将当前目标模块与 printf.o 等其他引用的目标模块整合,拼接生成最终的 hello.exe 可执行文件。
接下来我们来学习衡量一个计算机的一系列性能指标。
计算机性能指标

当然,我们有必要复习一下关于二进制存储的内容:

这是计算机的存储容量大小。

这里是一系列评价CPU性能的指标,CPU 时钟周期是时钟信号一次完整震荡的时长,主频为时钟周期的倒数,单位为赫兹;CPI 代表执行单条指令所需时钟周期数,不同指令的 CPI 存在差异,单条指令耗时等于 CPI 乘以时钟周期,由此推导出 CPU 执行程序的总时间等于指令条数乘以平均 CPI 再除以主频;IPS 表示 CPU 每秒能够执行的指令数量,数值为主频除以平均 CPI,衍生出 KIPS、MIPS 等单位,FLOPS 代表每秒完成的浮点运算次数,包含 KFLOPS、MFLOPS、GFLOPS、TFLOPS,图中 K、M、G、T 采用十进制数量标准。

数据通路带宽代表数据总线一次能够并行传输信息的位数,用于硬件间的数据传递;吞吐量是系统单位时间处理请求的数量,由于各项数据交互流程大多需要访问主存,系统吞吐量主要受主存存取周期制约;响应时间是用户发起请求到得到最终结果的总等待时长,由程序运行占用的 CPU 时间,以及磁盘访问、内存读写、I/O 操作、系统调度等各类等待时间共同组成。
存储系统
我们接着来学习存储器的层次结构:

自顶层 CPU 内的寄存器向下依次为高速缓存 Cache、主存(内存)、磁盘(辅存)、磁带与光盘(外存),整体呈现出越靠近 CPU,读写速度越快、存储容量越小、单位成本越高,越底层则读写速度越慢、存储容量越大、单位成本越低的规律。

该图展示存储器层次化结构,分为 Cache— 主存层、主存 — 辅存层两大层级,CPU 优先访问 Cache,Cache 与主存之间进行数据调度,辅存内的数据必须调入主存之后才能被 CPU 访问,主存和辅存相互传输数据,整套存储层级依靠数据在相邻存储部件间调度,平衡访问速度与存储容量的需求。
主存 — 辅存层次用以实现虚拟存储,解决主存容量不足的问题;Cache— 主存层次则用来缓解主存运行速度与 CPU 运算速度不匹配的矛盾。

相联存储器 CAM 依靠内容检索存储位置进行读写,Cache 中的快表就是典型应用;随机存取存储器 RAM 访问任意存储单元耗时一致,和单元物理位置无关;顺序存取存储器 SAM 读写耗时由存储单元所处物理位置决定,磁带属于此类;直接存取存储器 DAM 兼具随机与顺序存取特点,先定位信息区域再顺序访问,硬盘是典型代表,其中顺序存取存储器与直接存取存储器都归属于串行访问存储器,访问耗时受存储单元物理位置影响。速度从快 → 慢排序: 相联存储器(CAM) > 随机存取存储器(RAM) > 直接存取存储器(DAM,硬盘) > 顺序存取存储器(SAM,磁带)。
这里的原理可以这样理解:相联存储器不用地址,硬件同时扫全部存储单元、拿内容直接比对找目标,查找速度最快;随机存取存储器RAM就像独立储物柜,随便选哪个格子,开门耗时都一样,不受存放位置影响;直接存取存储器代表硬盘,先移动磁头跳到目标所在区域,之后再顺着轨道顺序读取数据;顺序存取存储器磁带需要从头一路卷到目标位置,目标离起点越远,等待时间就越长,速度最慢。
然后我们学习一下如何寻址。
寻址

这张图以总容量 1KB、字长 4B的存储器为例,讲解寻址方式的核心逻辑:存储器芯片依靠地址线送入地址,经过译码驱动选中存储矩阵里对应的存储单元,再通过读写电路、数据线完成数据交互;同样 1KB 的总存储空间,按照不同寻址粒度划分,存储单元数量与单个单元大小完全不同,按字节寻址时拥有 1024 个单元,每个单元存放 1 字节;按字寻址共 256 个单元,每个单元存放 4 字节;按半字寻址共 512 个单元,每个单元存放 2 字节;按双字寻址共 128 个单元,每个单元存放 8 字节,地址线根数由存储单元总数决定,而非单个单元的大小,这也是现代计算机普遍选择按字节寻址的根本原因,能够灵活访问任意字节的数据。
指令寻址

指令寻址的核心目的是找到下一条待执行指令在内存中的地址,以此控制程序执行流程;顺序寻址依靠PC自增实现代码逐行依次执行,跳跃寻址通过转移指令修改PC值完成分支、循环、函数调用,二者配合让CPU能正常按逻辑完整运行程序。

顺序寻址是程序默认的执行方式,这里的加 1 不是数值加 1,而是让程序计数器 PC 增加1 个指令字长,每次取完一条指令后 PC 都会自动完成这个增量操作,按顺序逐条执行后续指令;跳跃寻址则是遇到跳转、分支这类转移指令时,不再让 PC 按常规自增,而是根据指令要求直接修改 PC 的值,打破原本的顺序执行流程,跳转到指定的指令位置继续运行。
数据寻址
指令寻址是找下一条要执行指令的内存地址,依靠 PC 完成,分顺序自增、跳转修改 PC 两种,只服务取指流程;数据寻址是找运算所需操作数的存放地址,用来读写变量数据,依附指令里的地址码,二者查找对象、用途、依托硬件完全不同。

常见的数据寻址方法包括:

常见的数据寻址方式分为两大分支:左侧包含相对寻址、基址寻址、变址寻址、堆栈寻址这四类偏移 / 栈相关的寻址方式,右侧包含隐含寻址、立即寻址、直接寻址、间接寻址、寄存器寻址、寄存器间接寻址这六类基础常见寻址方式。
这个部分,我个人认为暂时用不到这些内容,所以我就先不深入学习了,我来快速过一下这十种数据寻址的大概概念即可。
隐含寻址无需单独地址字段,操作数隐含在操作码中;立即寻址的地址码本身就是操作数,无需访存,速度最快;直接寻址地址码为数据真实内存地址,仅一次访存;间接寻址存放的是数据地址,需要多次访存;寄存器寻址操作数存于CPU寄存器,无内存访问开销,效率极高;寄存器间接寻址寄存器保存数据内存地址,需一次访存;相对寻址以PC值搭配偏移量计算有效地址,多用于跳转指令;基址寻址依靠基址寄存器加偏移寻址整块连续内存,适配内存重定向;变址寻址通过变址寄存器加基地址遍历数组、批量数据;堆栈寻址默认从栈顶存取操作数,支撑函数局部变量与临时运算。
我们来学习主存储器内部的组成部分:
主存储器组成部分
DRAM和SRAM
我们现在来说两种RAM。


SRAM靠稳定电路保存数据,通电就能一直存着不用反复维护,读取数据不会破坏原有内容,读写速度很快,但造价高、芯片里放不下太多存储单元,常用来做CPU高速缓存;DRAM依靠电容带电与否记录信息,电容会漏电,不仅读取数据时会清空电荷需要重新写入,还要不断刷新补电才能保住数据,行列地址得分两次发送,读写速度慢一些,不过更容易大规模制造、价格便宜,适合做成大容量内存条当作主存,这两种内存断电之后里面的数据都会直接消失。
ROM

与RAM对应的是ROM,RAM 属于易失性存储器,断电后数据消失,ROM 属于非易失性存储器,断电数据能够保留;ROM 发展衍生出多种类型,包括出厂固化数据、用户无法修改的 MROM,仅能一次性写入的 PROM,依靠紫外线整体擦除重写的 EPROM,支持电擦写的闪存,以及基于闪存构成的 SSD;RAM 读写速度快,用作程序运行时临时存放数据的内存,传统 ROM 以读取为主,如今闪存、SSD 这类 ROM 衍生品可反复擦写,用于长久保存各类文件资料。
实际的计算机中,我们常用的ROM主要是:

主板上的BIOS 芯片属于 ROM 类型存储器,断电后内部数据不会丢失,芯片里存放着自举装入程序。电脑开机时,首先运行 BIOS 里的这段程序,先完成硬件自检、初始化主板硬件,随后引导硬盘上的操作系统启动,是电脑开机必不可少的底层固件。
双端口RAM与多模块存储器

双端口 RAM 配备两套相互独立的地址线、数据线与控制线,能够支持两个 CPU 同时访问这块内存,以此提升多核访问内存的效率;当两个端口操作内存时分为四种情形:访问不同地址单元、同时读取同一地址单元不会产生冲突,但同时写入同一地址单元、一个端口写同一地址而另一个端口读时会出现访问冲突,需要额外的控制电路进行协调处理。
双端口RAM内置仲裁电路处理访问冲突,若两个端口同时操作同一地址,会按照预设优先级或轮转策略分配访问权限,一方先执行,另一方阻塞等待;同地址一读一写、同时写入均会触发冲突,不同地址并发访问或同地址同时读取则无需协调、可并行执行。

多体并行存储器由多个独立存储体组成,依靠并行访问提高主存带宽。高位交叉编址用地址高位选择存储体,连续数据处于同一个存储体,难以并行;低位交叉编址用地址低位选择存储体,连续地址分散在不同存储体,顺序访问时可实现多体流水线并行,效率更高,是主流方案。
通俗的说,低位交叉编址就是把连续的内存地址像发牌一样轮流分配给各个独立存储体,相邻编号的数据落在不同内存条上,CPU顺序读取连续数据时,可以流水线式同时调度多个存储体并行读出,充分利用内存带宽;但缺点是一块大的数据很容易分散横跨多个存储体,若频繁随机跳着访问内存,就会反复抢占同一个存储体,并行优势无法发挥。

单体多字存储器仅有一个存储体,每个存储单元存放m个字,总线宽度与之匹配,每次访问只能一次性读出单元内全部m个字,无法单独读取其中任意一个字,想要发挥存取效率,指令与数据需要在主存中连续存放;由于只有单个存储体,无法实现多个存储体并行工作,零散随机访问场景下容易造成带宽浪费。
主存储器与CPU的连接
聊完了主存里面的内容,我们继续了解主存怎么和CPU通信。

整体分为左侧 CPU 模块、右侧主存模块,二者通过三组独立总线完成交互,右侧红色气泡标注补充说明主存硬件构成,CPU 内部包含 MAR 地址寄存器与 MDR 数据寄存器两个关键专用寄存器,其中 MAR 仅单向连接地址总线,用于向主存传输待访问的内存地址,MDR 双向对接数据总线,负责 CPU 和主存之间数据的双向中转,三条功能总线各司其职:地址总线单向由 CPU 指向主存,用来传递内存地址;读、写控制信号线单向从 CPU 发往主存,告知主存执行读取或写入操作;数据总线为双向通道,实现 CPU 和主存间数据的互相传输。
但是我们会发现一个问题,CPU 规定了固定地址位宽(决定总寻址空间)和数据位宽(单次读写位数),但单片存储芯片的容量、数据线位数有限,无法直接匹配 CPU 需求。
由此引出我们的下一个内容:内存扩容。

三种内存扩容分别解决不同硬件短板,位扩展是多片芯片共用地址线路、拼接数据线,只加宽单次能读写的数据长度,不增加内存总容量;字扩展用来提升整体存储容量,包含接线简易但地址零散浪费的线选法,以及搭配译码器、能划分规整连续地址区间的译码片选法,需要掌握译码器接线和地址范围计算;字位同时扩展是实际最常用的方案,同时补齐数据位宽与扩容总容量,操作上先挑选适配的存储芯片,再完成整套芯片组和CPU的线路连接,三者分别对应仅位宽不足、仅容量不足、位宽和容量都不足这三种内存搭建场景。
我们用货架比喻存储扩展,位扩展是并排多排货架、共用楼层编号,每层合并存放更多商品,只加宽数据位宽、不增加存储字数;字扩展是新增完整一排货架、划分不同楼层区间,每层存放商品数量不变,仅增加存储总字数,二者分别用来解决芯片位宽不足、总容量不够的问题。
磁盘,固态SSD与Cache
我们来展开学学几种存储介质:

机械硬盘的原理就像一台精密的黑胶唱片机,核心是几张叠在一起、能高速旋转的磁性盘片,盘片表面涂满了无数能记录 0 和 1 的微小磁粉,还有一个能来回移动的磁头(相当于唱片机的唱头)。工作时,主轴电机带着盘片以每分钟几千甚至上万转的速度不停旋转,磁头通过磁臂的移动,精准滑到盘片的指定位置:写入数据时,通过磁场改变磁粉的磁极方向,用不同磁极代表 0 和 1;读取数据时,感应磁粉的磁极变化,就能把存好的数据读出来。它的速度完全被机械运动限制,磁头移动、盘片转动都需要实打实的时间,哪怕只是找一个很小的文件,也得等磁头移到位、盘片转到对应位置,这就是它慢的核心原因。
固态硬盘的本质,就是把无数个 U 盘里的闪存颗粒、加上主控芯片、缓存芯片封装到一起的 “超大号 U 盘阵列”,它完全没有机械部件,所有读写全靠电信号完成。核心的 NAND 闪存颗粒里,有无数个能储存电荷的微小单元,写入数据时,通过给这些单元施加不同的电压,控制里面储存的电荷多少,用电荷的有无 / 多少来记录 0 和 1;读取数据时,直接检测单元里的电荷状态,就能瞬间读出数据。因为全程没有任何机械运动,电信号的传输速度接近光速,不管是找数据还是读写数据,都几乎没有延迟,这就是它速度比机械硬盘快几十上百倍的根本原因。
我们接着来学习一下Cache的基本原理。

图中展示了三级存储层级:最右侧 128GB 辅存(固态 / 磁盘)长期存放微信、王者荣耀等软件安装包,启动微信时系统会将微信运行所需数据整体复制到中间 4GB DRAM 内存中,CPU 不会直接访问内存,而是会把当前高频使用的视频聊天等热点数据拷贝至 CPU 内部由 SRAM 构成的 4MB 高速 Cache 里,CPU 直接从 Cache 取指令和数据,依靠 Cache 远超内存的读写速度大幅缓解 CPU 与内存之间的速度差距。
这里完整体现了程序局部性原理,分为时间局部性与空间局部性两层:程序启动微信后,所有运行数据先从辅存载入内存,CPU 当下高频使用的视频聊天相关数据会被复制进 Cache,这是时间局部性—— 刚访问过的数据短时间内会反复使用,放到极快的 Cache 里能快速复用;同时视频聊天配套的相邻相关数据也会一同存入 Cache,对应空间局部性—— 访问某块数据时,周边相邻数据大概率很快会被读取,Cache 一次性预加载相邻内容减少访问内存的次数;正是依靠程序天然的局部性,容量很小的 Cache 就能缓存 CPU 绝大多数需要的数据,大幅减少低速内存的访问次数,抹平 CPU 与内存巨大的速度差,让缓存架构能发挥出理想的加速效果。

缓存命中率H指CPU要读取的数据恰好存放在Cache中的访问占比:如果数据在Cache里(命中),可以快速读取;数据不在Cache,就要额外耗时访问主存,命中率越高,系统平均访问速度越快,是衡量缓存性能的核心指标。

然后现在就会面临新的问题。

这里对应我们接下来要学习的内容。
Cache与主存的映射方式

全相联映射允许主存块存入 Cache 任意位置,直接映射规定每个主存块只能固定存入 Cache 唯一对应位置,组相联映射将 Cache 划分多组,主存块先确定专属分组再存入组内任意位置,三者在映射灵活度、硬件实现难度上依次折中。

电脑内存总大小 256MB,缓存一共 8 个存储块,每块能存 64 字节,所以 28 位内存地址拆成前面 22 位代表内存整块编号、后面 6 位用来找块里具体数据;每个缓存格子都会存一份 22 位的块编号标记和一个有效位标记这块有没有存有效数据,CPU 读取数据时,先拿出地址前 22 位编号,挨个和缓存里 8 个格子的标记对比,只要找到编号对上、且有效位是 1 的格子,就直接用后 6 位地址取出缓存里的数据,要是全部标记都对不上或者对应格子数据失效,就只能去内存里读取,这种方式下内存任意数据块都能放进缓存任意空位,但每次查找都要遍历全部缓存格子。

核心公式为主存块存放位置 = 主存块号 % 缓存总块数,缓存共 8 块即 2³,会截取主存块号末尾 3 位确定唯一缓存行,剩下 19 位作为标记存入对应缓存行,搭配 1 位有效位判断数据是否有效;CPU 访存时先通过地址末尾 3 位锁定唯一缓存行,只用该行存储的 19 位标记和地址高位对比,匹配且有效位为 1 就命中读取,否则访问主存,和全相联映射不同,直接映射里每一块内存数据只能固定存到缓存指定格子,查找不用遍历全部缓存,硬件更简单,但容易出现多个内存块争抢同一个缓存位置的冲突。

这张图以主存256MB、8行Cache、每行64B的配置,讲解2路组相联映射的访存原理: 主存地址共28位,拆分为20位标记、2位组号、6位块内地址;Cache共8行,每2行划为1组,一共分成4组,主存块所属分组由主存块号%分组数(4)确定,也就是用主存块号最后2位作为组号锁定对应Cache组。 CPU访问主存地址时,先依据地址里的2位组号定位到Cache对应的那一组,再把地址高20位标记和该组内2行Cache的标记逐一比对,若有标记匹配且对应行有效位为1,就Cache命中,用6位块内地址读取目标数据;若组内标记都不匹配或有效位无效,就需要直接访问主存。 这种方式折中了全相联和直接映射的优缺点:主存块可以放进指定分组内的任意一个Cache行,降低了直接映射的位置冲突概率,同时只需对比一组内少量标记,硬件开销又比全相联更小。
简单地说的话,我们用储物柜类比三种Cache映射方式:全相联映射是内存数据能放进缓存任意储物柜,存放自由但查找要遍历全部柜子;直接映射规定每块内存数据只能存入缓存唯一固定柜子,查找快但极易出现多份数据争抢同一柜子的冲突;组相联映射作为折中方案,将缓存柜子划分成多组,数据只能进入指定分组,但可在组内任意空位存放,冲突概率更低、硬件实现成本适中,也是当下主流的缓存映射方式。
Cache替换算法
当Cache空间装满、又有新的主存数据要存入缓存时,缓存替换算法用来决定淘汰缓存里哪一块旧数据,解决缓存容量有限、无法同时存放所有数据的冲突问题,通过合理挑选要清掉的数据,尽可能提升缓存命中率,减少去内存读取的次数,保障整体读写速度。

值得注意的是,直接映射的话并不需要考虑替换算法,因为只要内存对应的cache非空我们就需要替换。

老生常谈的几个算法。

Cache随机替换算法RAND的规则是缓存存满需要淘汰数据时,无规律随机挑选一块覆盖,示例用4个缓存块、给定一串内存块访问序列演示了命中与替换过程;该算法硬件实现简单,但完全不遵循程序局部性原理,可能误删掉短期内还要用到的数据,缓存命中率偏低,运行效果不稳定,很少在实际CPU缓存中使用。

FIFO先进先出缓存替换算法,规则是缓存存满时淘汰最早放进缓存的数据块,示例用4个缓存块、同一串内存访问序列演示替换流程,按存入顺序轮流覆盖缓存位置;该算法硬件实现简单,但完全不考虑程序局部性,即便最早存入的数据还会频繁用到,依然会被优先替换,容易降低缓存命中率。

LRU(近期最少使用)算法会给每个 Cache 块配置独立计数器,用来记录块距离上次访问的闲置时长,Cache 被占满时就淘汰计数器数值最大、最久没被使用的块。以图中 4 个 Cache 块、全相联映射、访问序列 {1,2,3,4,1,2,5,1,2,3,4,5} 为例,它有清晰的计数更新规则:访问命中时,命中块计数器清零,其他块计数器加 1;未命中但有空位时,新调入块计数器置 0,其余块计数器加 1;未命中且无空位时,剔除计数器最大的块,新块计数器置 0,其余块计数器加 1。它贴合程序局部性原理,优先淘汰近期大概率不会再用到的数据,缓存命中率远高于 RAND 和 FIFO,硬件实现复杂度适中,是 CPU 缓存里最常用的替换算法之一。

LFU 最不经常使用替换算法每个缓存块设置计数器统计总共被访问的次数,缓存存满时淘汰访问次数最少、计数器数值最小的块,并用 4 块缓存、相同访问序列演示了替换过程;该算法只记录历史总访问频次,不会区分近期与久远访问,曾经高频使用但现在不再需要的数据会长期留在缓存,违背程序局部性原理,实际命中率不如 LRU。
Cache写策略
Cache写策略用来解决CPU修改缓存数据后缓存与主存数据不一致、频繁写入低速主存拖慢性能的问题,通过规定数据同步到主存的时机,兼顾数据一致性与读写速度。


写回法(write-back)缓存写命中策略,缓存每行新增脏位标记该行数据是否被 CPU 修改,CPU 修改缓存内数据时只更新 Cache、不立刻同步主存,仅将对应行脏位置 1;只有该缓存块被替换淘汰时,才会把整块修改过的数据写回主存,脏位为 0 的干净块无需回写,该方式能大幅减少访问主存的次数、提升速度,但会短暂存在 Cache 与主存数据不一致的隐患。

写缓冲的全写法缓存机制,CPU 写命中时既要更新 Cache,还要把数据送入 SRAM 做成的 FIFO 写缓冲队列,由独立控制电路慢慢同步写入主存,能让 CPU 不用等待慢速主存、提升写入速度;该方式始终保证缓存与主存数据一致,写操作不多时效率优秀,但频繁写入会填满写缓冲,造成 CPU 阻塞等待。

Cache 写不命中场景下的写分配法,当 CPU 要修改的数据不在缓存中时,该策略会先把主存对应数据块加载进 Cache,再在缓存里完成修改,这种方式一般搭配写回法使用,修改后不会立刻同步主存,仅在缓存块被替换淘汰时才统一写回主存。

写不命中场景下的非写分配法,CPU 要修改的数据不在缓存时,不会把主存对应块加载进 Cache,而是直接把修改数据写入主存,只有读数据缺失时才会将主存块调入缓存,该策略通常搭配全写法使用,全程保证主存数据实时更新。

现代计算机多级 Cache 存储架构,CPU 紧邻速度最快、容量最小的 L1 缓存,搭配写缓冲,再连接容量更大、速度稍慢的 L2 缓存,最后对接大容量低速 DRAM 主存,性能测试数据也直观体现出越靠近 CPU 读写带宽越高、访问延迟越低;硬件设计有固定搭配规则,L1 与 L2 各级缓存之间一般使用全写法加非写分配法,而缓存和主存之间则采用写回法搭配写分配法,以此平衡读写速度、数据一致性与缓存命中率。原则就是缓存内部层级优先保证数据统一、不存脏数据;缓存到主存优先压榨缓存速度,允许短暂缓存与主存数据不一致。
指令系统
从这里开始是指令系统的内容。

首先我们来学习指令的格式以及基本的分类。

这是指令的基本定义。

通常来说,指令分为操作码与地址码两个部分,操作码指明需要做什么,地址码之指明操作的对象。
我们第一种分类,就是基于地址码内操作的对象数量分类。
按地址码数目分类的指令

零地址指令编码仅包含操作码 OP,分为两类使用场景,一类是无操作数的控制指令如停机、空操作,另一类用于栈计算机,运算所需操作数默认取自栈顶与次栈顶,计算结果自动压回栈顶,天然适配后缀表达式。

一地址指令,指令编码分为操作码 OP 和唯一地址段 A1 两部分,A1 代表主存地址,(A1) 指代该地址存储的数据,类比 C 语言指针取值逻辑;第一种场景是单操作数运算,如自增、取反,执行逻辑为对 A1 对应数据运算后结果存回 A1,即 OP (A1)→A1;第二种场景是双操作数运算,借助隐含累加器 ACC,一个操作数来自 ACC、另一个取自 A1,运算结果写回 ACC,执行逻辑为 (ACC) OP (A1)→ACC。

二地址指令与三地址指令的编码格式、运算规则和访存次数,两类指令都适用于加减乘除、与或非这类双操作数运算;二地址指令编码由操作码 OP、目的地址 A₁、源地址 A₂组成,运算规则是取出 A₁、A₂对应内存数据完成 OP 运算,最终结果覆盖存入 A₁,完整执行流程要四次访存,依次为取指令、读取 A₁数据、读取 A₂数据、向 A₁写入结果;三地址指令编码包含 OP、两个源操作数地址 A₁、A₂以及独立结果地址 A₃,运算逻辑为 (A₁) OP (A₂) 的结果存入 A₃,不会修改原始两个操作数,同样需要四次访存:取指令、读 A₁、读 A₂、写 A₃;二者核心区别在于二地址会覆盖原有操作数,三地址保留源数据,但二者完整执行时访存次数一致。

四地址指令的结构、执行逻辑与 PC 更新规则,指令编码由操作码 OP、源操作数地址 A₁、A₂、结果存储地址 A₃、下一条指令地址 A₄五部分组成,运算逻辑为读取 A₁、A₂对应数据完成 OP 运算后存入 A₃,运算阶段仅需四次访存,流程为取指令、读取 A₁数据、读取 A₂数据、向 A₃写入结果;普通指令执行完毕会自动令 PC 自增 1 以顺序读取下一条指令,而四地址指令执行结束后会直接把 PC 赋值为 A₄,强制跳转至 A₄对应的内存地址取指,自带跳转能力,但该格式会大幅拉长单条指令长度、占用更多存储带宽,现代通用计算机极少采用。
你发现了吗,假如我们想做一个加法,我们其实可以用二地址,三地址,甚至四地址来实现,那么区别到底在哪里呢?
三者运算访存次数一致,二地址指令最短但会覆盖原有目的操作数;三地址保留两个源数据、指令更长;四地址额外携带下一条指令地址可直接跳转,但指令字段最多、存储开销最大,现代CPU极少使用。

按指令长度分类的指令

我们先来回顾一下目前为止学习了哪些字长相关的内容。
指令字长是单条机器指令整体二进制位数,可变长架构中不同指令长度不一样;机器字长是 CPU ALU 单次整数运算能处理的数据位数,也就是常说的 32 位、64 位处理器;存储字长指内存单个存储单元存放的二进制位数,和内存数据寄存器 MDR 位宽保持一致,三者分别对应指令编码、CPU 运算、内存存储三个不同硬件维度。

以机器字长为基准划分出半字、单字、双字长指令,指令越长访存取指次数越多,同时区分了定长(全部指令长度统一,译码简单)与变长(指令长短不一,省内存)两类指令架构,二者分别对应RISC、CISC主流设计,指令字长直接影响取指耗时、硬件复杂度与内存利用率。
按操作码长度分类的指令

按操作码长度可把指令分为两类:定长操作码让所有指令操作码位数一致,n 位最多编码2n条指令,控制器译码电路设计简单,但拓展新指令的灵活性不足;可变长操作码的操作码位数不固定,能按需分配位数、提升编码利用率,可实现更多指令拓展,却会大幅增加控制器译码电路的设计难度。将定长指令字结构和可变长操作码结合,就能得到扩展操作码指令格式,在保持指令总长度固定的前提下,短指令用短操作码腾出空间,让长指令使用更长的操作码,兼顾硬件译码规整度与指令拓展灵活性。
按操作类型分类的指令

按操作类型将计算机指令分为五大类,数据传送类通过 LOAD、STORE 完成主存与 CPU 寄存器的数据交互,算术逻辑与移位操作同属运算类,负责各类数值计算、位运算和数据移位,转移操作属于程序控制类,依靠无条件跳转、条件跳转、函数调用返回、陷阱指令修改 PC 以变更代码执行顺序,输入输出操作则专门实现 CPU 与外部 IO 端口设备之间的数据传输。
高级语言与机器代码的对应

计算机指令整体分为改变程序执行流、处理数据两大核心作用,基础格式统一由代表操作行为的操作码和标记数据位置的地址码两部分构成,地址码的本质就是指明待操作数据存放的三类位置:一是存放在 CPU 寄存器中,汇编直接书写 eax、ebx 等通用寄存器,esi、edi 变址寄存器,ebp、esp 堆栈寄存器名即可寻址,无需访问内存;二是存放在主存里,汇编需搭配 dword/word/byte ptr 标注 32/16/8 位读写长度并包裹内存地址;三是数据直接嵌在指令内部,也就是立即寻址,汇编可直接书写十进制或后缀带 h 的十六进制常量作为操作数,完整串联起指令功能、组成结构与三类数据存储位置对应的汇编书写规则。
这里提到了三种寄存器,稍微展开说一说:
三类寄存器都是CPU里高速存储单元,不用读内存速度很快,通用寄存器随便用来算数据、存临时数字,esi、edi变址寄存器专门批量拷贝数组、内存数据,ebp、esp堆栈寄存器只管函数栈,存函数变量和栈的位置,各司其职可以搭配使用。
常用的x86汇编指令,这里统一记录一下:


函数调用的机器级表示
在学习函数调用的机器级表示之前,我们首先需要学会选择语句与循环语句的机器级表示。
高级语言里的if选择语句,编译后没有专门的分支硬件指令,依靠比较指令设置标志位,再通过条件跳转指令决定走哪一段代码;一般先判断条件,不满足就跳到另一个分支,分支结束用无条件跳转防止串代码,优化版本还能使用条件传送指令,不用跳转,避免CPU分支预测失误拖慢速度。

jmp是无条件转移指令,作用是直接修改PC的值,让CPU跳转到指定地址执行指令;跳转目标地址存在三种形式:直接使用常数地址、取自寄存器中存放的地址、从主存对应地址读取地址。
但是以上方法都需要提前知道指令的内存地址,有没有什么办法可以不知道地址也能跳转呢?

可以在汇编代码中使用标号来标记跳转目标,汇编阶段标号会被换算成真实内存地址。

条件跳转指令jxxx一般要配合cmp比较指令使用,cmp用来对比两个数并设置状态标记,两个数可以存放在寄存器、内存里或是直接写常数;各类jxxx指令会根据标记判断要不要跳转,相等就用je、不相等用jne,有符号数大于用jg、大于等于jge,小于用jl、小于等于jle,比如cmp eax,ebx; jg NEXT,意思就是如果eax的值大于ebx,程序就跳到标号NEXT的位置运行。
总的来说,if-else 选择语句编译为汇编后,一般通过比较指令设置标志位,结合条件跳转指令实现分支选择,通常辅以无条件跳转指令隔离不同分支;缺少 else 分支时无需无条件跳转,经过优化也可采用条件传送指令消除全部跳转。
然后我们学习循环语句的机器级表示:

图中C语言for循环编译为汇编代码后,分为四个组成部分:先完成循环变量初始化,随后通过比较指令与条件跳转在进入循环前判断条件,不满足则直接跳过循环体;满足条件则执行循环主体,更新变量后再次借助比较指令和条件跳转判断是否返回循环头部继续执行,以此依靠条件转移指令实现循环逻辑。

图中展示了x86汇编loop指令实现循环的方式:以ecx作为循环计数器,loop指令等价于dec ecx后判断ecx非零则跳转,属于封装自减与条件跳转的复合指令;另有loopz、loopnz变体可结合ZF标志位判定循环,loop系列指令能实现的循环均可使用基础条件转移指令完成,可简化代码。
汇编实现循环有两种手段:一是使用比较指令搭配通用条件跳转指令,灵活支持任意循环条件;二是使用loop系列专用指令,该指令封装了 ecx 自减与条件跳转,写法简洁但受 ecx 寄存器与固定判断规则约束,它所能实现的循环逻辑均可以用条件跳转指令等价实现。
然后就是我们的函数调用的机器级表示了:

高级语言进行函数调用时,会依托内存中的函数调用栈运行,每个函数执行时都会创建专属栈帧,用于存放局部变量与函数调用相关信息;新被调用函数的栈帧位于栈顶,函数逐层调用时栈帧不断向栈顶增长,函数执行 return 返回后,对应栈帧回收,控制流回到调用处。

而在汇编语言中,当我们调用某个函数,汇编语言就会执行指令call,当我们从某个函数返回时,汇编语言会执行指令ret。

call与ret指令配合完成函数调用与返回:call指令将当前IP旧值压入栈保存,再修改IP跳转到被调用函数入口;ret指令从栈中取出保存的IP旧值并恢复IP寄存器,使程序回到调用位置继续执行,二者依托栈实现函数跳转与返回。

这张图展示了用户栈的大概位置,在32位系统4GB进程虚拟地址空间中,用户栈位于高地址区域,栈底处于高地址、栈顶处于低地址,栈向低地址方向增长,用于存放各个函数栈帧;地址空间自低向高还依次存在只读代码区、读写数据区、堆、共享库映射区,最高地址部分为操作系统内核区域。

访问栈帧存在两种方式,一是使用 push、pop 指令自动调整 esp 指针,完成栈顶元素的入栈与出栈;二是利用 mov 指令,结合 ebp(栈帧底部)与 esp(栈帧顶部)配合偏移访问栈中数据,也可通过 add、sub 指令手动修改 esp 调整栈空间大小。

ebp、esp 都是通用寄存器,内部存放内存地址;进行函数调用时,先把调用者的 ebp 压栈备份,再将 ebp 更新,作为被调用函数栈帧的基底;函数执行结束后,再从栈中取出备份的旧 ebp 写回寄存器,恢复调用者原本的栈帧基准。
这个部分有些抽象,可以多花点时间思考一下。

栈帧从高地址到低地址依次排布上一层栈帧基址(由函数序言push ebp保存,用于恢复上层栈帧,必定存在)、函数调用产生的 IP 返回地址(由 call 指令压栈保存,发生函数调用时一定存在)、若干调用参数(参数列表靠前的参数更靠近栈顶,无参函数则不存在这部分)、对齐产生的未使用填充区域(栈帧大小需为 16B 整数倍,刚好满足对齐要求时就不会出现该区域)、若干局部变量(C 语言里靠前定义的局部变量更靠近栈顶,无局部变量的函数可没有这部分),各组成部分根据函数是否传参、有无局部变量、尺寸对齐情况,存在与否有所区别,只有上层栈帧基址和调用时保存的返回地址是函数调用场景下必然存在的结构。

函数调用分为调用者与被调用者两部分:调用者先按需保存寄存器,将参数布置在栈上,执行call指令压入返回地址并跳转至被调用函数,待返回后从eax读取返回值并恢复寄存器;被调用者通过push ebp、mov ebp,esp搭建栈帧,完成局部变量初始化与业务逻辑,结果存入eax作为返回值,随后依靠mov esp,ebp、pop ebp销毁当前栈帧,最后执行ret指令弹出返回地址,跳转回调用者继续执行。
CISC与RISC

CISC即复杂指令集计算机,设计思路为单条指令实现复杂功能,代表架构x86多用于台式机、笔记本,会增设各类专用复杂指令,但复杂指令硬件实现难度较高;RISC即精简指令集计算机,单条指令只完成基础动作,依靠多条简单指令组合实现复杂功能,代表架构ARM多用于移动端,电路设计简单、功耗更低,更便于实现流水线与并行处理;二者设计分歧源自80-20规律,程序运行时大部分场景仅频繁使用少量指令。
CPU——中央处理器
终于要开始学习CPU相关的内容了。

CPU主要由运算器和控制器组成。

运算器是CPU负责完成算术与逻辑运算的核心部件,核心计算模块为ALU算术逻辑单元,依靠内部电路实现加减乘除、与或非等各类运算,同时搭配三类专用寄存器协同工作:累加器ACC用来临时存放参与计算的数据或是运算得出的结果,乘商寄存器MQ专门为乘法、除法运算服务,存储乘除过程中的操作数、积或商,通用操作数寄存器X则用来存放另一路待参与运算的输入数据,四类组件相互传输数据、配合协作,所有运算操作都在ALU中完成,寄存器负责数据中转与暂存,共同支撑起计算机全部数值和逻辑计算工作。

计算机CPU中的控制器由控制单元CU、指令寄存器IR、程序计数器PC三部分构成,PC负责存储下一条待取指令的内存地址并具备自增能力,取指阶段会依据PC地址取出指令存入IR暂存当前指令,CU读取IR内的指令完成译码分析并生成各类硬件控制信号,三者协同完成取指令、分析指令、执行指令的完整指令周期,其中PC与IR共同承担取指工作,CU负责指令解析与执行阶段的控制调度。
这里再重新复习一下CPU的一条指令的一整个工作流程。

CPU完整工作流程分为取指令、分析指令、执行指令三大阶段,初始时PC程序计数器存放第一条指令的主存地址,首先进入取指阶段,PC将指令地址送入MAR存储器地址寄存器,主存依据MAR中的地址读出对应单元内的整条指令并传送至MDR存储器数据寄存器,再由MDR把完整指令送入IR指令寄存器,同时PC自动自增以预备下一条指令地址;随后进入译码分析阶段,IR拆分出指令的操作码与地址码,操作码传输至CU控制单元完成译码,CU识别指令需要执行的操作并生成配套硬件控制信号;最后进入执行阶段,IR中的地址码送入MAR,主存根据该地址读取所需操作数据至MDR,MDR再将数据转发给运算器的寄存器暂存,若指令需要算术或逻辑运算,ACC、MQ、X等寄存器会向ALU算术逻辑单元输送操作数完成计算,运算结果回存至ACC,若是存数指令则将ACC的数据经MDR写入主存对应地址,整套流程循环往复直至读取到停机指令。
CPU的功能和基本结构

CPU一共干五件事:第一,从内存里拿程序指令、读懂指令、按顺序一条条跑程序;第二,读懂指令后给电脑各个硬件发对应的操作信号,指挥它们干活;第三,把控所有操作的先后节奏,保证每一步都按规定时间有序执行;第四,负责算数据,做加减乘除、逻辑判断;第五,电脑运行时如果出现报错、外设发来请求,及时停下当前任务去处理这些突发情况。其中读取、指挥、控节奏、处理突发状况靠控制器完成,计算数据靠运算器完成。

而在我们刚才列举的内容中,CPU的运算器只负责对数据进行加工,其他部分都是由控制器完成的。
然后我们这里超展开一下:

这张图完整展示了单内部总线架构的CPU内部全部硬件以及它和外部内存的通信线路,CPU中间有一条贯穿所有部件的内部总线作为内部唯一的数据传输通道,总线上挂载着运算器、控制器的各类寄存器,每个寄存器都配有输入、输出控制门,由控制器统一管控数据收发;图左侧是负责计算的运算器,包含通用寄存器R0~R3、存放运算数据与结果的ACC累加器、执行加减逻辑运算的ALU、配套移位寄存器,还有记录运算正负、溢出等状态标记的PSW程序状态字;图右侧是统筹所有硬件工作的控制器,既有PC、IR、MAR、MDR这四个和内存交互的专用寄存器,PC存下一条指令地址、IR存当前指令、MAR向外发送内存地址、MDR充当CPU与内存的数据中转站,也有指令译码器、时序系统、微操作信号发生器这套核心控制电路,IR拆分出的指令操作码交给译码器翻译,再结合时序节拍、PSW运算标志,由微操作信号发生器生成所有硬件读写、传输的控制信号;CPU最外侧还引出了两条对外总线,单向的绿色地址总线用来把MAR里的内存地址发给主存,双向黄色数据总线用来在MDR和主存之间传递指令、数据,整套架构依靠内部总线完成CPU内部数据流转,依靠外部总线完成CPU和内存的数据交互,再由控制器全程把控每一步硬件动作的顺序与时机。
CPU指令执行过程
当我们把目光聚焦到CPU的一条指令时,我们会这样分析一条指令。

我们会把一条指令的生命周期分为取指周期和执行周期。

流程先进入取指周期从内存取出指令,之后判断指令是否需要间址寻址,若需要就先执行间址周期获取有效地址,不需要则直接进入执行周期完成指令对应的操作;执行结束后会判断是否出现中断请求,若有中断就执行中断周期保存现场、处理中断事务,没有中断就直接回到起始的取指周期,开始下一条指令的处理,形成循环往复的指令执行流程。
值得一提的是,这四个过程都会涉及到CPU的访存:


这里介绍了 CPU 三种指令执行方案,先说明一条指令完整执行的总时长叫指令周期,完成取指、间址、执行等单一步骤的时间段为机器周期,最小时间单位是时钟节拍;方案一是单指令周期,所有指令统一采用最长指令所需的固定机器周期完成,简单但短指令会空耗时间、拖慢整体效率;方案二是多指令周期,不同指令匹配对应数量的机器周期,用时更合理,但硬件设计复杂度更高,前两种方案都只能串行执行指令;方案三为流水线方案,每个时钟周期就能启动一条新指令,多条指令可同时处于不同执行阶段并行处理,以此大幅提升 CPU 执行效率。
数据通路
什么是数据通路呢?数据通路就是 CPU 内部数据走动的完整道路网络,由内部总线、各类寄存器、ALU 运算单元、输入输出门控线路组成,所有指令里的取数、计算、存数操作,本质都是数据在这套通路里来回搬运;控制器只负责发指令、指挥什么时候开门 / 关门,数据真正跑起来全靠数据通路。

我们首先从最基本的单总线结构开始学起:

以一条共用的 CPU 内部总线作为所有寄存器、ALU 之间唯一的数据传输通道,CU 控制单元输出各类读写门控信号(如 PCout、MARin、ACCin 等)管控数据流转,图中分三类典型操作完整演示数据传输流程:一是寄存器间数据传递,例如 PC 数据送往 MAR,依靠 PCout、MARin 信号分两步完成总线转发;二是 CPU 与主存交互读取指令,先把 PC 地址送入 MAR,CU 下发读内存信号,主存对应单元的指令经数据线载入 MDR,再通过总线送入 IR 指令寄存器;三是加法算术运算的完整微操作流程,先提取 IR 内的地址码经总线送入 MAR,读取主存操作数存入 MDR 再转发至 Y 寄存器,ACC 累加器与 Y 的数据送入 ALU 完成加法运算,运算结果暂存 Z 后回写至 ACC,整套流程里所有数据搬运、读写、运算动作都由 CU 下发对应控制信号驱动,所有数据流转都依托单条内部总线分时完成。
单总线CPU内部仅靠一条公共总线连通全部寄存器与运算单元,优点是硬件线路简单、设计成本低、容易理解搭建,缺点是总线为独占分时使用,同一时间只能传输一组数据,连续运算需要多次抢占总线来回搬运数据,数据传输存在排队等待,整体运行效率偏低。

专用数据通路架构没有共用的单一内部总线,各个寄存器、ALU 之间都配备独立专属传输线路,依靠 CU 下发的 C0~C13 多路独立控制信号分别管控每一条通路的数据传输,优势是多条线路可同时并行传递数据,不用排队抢占通道,运算、数据搬运能同步执行,CPU 处理速度更快;缺点是硬件布线数量大幅增加,线路结构复杂,控制器需要配套更多控制信号,硬件设计成本和难度远高于单总线结构。
关于控制器
硬布线控制器是靠焊死的硬件电路直接算出控制信号,速度飞快,现在电脑、手机游戏CPU都用它,但要新增或修改指令就得改动硬件,灵活性很差;微程序控制器是把每条指令拆成很多微小操作存在专用存储里,运行时读取这些微指令来产生控制信号,改指令只需要更新存储里的微程序,调整起来很方便,可每次都要读存储,运行速度慢不少,二者核心差别就是控制信号一个靠硬件实时生成、一个靠提前存好的微指令读取,高性能设备选硬布线,需要灵活调整指令的场景选微程序。
指令流水线
用通俗的话讲,指令流水线就是给 CPU 搭了一条分工明确的加工流水线,把一条指令完整执行拆成取指令、译码、取操作数、运算、写回这好几道独立工序,就像工厂流水线,上一条指令做完取指去译码时,CPU 立刻就能启动下一条指令去取指,多条指令同时卡在不同工序并行干活,不用等一条指令从头到尾全部做完才开始下一条,大幅提升 CPU 单位时间处理指令的总量。

衡量流水线的性能指标主要有:

这里就不展开说了,全是考试的内容,没啥意思。
多处理器系统
我们先来了解一些基本的CPU架构:

计算机按指令流、数据流分为四类架构,SISD 是单核串行执行单条指令处理单组数据,普通单核 CPU、基础流水线 CPU 都属于这类;SIMD 单条指令同时批量运算多组数据,多用于游戏向量矩阵批量计算;MISD 多指令处理同一数据,无实际商用设备;MIMD 就是多处理器多核架构,多个核心各自独立执行指令、处理不同数据,游戏多线程渲染、物理计算依靠它实现并行提速。

SISD就是一条指令对应一个数据。

SIMD即单指令流多数据流,它由一个统一的控制单元CU(带程序计数器PC)下发同一条控制指令,同时驱动多个独立的ALU运算单元,各个ALU搭配专属局部存储器、寄存器组,并行处理多组不同的数据,再通过总线和主存储器完成数据交互;原理就是用一套指令控制信号,让多个运算单元同步开展同一种运算,实现数据级并行计算;优势是能在单次指令调度中批量处理海量同类数据,大幅提升图像渲染、音视频编解码、矩阵运算这类规整并行任务的处理效率,硬件控制逻辑相对简洁;劣势是只适合高度同质化的并行运算场景,面对分支跳转、数据差异大的不规则任务时,部分运算单元会闲置空转,并行效率会明显下降。

这张图是MIMD架构下的共享存储多处理器SMP系统,内部包含多个完全独立的CPU核心,每个核心都配备专属CU控制单元、PC程序计数器、ALU运算单元与寄存器组,各核心拥有独立指令流,可并行执行完全不同的程序、处理各自数据流,所有核心共用一块LLC末级缓存并通过总线访问同一主存;它的优势是多核共享内存,线程间数据交互简单高效,游戏引擎多线程渲染、物理、逻辑任务都基于这种架构开发,劣势是多核争抢共享缓存与总线会产生资源冲突,核心数量提升到一定规模后性能提升会明显受限。

向量处理器就是基于 SIMD 架构设计的专用运算部件,依靠单一控制单元下发同一条运算指令,驱动多组独立 ALU 并行对数组里的批量数据同步计算,原理是把零散标量数据打包成连续向量一次性送入运算单元批量处理,无需循环逐个计算,在游戏开发中主要用来加速矩阵变换、顶点坐标、颜色采样、物理向量运算等大量重复的同类数值计算,能大幅减少指令执行次数、降低 CPU 循环分支开销,显著提升渲染、物理、动画模块的运行效率。
总线
基本概念
什么是总线?

计算机系统里 CPU、主存、硬盘、打印机四类硬件设备共同连接三组并行传输总线,分别是绿色地址总线、黄色数据总线、红色控制总线;地址总线用于 CPU 发送要访问的设备 / 内存地址,数据总线负责各部件间双向传输读写数据,控制总线传递读写、应答等操作控制信号,所有硬件共享同一套总线完成相互间的通信交互。

一根逻辑意义上的总线本身就是由多根物理信号线组合而成,总线具备单写多读的传输特性,同一时间仅允许一个硬件部件向总线输出数据,避免信号冲突,而总线上其余多个部件能够同步读取总线上的信号数据。
我们可以这样给总线分类:

首先按照数据传输格式来分类:

串行总线是依靠单条传输线,把数据一位一位依次收发的总线形式,并行总线是用多条信号线,把一整组数据的多位同时同步传输的总线;二者核心差异体现在传输方式、优缺点和适用场景上:串行总线只用一根线,布线省钱省空间、适合长距离传输,但数据要做并串、串并格式转换,处理流程更繁琐;并行总线多条线同步传数据,时序逻辑简单、电路实现容易,可信号线多会占用大量布线空间,长距离使用成本高,高频工作时线路间容易出现信号干扰,很难再继续提速。

片内总线局限 CPU 芯片内部,连通寄存器、ALU;系统总线在机箱内部,串联 CPU、主存、各类外设接口;通信总线作为外部总线,负责计算机和其他计算机、远程外设、测试设备之间的跨系统传输。
我们一般来说主要讨论系统总线,这里讨论一下系统总线的分类:

单总线结构只用一条系统总线连接 CPU、主存、所有 I/O 设备,构造简单、成本低、易扩展,但所有设备抢同一条通道,数据传输容易拥堵、效率上限低;双总线拆分出主存总线和I/O 总线,让 CPU 和内存走专属高速通道,低速外设走独立 I/O 通道,缓解了单总线的拥堵问题,性能中等、复杂度适中;三总线在双总线基础上新增DMA 总线,主存、普通 I/O、高速外设 DMA 传输各有专属通路,高速外设可绕开 CPU 直接和内存大批量传数据,整体并行能力最强、传输效率最高,不过硬件结构和成本也相对更高。
双总线只有CPU分别对接内存、外设,内存和外设想交换数据必须先传给CPU再转发,没法直接互通;三总线新增DMA总线后,高速外设和内存能绕开CPU直接传输,既保留CPU直达内存、外设的两条通道,又实现内存与外设直连,大批量数据传输不用占用CPU。
总线仲裁
什么是总线仲裁?总线仲裁是当多个设备同时申请占用共享系统总线时,按照既定规则选出唯一总线使用权申请者、避免传输冲突的协调机制,分为集中仲裁和分布仲裁两大类。

集中仲裁依靠统一中央仲裁器分配总线权限,其中链式查询通过串联链路逐级下放授权、硬件简单但优先级固定,计数器定时查询依靠计数器轮询设备、优先级可调更灵活,独立请求方式给每个设备配置独立请求与应答线路、响应最快但硬件布线成本最高;分布仲裁无中央管控单元,各主设备依靠自身仲裁逻辑自主协商总线使用权,系统扩展性和容错性更优。
| 对比维度 | 链式查询 | 计数器定时查询 | 独立请求方式 |
|---|---|---|---|
| 核心原理 | 设备串联成菊花链,总线授权信号顺着线路依次传递 | 仲裁器计数器循环遍历设备编号,查到有请求的设备就分配总线 | 每个设备单独配一对总线请求、总线授权信号线直连仲裁器 |
| 优先级规则 | 硬件固定,离仲裁器更近的设备优先级更高,无法修改 | 修改计数器起始值就能灵活更改设备优先级 | 硬件预先设定好各设备优先级,仲裁器直接择优授权 |
| 硬件线路复杂度 | 最简单,仅 3 根公共控制线(BS、BR、BG) | 中等,控制线数量固定,不需要给每个设备单独布线 | 最复杂,设备越多请求 / 授权信号线数量成倍增加 |
| 总线响应速度 | 慢,高优先级设备占用线路后,低优先级设备要等信号逐位传递 | 中等,轮询存在等待开销 | 最快,仲裁器可同时接收所有设备请求,直接判断授权 |
| 故障影响 | 一处设备断线,后方所有设备彻底失去总线使用权 | 单个设备故障不影响其他设备轮询 | 单个设备线路故障仅影响自身,不干扰其他设备 |
| 适用场景 | 设备少、成本预算低、对速度要求不高的简单系统 | 设备数量中等,需要灵活调整优先级的场景 | 高速计算机系统,追求极低总线响应延迟 |
输入输出系统
基本概念
我们都知道一个计算机一般分为主机和外设,外设最常见的就是比如鼠标键盘等外部插入的设备。

事实上,我们的外部设备通过IO接口与主机产生交互。

具体如何实现交互呢?

左边 CPU 通过系统里的数据、地址、控制三条系统总线和中间的 I/O 接口交互:地址总线用来定位接口寄存器、数据总线双向传输按键数据、控制总线下发读写指令;I/O 接口内部设有三类专用寄存器,数据寄存器存放外设收发的数据,控制寄存器接收 CPU 下发的工作指令,状态寄存器向 CPU 反馈设备忙 / 空闲等运行状态;最后 I/O 接口通过 USB 这条外部通信总线,连接键盘这类外设,实现主机和外接设备的数据中转,I/O 接口相当于 CPU 和外设之间的翻译中转站,两边总线标准不同,由它完成信号、协议的转换适配。

CPU操控键盘I/O的两种方式,程序查询方式下CPU会持续循环读取I/O控制器的状态寄存器,不断检测键盘输入是否完成,确认完成后再读取数据,全程占用CPU资源、效率低下;程序中断方式则让CPU发起键盘操作后转去执行其他任务,键盘输入完成时I/O控制器主动向CPU发送中断信号,CPU收到请求后再暂停现有任务读取按键数据,大幅提升CPU利用率。
| 方式 | CPU 等待状态 | 触发数据读取的主体 | CPU 利用率 |
|---|---|---|---|
| 程序查询 | 全程循环轮询、阻塞等待 | CPU 主动反复查询 | 低 |
| 程序中断 | 后台执行其他任务 | I/O 控制器主动发信号通知 | 高 |
这就引出了我们两种主要的IO控制方式:

上图通过时间轴直观对比两种I/O处理模式的CPU运行状态,程序查询方式启动I/O后CPU全程持续循环等待、同步完成I/O设备的数据准备与传输,此阶段无法执行其他程序,CPU资源被完全占用;程序中断方式启动I/O后CPU可以继续运行原有程序,I/O设备独立完成数据准备,仅当设备就绪发出中断请求时,CPU短暂暂停当前任务执行中断服务程序完成数据交互,处理完毕后立刻恢复原有程序执行,大幅减少CPU闲置等待时间,资源利用率更高。
除此之外,还有一种IO控制方式:

这张DMA控制方式结构图展示三总线架构下的硬件分工,系统通过主存总线连通CPU与内存,I/O总线对接各类外设接口,新增DMA总线专门连接内存和DMA控制器;低速的键盘、打印机走普通I/O接口和CPU交互,磁盘这类高速外设搭载DMA控制器,可借助DMA总线绕开CPU直接和主存批量传输数据,DMA控制器本质是特殊I/O控制器,仅在传输起止阶段需要CPU下发指令,数据搬运全程无需CPU中转,大幅降低CPU占用。
还有一种就是:通道控制方式。

相比 DMA 方式,通道能自主管控多台 I/O 设备、执行成批的 I/O 指令,进一步把 CPU 从繁琐的 I/O 管控工作中解放出来,CPU 仅需在任务发起和最终收尾阶段参与工作,I/O 全程的细节调度都由通道独立完成,设备管理能力和 CPU 资源利用率又提升了一个层级。
| 控制方式 | 核心逻辑 | CPU 参与程度 | 适用设备 | 优缺点 |
|---|---|---|---|---|
| 程序查询(轮询) | CPU 循环读取设备状态寄存器,就绪才读数据 | 全程阻塞等待,I/O 期间无法干别的 | 低速简单设备(早期键盘) | 电路最简单,CPU 利用率极低 |
| 程序中断 | CPU 发起 I/O 后继续运行程序,设备就绪主动发中断通知 CPU | 仅数据读写、中断处理时占用 CPU | 普通低速外设(键盘、鼠标) | CPU 不用空等,单次少量数据传输效率高,大批量传输开销大 |
| DMA 方式 | DMA 控制器绕开 CPU,外设与内存直接批量交换数据 | 仅初始化、传输结束两段短暂参与 | 高速块设备(SSD、磁盘、网卡) | 大批量 IO 几乎不占用 CPU,一次只能管控一台设备 |
| 通道方式 | 专用通道处理器独立执行通道指令,自主调度多台外设 | 只下发 I/O 指令、处理最终完成中断 | 大型服务器多外设集群 | 完全解放 CPU,可同时管理多个 I/O 设备,硬件成本高,个人 PC 极少使用 |
最后我们看看IO系统的组成部分。

I/O系统分为硬件与软件两大模块,硬件由主机、I/O接口、外部I/O设备通过I/O总线相连构成,软件包含驱动、用户程序等程序,依靠两类指令完成主机与外设交互:一是CPU识别的I/O指令,通过操作码、命令码、设备码确定操作行为与目标设备,二是仅通道可识别的通道指令,通道程序预先存放在内存,带通道架构的计算机里CPU仅下发I/O指令调度通道,再由通道执行批量通道指令自主管控外设,减轻CPU的I/O管理负担。
IO接口
我们来展开聊聊IO接口的内部。

CPU依靠数据、地址、控制三条系统总线和I/O接口交互,I/O接口内置数据、控制、状态三类寄存器,作为CPU与键盘这类外设的中转桥梁,既能缓存数据抹平主机与外设的速度差距、反馈设备运行状态、接收CPU下发的控制指令,还能完成串行并行数据格式转换,依托USB等线路实现主机和外设的双向通信。

I/O接口分为连接系统总线的主机侧与外接外设电缆的设备侧,内部包含地址译码逻辑、数据缓冲寄存器、状态/控制寄存器,这些寄存器统称为I/O端口,CPU通过地址线选中对应端口,分三步完成交互:一是往控制寄存器写入命令字下发设备操作指令,二是读取状态寄存器里的状态字获取设备运行情况,三是读写数据缓冲寄存器完成主机和外设的数据传递,接口内部逻辑会同步完成主机总线与外设之间的信号、数据适配,且控制、状态寄存器分时复用,部分接口可将二者合并。

IO的端口其实就是可以被CPU直接访问的寄存器。

这张图对比了I/O端口的两种编址方式,统一编址将I/O端口映射到内存地址空间,依靠地址范围区分内存与外设,普通读写内存指令就能访问I/O端口,是RISC架构常用方案;独立编址拥有完全独立的I/O地址空间,和内存地址互不重叠,必须使用IN、OUT这类专用I/O指令操作外设端口,Intel x86处理器普遍采用该方式。
IO方式

这是计算机发展里 CPU 和外设数据交互的三代演进方案,核心差异是CPU 参与搬运数据的程度越来越低。

程序查询方式:CPU 发起 IO 请求后,会停下原有程序,持续循环查询外设状态、等待外设完成准备并逐字节搬运数据,在整个 IO 处理的全部时间段内 CPU 都无法执行其他计算任务,CPU 资源被完全占用,利用率极低。
程序中断方式:CPU 发出 IO 启动指令后立刻回去运行自身程序,外设独立完成数据准备工作,等外设就绪后主动向 CPU 发送中断信号,CPU 仅在指令执行完毕的间隙暂停当前任务,快速执行一段中断服务程序完成数据传输,处理完中断后马上恢复原有程序运行,外设准备阶段 CPU 可以并行工作。
DMA 方式:CPU 下发 IO 传输参数后继续执行程序,DMA 控制器接管总线完成外设与内存间整块数据的直接传输,传输全程几乎不打扰 CPU,仅在总线存取周期短暂挪用总线,等大批量数据全部传输结束后才发起一次中断通知 CPU 收尾,CPU 介入的时间被压缩到极致,是三者中 CPU 开销最小、适合高速大块数据传输的 IO 方式。

CPU 通过系统总线的地址线、数据线、控制线连接外设接口电路,接口内包含两块核心寄存器:存放待打印数据的数据缓冲寄存器、记录打印机忙 / 就绪状态的状态控制寄存器;CPU 依靠 x86 的 IN/OUT IO 指令读写这两个端口,整个流程完全由 CPU 主动控制,CPU 会循环读取状态寄存器判断打印机是否空闲,只有查询到外设就绪时,才通过数据缓冲寄存器把字符逐次发给打印机,全程所有状态判断、数据传输动作都由 CPU 亲自执行。

程序查询方式就是 CPU 主动用软件循环反复读取 IO 接口的状态寄存器,持续轮询外设是否就绪,在等待期间 CPU 被完全占用,无法执行其他任务。
然后我们再来聊聊程序中断:

程序中断就是CPU正常跑自己的程序时不用主动去轮询外设,像键盘按键、打印机打印完成这类外设会主动给CPU发中断请求信号,CPU收到请求后,会先记下当前程序执行到的断点位置,暂停原有程序,跳转到对应外设专属的中断服务程序处理IO事务,处理完毕后再回到之前记下的断点,继续运行原来没跑完的程序;如果多个外设同时发起中断,硬件还会根据优先级逻辑先处理高优先级的中断请求。

中断分为广义中断与狭义中断,广义中断包含源自CPU内部、由当前指令触发的内中断,以及来自CPU外部、与当前指令无关的外中断(狭义中断);内中断又分为程序主动发起的自愿指令中断,和被动出现的硬件故障、软件运算错误类强迫中断,外中断则分为I/O设备完成任务发出的外设请求中断、用户操作引发的人工干预中断;同时中断还能根据屏蔽特性划分为不受关中断状态限制、必须响应的非屏蔽中断,以及由PSW寄存器IF标志位控制、IF=1开中断才响应、IF=0关中断则屏蔽的可屏蔽中断,关中断操作常用来实现原子操作。

CPU循环完成取指令、执行指令的基础流程,每条指令执行结束后都会检查是否存在中断请求,若无中断则继续取下一条指令,若检测到中断就进入中断周期完成断点压栈保存、关闭中断、加载中断向量地址修改程序计数器PC的中断响应操作(CPU自带的中断隐指令),随后运行中断服务程序,依次执行保护现场、完成外设对应的设备服务、恢复现场、开启中断、中断返回的步骤,最后回到程序之前被打断的断点处继续执行原有程序,不同中断源会跳转至各自专属的中断服务程序入口分别处理。
然而我们其实在这个过程中会发现一个点:我们在执行中断的任务时会关闭中断,可是要是我们不中断的话会怎么样呢?

| 对比维度 | 单重中断 | 多重中断 |
|---|---|---|
| 核心定义 | CPU 处理中断服务程序期间,全程屏蔽所有新中断,不能响应其他中断请求 | CPU 处理中断时,允许优先级更高的新中断抢占当前流程,支持中断嵌套 |
| 开关中断时机 | 进入中断响应时关中断,整个中断服务流程结束后才开中断 | 中断响应阶段关中断,保护现场完成后立刻开中断,允许接收更高优先级中断 |
| 中断抢占能力 | 无抢占,新中断只能排队等待当前中断完全处理完 | 支持抢占,高优先级中断可打断低优先级中断,形成嵌套 |
| 现场保存层数 | 仅保存一层主程序断点现场 | 多层现场堆栈保存,主程序、各级中断现场依次入栈 |
| 优先级作用 | 无需中断优先级机制 | 必须依靠中断优先级,判断是否允许新中断抢占 |
| 适用场景 | 简单低速外设、对实时性要求低的场景 | 高速多外设、实时操作系统,需要紧急事件优先处理 |
| 执行流程特点 | 串行处理所有中断,中断之间互不穿插 | 嵌套处理,高优先级中断优先执行,处理完逐层返回 |
程序中断方式下,CPU下发I/O启动命令后即可继续执行原有程序,外设自行准备数据的阶段CPU完全不被占用;外设就绪发起中断请求后,CPU会短暂占用硬件完成中断隐指令操作,随后运行中断服务程序处理I/O事务,此阶段CPU被I/O任务占用,中断处理完成后CPU回到原程序断点继续运算,仅在中断响应与服务处理的短暂时段占用CPU,整体利用率远高于程序查询方式。
最后我们再来看看DMA方式怎么处理:

DMA传输分为预处理、批量数据传送、后处理三个阶段,预处理阶段CPU向DMA控制器填入主存起始地址、设备地址、传输数据总量并启动外设,随后CPU继续运行自身主程序,DMA控制器接管总线,依靠内部地址计数器、长度计数器、数据缓冲寄存器完成外设与主存间一整批数据的直接传输,传输全程仅短暂挪用总线几乎不占用CPU;当全部数据传输完毕,DMA控制器发送溢出信号触发中断,CPU执行中断服务程序完成DMA收尾后,继续运行原本的主程序。
综合来看,这三种所谓的IO方式的差异在于:
| 对比项 | 程序查询方式(轮询) | 程序中断方式 | DMA 方式 |
|---|---|---|---|
| 核心逻辑 | CPU 主动反复盯着外设状态,全程亲自搬运每一个数据 | CPU 启动外设后干自己的活,外设每传完一小段数据就主动喊 CPU 处理 | CPU 提前给 DMA 控制器填好所有传输参数,DMA 硬件自动批量搬运整块数据 |
| CPU 占用情况 | 外设工作全程霸占 CPU,CPU 只能等设备,完全没法做别的事 | 外设准备数据时 CPU 自由;每次传输少量数据都要打断 CPU,频繁占用 CPU | 仅最开始配置参数、传输完成后收尾占用 CPU,大批量传输过程几乎不打扰 CPU |
| 数据搬运执行者 | CPU 亲手搬运每一字节数据 | CPU 亲手搬运每次中断的少量数据 | DMA 控制器硬件直接搬运,不用 CPU 插手 |
| 中断次数 | 无中断机制 | 传输 N 个数据就触发 N 次中断 | 整块数据传输完成仅 1 次中断 |
| 适用场景 | 极低速度、简单外设(简单按键) | 中低速、少量零散数据(键盘、鼠标) | 高速、大批量连续数据(硬盘、显卡) |
| CPU 利用率 | 最低,大量算力空耗在循环等待 | 中等,设备空闲阶段 CPU 可并行工作 | 最高,绝大多数时间 CPU 专注计算任务 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)