全国硕士研究生计算机学科专业基础综合(408)考试,包含数据结构、计算机组成原理、操作系统和计算机网络4大专业基础课程,满分150分。其中,计算机组成原理45分,占总分的30%。
计算机组成原理的考察目标为:
掌握单处理器计算机系统中主要部件的工作原理、组成结构以及相互连接方式。
掌握指令集体系结构的基本知识和基本实现方法,对计算机硬件相关问题进行分析,并能够对相关部件进行设计。
理解计算机系统的整机概念,能够综合运用计算机组成的基本原理和基本方法,对高级编程语言(C语言)程序中的相关问题进行分析,具备软硬件协同分析和设计能力。
计算机组成原理,主要讲的就是计算机系统的“组成”,以及其中各部件的具体实现和连接的原理。
(一) 计算机系统层次结构
计算机系统的基本组成
计算机硬件的基本结构
计算机软件和硬件的关系
计算机系统的工作原理
“存储程序"工作方式,高级语言程序与机器语言程序之间的转换,程序和指令的执行过程。
(二) 计算机性能指标
吞吐量、响应时间;
CPU时钟周期、主频、CPI、CPU执行时间;
MIPS、MFLOPS 、GFLOPS、TFLOPS、PFLOPS、EFLOPS、ZFLOPS。
本章是计算机组成原理的概述,一般会考察相关概念,以选择题为主,也可能结合后续章节以综合题的形式来考察性能的分析。掌握本章的概念,是整个组成原理课程的基础。 计算机性能指标在历年真题中出现的频次较高,需要重点掌握。
| 考点 | 考查次数 | |
|---|---|---|
| 单项选择题 | 综合应用题 | |
| 计算机系统层次结构 | 6 | 0 |
| 计算机性能指标 | 9 | 2 |
计算机系统由“硬件”和“软件”两大部分组成。
所谓“硬件”即指计算机的实体部份,它由看得见摸得着的各种电子元器件、各类光、电、机设备的实物组成,如主机、外设等等。
所谓“软件”,它是看不见摸不着的,由人们事先编制成具有各类特殊功能的信息组成。通常把这些信息, 诸如各类程序寄寓于各类媒体中, 如RAM、ROM、磁带、磁盘、光盘、甚至纸带等。它们通常被作为计算机的主存或辅存的内容。
计算机的软件通常又可以分为两大类:系统软件和应用软件。
系统软件又称为系统程序,主要用来管理整个计算机系统,监视服务,使系统资源得到合理调度,确保高效运行。它包括:标准程序库、语言处理程序(如将汇编语言翻译成机器语言的汇编程序;将高级语言翻译成机器语言的编译程序)、操作系统(如批处理系统、分时系统、实时系统)、服务性程序(如诊断程序、调试程序、连接程序等)、数据库管理系统、网络软件等等。
应用软件又称为应用程序,它是用户根据任务需要所编制的各种程序。如科学计算程序,数据处理程序,过程控制程序,事务管理程序等等。
(一) 硬件的发展
1943 年,第二次世界大战进入后期,因战争的需要,美国国防部主导建造了第一台计算机ENIAC(Electronic Numerical Integrator And Computer,ENIAC),它的全称是"用电子管组成的电子数字积分机和计算机”。
从此以后,计算机的发展经历了电子管、晶体管、集成电路的世代发展,体积越来越小、性能越来越强,并从军事领域迅速扩展应用到生活生产的各个行业,成为了现代信息社会不可或缺的基础设备。
| 代 | 时间 | 硬件技术 | 速度(次/秒) |
|---|---|---|---|
| 一 | 1946~1957 | 电子管 | 40, 000 |
| 二 | 1958~1964 | 晶体管 | 200, 000 |
| 三 | 1965~1971 | 中、小规模集成电路 | 1, 000, 000 |
| 四 | 1972~1977 | 大规模集成电路 | 10, 000, 000 |
| 五 | 1978~现在 | 超大规模集成电路 | 100, 000, 000 |
摩尔定律
集成电路出现之后,芯片集成度不断提高,从在一个芯片上集成成百上千个晶体管的中、小规模集成电路,逐渐发展到能集成成千上万个晶体管的大规模集成电路(LSI)和能容纳百万个以上晶体管的超大规模集成电路(VLSI)。
微芯片集成晶体管的数目增长非常迅速,其规律被总结为“微芯片上集成的晶体管数目每3年翻两番”,这就是所谓的“摩尔定律”。
摩尔定律的另一个常见表述是:每平方英寸电路板上的晶体管数量,每18个月翻一倍。
(二) 软件的发展
(1)编程语言的发展
软件的编写离不开编程语言。编程语言的发展经历了机器语言、汇编语言和高级语言三个阶段。我们现在使用的编程语言一般都是高级语言。
(2)操作系统的发展
随着计算机的发展及应用范围的扩大,逐渐形成了软件系统。而其中最重要的一类软件,是为了提高计算机性能和资源利用率而设计的,这就是“操作系统”。
操作系统的发展经历了批处理系统、分时系统、实时系统、PC操作系统、网络操作系统、分布式操作系统等多个阶段。目前我们的个人电脑使用的操作系统主要有:Windows、Mac OS、Linux。
(一) 计算机硬件
冯·诺依曼计算机
冯·诺依曼在研究EDVAC计算机时提出了 “存储程序”的概念,“存储程序”的思想奠定了现代计算机的基本结构,以此概念为基础的各类计算机通称为冯•诺依曼计算机,其特点如下:
采用“存储程序”的工作方式。
计算机硬件系统由运算器、存储器、控制器、输入设备和输出设备5大部件组成。
指令和数据以同等地位存储在存储器中,形式上没有区别,但计算机应能区分它们。
指令和数据均用二进制代码表示。
指令由操作码和地址码组成,操作码指出操作的类型,地址码指出操作数的地址。

计算机的功能部件
存储器:分为主存和辅存,中央处理器可以直接访问的程序和数据存放在主存中。
运算器:完成对信息或数据的处理和运算,如算术和逻辑运算。
控制器:完成对计算机各部件协同运行的指挥控制,即保证指令按预定的次序执行,保障每一条指令按规定的执行步骤正确执行,还要处理各项紧急事件。
输入设备:用来输入原始数据和程序,如键盘、鼠标。
输岀设备:用来输出计算机的处理结果,如显示器和打印机。
一般将运算器和控制器集成到同一个芯片上,称为中央处理器(CPU)。CPU和主存储器共同构成主机,而除主机外的其他硬件装置(外存、I/O设备等)统称为外部设备,简称外设。
(二) 计算机软件
软件的分类
软件按其功能分类,可分为系统软件和应用软件。
三个级别的计算机语言

(1) 机器语言
机器语言由二进制编码组成,它是计算机唯一可以直接识别和执行的语言。
(2) 汇编语言
汇编语言是用英文单词或其缩写代替二进制的指令代码,更容易为人们记忆和理解。汇编语言程序必须经过汇编操作,转换为机器语言后,才能在计算机硬件上执行。
(3) 高级语言
高级语言(如C、C++、Java等)程序需要先经过编译程序编译成汇编语言程序,再经过汇编操作 成为机器语言程序。高级语言程序也可直接通过解释的方式“翻译”成机器语言程序。
由于计算机无法直接理解和执行高级语言程序,因此需要将高级语言程序转换为机器语言程序,通常把进行这种转换的软件系统称为翻译程序。翻译程序有以下三类:
汇编程序(汇编器):将汇编语言程序翻译成机器语言程序。
解释程序(解释器):将高级语言源程序中的语句按执行顺序逐条翻译成机器指令并立即执行。
编译程序(编译器):将高级语言源程序翻译成汇编语言程序或机器语言程序。
典型的冯·诺依曼计算机是以运算器为中心的,如下图所示。其中,输入、输出设备与存储器之间的数据传送都需通过运算器。图中实线为数据线,虚线为控制线和反馈线。

现代的计算机已转化为以存储器为中心,如下图所示。图中实线为控制线,虚线为反馈线,双线为数据线。

图中各部件的功能是:
运算器用来完成算术运算和逻辑运算,并将运算的中间结果暂存在运算器内;
存储器用来存放数据和程序;
控制器用来控制、指挥程序和数据的输入、运行以及处理运算结果;
输入设备用来将人们熟悉的信息形式转换为机器能识别的信息形式,常见的有键盘、鼠标等。
输出设备可将机器运算结果转换为人们熟悉的信息形式,如打印机输出、显示器输出等。
由于运算器和控制器在逻辑关系和电路结构上联系十分紧密,尤其在大规模集成电路制作工艺出现后,这两大部件往往制作在同一芯片上,因此,通常将它们合起来统称为中央处理器(Central Processing Unit) , 简称CPU。把输入设备与输出设备简称为I/O设备(Input/Output equipment) 。
这样, 现代计算机可认为由三大部分组成:CPU、IO设备及主存储器(Main Memory,MM) 。CPU与主存储器合起来又可称为主机, I/O设备叫作外部设备。

主存储器是存储器子系统中的一类,用来存放程序和数据, 它可以直接与CPU交换信息。另一类叫辅助存储器, 简称辅存, 又叫外存。
ALU(Arithmetic Logic Unit) 算术逻辑运算单元,用来完成算术逻辑运算。
CU(Control Unit) 控制单元, 用来解释存储器中的指令, 并发出各种操作命令来执行指令。
ALU和CU是CPU的核心部件。I/O设备也受CU控制,用来完成相应的输入、输出操作。可见,计算机有条不紊地自动工作,都是在控制器统一指挥下完成的。
硬件实现的往往是最基本的算术和逻辑运算功能,而其他功能大多通过软件的扩充得以实现。对某 一功能来说,既可以由硬件实现,也可以由软件实现。从用户的角度来看,它们在功能上是等价的。这 一等价性被称为软、硬件逻辑功能的等价性。
由于“软件”的发展,它不仅可以充分发挥计算机的“硬件”功能,提高计算机的工作效率,而且已经发展到能局部模拟人类的思维活动,因此在整个计算机系统内,“软件”的地位和作用已经成为评价计算机系统性能好坏的重要标志。当然,“软件”性能的发挥,也必须依托“硬件”的支撑。因此,概括而言,计算机性能的好坏,取决于 “软”、 “硬”件功能的总和。
从用户的角度看,人们在操作系统提供的运行环境下,首先用高级语言编写程序(称为源程序),然后将源程序翻译成汇编语言程序,再将其翻译成机器能识别的机器语言程序(称为目标程序),最后用微程序解释每条机器指令。这样,就构成一个常见的计算机系统的5级层次结构,如下图所示:

从计算机系统的5级层次结构来看,可以将硬件研究的对象归结为微程序机器 M0 与传统机器 M1,也就是实际机器。而软件研究的对象主要是操作系统及其以上的各级虚拟机器。通常将除硬件系统外的其余层级称为虚拟机器,包括操作系统机器 M2、汇编语言机器 M3 和高级语言机器 M4。简单来说,虚拟机器就是由软件实现的机器。
相邻层级之间的关系,下层是上层的基础,上层是下层的扩展。随着超大规模集成电路技术的不断发展,部分软件功能可以由硬件来实现,所以软/硬件交界面的划分也不是绝对的。
用高级语言编写好一段程序之后,需要经过一系列“翻译“过程,才能得到计算机能够执行的机器代码。比如,我们用C语言写了一个简单的 hello world 程序,源程序文件命名为 hello.c,用GCC编译器可以将它翻译成一个可执行目标程序 hello。具体的过程可以分为4个阶段,如下图所示:

(1)预处理阶段:预处理器(cpp)对源程序中以 ”#“ 开头的命令进行处理,输出结果是一个以 ”.i“ 为扩展名的文件 hello.i。例如 ”#include“ 就会将后面的头文件内容插入程序文件中。
(2)编译阶段:编译器(ccl)对预处理后的源程序进行编译,生成一个汇编语言源程序 hello.s。汇编语言源程序中的每条语句,都用文本格式描述了一条机器语言指令。
(3)汇编阶段:汇编器(as)将 hello.s 翻译成机器语言指令,把这些指令打包成一个”可重定位目标程序“ hello.o,它是一个二进制文件,用文本编辑器打开会显示乱码。
(4)链接阶段:链接器(ld)将多个可重定位目标程序和标准库函数合并成一个可执行目标程序。上面的例子中,链接器将 hello.o 和标准库函数 printf 所在的可重定位目标模块 printf.o 合并,生成可执行程序 hello。最终生成的可执行程序被保存在磁盘上。
“存储程序”的基本思想,就是将程序和数据一样,存放在主存中;运行时通过地址访问到程序的内容,解析出对应的指令进行执行。

程序执行前,先将第一条指令的地址存放在程序计数器(PC)中;
将PC的内容作为地址访问主存,取出指令;
在每条指令执行过程中,都需要计算下一条将执行指令的地址,并送至PC。如果当前指令是顺序执行的,则下一条指令地址是PC的内容加上当前指令的长度;如果是跳转指令,则下一条指令的地址是指定的目标地址;
当前指令执行完毕后,再根据PC的值作为地址访问主存,取出的是下一条将要执行的指令。
这样,计算机就可以周而复始地自动执行程序中的每一条指令了。
为了更清楚地了解计算机的工作过程,我们需要将计算机的组成部件进一步细化,如下图所示。

除了之前已经列出的核心部件 ALU、CU 以及主存的存储器M,CPU和主存储器中还必须配置一些寄存器(Register),用来存放特定的信息。下面我们分别进行简单介绍。
(1)主存储器
主存储器(简称主存或内存)包括了存储体M、各种逻辑部件以及控制电路等。存储体由许多存储单元组成,每个存储单元又包含若干个存储元件;每个存储元件能存放一位二进制代码(0或者1)。这样,每个存储单元可以存储一串二进制代码,这就被称为一个”存储字“;存储字的二进制位数称为”存储字长“。
主存中的每个存储单元有一个唯一的编号,叫做存储单元的”地址“(Address)。主存的工作方式就是按照存储单元的地址,来实现对存储字各位的存(写入)取(读出)。这种存取方式叫做”按地址访问存储器“。
为了实现按地址访问的方式,主存中还必须配置两个寄存器:MAR 和 MDR。
MAR(Memory Address Register,存储器地址寄存器):用来存放想要访问的存储单元的地址,它的位数决定了能访问的存储单元的最大个数。
MDR(Memory Data Register,存储器数据寄存器):用来存放从存储体单元中取出,或者准备向存储体单元存入的数据,它的位数和存储字相等。
当然,如果想要完整地实现一个存取数据的操作,还需要 CPU 给主存加上各种控制信号,比如读命令、写命令以及地址译码驱动信号等。随着硬件技术的发展,主存现在都是统一制作的大规模集成电路芯片,所以一般都将MAR 和 MDR 集成在 CPU 芯片中。
(2)运算器
运算器包括了一个算术逻辑单元(ALU)和最少三个寄存器。
ACC: Accumulator,累加器;
MQ: Multiplier-Quotient Register,乘商寄存器;
X: 操作数寄存器
这三个寄存器在完成不同的算术运算时,所存放的操作数也各不相同。具体的情况如下表所示:
| 加法 | 减法 | 乘法 | 除法 | |
|---|---|---|---|---|
| ACC | 被加数及和 | 被减数及差 | 乘积高位 | 被除数及余数 |
| MQ | —— | —— | 乘数及乘积低位 | 商 |
| X | 加数 | 减数 | 被乘数 | 除数 |
不同机器的运算器结构也有所不同,有的机器用 MDR 取代 X 寄存器。
(3)控制器
控制器由控制单元(CU)和程序计数器(PC)、指令寄存器(IR)组成。
PC: Program Counter,程序计数器,用来存放当前将要执行指令的地址。它与主存的 MAR 之间之间有一条直接通路,且具有自动加1的功能,也就是可以自动形成下一条指令的地址。
IR: Instruction Register,指令寄存器,用来存放当前的指令。IR 的内容来自主存的 MDR,包含了操作码和地址码。IR 中的操作码 OP(IR)会送至 CU,可以记作 OP(IR) → CU,用来分析指令;而地址码 Ad(IR)作为操作数的地址送至存储器的 MAR,可以记作 Ad(IR) → MAR。
CU 是控制器的核心组件,用来分析当前指令所需完成的操作,并发出各种微操作命令序列,从而控制所有被控对象。控制器是计算机的神经中枢,由它指挥各部件自动协调地工作;完成一条指令操作,需要取指、分析和执行3个阶段。
(4)I/O
I/O 系统包括各种 I/O 设备及其相应的接口。每一种 I/O 设备都由 I/O 接口与主机联系,它接收 CU 发出的各种控制命令,并完成相应的操作。
总结一下,当计算机接收到机器语言程序后,硬件的工作过程分为以下几步:
把程序和数据装入主存储器;
从程序的起始地址运行程序;
按照程序的首地址从存储器中取出第一条指令,经过译码等步骤控制计算机各功能部件协同运行,完成这条指令的功能,并计算下一条指令的地址;
用新得到的指令地址继续读出第二条指令并执行,直到程序结束为止。每条指令都是在取指、译码和执行的循环过程中完成的。
我们现在以从主存中取数据的指令为例,详细分析一下它的执行过程:
(1)取指令: PC → MAR → M → MDR → IR
根据 PC 取指令到 IR。将 PC 的内容送至 MAR,将 MAR 的内容送至地址线,同时控制器将读信号送至读/写信号线,从主存指定存储单元读出指令,并通过数据线送至 MDR,再传送至 IR。
(2)分析指令: OP(IR) → CU
指令译码并送出控制信号。控制器根据 IR 中指令的操作码,生成相应的控制信号,送到不同的执行部件。这里 IR 中是取数指令,所以读控制信号被送到总线的控制线上。
(3)执行指令:Ad(IR) → MAR → M → MDR → ACC
取数操作。将 IR 中指令的地址码送至 MAR,将 MAR 的内容送至地址线,同时控制器将读信号送至读/写信号线,从主存指定存储单元读出操作数,并通过数据线送至 MDR,再传送到 ACC 中。
(4)每取完一条指令,还必须计算下一条指令的地址,为取下一条指令做准备:(PC)+ 1 → PC
衡量一台计算机的性能是由多项技术指标综合确定的,既包含硬件的各类性能,又包括软件的各种功能,这里主要讨论硬件的技术指标。
机器字长
机器字长,是指 CPU 一次能处理数据的位数,也就是 CPU 内部用于整数运算的数据通路的宽度。字长通常就等于 CPU 的通用寄存器宽度,也就是 CPU 内用于整数运算的运算器位数,它反映了计算机处理信息的能力。我们平常所说”一台16位或32位的机器“,这里的16、32就是指字长。
字长越长,数的表示范围也越大,精度也越高。机器的字长也会影响机器的运算速度。倘若CPU字长较短, 又要运算位数较多的数据, 那么需要经过两次或多次的运算才能完成,这样势必影响整机的运行速度。当然,机器字长对硬件的造价也有较大的影响。它将直接影响加法器(或ALU) 、数据总线以及存储字长的位数。
指令字长:一条指令中包含的二进制代码的位数。
存储字长:一个存储单元中存储的二进制代码的长度。
指令字长和存储字长,都必须是字节(Byte)的整数倍。指令字长一般取存储字长的整数倍:如果指令字长等于存储字长的2倍,那么取一条指令就需要2个机器周期;如果指令字长等于存储字长,那么取指周期就等于机器周期。
数据通路带宽
数据通路带宽,是指数据总线一次所能并行传送信息的位数,它关系到数据的传送能力。这里所说的数据通路带宽是指外部数据总线的宽度,它与 CPU 内部的数据总线宽度(机器字长)可能不同。
存储容量
存储器的容量,包括主存容量和辅存容量。我们一般主要关心主存容量。
主存容量是指主存中所能存储信息(二进制代码)的最大容量,通常以字节数来衡量。
存储容量 = 存储单元个数 × 存储字长
在主存储器中,MAR 的位数反映了存储单元的个数, MDR 的位数则反映了存储字的长度。例如, MAR 为16位, 表示 216 = 65536, 也就是说对应的存储体内有65536个存储单元(一般称为64K内存, 1K=1024);而如果 MDR 为32位, 那么主存的存储容量为 216 × 32 = 221 = 2M 位(1M=220)。
现代计算机中常以字节的个数来描述容量的大小,一个字节(Byte)被定义位8位二进制代码。所以上述存储容量是 2M 位(bit),也可用 218 字节(Byte,简写为 B)表示,记作 218 B 或 256KB。
同理,辅存容量也可用字节数来表示,例如,某机辅存(如硬盘)容量为 128 GB(1G = 1KM = 230 )。
运算速度
计算机的运算速度与许多因素有关,如机器的主频、CPU 的结构、执行什么样的操作、主存本身的速度(主存速度快,取指、取数就快)等等都有关。
吞吐量:系统在单位时间内处理请求的数量,主要取决于主存的存取周期。
响应时间:从用户向计算机发送一个请求,到系统对该请求做出响应并获得所需结果的时间。通常包括 CPU 时间(计算机执行程序的时间)和等待时间(用于磁盘访问、存储器访问、I/O操作等的时间)。
主频(CPU 时钟频率):机器内部主时钟的频率,它是衡量机器速度的重要参数。对统一型号的计算机,主频越高,完成指令的一个步骤所用的时间越短,执行指令的速度越快。通常以赫兹(Hz)为单位。
CPU 时钟周期:节拍脉冲的宽度或周期,也就是主频的倒数,它是 CPU 中最小的时间单位。
CPU 时钟周期 = 1 / 主频
CPI:Clock cycle Per Instruction,执行一条指令所需的时钟周期数。对一个程序或一台机器来说,CPI 指的是该程序或该机器指令集中所有指令执行所需要的平均时钟周期数。
CPU 执行时间:运行一个程序所花费的时间。
CPU 执行时间 = (指令数 × CPI)/ 主频
对于同一个程序,CPU 的执行时间就代表了 CPU 的性能,它主要取决于三个要素:主频、CPI 和 指令数。这三者是相互制约的。不同的机器可以有不同的指令集,更改指令集可以让程序的指令数更少,但 CPI 可能就会增大;同时可能引起 CPU 结构的调整,从而造成主频的降低。
现在机器的运算速度,普遍采用单位时间内执行指令的平均条数来衡量,并用 MIPS(Million Instruction Per Second)作为计量单位, 即每秒执行百万条指令。比如,某机每秒能执行200万条指令, 则记作2 MIPS。
MIPS:Million Instructions Per Second,每秒执行百万条指令的数目。
MIPS = 主频 /(CPI × 106)
FLOPS:FLoating-point Operations Per Second,每秒执行浮点运算的次数。
MFLOPS:百万次浮点运算每秒。 MFLOPS = 浮点操作次数 /(执行时间 * 106)
GFLOPS:十亿次浮点运算每秒。 GFLOPS = 浮点操作次数 /(执行时间 * 109)
TFLOPS:万亿次浮点运算每秒。 TFLOPS = 浮点操作次数 /(执行时间 * 1012)
PFLOPS:千万亿次浮点运算每秒。 PFLOPS = 浮点操作次数 /(执行时间 * 1015)
EFLOPS:百京次浮点运算每秒。 EFLOPS = 浮点操作次数 /(执行时间 * 1018)
ZFLOPS:十万京次浮点运算每秒。 ZFLOPS = 浮点操作次数 /(执行时间 * 1021)
需要注意,在计算机中,描述存储容量、文件大小时,K、M、G、T 等数量单位通常用2的幂次表示,比如 1 KB = 210 B;而在描述速率、频率等概念时,通常用10的幂次表示,比如 1 kb/s = 103 b/s。
【2009真题】冯 · 诺依曼计算机中指令和数据均以二进制形式存放在存储器中,CPU 区分它们的依据是 ( )。
A. 指令操作码的译码结果 B. 指令和数据的寻址方式
C. 指令周期的不同阶段 D. 指令和数据所在的数据单元
答案: C
【2015真题】计算机硬件能够直接执行的是 ( )。
I. 机器语言程序 II. 汇编语言程序 III. 硬件描述语言程序
A. 仅 I B. 仅 I、II C. 仅 I、III D. I、II、III
答案: A
【2016真题】将高级语言源程序转换为机器级目标代码文件的程序是 ( )。
A. 汇编程序 B. 链接程序 C. 编译程序 D. 解释程序
答案: C
【2019真题】下列关于冯 · 诺依曼结构计算机基本思想的叙述中,错误的是 ( )。
A. 程序的功能都通过中央处理器执行指令实现 B. 指令和数据都用二进制表示,形式上无差别
C. 指令按地址访问,数据都在指令中直接给出 D. 程序执行前,指令和数据需预先存放在存储器中
答案: C
【2020真题】下列给出的部件中,其位数(宽度)一定与机器字长相同的是 ( )。
I. ALU II. 指令寄存器 III. 通用寄存器 IV. 浮点寄存器
A. 仅 I、II B. 仅 I、III C. 仅 II、III D. 仅 II、III、IV
答案: B
【2010真题】下列选项中,能缩短程序执行时间的是( )。
I. 提高 CPU 时钟频率 II. 优化数据通路结构 III. 对程序进行编译优化
A. 仅 I 和 II B. 仅 I 和 III C. 仅 II 和 III D. I、II、III
答案: D
【2011真题】下列选项中,描述浮点数操作速度的是( )。
A. MIPS B. CPI C. IPC D. MFLOPS
答案: D
【2012真题】假定基准程序 A 在某计算机上的运行时间为 100s,其中 90s 为 CPU 时间,其余为 I/O 时间。若 CPU 速度提高 50%,I/O 速度不变,则运行基准程序 A 所耗费的时间是 ( )。
A. 55s B. 60s C. 65s D. 70s
答案: D
【2013真题】某计算机的主频为 1.2 GHz,其指令分为4类,它们在基准程序中所占比例及CPI如下表所示。
| 指令类型 | 所占比例 | CPI |
|---|---|---|
| A | 50% | 2 |
| B | 20% | 3 |
| C | 10% | 4 |
| D | 20% | 5 |
该机的 MIPS 是 ( )。
A. 100 B. 200 C. 400 D. 600
答案: C
【2014真题】程序 P 在机器 M 上的执行时间是 20s,编译优化后,P 执行的指令数减少到原来的70%,而 CPI 增加到原来的1.2倍,则 P 在 M 上的执行时间是 ( )。
A. 8.4s B. 11.7s C. 14s D. 16.8s
答案: D
【2017真题】假定计算机 M1 和 M2 具有相同的指令集体系结构(ISA),主频分别为 1.5GHz 和 1.2 GHz。在 M1 和 M2 上运行某基准程序 P,平均 CPI 分别为 2 和 1,则程序 P 在 M1 和 M2 上运行时间的比值是 ( )。
A. 0.4 B. 0.625 C. 1.6 D. 2.5
答案: C
说明机器字长、指令字长、存储字长的区别和联系。
答案:
机器字长:计算机能直接处理的二进制数据的位数,机器字长一般等于内部寄存器的大小,它决定了计算机的运算精度。
指令字长:一个指令字中包含二进制代码的位数。
存储字长:一个存储单元存储二进制代码的长度。
它们都必须是字节的整数倍。指令字长一般取存储字长的整数倍,如果指令字长等 于存储字长的2倍,就需要2次访存来取出一条指令,因此,取指周期为机器周期的2倍;如果指令字长等于存储字长,则取指周期等于机器周期。早期的计算机存储字长一般和机器的指令字长与数据字长相等,故访问一次主存便可以取出一条指令或一个数据。随着计算机的发展,指令字长可变,数据字长也可变,但它们都必须是字节的整数倍。
用一台 40MHz 的处理器执行基准程序,它所包含的混合指令数和响应所需的时钟周期见下表。求有效的 CPI、MIPS 和程序的执行时间(程序的指令条数为 I)。
| 指令类型 | CPI | 指令混合比 |
|---|---|---|
| 算术和逻辑 | 1 | 60% |
| 转移 | 4 | 12% |
| 高速缓存命中的访存 | 2 | 18% |
| 高速缓存失效的访存 | 8 | 10% |
答案:
CPI 是执行一条指令所需的平均时钟周期数。本程序中包含4种指令,根据它们不同的占比,CPI 就是这4种指令的数学期望:
CPI = 1 × 60% + 4 × 12% + 2 × 18% + 8 × 10% = 0.6 + 0.48 + 0.36 +0.8 = 2.24
MIPS 是每秒执行的百万条指令数。已知时钟频率为 40MHz,也就是每秒有 40M 个时钟周期,所以:
MIPS = 40 × 106 /(2.24 × 106 )≈ 17.9
程序的执行时间 T = 平均每条指令执行时间 × 指令条数,而平均每条指令执行的时间,就是 CPI × 时钟周期:
T = CPI × 时钟周期 × 指令条数 = 2.24 ×(1 / 40MHz)× I = 5.6 × 10-8 × I 秒
(一)数制与编码
进位计数制及其数据之间的相互转换
定点数的表示和运算
(二)运算方法和运算电路
基本运算部件:加法器、算数逻辑部件(ALU)
加/减运算:补码加/减运算器,标志位的生成
乘/除运算:乘/除运算的基本原理,乘法电路和除法电路的基本结构
(三)整数的表示和运算
无符号整数的表示和运算
有符号整数的表示和运算
(四)浮点数的表示和运算
浮点数的表示:IEEE 754标准
浮点数的加/减运算
本章内容是考研考察的一个重点和难点,往往会有综合应用题出现。
需要重点掌握的内容包括:
真值、机器数,定点数的表示及原理
C 语言中的整型数据,有符号数与无符号数、不同字长整数之间的类型转换
ALU 的基本组成,标志位的产生,定点数的运算及相关电路,溢出概念与判断方法
IEEE 754标准浮点数的表示和特点,浮点数的加/减运算方法
C 语言中的浮点型数据,浮点型与整型、浮点型之间的类型转换,隐式类型转换
数据按边界对齐方式的存储,数据按大端和小端方式存储
| 考点 | 考查次数 | |
|---|---|---|
| 单项选择题 | 综合应用题 | |
| 定点数的表示与运算 | 10 | 8 |
| IEEE 754标准浮点数,浮点数的运算 | 10 | 3 |
| C语言中各种数据的转换 | 3 | 2 |
| 数据按边界对齐方式的存储,数据按大小端方式存储 | 4 | 0 |
计算机的应用领域极其广泛,但不论其应用在什么地方,信息在机器内部的形式都是一致的,采用的是二进制的表达,即均为0和1组成的各种编码。
(一)进位计数制
进位计数制简称“进制”,是人为定义的一种带进位的计数方法,可以用有限的数字符号表示所有的数。定义好的数字符号的个数,称为基数;当计数超出基数个数时,就需要向前进位。基数为n的进位计数制,就被称为“n进制”,特点是“逢n进一”。
我们日常生活中最常见的是十进制,使用0~9十个阿拉伯数字,逢十进一;而计算机系统底层的信息,使用的是二进制,也就是只有0和1两个数字,逢二进一。在计算机系统中,也经常使用八进制和十六进制来表示数据。下表是十进制数、二进制数、十六进制数对照表。
书写时,可在十六进制数后面加上“H”,如17DBH 或(17DB)16;八进制数后面加上“O”,如372O或(372)8;若在数的后面加上“B”,如10101100B,即表示此数为二进制数,或写成(10101100)2。
十进制数、二进制数、八进制数、十六进制数对照表
| 十进制数 | 二进制数 | 八进制数 | 十六进制数 | 十进制数 | 二进制数 | 八进制数 | 十六进制数 |
|---|---|---|---|---|---|---|---|
| 0 | 00000 | 0 | 0 | 16 | 10000 | 20 | 10 |
| 1 | 00001 | 1 | 1 | 17 | 10001 | 21 | 11 |
| 2 | 00010 | 2 | 2 | 18 | 10010 | 22 | 12 |
| 3 | 00011 | 3 | 3 | 19 | 10011 | 23 | 13 |
| 4 | 00100 | 4 | 4 | 20 | 10100 | 24 | 14 |
| 5 | 00101 | 5 | 5 | 21 | 10101 | 25 | 15 |
| 6 | 00110 | 6 | 6 | 22 | 10110 | 26 | 16 |
| 7 | 00111 | 7 | 7 | 23 | 10111 | 27 | 17 |
| 8 | 01000 | 10 | 8 | 24 | 11000 | 30 | 18 |
| 9 | 01001 | 11 | 9 | 25 | 11001 | 31 | 19 |
| 10 | 01010 | 12 | A | 26 | 11010 | 32 | 1A |
| 11 | 01011 | 13 | B | 27 | 11011 | 33 | 1B |
| 12 | 01100 | 14 | C | 28 | 11100 | 34 | 1C |
| 13 | 01101 | 15 | D | 29 | 11101 | 35 | 1D |
| 14 | 01110 | 16 | E | 30 | 11110 | 36 | 1E |
| 15 | 01111 | 17 | F | 31 | 11111 | 37 | 1F |
计算机系统为什么要采用二进制?
使用有两个稳定状态的物理器件就可以表示二进制数的每一位,制造成本比较低。
二进制的1和0正好与逻辑值“真”和“假”对应,为计算机实现逻辑运算提供了便利。
二进制的编码和运算规则都很简单,通过逻辑门电路能方便地实现算术运算。
(二)不同进制数的相互转换
任意一个数 N,可以用 r 进制表示成下面的形式:
N =(dn-1dn-2 … d1d0.d-1d-2 … d-m)
= dn-1rn-1 + dn-2rn-2 + … + d1r1 + d0r0 + d-1r-1 + d-2r-2 + … + d-mr-m
= ∑ diri
其中,r 为基数;d 为系数,di 代表第 i 位上的数,可以是 0 ~ (r-1) 中的任意一个数字;ri 叫做第 i 位上的权值。n、m 分别代表 N 的整数部分和小数部分的位数。
(1)二进制和八进制、十六进制间的转换
二进制数数位较多,书写不方便,在计算机系统中一般需要进行“缩写”。由于 23=8, 24=16,从而3位二进制数就对应着一个8进制数、4位二进制数对应着一个16进制数;对于一个小数而言,以小数点为界,整数部分从小数点左侧第一位起向左数,小数部分从小数点右侧第一位起向右数,不够就补0。这样二进制数和八进制数、十六进制数就可以非常方便地互相转换了。
例如,将二进制数1110011101.0010111转换为八进制数为:
左侧补0 分界点 右侧补0
↓ ↓ ↓
001 110 011 101 . 001 011 100
1 6 3 5 . 1 3 4
所以 (1110011101.0010111)2 = (1635.134)8 ;
同样道理,转换为十六进制数为:
0011 1001 1101 . 0010 1110
3 9 D . 2 E
所以 (1110011101.0010111)2 = (39D.2E)16 ;
二进制转换为八进制:每数三位就转换成对应的八进制数,位数不够则补0。
二进制转换为十六进制:每数四位就转换成对应的十六进制数,位数不够则补0。
八进制转换为二进制:每位都转换成对应的3位二进制数。
十六进制转换为二进制:每位都转换成对应的4位二进制数。
(2)任意进制数转换为十进制数
任意进制数的各位数码与它的权值相乘,再把乘积相加,即得到相应的十进制数。这种转换方式称为 按权展开法。
例如,将二进制数 11011.101 转换为十进制数为:
(11011.101)2 = 1 × 24 + 1 × 23 + 0 × 22 + 1 × 21 + 1 × 20 + 1 × 2-1 + 0 × 2-2 + 1 × 2-3
= 27.625
另一种方法是“按基数重复相乘/除法”,需要分整数部分和小数部分分别转换。
整数部分从高到低,将每一位乘以基数值、再加上后一位,进行“重复相乘”:
(11011)2 = (((1 × 2 + 1) × 2 + 0 ) × 2 + 1) × 2 + 1 = 27
小数部分从低到高,将每一位除以基数值、再加上前一位,进行“重复相除”:
(0.101)2 = ((1 ÷ 2 + 0) ÷ 2 + 1 ) ÷ 2 + 0 = 0.625
(3)十进制数转换为二进制数
将十进制数转换为二进制数,一般采用 基数乘除法。整数部分和小数部分分别处理,最后将整数部 分与小数部分的转换结果拼接起来。
整数部分的转换规则:除2取余,最先取得的余数为数的最低位,最后取得的余数为数的最高位,商为0时结束。 (即除2取余,先余为低,后余为高)
小数部分的转换规则:乘2取整,最先取得的整数为数的最高位,最后取得的整数为数的最低位,乘积为0或精度满足要求时结束。(即乘2取整,先整为高,后整为低)
例如,将十进制数 123.6875 转换为二进制数。
整数部分:
除2得商 余数
2 |123 … 1 最低位
2 |61 … 1
2 |30 … 0
2 |15 … 1
2 |7 … 1
2 |3 … 1
2 |1 … 1 最高位
2 |0
所以 (123)10 = (1111011)2
小数部分:
乘积取小数 乘2得积 取整数部分
0.6875 × 2 = 1.375 1 最高位
0.375 × 2 = 0.75 0
0.75 × 2 = 1.5 1
0.5 × 2 = 1 1 最低位
所以 (0.6875)10 = (0.1011)2
综合整数和小数部分,得到 (123.6875)10 = (1111011.1011)2
另一种方法是“减权定位法”,利用记忆好的2的幂次的十进制表示,从原始数中依次减去所含最大的2的幂次,就可以快速得到对应的结果。例如,对于十进制数123:
十进制数 位权 转换后的结果
123 26 25 24 23 22 21 20
➖ 64 26 1
59
➖ 32 25 1
27
➖ 16 24 1
11
➖ 8 23 1
3
➖ 2 21 1
1
➖ 1 20 1
0
所以 (123)10 = (1111011)2
这种方法一般在转换很大的十进制数时比较方便。
在计算机中,如果不加特别的定义,用二进制存储的数都是非负数,不需要加正负号,也就是“无符号数”。
对有符号数而言,符号的“正”、“负”机器本身是无法识别的;不过由于“正”、“负”恰好是两种截然不同的状态,我们可以用“0”表示“正”,用“1”表示“负”,这样符号也被数字化了,并且规定将它放在有效数字的前面,即组成了有符号数。
例如,一个有符号的小数:
+ 0.1011 在机器中表示为 0 1 0 1 1
- 0.1011 在机器中表示为 1 1 0 1 1
再比如,一个有符号的整数:
+ 1100 在机器中表示为 0 1 1 0 0
- 1100 在机器中表示为 1 1 1 0 0
把符号“数字化”的数称为机器数,而把带“+”或“-”符号的数称为真值。一旦符号数字化后,符号和数值就形成了一种新的编码。
真值:正、负号加某进制数绝对值的形式,即机器数所代表的实际值。
机器数:一个数值数据的机内编码,即符号和数值都数码化的数。常用的有原码和补码表示法等,这几种表示法都将数据的符号数字化,通常用“0”表示“正”,用“1”表示“负”。
在计算机中,小数点不用专门的器件表示,而是按约定的方式标出。根据小数点位置是否固定,可以分为两种方法表示小数点的存在,即定点表示和浮点表示。
另外需要考虑的问题是:在运算过程中,符号位能否和数值部分一起参加运算?如果参加运算,符号位又需作哪些处理?这些问题都与符号位和数值位所构成的编码有关。
在现代计算机中,通常用定点补码整数表示整数,用定点原码小数表示浮点数的尾数部分,用移码表示浮点数的阶码部分。
小数点固定在某一位置的数为定点数,有以下两种格式。

当小数点位于数符和第一数值位之间时,机器内的数为纯小数;当小数点位于数值位之后时,机器内的数为纯整数。采用定点数的机器称为定点机。数值部分的位数n决定了定点机中数的表示范围。
在定点机中,由于小数点的位置固定不变,故当机器处理的数不是纯小数或纯整数时,必须乘上一个比例因子,否则会产生“溢出”。
当一个编码的全部二进制位均为数值位时,相当于数的绝对值,该编码表示无符号整数。在字长相同的情况下,它能表示的最大数比带符号整数大。例如,8位无符号整数的表示范围为 0 ~ 28-1,也就是能表示的最大数为255;而8位带符号整数的最大数是127。通常,在全部是正数运算且不出现负值结果的情况下,使用无符号整数表示。例如,可用无符号整数进行地址运算,或用它来表示指针。
最高位用来表示符号位,而不再表示数值位。
(1)定点整数
约定小数点在有效数值部分最低位之后。数据 x = x0x1x2…xn (其中 x0 为符号位,x1 ~ xn 是数值的有效部分,也称尾数),在计算机中的表示形式如图所示:

(2)定点小数
约定小数点在有效数值部分最高位之前。数据 x = x0.x1x2...xn(其中 x0 为符号位,x1~xn 是尾数,x1 是最高有效位),在计算中的表示形式如下图所示:
事实上,在计算机中,并没有小数点的表示,只是认为约定了小数点的位置:小数点在最右边的就是定点整数,在最左边的就是定点小数。它们原理相同,只是由于小数点位置不同而可以表示不同范围的数。我们这里重点只考虑定点整数就可以了。
对于有符号的定点数,真实底层的机器数怎样表示,跟选择的编码方式有关。计算机中常用的编码方式有原码、补码、反码和移码。
原码表示法
用机器数的最高位表示数的符号,其余各位表示数的绝对值。纯小数的原码定义如下:

式子中 x 为真值,[ x ]原 表示原码机器数。
类似,纯整数的原码定义如下:

原码的性质:
由符号位与数的绝对值组成,符号位是0为正、1为负
简单直观,与真值的转换简单
0有 ±0 两个编码,即 [+0]原 = 00000 和 [-0]原 = 10000
原码加减运算规则比较复杂,乘除运算规则简单
补码表示法
纯整数的补码定义为:

这里 n 为整数的位数,真值 x 和补码机器数 [ x ]原 互为以 2n+1 为模的补数。如果字长为 n+1,那么补码的表示范围为 -2n ≤ x ≤ 2n - 1,比原码多表示了一个数 -2n。
补码的性质:
补码和其真值的关系:[x]补 = 符号位 × 2n+1 + x
0的编码唯一,因此整数补码比原码多1个数,表示 -2n
符号位参与补码加减运算,统一采用加法操作实现
将 [x]补 的符号位与数值位一起右移并保持原符号位的值不变,可实现除法功能
例如,当 x = + 1010 时(n = 4),
[x]补 = 0, 1010
而当 x = - 1010 时,
[x]补 = 2n+1 + x = 100000 - 1010 = 1, 0110
补码和真值的转换:
真值转为补码:对于正数, 与原码的转换方式一样;对于负数,符号位为1,其余各位由真值“取反加1”得到。
补码转为真值:若符号位为0,真值为正,跟原码的转换一样;若符号位为1,真值为负,其数值部分(绝对值)各位由补码“取反加1”得到。
变形补码是采用双符号位的补码表示法,其定义为

变形补码用于算术运算的ALU部件中,双符号位00表示正,11表示负,10和01表示溢出。
反码表示法
负数的补码可采用“数值位各位取反,末位加1”的方法得到,如果数值位各位取反而末位不加1,那么就是负数的反码表示。正数的反码定义和相应的补码(或原码)表示相同。
反码表示存在以下几个方面的不足:0的表示不唯一(即存在±0);表示范围比补码少一个最小负 数。反码在计算机中很少使用,通常用作数码变换的中间表示形式。
原码、补码、反码三种编码表示总结如下:
三种编码的符号位相同,正数的机器码相同。
原码和反码的表示在数轴上对称,二者都存在 ±0 两个零。
补码的表示在数轴上不对称,0的表示唯一,补码比原码和反码多表示一个数。
负数的反码、补码末位相差1。
原码很容易判断大小。而负数的补码和反码很难直接判断大小,可采用这条规则快速判断:对于一个负数,数值部分越大,它的绝对值就越小,所以真值就越大(更靠近0)。
移码表示法
移码是在真值 x 上加上偏置值 2n 构成的,相当于 x 在数轴上向正方向偏移了若干单位。

移码定义为:

移码的性质:
0的表示唯一, [+0]移 = 2n + 0 = [-0]移 = 2n - 0 = 100...0
符号位“1”表示正,“0”表示负,这与其他机器数正好相反。
一个真值的移码和补码仅差一个符号位,[x]补 的符号位取反即得 [x]移,反之亦然。
移码全0时,对应真值的最小值- 2n ;移码全1时,对应真值的最大值 2n -1。
保持了数据原有的大小顺序,移码大真值就大,便于进行比较操作。
移码常用来表示浮点数的阶码。它只能表示整数。
C 语言中的整型数据简介
C 语言中的整型数据就是定点整数,一般用补码表示。根据位数的不同,可以分为 字符型(char)、短整型(short)、整型(int)、长整型(long)。
C 语言中的整型数据,可以分为 无符号整型 和 有符号整型 两种类型,在定义时只要加上 signed/unsigned 就可以明确指定了。
char 是整型数据中比较特殊的一种,其他如 short/int/long 等都默认是带符号整数,但 char 默认是无符号整数。无符号整数(unsigned short/int/long)的全部二进制位均为数值位,没有符号位,相当于数的绝对值。
signed/unsigned 整型数据都是按补码形式存储的,在不溢出条件下的加减运算也是相同的,只是 signed 型的最高位代表符号位,而在 unsigned 型中表示数值位,而这两者体现在输出上则分别是%d 和%u。
有符号数和无符号数的转换
C 语言允许在不同的数据类型之间做类型转换。C 语言的强制类型转换格式为“TYPE b = (TYPE) a”, 强制类型转换后,返回一个具有TYPE类型的数值,这种操作并不会改变操作数本身。
先看由 short 型转换到 unsigned short 型的情况。考虑如下代码片段:
xxxxxxxxxx31short x = -4321;2
3unsigned short y = (unsigned short)x;执行上述代码后,x = -4321, y = 61215,得到的 y 似乎与原来的 x 没有一点关系。不过将这两个数转化为二进制表示时,我们就会发现其中的规律。
通过本例可知:强制类型转换的结果是保持每位的值不变,仅改变了解释这些位的方式。有符号数转化为等长的无符号数时,符号位解释为数据的一部分,负数转化为无符号数时数值将发生变化。同理,无符号数转化为有符号数时,最高位解释为符号位,也可能发生数值的变化。
不同字长整数之间的转换
另一种常见的运算是在不同字长的整数之间进行数值转换。
先看长字长变量向短字长变量转换的情况。考虑如下代码片段:
xxxxxxxxxx31int x = 165537, u = -34991; //int型占用4字节2
3short y = ( short )x, v = ( short )u; //short型占用2字节执行上述代码后,x= 165537, y=-31071, u =-34991, v = 30545。x、y、u、v 的十六进制表示分别 是0x000286a1 0x86a1 . 0xffff7751、0x7751。由本例可知:长字长整数向短字长整数转换时,系统把多余的高位部分直接截断,低位直接赋值,因此也是一种保持位值的处理方法。
最后来看短字长变量向长字长变量转换的情况。考虑如下代码片段:
xxxxxxxxxx71short x = -4321;2
3int y = (int)x;4
5unsigned short u = (unsigned short)x;6
7unsigned int v = (unsigned int)u;执行上述代码后,x = -4321, y = -4321, u = 61215, v = 61215。x、y、u、v 的十六进制表示分别是0xef1f. 0xffffef1f、0xef1f、0x0000ef1f。所以,短字长整数向长字长整数转换时,仅要使相应的位值相等,还要对高位部分进行扩展。如果原数字是无符号整数,则进行零扩展,扩展后的高位部分用 0填充。否则进行符号扩展,扩展后的高位部分用原数字符号位填充。其实两种方式扩展的高位部分都可理解为原数字的符号位。
从位值与数值的角度看,前3个例子的转换规则都是保证相应的位值相等,而短字长到长字长的转换可以理解为保证数值的相等。
运算器由算术逻辑单元(ALU)、累加器(AC)、状态寄存器(PSW)、通用寄存器组等组成。
算术逻辑单元:完成加、减、乘、除四则运算,与、或、非、异或等逻辑运算。
累加器:暂存参加运算的操作数和结果的部件,为 ALU 执行运算提供一个工作区。
状态寄存器:也称作标志寄存器,用来记录运算结果的状态信息。
通用寄存器组:保存参加运算的操作数和运算结果。
用半导体元器件可以构建出基本的逻辑门电路(与、或、非),能够表示基本的逻辑运算。

通过对与门、或门、非门的组合,可以构建出更加复杂的逻辑电路,进行各种复杂的组合逻辑运算。

逻辑运算中的“与”类似于算术中的乘法,“或”类似于算术中的加法,两者组合在一起时,与运算的优先级要更高。逻辑运算满足以下的规则:

全加器(Full Adder,FA),是用逻辑门电路实现两个二进制数相加并求出和的组合线路,这称为一位全加器。一位全加器可以处理低位进位,并输出本位加法进位。多个一位全加器进行级联可以得到多位全加器。
一位全加器的真值表如下所示,其中 A 为被加数,B 为加数,相邻低位传来的进位数为 Cin,输出本位和为S,向相邻高位输出的进位数为 Cout 。

根据真值表,很容易写出一位全加器的输出表达式:

所以,一位全加器可以利用两个异或门、两个与门和一个或门来实现:

对于 n 位加法器,可以用 n 个全加器(实现两个本位数加上低位进位,生成一个本位和一个向高位的进位)串接起来实现逐位相加,位间进行串行传送,称为 串行进位加法器。


这样,一位全加器的输出表达式可以写为:

在串行进位链中,进位按串行方式传递,高位仅依赖低位进位,因此速度较慢。
为了提高加法器的速度,必须尽量避免进位之间的依赖。引入进位生成函数和进位传递函数,可以使各个进位并行产生,这种以并行进位方式实现的加法器称为 并行进位加法器。
在全加器的表达式中可以看到,进位信号 Ci 由两部分组成:
AiBi 与低位无关,可以称为“本地进位”,记作 di;
(Ai + Bi) Ci-1 与低位进位 Ci-1 有关,可以称为“传递进位”,系数 (Ai + Bi) 称作“传递系数”,记作 ti。
这样进位信号就可以简写为:

以 4 位并行加法器为例,串行进位链的进位表达式就可以写为:
如果我们将 C0 的表达式代入 C1,再依次迭代进 C2、C3 ,那么所有进位信号就只依赖于 C-1 了:

并行进位链又称先行进位、跳跃进位,理想的并行进位链就是 n 位全加器的 n 个进位全部同时产生,但实际实现会有困难。一般会使用分组的方式来进行实现,小组内的进位同时产生,小组之间则采用串行进位,这种方式可以总结为“组内并行、组间串行”。
例如,对于 16 位的并行全加器,我们可以 4 位分为一组,得到并行进位链如下:

当 n 越来越大时,随着分组的增加,组间串行进位的延迟时间也会越来越长。解决策略是可以采用多重分组,让“小组”间的进位也同时产生,而“大组”间采用串行进位。当有两个层级的分组时,称为“双重分组跳跃进位”;与之对应,之前只有一个层级的分组方式,被称为“单重分组跳跃进位”。
针对每一种算术运算,都必须有相对应的基本硬件配置,其核心部件就是加法器和寄存器;而当需要完成逻辑运算时,又必须配置相应的逻辑电路。将这些功能结合起来,就构成了“算术逻辑单元”(ALU)。
ALU 是一种能进行多种算术运算和逻辑运算的组合逻辑电路,ALU的核心是“带标志加法器”,基本结构如下所示。
其中 A 和 B 是两个 n 位操作数输入端;Cin 是进位输入端;ALUop 是操作控制端,用来控制 ALU 所执行的处理操作。例如,ALU~op~ 选择 Add 运算,ALU 就执行加法运算,输出的结果就是 A 加 B 之和。ALU~op~ 的位数决定了操作的种类。例如,当位数为3时,ALU最多只有8种操作。
F 是结果输出端,此外,还输出相应的标志信息(ZF、OF、SF、CF);在ALU进行加法运算时,可以得到最高位的进位 Cout 。
移位运算根据操作对象的不同,可以分为算术移位和逻辑移位。算术移位针对的是有符号数,逻辑移位针对的是机器码,可以看作无符号数。
算术移位的对象是有符号数,有符号数在计算机中采用补码表示。算术移位的特点是,移位后符号位保持不变;空出的位置根据正负和左右移位的情况,决定补 0 还是 1。
对于正数,由于 [ x ]原 = [ x ]补 = 真值,因此移位后的空位均补 0。
对于负数,算术左移时,高位移出,低位补 0;算术右移时,低位移出,高位补 1。
可见,不论是正数还是负数,移位后其符号位均不变。
例如,假设机器字长为 8,[4]补 = 0000 0100,[-4]补 = 1111 1100;
将 4 算术左移一位,就得到了 0000 1000 = [8]补;算术右移一位,就得到了 0000 0010 = [2]补;
将 -4 算术左移一位,就得到了 1111 1000 = [-8]补;算术右移一位,就得到了 1111 1110 = [-2]补;
对于有符号数,左移一位若不产生溢出,相当于乘以2 (与十进制数左移一位相当于乘以10类似); 右移一位,若不考虑因移出而舍去的末位尾数,相当于除以2。
逻辑移位不考虑符号位。
移位规则:逻辑左移时,高位移出,低位补 0;逻辑右移时,低位移岀,高位补 0 。
加减法运算是计算机中最基本的运算,由于减法可以看成是负值的加法,因此计算机中使用补码表示有符号数之后,可以将减法运算和加法运算合并在一起讨论。
补码加减运算的规则简单,易于实现。补码加减运算的公式如下(设机器字长为 n): [A + B]补 = [A]补 + [B]补 (mod 2n) [A - B]补 = [A]补 + [-B]补 (mod 2n)
补码运算的特点如下:
按二进制运算规则运算,逢二进一。
如果做加法,两数的补码直接相加;如果做减法,则将被减数加上减数的机器负数。
符号位与数值位一起参与运算,加、减运算结果的符号位也在运算中直接得出。
最终将运算结果的高位丢弃,保留 n 位,运算结果也是补码。
例如,假设机器字长为 8 (n = 8),那么
[5]补 = 0000 0101,[4]补 = 0000 0100;
[-5]补 = 1111 1011,[-4]补 = 1111 1100;
[5 + 4]补 = 0000 0101 + 0000 0100 = 0000 1001 = [9]补;
[5 - 4]补 = [5 + (-4)]补 = 0000 0101 + 1111 1100 = 1 0000 0001 = [1]补;
[4 - 5]补 = [4 + (-5)]补 = 0000 0100 + 1111 1011 = 1111 1111 = [-1]补;
[-5 - 4]补 = [-5 + (-4)]补 = 1111 1011 + 1111 1100 = 1 1111 0111 = [-9]补;
利用带标志的加法器电路,可实现补码加减运算。

当控制端信号 Sub 为 0 时,做加法,Sub 控制多路选择器将
无符号整数的二进制表示相当于正整数的补码表示,因此,该电路同时也能实现无符号整数的加/减运算。可通过标志信息对运算结果进行不同的解释。
零标志 ZF = 1 表示结果 F 为 0。不管对于无符号整数还是有符号整数运算,ZF 都有意义。
溢出标志 OF = 1 表示有符号数运算时发生溢出。对于无符号数运算,OF 没有意义。
符号标志 SF = 1 表示有符号数运算结果为负;有符号数运算结果为正时 SF = 0。对于无符号数运算,SF 没有意义。
进/借位标志 CF 表示无符号整数运算时的进/借位,判断是否发生溢出。做加法时,CF = 1 表示结果溢出,因此 CF 等于进位输岀 C~out~。做减法时,CF = 1 表示有借位,即不够减,故 CF 等于进位输出 C~out~ 取反。综合可得 CF = Sub ㊉ C~out~。对于有符号数运算,CF 没有意义。
在之前的例子中,如果表示的是无符号数,那么:
[5 + 4]补 = 0000 0101 + 0000 0100 = [5]补 + [4]补 = 0000 1001 = [9]补; (加法不溢出)
[5 - 4]补 = 0000 0101 + 1111 1100 = [5]补 + [252]补 = 1 0000 0001 = [1]补; (加法溢出、减法不溢出)
[4 - 5]补 = 0000 0100 + 1111 1011 = [4]补 + [251]补= 1111 1111 = [255]补; (加法不溢出、减法溢出)
[-5 - 4]补 = 1111 1011 + 1111 1100 = [251]补 + [252]补 = 1 1111 0111 = [247]补; (加法溢出)
溢出 是指运算结果超出了数的表示范围。通常,大于能表示的最大正数称为正上溢,小于能表示的最小负数称为负上溢。仅当两个符号相同的数相加,或两个符号相异的数相减才可能产生溢出。
在之前的例子中,如果假设机器字长为 4(n = 4),能表示的有符号数范围为 -8 ~ 7,那么就有:
[5]补 = 0101,[4]补 = 0100;
[-5]补 = 1011,[-4]补 = 1100;
[5 + 4]补 = 0101 + 0100 = 1001 = [-7]补; (正溢出)
[5 - 4]补 = [5 + (-4)]补 = 0101 + 1100 = 1 0001 = [1]补;
[4 - 5]补 = [4 + (-5)]补 = 0100 + 1011 = 1111 = [-1]补;
[-5 - 4]补 = [-5 + (-4)]补 = 1011 + 1100 = 1 0111 = [7]补; (负溢出)
补码加减运算的溢出判断方法有以下 3 种:
(1)采用一位符号位。
由于减法运算在机器中是用加法器实现的,减法可以看作一个正数和一个负数的加法;因此无论是加法还是减法,只要参加操作的两个数符号相同,结果又与原操作数符号不同,就表示结果溢出。
比如上例中,一正一负相加必然不会溢出;两正数相加得到一个负数(符号位为1),则正溢出;两负数相加得到一个正数,则负溢出。
在实际应用中,为了节省时间,通常可以直接判断符号位产生进位 C~s~ 与最高数位的进位 C~1~ 。如果相同说明没有溢出,否则说明发生溢出。溢出标志 OF = C~s~ ㊉ C~1~。
(2)采用双符号位。
运算结果的两个符号位相同,表示未溢出;运算结果的两个符号位不同,表示溢出,此时最高位就代表真正的符号。也就是说,符号位 S~1~S~2~ = 00 表示结果为正数,无溢出; S~1~S~2~ = 11 表示结果为负数,无溢出。 S~1~S~2~= 01 表示结果正溢出; S~1~S~2~ = 10 表示结果负溢出。溢出标志 OF = S~1~ ㊉ S~2~。
比如上例中,如果采用双符号位,机器字长就应该扩展为 5,那么:
[5]补 = 00 101,[4]补 = 00 100;
[-5]补 = 11 011,[-4]补 = 11 100;
[5 + 4]补 = 00 101 + 00 100 = 01 001 = [1]补; (正溢出)
[5 - 4]补 = [5 + (-4)]补 = 00 101 + 11 100 = 1 00 001 = [1]补;
[4 - 5]补 = [4 + (-5)]补 = 00 100 + 11 011 = 11 111 = [-1]补;
[-5 - 4]补 = [-5 + (-4)]补 = 11 011 + 11 100 = 1 10 111 = [-1]补; (负溢出)
乘除运算的原理难度较大,考查的概率也较低,做基本了解即可。
在计算机中,乘法运算由累加和右移操作实现。根据机器数的不同,可分为原码一位乘法和补码一位乘法。原码一位乘法的规则比补码一位乘法的规则简单。
原码乘法运算的符号位与数值位分开计算。
确定乘积的符号位。由两个乘数的符号进行异或运算得到。
计算乘积的数值位。两个乘数的数值部分之积,可看作两个无符号数的乘积。
原码一位乘法的基本思路,就是类似竖式乘法的做法,让被乘数 x 分别乘以乘数 y 的每一位,然后再做叠加。不过竖式乘法需要做连加运算,这在电路实现上会有一些困难;改进的做法是,借鉴进制转换的“重复相乘/除法”,对每一位进行迭代计算。
回忆一下二进制数转换成十进制数的重复相乘/除法:
整数部分从高到低,将每一位乘以基数值、再加上后一位,进行“重复相乘”:
(11011)2 = (((1 × 2 + 1) × 2 + 0 ) × 2 + 1) × 2 + 1 = 27
小数部分从低到高,将每一位除以基数值、再加上前一位,进行“重复相除”:
(0.101)2 = ((1 ÷ 2 + 0) ÷ 2 + 1 ) ÷ 2 + 0 = 0.625
所以,两数相乘时,就可以把乘数 y 用这种方式按每一位拆开,并乘以 x 、再逐位叠加就可以了。由于每次乘以 2 就相当于左移一位、除以 2 就相当于右移一位,因此只需要反复迭代这样的 移位 和 加法 运算就可以很容易地实现乘法了。
以纯小数为例,已知 [ x ]~原~ = x~0~ . x~1~x~2~...x~n~,[ y ]~原~ = y~0~ . y~1~y~2~...y~n~,那么

原码一位乘法的运算规则如下:
被乘数和乘数均取绝对值|x| 和 |y|参加运算,看作无符号数,符号位为 x~0~ ㊉ y~0~。
乘数的每一位 y~i~ 乘以被乘数 |x| 得到 |x| · y~i~,将该结果与前面所得的结果相加,作为部分积;初始值为 0。
从乘数的最低位 y~n~ 开始判断:若 y~n~ = 1,则部分积加上被乘数 |x|,然后右移一位;若 y~n~ = 0,则部分积加上 0,然后右移一位。
重复上一步骤,判断 n 次。
由于参与运算的是两个数的绝对值,因此运算过程中的右移操作均为逻辑右移。
例如,当 x = 0.1101 = (0.8125)~10~,y = 0.1011 = (0.6875)~10~ 时,计算 x · y。

最终的乘积,高位在“部分积”中,低位在“当前乘数”中,所以得到:
x · y = 0.1101 × 0.1011 = 0.10001111 = (0.55859375)~10~
下面是实现两个 32 位无符号数乘法运算的逻辑结构图。

X、Y 均为 32 位寄存器,开始时分别用来存放被乘数 x 、乘数 y ;P 也是 32 位寄存器,用来存放乘积(部分积),初始值置 0。部分积和被乘数做无符号数加法时,可能产生进位,因此还需要一个专门的进位位 C。 C~n~ 为计数器,初始值为 32,此后每进行一轮运算就减 1。
每一轮运算都由寄存器 Y 的最低位 y~n~ 来控制具体逻辑:如果 y~n~ = 1,那么就让 ALU 对乘积寄存器 P 和被乘数寄存器 X 的内容做“无符号加法”运算,运算结果送回寄存器 P,进位存放在 C 中;如果 y~n~ = 0 则不做相加。无论是否做了加法,每一轮运算都会对进位位 C、乘积寄存器 P 和乘数寄存器 Y 实现同步的“逻辑右移”;此时,进位位 C 移入寄存器 P 的最高位,寄存器 Y 的最低位 y~n~ 被移出。每次从寄存器 Y 移出的最低位 y~n~ 都被送到控制逻辑,以决定被乘数是否被加到部分积上。
最终,当原始乘数所有位全部被移出时,寄存器 P 和 Y 就分别存放了乘积的高 32 位和低 32 位。在 CPU 的运算器中,就分别对应了累加器 ACC 和乘商寄存器 MQ。
原码一位乘法容易理解,规则也比较简单;缺点是符号位不参与计算,需要单独判断。更重要的是,在计算机中为了方便做加减计算,数据一般是用补码来表示的;这就使得如果我们采用原码乘法,需要计算前先把补码转换成原码,计算结束后再把结果的原码转换为补码,增加了额外的运算。
补码一位乘法 是一种带符号数的乘法,采用相加/相减的校正操作,直接计算补码数据的乘积。
补码乘法是直接对补码进行的。对于纯整数,补码表达为:

而类似的,纯小数补码定义为:

所以,当取不同的正负符号时,补码表达会有所不同,继而影响到逐位相乘叠加的效果。
已知 [ x ]~补~ = x~0~ . x~1~x~2~...x~n~,[ y ]~补~ = y~0~ . y~1~y~2~...y~n~,那么需要分不同的情况讨论:
① 被乘数 x 和乘数 y 符号均为正,即 x~0~ = y~0~ = 0 时,
[ x ]~补~ = x,[ y ]~补~ = y,所以就有:

类似原码一位乘法,利用移位和加法的叠加,就可以计算出补码的乘积;这也就是最终计算结果的补码。
② 被乘数 x 为正,乘数 y 为负,即 x~0~ = 0,y~0~ = 1 时,
[ y ]~补~ = 1. y~1~y~2~...y~n~ = 2 + y,所以:

那么两数的乘积就可以写成:

这样一个计算结果,它的补码表示为:

可以看到,当乘数为负时,可以把乘数补码 [ y ]~补~ 直接去掉符号位,当成一个正数与 [ x ]~补~ 相乘;得到的结果再加上 [ -x ]~补~ 进行校正。所以这种方法也叫做“校正法”。
例如,当 x = 0.1101 = (0.8125)~10~,y = 1.1011 = (-0.3125)~10~ 时,计算 x · y。
我们可以直接计算 0.1101 × 0.1011 = 0.10001111,再加上 [ -x ]~补~ = 1. 0011,得到 :
0.10001111 + 1. 0011 = 1.10111111 = ( -0.25390625 )~10~
③ 被乘数 x 为负,乘数 y 为正,即 x~0~ = 1,y~0~ = 0 时,
我们可以交换被乘数和乘数,直接按情况②来处理;也可以仔细分析,发现乘数 y 为正数,可以写成
[ y ]~补~ = 0. y~1~y~2~...y~n~ 的形式,同样可以借鉴情况②中的分析和原码一位乘的方法。当两数的补码相乘时:

观察可以发现,与原码一位乘完全类似,补码相乘也可以将乘数展开,逐位进行相乘、右移和叠加。不过需要注意的是,这时由于被乘数 x 是负数,右移时就需要在左侧高位补 1,也就是做算术右移、而不是逻辑右移。
这样一来,算术右移就实现了对真值 x 的“除以 2”操作,最终叠加之后的结果,就是 x · y 的补码了。
例如,当 x = 1.1 = (-0.5)~10~,y = 0.011 = (0.375)~10~ 时,计算 x · y。

最终的乘积,高位在“部分积”中,低位在“当前乘数”中,所以得到:
x · y = 1.1 × 0.011 = 1.1101 = ( - 0.1875 )~10~
④ 被乘数 x 和乘数 y 符号均为负,即 x~0~ = y~0~ = 1 时,
通过情况②和③的分析可以看出,当乘数 y 为正时,可以直接按照原码一位乘的方式进行补码乘法,注意需要进行算术右移;而当乘数 y 为负时,则可以先不考虑 y 的符号位,同样按照原码一位乘进行补码乘法,最后的结果要再加上 [ -x ]~补~ 进行校正。
例如,当 x = 1.1 = (-0.5)~10~,y = 1.011 = (-0.625)~10~ 时,计算 x · y。
我们可以直接计算 1.1 × 0.011 = 1.1101,再加上 [ -x ]~补~ = 0.1,得到 :
1.1101 + 0.1 = 1 0.0101 = ( 0.3125 )~10~
可以看出,如果使用双符号位来表示正负,会更加方便。
⑤ Booth算法
以上的 4 种情况需要分别讨论,根据乘数的符号来决定是否需要进行校正,这就导致校正法的逻辑控制电路比较复杂。
如果不考虑操作数的符号,直接用统一的规则来处理所有情况,可以采用 比较法。这种方式是 Booth 夫妇首先提出的,所以又叫 Booth 算法。
当被乘数 x 和乘数 y 符号任意时,按照之前讨论的校正法规则,可以写出一个统一的计算公式:

容易推出,对于纯小数,在 mod 2 的前提下,[ -x ]~补~ = - [ x ]~补~,所以可以进一步推导得到:

令 y~n+1~ = 0,那么就可以得到一个通项系数: d~i~ = y~i+1~ - y~i~ ,上式可以进一步化简为:

这样一来,补码乘法的计算方式就跟原码一位乘完全一样了,只是被乘数每次乘的不再是乘数 y 的每一位 y~i~,而是变成了 d~i~ = y~i+1~ - y~i~ 。这样就有 1、-1 和 0 三种情况,每一次计算都由 d~i~ 来决定部分积叠加的是 [ x ]~补~、[ -x ]~补~ 还是 0;然后再做一位算术右移得到新的部分积。最后一步,需要由 d~0~ = y~1~ - y~0~ 决定是否有叠加项,但不再做位移。
Booth 算法的移位规则如下表所示:

Booth 算法的具体运算规则如下:
① 符号位参与运算,运算的数均以补码表示。
② 被乘数一般取 双符号位 参与运算,部分积取 双符号位,初值为 0,乘数取单符号位。
③ 乘数末尾增加一个“附加位” y~n+l~,初始值为 0。
④ 根据(y~i~,y~i+1~)的取值来确定操作,如上表所示。
⑤ 移位按补码右移规则(算术右移)进行。
⑥ 按照上述算法进行 n + 1 步操作,但第 n + 1 步不再移位,仅根据 y~0~ (符号位)与 y~1~ (第一位数值位)的比较结果做相应的叠加运算。所以总共需要进行 n + 1 次累加和 n 次右移。
例如,当 x = 1.1101 = (-0.1875)~10~,y = 1.1011 = (-0.3125)~10~ 时,计算 x · y。
首先得到 [x]~补~ = 11.1101,[-x]~补~ = 00.0011。具体计算步骤如下:
.png)
同样,最终的乘积,高位在“部分积”中,低位在“当前乘数”中,所以得到:
x · y = 1.1101 × 1.1011 = 0.00001111 = (0.05859375)~10~
对于有符号数的乘法运算,可以采用补码一位乘。与原码一位乘类似,我们可以用下面的逻辑结构图来实现补码一位乘法的运算电路:

因为是带符号数运算,所以不需要专门的进位位。由于采用 Booth 算法,还需要一个额外的“附加位” y~n+1~。每轮运算,乘积寄存器 P 和乘数寄存器 Y 实现同步的一位“算术右移”,每次从寄存器 Y 移出的最低位成为新的附加位 y~n+1~,它的前一位则成为 y~n~,它们共同决定叠加项是 [ x ]~补~、[ -x ]~补~ 还是 0。另外由于 ALU 可以对加减法统一操作,所以电路种只需要选择加/减一项 [ x ]~补~ 就可以了。
除法是乘法的逆运算,但不像加减法那样可以直接整合。我们可以先从除法的竖式笔算入手,分析一下除法的具体步骤。
例如,当 x = (-0.1011)~2~ = (-0.6875)~10~,y = (0.1101)~2~ = (0.8125)~10~ 时,计算 x / y。
首先可以看出,商的符号为负,余数的符号为负;其次利用竖式计算绝对值的商:
所以可以得到, x / y = - 0.1101(商)... - 0.00000111(余数) = (- 0.8125 ... - 0.02734375 )~10~
如果完全按照竖式除法的规则,需要心算上商,本质就是由当前被除数减去除数乘以当前权值(第 i 轮就乘以 2-i),够减就上商 1,不够就上商 0 。得到的余数补 0 后再作为下一轮的被除数进行计算。这个过程中,每轮除数要乘以权值 2-i,相当于右移 i 位,得到的余数左侧全部是 0;如果被除数 x、除数 y 和商都是 4 位,就需要 8 位数据来保存余数,这就显得有些麻烦。
更加简单的做法是,每轮相减除数不变,把余数左移;这样效果一样,而电路实现会更加简单。当然,代价就是得到的余数是经过左移之后的;n 轮计算完成之后,需要再右移 n 位,也就是乘以 2-n 才是真正的余数。每轮的相减,也可以转换成负数补码的加法。
所以跟乘法类似,除法运算在计算机中,是转换成逐位的“累加-左移”操作来实现的,可以分为原码除法和补码除法。
原码一位除法和原码一位乘法一样,特点是符号位单独处理。商符由两个操作数的符号位做“异或”形成,减法操作用补码加法实现。
同样以小数为例,已知 [ x ]~原~ = x~0~ . x~1~x~2~...x~n~,[ y ]~原~ = y~0~ . y~1~y~2~...y~n~,那么

其中,0 . x~1~x~2~...x~n~ 就是 x 的绝对值,记作 x* ;0 . y~1~y~2~...y~n~ 是 y 的绝对值,记作 y* 。商符由两数符号位异或得到,商值由两数绝对值相除得到。
小数定点除法对被除数和除数有一定的约束条件:
0 <| 被除数 |≤| 除数 |
实现除法运算时,被除数应不大于除数,并且应该避免被除数和除数为 0。商的位数一般和操作数位数相同。
原码除法中,每轮计算需要用被除数绝对值减去除数绝对值:x* - y*。计算机中减法可以用负数补码的加法实现,所以最终的操作就是:[x*]~补~ + [- y*]~补~ 。如果结果为正,说明够减,上商 1,结果作为余数直接左移,并作为下一轮被除数;如果结果为负,说明不够减,上商 0,这时的结果并不是真实的余数。
根据对余数的处理方式不同,又可以分为 恢复余数法 和 不恢复余数法(加减交替法) 两种。
(1)恢复余数法
恢复余数法的特点是:当余数为负时,需要加上除数的绝对值,将其恢复成原本的余数。
由于每次得到的是商的高位,所以每轮计算可以将余数和商同时左移一位;余数加上 [- y*]~补~ ,判断正负来决定下一位商是 1 还是 0;如果为负,还需要先加上 [y*]~补~ 恢复余数,然后再做左移。
例如,当 x = (-0.1011)~2~ = (-0.6875)~10~,y = (-0.1101)~2~ = (-0.8125)~10~ 时,计算 x / y。
首先看出,商的符号为正,余数的符号为负。并且得到:
x* = 0.1011,y* = 0.1101,[y*]~补~ = 0.1101,[-y*]~补~ = 1.0011
具体计算过程如下:

所以商值为 x* / y* = 0.1101;而余数由于经过了 4 次左移,所以最终还应该做 4 次右移才是真正的余数:0.0111 * 2-4 = 0.00000111,另外还要注意余数符号为负,所以最终结果为:
x / y = 0.1101(商)... - 0.00000111(余数)
由上例也可以发现,如果最终商保留 4 位,那么我们需要做 5 次上商,第一次上商其实是商的整数位;由于我们要求 |被除数| ≤ |除数|,因此正常情况下第一位商总是 0。所以对于小数除法而言,可以用它来做溢出判断:当该位为 1 时,表示当前除法溢出,不能进行;当该位为 0 时,当前除法合法,可以进行。
(2)不恢复余数法(加减交替法)
在恢复余数法中,每当余数为负时都需要恢复余数,这就增加了运算量,操作也不规则,电路实现会比较复杂。加减交替法就克服了这一缺点。
加减交替法 又称 不恢复余数法,是对恢复余数法的一种改进。
通过分析恢复余数法可以发现,如果把第 i 轮计算的余数记作 R~i~,那么:
如果 R~i~ > 0,就上商 1,接下来需要将余数 R~i~ 左移一位,再减去除数绝对值 y*,即 2R~i~ - y*;
如果 R~i~ < 0,就上商 0,接下来先加上 y* 恢复余数,再做左移和减法,即 2 (R~i~ + y*) - y* = 2R~i~ + y* 。
这样一来,就不需要额外恢复余数了,每轮计算的规则完全统一起来,只是左移之后再加/减 y* 就可以了;所以把这种方法叫做“加减交替法”,或者“不恢复余数法”。
还是上面的例子,当 x = (-0.1011)~2~ ,y = (-0.1101)~2~ 时,计算 x / y。
同样的步骤,首先看出,商的符号为正,余数的符号为负。并且得到:
x* = 0.1011,y* = 0.1101,[y*]~补~ = 0.1101,[-y*]~补~ = 1.0011
具体计算过程如下:

所以商值为 x* / y* = 0.1101;而余数由于经过了 4 次左移,所以最终还应该做 4 次右移才是真正的余数:0.0111 * 2-4 = 0.00000111,另外还要注意余数符号为负,所以最终结果为:
x / y = 0.1101(商)... - 0.00000111(余数)
与乘法类似,我们同样可以直接使用补码来实现除法操作。补码除法也可以分为恢复余数法和加减交替法,由于加减交替法是改进版本,用得较多,所以我们只讨论加减交替法。
补码一位除法的特点是符号位与数值位一起参加运算,商符自然形成;所有操作数和结果都用补码表示。
二进制除法运算的核心,是“求余数”,本质上就是被除数和除数绝对值的相减;而补码除法是不区分正负统一进行计算的,因此我们需要讨论在不同的正负情况下,如何实现被除数和除数绝对值的相减操作。

被除数和除数的绝对值相减,得到了余数,接下来就可以根据是否“够减”(即 |x| ≥ |y|)来上商。
需要注意的是,由于商也需要用补码表示,因此当商为正时,够减时上 1,不够减时上 0;而当商为负时需要取反码,够减时上 0,不够减时上 1。补码是原码“取反加1”得到的,所以负商的最后一位还应该再加1,这比较麻烦;如果对精度没有特殊要求,我们一般可以约定商“末位恒置 1”,这种方法操作简单,最大误差为 2-n。
对于新余数 R~i+1~ 的计算,可以借鉴原码除法的加减交替法,根据当前余数 R~i~ 和除数 y 的符号,决定加/减 y 的绝对值,这最终可以用加上 [y]~补~ 或者 [-y]~补~ 来实现。事实上,将上一轮的余数 R~i~ 左移之后就得到了下一轮的被除数,再根据被除数、除数的符号关系就可以得到 R~i+1~ 的表达了。
上面表格中,对商值的判断有点繁琐,可以化简如下:

在补码除法中,商符可以在求商的过程中自动生成。
由于要求 |x| ≤ |y|,因此当做第一轮计算时必须”不够减“。如果 x 和 y 同号,那么它们都为正时 R~i~ 应该为负、都为负时 R~i~ 应该为正,即 R~i~ 和 y 必须异号;此时商值为 0,而商符也恰恰要求是正,说明除法运算是合法的。如果 R~i~ 和 y 同号,则说明够减,商值为1,此时发生了溢出。同样,当 x 和 y 异号时,也可以发现商值为 1 时表示”不够减“,是正常情况,这时商符也要求是负的,完全一致。
所以,补码除法中商的符号也可以用来判断溢出。
由于商符自动生成,我们可以把上表进一步化简:

可以总结补码一位除法的运算规则如下:
符号位参加运算,除数与被除数均用补码表示,商和余数也用补码表示。
如被除数与除数同号,则被除数减去除数;如被除数与除数异号,则被除数加上除数。
余数与除数同号,商上1,余数左移一位再减去除数;余数与除数异号, 商上 0,余数左移一位再加上除数。
重复执行上一步操作,操作 n 次。
如果对商的精度没有特殊要求,一般采用“末位恒置 1”法。
同样的例子,当 x = (-0.1011)~2~ ,y = (-0.1101)~2~ 时,计算 x / y。
首先得到: [x]~补~ = 1.0101,[y]~补~ = 1.0011,[-y]~补~ = 0.1101
具体计算过程如下:

所以商值为
x / y = 0.1101
n 位定点数的除法运算,可以看作用一个 2n 位的被除数 x,去除以一个 n 位的除数 y。这就需要对被除数进行扩展。下图是一个 32 位除法运算的逻辑结构图。

由于被除数 x 是 64 位,所以需要两个 32 位的寄存器 R 和 Q 来存放。初始时,寄存器 R 存放扩展被除数的高位部分,寄存器 Q 存放扩展被除数的低位部分。除数 y 则放在除数寄存器 Y 中。
跟乘法运算电路类似,ALU 也是除法器的核心部件,每轮计算中都要对余数寄存器 R 和除数寄存器 Y 的内容做加/减运算,运算结果送回寄存器 R。开始时,首先取 R 中的数据,也就是 x 的高 32 位,根据符号关系与 y 进行加/减运算,得到的余数写回 R 中;之后每轮计算,寄存器 R 和 Q 实现同步左移,左移时,Q 的最高位移入 R 的最低位,Q 中空出的最低位用来上商。所以 Q 也叫做 余数/商寄存器。每次都会根据余数的符号,交给控制逻辑电路来判断当前商为 1 还是 0,并控制 ALU 的加/减信号。
计算机中处理的数,不一定都是纯小数或者纯整数。对于一个同时有整数部分和小数部分的数,我们可以把它拆开,分别用定点整数和定点小数来表示;这样的缺点在于需要手动进行调整组合,往往要编程来调节小数点的位置。而且有些数据的数值范围相差很大,比如太阳的质量是 1.989 × 1030 kg,而电子的质量是 9.110 × 10-31 kg,如果直接写成 定点整数 + 定点小数 的形式就需要很大的机器字长,造成有效数位的浪费。
更好的方式是,小数点不再固定,使用 ”浮点数“ 来表示。
浮点数 就是小数点的位置可以浮动的数。例如:
365.242 = 3.65242 × 102
= 365242.0 × 10-3
= 0.365242 × 103
这其实就是”科学计数法“的思路。通常,浮点数被表示为下面的形式:
上式中,S 称作 尾数(可正可负),j 称作 阶码(可正可负),r 是 基数。在计算机中基数一般取 2,也可以取 4、8 或 16 等。
例如,一个二进制数 N = 11.0101,那么它可以写成各种不同的浮点形式:
N = 11.0101
= 1.10101 × 21
= 0.110101 × 210
= 11010.1 × 2-11
= 0.00110101 × 2100
...
为了提高数据精度,并且便于比较,在计算机中规定浮点数的尾数用 纯小数形式 表示。所以上面的 0.110101 × 210 和 0.00110101 × 2100 两种形式都可以在计算机中表示 N。不过很明显,后一种形式对有效数位是一种浪费,所以将尾数最高位为 1 的浮点数称为 规格化数。
这样,N 就有了唯一的规格化浮点形式:
N = 0.110101 × 210
浮点数表示为规格化形式,精度是最高的。
在计算机中,浮点数的格式如下图所示。采用这种数据格式的机器称为 浮点机。

浮点数由 阶码 j 和 尾数 S 两部分组成。
阶码是纯整数,阶符和阶码值合起来决定了小数点的实际位置;阶码值的位数 m 再结合阶符,可以反映浮点数的表示范围。
尾数是纯小数,数符 S~f~ 代表了浮点数的正负,而尾数值则是有效数位,位数 n 反映了浮点数的精度。
假设浮点数 N 的阶码 j 数值部分有 m 位,尾数 S 数值部分有 n 位。
阶码是纯整数,尾数是纯小数,它们可以各自选择编码方式。对于非规格化的浮点数,如果阶码和尾数都用原码表达,各自的取值范围如下:

那么阶码 j 和尾数 S 组合之后,能表示的浮点数最大范围就是:

正数最大为
负数最大为
当 S = 0 ,j = - (2m - 1) 时,可以表示 0 值。
在数轴上表示出来,如下图所示:

可以看到,原码是关于原点对称的,所以浮点数的表示范围也是关于原点对称的。
当运算结果大于能表示的最大正数时,称为正上溢;小于最小负数时。称为负上溢:两者统称 上溢。由于尾数的溢出可以通过移位、增加阶码来调整,因此上溢的本质就是 阶码大于最大阶码,这时机器会停止计算,进行中断溢出处理。
当运算结果在 0 至最小正数之间时,称为正下溢;在 0 至最大负数之间时,称为负下溢,统称 下溢。同样道理,下溢的本质是 阶码小于最小阶码,这时溢出的数值绝对值非常小,通常可以将尾数各位直接强置为 0,按 ”机器零“ 来处理,机器可以继续正常运行。
类似地,如果阶码和尾数都用补码表达,各自的取值范围如下:

具体应用当中,阶码和尾数到底应该选择哪种编码方式呢?
很明显,采用补码的优势在于方便做加减运算,但是在表示尾数时,数值范围就无法相对原点对称了。而且对于浮点数来说,尾数是不能直接做加减的,需要先把阶数 ”对齐“ ,所以使用补码好处并不明显;反倒是在做乘除运算时,阶码需要直接做加减操作。
所以我们可以发现,阶码可以用补码表示;而尾数用原码和补码表示都可以,用原码会更好一点。
进一步分析会发现,其实只要尾数 S = 0,无论阶码 j 取什么值都应该表示 0 值。不过浮点数的尾数是受精度限制的,只要阶码没有达到最小值,就还可以通过继续减小阶码、让尾数的数值左移,这样就能使得之前丢弃的低位重新成为有效数位。所以只有当阶码已经是最小值时,尾数 S = 0,才能说明当前的数值已经足够小了,可以认为就是 0;这时的尾数本质上是一个 ”接近 0 的很小的数“,那它的正负就还是有意义的,所以原码的 ± 0 也有了独特的含义。
不过这样一来,”机器零“ 的机器码不全为 0,而是 1 0 0 ... 0;这会对实现计算机的 ”判 0“ 电路造成一些麻烦。所以我们可以考虑把阶码做个调整,让它能够用 ”全 0 “ 的机器码来表示真正的 0 值。考虑到阶码范围 ”关于原点对称“ ,我们要表示的阶码真值范围还是 -2m ~ 2m - 1,所以要让全 0 的机器码对应最小的真值 -2m,只要整体做一个 2m 的偏移就可以了——这就是 “移码” 的编码方式。

用移码来表示阶码还有一个好处,就是方便进行阶数的比较和对齐,简称 “对阶”。这在进行浮点数加减运算时非常重要,尾数只有在阶数相同的时候才能做加减。如果用补码表示阶码,那么可以通过阶码相减来确定哪个阶码大;而采用移码则更加简单,直接比较两个阶码的二进制大小关系就可以了,这在电路上更加容易实现。
阶码采用移码、尾数采用原码编码后,各自的取值范围如下:

一旦确定了浮点数的位数,那么如何分配阶码和尾数的位数,将直接影响到浮点数的表示范围和精度。整体来说,阶码位数越多,说明浮点数的表示范围越大;而尾数位数越多,说明浮点数的精度越高。
由于规格化数的精度最高,所以当一个非零的浮点数不是规格化数时,应该通过左右移动尾数、并同时修改阶码的方法,将它转换为规格化数。把一个非规格化数转换成规格化数的过程,叫做 规格化。
规格化的本质类似于 “科学计数法” 的表达,通过保证尾数最高数位上是一个有效值,尽可能多地保留有效数字的尾数,从而提高精度。
规格化可以分为 “左规” 和 “右规” 两种。以基数 r = 2 为例:
左规:向左规格化。当运算结果尾数的最高数位不是有效位,即出现 0.0...01... 的形式时,需要向左规格化。左规时,尾数左移一位,阶码减 1;
右规:向右规格化。当运算结果尾数的小数点左侧出现有效位,即整数部分不为 0 时,需要向右规格化。右规时,尾数右移一位,阶码加 1;需要右规时,只需进行一次。
当基数不同时,规格化的原则会有相应的改变。比如,当基数 r = 4 时,阶码每次加/减 1,就相当于多乘/除以 4,也就是左/右移 2 位。所以左规就是尾数左移 2 位,阶码减 1;右规是尾数右移 2 位,阶码加 1。尾数的最高 2 位不全为 0 的数,就是规格化数。
用原码表示的尾数 S,经过规格化处理后,绝对值应该满足 2-1 ≤ |S| ≤ 1。它的取值范围如下:

正数为 0. 1×...× 的形式,最大值为 0. 11...1 = 1 - 2-n,最小值为 0. 10...0 = 2-1;
负数为 1. 1×...× 的形式,最大值为 1. 10...0 = -2-1,最小值为 1. 11...1 = - ( 1 - 2-n )。
这样,浮点数规格化后的取值范围被缩小了。在数轴上表示为:

在现代计算机中,浮点数的格式一般采用 IEEE 制定的国际标准。IEEE 754 标准规定的浮点数形式为:

S 为数符,直接表示浮点数的正负,它与尾数所表示的有效数位是分开的。
阶码 E 包含了阶符,用移码来表示,不过这里移码的偏移量不是 2 的整次幂,而是要再减去 1。 假设阶码 E 的位数为 m + 1,那么偏移量就是 2m - 1。
尾数 M 是原码表示的纯小数。
浮点数的位数不同,可以表示不同的数值范围和精度。IEEE 标准中常用的浮点数有三种:短浮点数(单精度)、长浮点数(双精度)和临时浮点数(延伸双精度)。

IEEE 标准默认的基数为 2。以短浮点数(单精度浮点数)为例,总共 32 位:最高 1 位是数符位;后面的 8 位是阶码域(指数部分),偏置值为 28-1 - 1 = 127,即阶码部分的 8 位无符号数值减去 127,就得到了真正的阶码 E;最后的 23 位是尾数域(小数部分)。
IEEE 标准用 规格化数 表示一般的数值,这时尾数 M 的最高有效位一定为 1;于是 IEEE 标准规定这个最高有效位的 1 可以省略,并且将它隐藏放在整数位上,称为 “隐藏位”。这样就可以多出一位有效数位,从而提高了精度。例如,短浮点数的 23 位尾数,可以表示 24 位有效数字;临时浮点数不采用隐藏位的方案。
IEEE 标准使用 非规格化数 表示 0 附近的很小的数。这时阶码 E 所有位全部为 0;尾数 M 不为 0,且没有隐藏位,或者说隐藏位为 0。
除此之外,IEEE 标准还规定了几种特殊情况:
当阶码 E 所有位全为 0,并且尾数 M 也为 0 时,表示 0 值(浮点数 0)。根据数符不同可以分别表示 ± 0。
当阶码 E 所有位全为 1,并且尾数 M 为 0 时,表示 无穷大。根据数符不同可以分别表示 ±∞。
当阶码 E 所有位全为 1,并且尾数 M 不为 0 时,表示这不是一个数(NaN)。
以 32 位的单精度浮点数为例,所有的机器码和对应的取值范围如下:

例如,对于十进制数 178.125,把它写成 IEEE 标准的短浮点数。
我们需要分整数部分和小数部分,首先转换成二进制数的表示;然后写成类似 “科学计数法” 的二进制浮点数表达。
_数的二进制浮点表达.png)
这是一个正数,符号位为 0;然后从二进制浮点表达中得到阶码和尾数。将 8 位二进制阶码加上偏移量 127,尾数隐藏整数位的 1 后补成 23 位,就是最终符合 IEEE 标准的 32 位短浮点数。
_IEEE标准短浮点数.png)
最后,我们也可以总结一下计算机中数据的定点、浮点表示的区别:
数值的表示范围不同。对于相同的字长,浮点表示法所能表示的数值范围远大于定点表示法。
数值精度不同。对于相同的字长,浮点数虽然扩大了数的表示范围,但精度降低了。
数据的运算不同。浮点数包括阶码和尾数两部分,运算时不仅要做尾数的运算,还要做阶码的运算;而且运算结果要做规格化,所以浮点数运算比定点数运算复杂。
溢出问题。在定点数运算中,当运算结果超出数的表示范围时,发生溢出;在浮点数运算中, 当运算结果超出尾数表示范围时,不一定发生溢出;只有规格化后,阶码超出所能表示的范围时,才发生溢出。
在 C 语言中,用 float 类型来表示 IEEE 标准中的 32 位单精度浮点数,用 double 类型来表示 64 位双精度浮点数。long double 类型对应扩展的双精度浮点数,具体的格式会随编译器和处理器架构的不同而改变。
在进行不同类型数据的混合运算时,遵循的原则是 “类型提升”,即由位数较少的类型(比如 char、short)向位数更多的类型(比如 int、long、double)转换。这时数据范围会扩大、精度也变大,因此不会溢出、也不会损失精度,实现了 “无损转换”。类型转换以 char → int → long → double 和 float → double 最为常见,从前到后范围和精度都从小到大、从低到高,转换过程没有损失。
例如,long 类型与 int 类型一起运算,需先将 int 类型转换为 long 类型,然后再进行运算,结果为 long 类型;如果 float 类型和 double 类型一起运算,需要先将 float 转换为 double 类型再进行运算,结果为 double 类型。所有这些转换都是系统自动进行的,这种转换称为 隐式类型转换。C 语言中的隐式转换有算术转换、赋值转换及输出转换。
在 C 语言程序里,数据类型除隐式转换外,也可以显式地进行转换(强制类型转换)。整型之间、浮点类型之间、整型和浮点型之间都可以进行转换。我们需要注意转换过程中数据的取值范围是否发生了变化(是否溢出)、精度是否发生了缺失。精度的缺失主要针对浮点数而言。
具体来说:
从 int 或者 float 转换为 double 时,因为 double 的表示范围更大、有效位数更多,因此可以无损转换;
从 double 转换为 float 时,因为 float 表示的范围更小,所以可能发生溢出;另外有效数位也减少了,所以可能发生舍入,从而丢失精度;
从 float 或 double 转换为 int 时,因为 int 的表示范围更小,所以可能发生溢出;另外 int 没有小数部分,所以数据会仅保留整数部分而丢失精度;
从 int 转换为 float 时,表示的范围扩大了,不会发生溢出;但 int 有 32 位,而 float 最多只有 24 位有效数位,所以可能发生数据的舍入,从而丢失精度。
浮点数运算的特点,是阶码运算和尾数运算分开进行。浮点数加/减运算可以分为 5 步进行:
(1)对阶
对阶的目的是使两个操作数的小数点位置对齐,使两个数的阶码相等。先求阶差,然后以 “小阶向大阶看齐” 的原则,将阶码小的尾数右移一位(基数为2),阶码加1,直到两个数的阶码相等为止。
(2)尾数求和
将对阶后的尾数,按定点数加/减运算规则运算。
(3)规格化
IEEE 754 规格化尾数的形式为 ±1.×...×,所以当计算结果为非规格化数时,需要进行规格化处理。
左规:当结果为 ±0.0...01x...x 时,需进行左规。尾数每左移一位,阶码减 1。可能需要左规多次,直到将第一位 1移到小数点左边。
右规:当结果为 ±1x.x...x 时,出现了尾数的溢出,需进行右规。尾数右移一位,阶码加 1。当尾数右移时,最高位 1 被移到小数点前一位作为隐藏位;当最后一位移出时,要考虑舍入。
左规一次相当于乘以2,右规一次相当于除以2;需要右规时,只需进行一次。
(4)舍入
在对阶和尾数右规时,尾数右移可能会将低位丢失,影响精度,IEEE 754有以下4种舍入方式:
就近舍入:舍入为最近的那个数,类似于 “四舍五入”,一般被叫做 “ 0 舍 1 入” 法;如果被舍入的值恰好是 100...0 形式,选择舍入为最近的偶数;
正向舍入:向 +∞ 方向舍入,即取右边那个数,也叫 “向上舍入”;
负向舍入:向 -∞ 方向舍入,即取左边那个数,也叫 “向下舍入”;
截断:朝 0 方向舍入,即取绝对值较小的那个数。
(5)溢出判断
浮点数的溢出,并不是以尾数溢岀来判断的;尾数溢出可以通过右规操作得到纠正。运算结果是否溢出,主要看结果的指数是否发生了溢出,因此是由阶码来判断的。
若一个正阶码超出了最大允许值(127 或 1023),则发生上溢,产生异常;
若一个负阶码超出了最小允许值(-149 或 -1074),则发生下溢,通常把结果按机器零处理。
例如,两个数 x = 29/32 × 27,y = 5/8 × 25,用浮点加法计算 x + y。假设浮点数的阶码和尾数均用补码表示,且阶码为 5 位(含 2 位阶符),尾数为 7 位(含 2 位数符)。
首先,将浮点数写成下面的规格化二进制形式:
x = 0.11101 × 2111,y = 0.101 × 2101
具体计算过程如下:
(1)对阶
阶码相减 00, 111 - 00, 101 = 00, 010,说明 x 的阶码比 y 的大 2,需要将 y 的尾数右移两位,阶码加 2:
y = 0.00101 × 2111
(2)尾数求和
尾数相加 00.11101 + 00.00101 = 01.00010
(3)规格化
运算结果的尾数出现溢出,需要进行右规:尾数右移一位,阶码加 1:
1.00010 × 2111 = 0.100010 × 21000,即计算结果为 01, 000; 00, 10001
(4)舍入
结果的尾数用补码表示为:00 10001,不需要舍入。
(5)溢出判断
结果的阶码用补码表示为:01 000,由于阶符为 01,说明结果溢出。
可以假设字长为 32 位,可按字节、半字、字寻址。在对准边界的 32 位计算机中,半字地址是 2 的整数 倍,字地址是 4 的整数倍,当所存数据不满足此要求时,可填充一个或多个空白字节。这种存储方式称为 “边界对齐” 。这样无论所存的数据是字节、半字还是字,均可一次访存取出。虽然浪费了一些存储空间,但可提高存取速度。
数据不按边界对齐方式存储时,半字长或字长的数据可能在两个存储字中,此时需要两次访存,并对高低字节的位置进行调整后才能取得所需数据,从而影响系统的效率。

在 C 语言的 struct 类型中,边界对齐方式存储有两个重要要求:
(1)每个成员按其类型的方式对齐,比如 char 类型的对齐值为 1,short 为 2,int 为 4(单位为字节)。
(2)struct 的长度必须是成员中最大对齐值的整数倍(不够就补空字节),以便在处理 struct 数组时保证每项都满足边界对齐的条件。
例如,下面是两个成员完全一样的结构体:
xxxxxxxxxx101struct A {2int i;3char c;4short s;5}6struct B {7char c;8int i;9short s;10}
但两者在内存中占据的空间却不同。这是因为结构体成员是按定义的先后顺序排列的,编译器要使它们在空间上对齐,所以应该有:
每个成员存储的起始地址 % 该成员的长度 = 0
同时,还需要让结构体的长度是最大成员长度的整数倍。

按边界对齐方式,相对于不按边界对齐方式是一种空间换时间的思想。
在存储数据时,通常用 最低有效字节(LSB)和 最高有效字节(MSB)来分别表示数据的低位和高位。例如,在 32 位机器中定义了一个 int 类型的变量 i,机器数为 18 0B C5 F3H,那么它的 MSB = 18H,LSB = F3H。
现代计算机基本都采用字节编址,也就是每个地址编号对应存放 1 个字节。不同类型的数据占用的字节数不 同,而程序中对每个数据只给定一个地址。变量 i 占据连续的四个字节,它们各有一个内存地址,而变量 i 的地址就是开始的那个字节的地址。假设 i 的地址为 6C 00H,那么 i 具体存放的四个字节的地址就是:6C 00H、6C 01H、6C 02H、6C 03H。而具体每个字节存放什么内容,可以有不同的定义方式。
多字节数据都存放在连续的字节序列中,根据数据中各字节在连续字节序列中的排列顺序不同,分为两种排列方式:大端方式(big endian)和 小端方式(little endian)。

大端方式:先存储高位字节,后存储低位字节。高位字节存储在低位地址中,字中的字节顺序和原序列相同。
小端方式:先存储低位字节,后存储高位字节。低位字节存储在低位地址中,字中的字节顺序和原序列相反。
在阅读以小端方式存储的机器代码时,要注意字节是按相反顺序显示的。
1. 【2018真题】冯·诺伊曼结构计算机中的数据采用二进制编码表示,其主要原因是( )。
I. 二进制的运算规则简单 II. 制造两个稳态的物理器件较容易
III. 便于用逻辑门电路实现算术运算
A. 仅 I、II B. 仅 I、III C. 仅 II、III D. I、II 和 III
答案: D
【 2015真题】由 3个“1”和 5个“0”组成的8位二进制补码,能表示的最小整数是 ( )。
A. -126 B. -125 C. -32 D. -3
答案:B
【2022真题】32 位补码所能表示的整数范围是 ( )。
A. -232~231-1 B. -231~231-1 C. -232~232-1 D. -231~232-1
答案:B
【2021真题】已知带符号整数用补码表示,变量 x、y、z 的机器数分别为 FFFDH、FFDFH、7FFCH,下列结论中,正确的是 ( )。
A. 若x、y和z为无符号整数,则z<x<y B. 若x、y和z为无符号整数,则x<y<z
C. 若x、y和z为带符号整数,则x<y<z D. 若x、y和z为带符号整数,则y<x<z
答案:D
5. 【2016真题】有如下 C 语言程序段
xxxxxxxxxx21short si = -32767;2unsigned short usi = si;
执行上述两条语句后, usi 的值为 ( )。
A. -32767 B. 32767 C. 32768 D. 32769
答案:D
6. 【2019真题】考虑以下 C 语言代码:
xxxxxxxxxx21unsigned short usi = 65535;2short si = usi;
执行上述程序段后, si 的值是 ( )。
A.-1 B.-32767 C.-32768 D.-65535
答案:A
7. 【2012真题】假定编译器规定 int 和 short 型长度分别为 32 位和 16 位,执行下列 C 语言语句:
xxxxxxxxxx21unsigned short x=65530;2unsigned int y=x;
得到 y 的机器数为 ( )。
A. 0000 7FFAH B. 0000 FFFAH C. FFFF 7FFAH D. FFFF FFFAH
答案:B
【2009真题】一个 C 语言程序在一台 32 位机器上运行。程序中定义了三个变量 x、 y 和 z,其中 x 和 z 为 int 型, y 为 short 型。当 x=127, y=-9 时,执行赋值语句 z=x+y 后, x、 y 和 z 的值分别是 ( )。
A. x=0000007FH, y=FFF9H, z=00000076H B. x=0000007FH, y=FFF9H, z=FFFF0076H C. x=0000007FH, y=FFF7H, z=FFFF0076H D. x=0000007FH, y=FFF7H, z=00000076H
答案:D
9. 【2018真题】整数x的机器数为1101 1000,分别对x进行逻辑右移1位和算术右移1位操作,得到的机器数各 是 ( )。
A.1110 1100、1110 1100 B. 0110 1100、1110 1100
C.1110 1100、0110 1100 D. 0110 1100、 01101100
答案:B
【2013真题】某字长为 8 位的计算机中,已知整型变量 x、y的机器数分别为
A. 1 1000000 B. 0 0100100 C. 1 0101010 D. 溢出
答案:A
【2018真题】假定带符号整数采用补码表示,若 int 型变量 x 和 y 的机器数分别是 FFFF FFDFH 和 0000 0041H,则 x、y 的值以及 x-y 的机器数分别是 ( )。
A.x=-65,y=41,x-y 的机器数溢出 B.x=-33,y=65,x-y 的机器数为FFFF FF9DH
C.x=-33,y=65,x-y 的机器数为FFFF FF9EH D. x = -65,y = 41,x-y 的机器数为FFFF FF96H
答案:C
【2016真题】某计算机字长为 32 位,按字节编址,采用小端(Little Endian)方式存放数据。假定有一个 double 型变量,其机器数表示为 1122 3344 5566 7788H,存放在 0000 8040H 开始的连续存储单元中,则存储单元 0000 8046H 中存放的是 ( )。
A. 22H B. 33H C. 66H D. 77H
答案:A
【2018真题】某32位计算机按字节编址,采用小端(Little Endian)方式。若语句 “inti=0;” 对应指令的机器代码为 “C7 45 FC 00 00 00 00”,则语句 “int i = -64;” 对应指令的机器代码是 ( )。
A.C7 45 FC C0 FF FF FF B.C7 45 FC 0C FF FF FF C.C7 45 FC FF FF FF C0 D.C7 45 FC FF FF FF 0C
答案:A
【2012真题】某计算机存储器按字节编址,采用小端方式存放数据。假定编译器规定 int 型和 short 型长度分别为 32 位和 16 位,并且数据按边界对齐存储。某 C 语言程序段如下:
xxxxxxxxxx61struct{2 int a;3 char b;4 short c;5} record;6record.a=273;若 record 变量的首地址为 0xC008,则地址 0xC008 中内容及 record.c 的地址分别为 ( )。
A. 0x00、 0xC00D B. 0x00、 0xC00E C. 0x11、 0xC00D D. 0x11、 0xC00E
答案:D
【2020真题】在按字节编址,采用小端方式的 32 位计算机中,按边界对齐方式为以下 C语言结构型变量a分配存储空间。
xxxxxxxxxx41Struct record{2 short x1;3 int x2;4} a;若a的首地址为 2020 FE00H,a的成员变量x2的机器数为1234 0000H,则其中34H所在存储单元的地址是 ( )。
A. 2020 FE03H B. 2020 FE04H C.2020 FE05H D. 2020 FE06H
答案:D
【2012真题】 float 类型(即 IEEE754 单精度浮点数格式)能表示的最大正整数是 ( )。
A. 2126-2103 B. 2127-2104 C. 2127-2103 D. 2128-2104
答案:D
【2013真题】某数采用 IEEE 754 单精度浮点数格式表示为C640 0000H,则该数的值是 ( )。
A.
答案:A
【2014真题】float 型数据常用 IEEE 754 单精度浮点格式表示。 假设两个 float 型变量 x 和 y 分别存放在32位寄存器 f1 和 f2 中,若(f1)=CC90 0000H, (f2)= B0C0 0000H, 则 x 和 y 之间的关系为 ( )。
A. x<y且符号相同 B. x<y且符号不同 C. x>y且符号相同 D. x>y且符号不同
答案:A
【2015真题】下列有关浮点数加减运算的叙述中,正确的是 ( )。 Ⅰ .对阶操作不会引起阶码上溢或下溢 Ⅱ .右规和尾数舍入都可能引起阶码上溢 Ⅲ.左规时可能引起阶码下溢 Ⅳ.尾数溢出时结果不一定溢出
A.仅Ⅱ 、 Ⅲ B. 仅Ⅰ 、 Ⅱ 、 Ⅳ C.仅Ⅰ 、 Ⅲ、 Ⅳ D. Ⅰ 、 Ⅱ 、 Ⅲ、 Ⅳ
答案: D
【2018真题】IEEE 754 单精度浮点格式表示的数中,最小的规格化正数是 ( )。
A.1.0x2-126 B. 1.0x2-127 C.1.0x2-128 D.1.0x2-149
答案:A
【2020真题】已知带符号整数用补码表示,float 型数据用 IEEE 754 标准表示,假定变量 x 的类型只 可能是 int 或 float,当 x 的机器数为 C800 0000H时,x 的值可能是 ( )。
A.-7x227 B.-216 C. 217 D. 25x227
答案:A
【2021真题】下列数值中,不能用 IEEE 754 浮点格式精确表示的是 ( )。
A.1.2 B.1.25 C.2.0 D.2.5
答案:A
【2022真题】-0.4375 的 IEEE 754 单精度浮点数表示为( )。
A. BEE0 0000H B. BF60 0000H C. BF70 0000H D. C0E0 0000H
答案:A
【2020真题】有实现 x*y 的两个 C 语言函数如下:
xxxxxxxxxx21unsigned umul (unsigned x, unsigned y){return x*y; }2int imul (int x,int y) {return x* y;}假定某计算机 M 中 ALU 只能进行加运算和逻辑运算。请回答下列问题。
(1)若 M 的指令系统中没有乘法指令,但有加法、减法和位移等指令,则在 M 上也能实现上述两个函数中的乘法运算,为什么?
(2)若 M 的指令系统中有乘法指令,则基于 ALU、位移器、寄存器以及相应控制逻辑实现乘法指令时,控制逻辑的作用是什么?
(3)针对以下三种情况:a)没有乘法指令;b)有使用ALU和位移器实现的乘法指令;c)有使用阵列乘法器实现的乘法指令,函数 umul() 在哪种情况下执行时间最长?哪种情况下执行的时间最短?说明理由。
(4)n 位整数乘法指令可保存 2n 位乘积,当仅取低 n 位作为乘积时,其结果可能会发生溢出。当 n=32、x=231-1、y=2 时,带符号整数乘法指令和无符号整数乘法指令得到的 x*y 的 2n 位乘积分别是什么(用十六进制表示)?此时函数 umul() 和 imul() 的返回结果是否溢出?对于无符号整数乘法运算,当仅取乘积的低 n 位作为乘法结果时,如何用 2n 位乘积进行溢出判断?
答案:
(1)编译器可以将乘法运算转换为一个循环代码段,在循环代码段中通过比较、加法、移位等指令实现乘法运算。
(2)控制逻辑的作用为: 控制循环次数,控制加法和移位操作。
(3)a)最长, c)最短。
对于 a), 需要用循环代码段(软件)实现乘法操作,因而需反复执行很多条指令, 而每条指令都需要取指令、译码、取数、执行并保存结果,所以执行时间很长; 对于 b)和 c), 都只要用一条乘法指令实现乘法操作,不过, b)中的乘法指令需要多个时钟周期才能完成,而 c)中的乘法指令可以在一个时钟周期内完成, 所以 c)执行时间最短。
(4)当 n=32、 x=231-1、 y=2 时,带符号整数和无符号整数乘法指令得到的 64 位乘积都为 0000 0000 FFFF FFFEH。
函数 imul 的结果溢出,而函数 umul 结果不溢出。对于无符号整数乘法,若乘积高 n 位全为 0, 则不溢出,否则溢出。
【2017真题】 已知

计算 f(n) 的 C 语言函数 f1 如下:
xxxxxxxxxx81int f1(unsigned n){2 int sum=1, power=1;3 for(unsigned i=0;i<=n-1;i++){4 power *= 2;5 sum += power;6 }7 return sum;8}将 f1 中的 int 都改为 float, 可得到计算 f(n) 的另一个函数 f2。假设 unsigned 和 int 型数据都占 32 位, float 采用 IEEE 754 单精度标准。请回答下列问题。
(1)当 n=0 时, f1 会出现死循环,为什么?若将 f1 中的变量 i 和 n 都定义为 int 型,则 f1 是否还会出现死循环?为什么?
(2)f1(23) 和 f2(23) 的返回值是否相等?机器数各是什么(用十六进制表示)?
(3)f1(24)和 f2(24)的返回值分别为 33 554 431 和 33 554 432.0, 为什么不相等?
(4)f(31)=232-1, 而 f1(31) 的返回值却为 -1,为什么?若使 f1(n) 的返回值与 f(n) 相等,则最大的 n 是多少?
(5)f2(127) 的机器数为 7F80 0000H, 对应的值是什么?若使 f2(n) 的结果不溢出,则最大的 n 是多少?若使 f2(n) 的结果精确(无舍入),则最大的 n 是多少?
答案:
(1)由于 i 和 n 是 unsigned 型,故“i<=n-1”是无符号数比较; n=0 时, n-1 的机器数为全1,值是 232-1,为 unsigned 型可表示的最大数,条件“i<=n-1”永真,因此出现死循环。
若 i 和 n 改为 int 类型,则不会出现死循环。因为“i<=n-1”是带符号整数比较, n=0 时, n-1 的值是-1,当 i=0 时条件“i<=n-1”不成立,此时退出 for 循环。
(2)f1(23)与 f2(23)的返回值相等。
f(23) = 223+1-1 = 224-1,它的二进制形式是 24 个1。 int 占 32 位,没有溢出。 float 有 1 个符号位, 8 个指数(阶码)位, 23 个底数(尾数)位, 23 个底数位可以表示 24 位的底数。所以两者返回值相等。
f1(23)的机器数是 00FF FFFFH;f2(23)的机器数是 4B7F FFFFH。显而易见前者是 24 个 1,即 0000 0000 1111 1111 1111 1111 1111 1111(2),后者符号位是 0,指数位为 23+127(10) = 1001 0110(2),底数位是 111 1111 1111 1111 1111 1111(2)。
(3)当 n=24 时, f(24) = 1 1111 1111 1111 1111 1111 1111 B,而 float 型数只有 24 位有效位,舍入后数值增大,所以 f2(24) 比 f1(24) 大 1。
(4)f(31) 已超出了 int 型数据的表示范围,用 f1(31) 实现时得到的机器数为 32 个 1,作为 int 型数解释时其值为-1,所以 f1(31) 的返回值为-1。
因为 int 型最大可表示数是 0 后面加 31 个 1,故使 f1(n)的返回值与 f(n)相等的最大 n 值是 30。
(5) f2 返回值为 float 型,7F80 0000H 中数符为0,阶码全为1,尾数为0。IEEE 754 标准用“阶码全 1、尾数全 0”表示无穷大。所以机器数 7F80 0000H 对应的值是+∞。
当 n=126 时, f(126) = 2127-1 = 1.1…1×2126,对应阶码为 127+126=253,尾数部分舍入后阶码加 1,最终阶码为 254,是 IEEE 754 单精度格式表示的最大阶码。故使 f2 结果不溢出的最大 n 值为 126。
当 n=23 时, f(23) 为 24 位 1, float 型数有 24 位有效位,所以不需舍入,结果精确。所以使 f2 获得精确结果的最大 n 值为 23。
(一)存储器的分类
(二)层次化存储器的基本结构
(三)半导体随机存储器
SRAM 存储器
DRAM 存储器
Flash 存储器
(四)主存储器
DRAM 芯片和内存条
多模块存储器
主存和CPU之间的连接
(五)外部存储器
磁盘存储器
固态硬盘(SSD)
(六)高速缓冲存储器(Cache)
Cache 的基本工作原理
Cache 和主存之间的映射方式
Cache 中主存块的替换算法
Cache 写策略
(七)虚拟存储器
虚拟存储器的基本概念
页式虚拟存储器
基本原理,页表,地址转换,TLB(快表)
段式虚拟存储器
段页式虚拟存储器
本章内容是考研考察的一个重点和难点,往往会有综合应用题出现。
需要重点掌握的内容包括:
半导体存储芯片的特性、工作原理、扩展技术及与 CPU 的连接,多模块存储器的原理。
磁盘存储器的原理、特点、性能指标,RAID 的原理,固态硬盘的特点和原理。
程序访问的局部性原理,Cache 的工作原理及性能计算,Cache 和主存的三种映射方式的原理、 特点、地址结构、访存过程,Cache 替换算法(常考 LRU) , Cache 写策略,Cache 块中的标记项。
虚拟存储器的基本原理,页表机制(二级页表结合操作系统考查),快表的原理,具有快表和 Cache 的多级页式存储系统的工作原理(综合性较强),段式和段页式虚拟存储器的基本原理。
| 考点 | 考查次数 | |
|---|---|---|
| 单项选择题 | 综合应用题 | |
| 高速缓冲存储器(Cache) | 12 | 9 |
| 虚拟存储器 | 5 | 9 |
| 半导体存储器 | 6 | 1 |
| 主存的扩展及与 CPU 的连接 | 6 | 1 |
| 磁盘存储器 | 4 | 0 |
| 低位交叉存储器 | 2 | 1 |
存储器是计算机系统中的记忆设备,用来存放程序和数据。存储器的种类繁多,从不同的角度对存储器可作不同的分类。

存储介质是指能寄存“0”、“1”两种代码并能区别两种状态的物质或元器件。存储介质主要有半导体器件、磁性材料和光盘等。
半导体存储器
存储元件由半导体器件组成的存储器称为半导体存储器。现代半导体存储器都用超大规模集成电路工艺制成芯片,其优点是体积小、功耗低、存取时间短。
半导体存储器又可按其材料的不同, 分为双极型(TTL)半导体存储器和 MOS 半导体存储器两种。前者具有高速的特点;后者具有高集成度的特点,并且制造简单,成本低廉,功耗小,所以 MOS 半导体存储器被广泛用。
磁性材料存储器
磁性材料存储器主要依靠磁性材料作为记录的介质,是不易失的永久记忆存储器。又可以分为磁表面存储器和磁芯存储器。
磁表面存储器是在金属或塑料基体的表面上涂一层磁性材料作为记录介质,工作时磁层随载磁体高速运转,用磁头在磁层上进行读/写操作,故称为磁表面存储器。按载磁体形状的不同,可分为磁盘、磁带和磁鼓。
磁芯是由硬磁材料做成的环状元件,在磁芯中穿有驱动线(通电流)和读出线,这样便可进行读/写操作,这种存储器称为磁芯存储器。磁芯属磁性材料,故它也。不过,磁芯存储器的体积过大、工艺复杂、功耗太大,目前几乎已不被采用。
光盘存储器
光盘存储器是应用激光在记录介质(磁光材料)上进行读/写的存储器,具有非易失性的特点。光盘具有记录密度高、耐用性好、可靠性高和可互换性强等特点。
按存取方式可把存储器分为随机存储器、只读存储器、顺序存取存储器和直接存取存储器。
随机存储器(Random Access Memory, RAM)
RAM是一种可读/写存储器, 其特点是存储器的任何一个存储单元的内容都可以随机存取,而且存取时间与存储单元的物理位置无关。计算机系统中的主存都采用这种随机存储器。
由于存储信息原理的不同, RAM 又分为静态 RAM(以触发器原理寄存信息)和动态 RAM(以电容充放电原理寄存信息)。
只读存储器(Read Only Memory, ROM)
只读存储器是能对其存储的内容读出,而不能对其重新写人的存储器。这种存储器一旦存入了原始信息后,在程序执行过程中,只能将内部信息读出,而不能随意重新写人新的信息去改变原始信息。
所以 ROM 通常用来存放固定不变的程序、常数和汉字字库,甚至用于操作系统的固化。它与随机存储器可共同作为主存的一部分,统一构成主存的地址域。
串行访问存储器
如果对存储单元进行读/写操作时,需按其物理位置的先后顺序寻找地址,则这种存储器称为串行访问存储器,也称为顺序存取存储器。显然这种存储器由于信息所在位置不同,使得读/写时间均不相同,比如磁带存储器就是一种顺序存取存储器。
直接存取存储器
还有一种属于部分串行访问的存储器,比如磁盘。在对磁盘读/写时,首先直接指出该存储器中的某个小区域(磁道),然后再顺序寻访,直至找到位置。故其前段是直接访问,后段是串行访问,称为直接存取存储器。
按在计算机系统中的作用不同,存储器可以分为主存储器、辅助存储器、缓冲存储器。
主存储器(简称主存):用来存放程序和数据,可以和CPU直接交换信息。
辅助存储器(简称辅存):主存储器的后援存储器, 用来存放当前暂时不用的程序和数据, 它不能与CPU直接交换信息。两者相比,主存速度快、容量小、每位价格高;辅存速度慢、容量大、每位价格低。
缓冲存储器(简称缓存,Cache) 用在两个速度不同的部件之中。

存储器有 3 个主要性能指标:速度、容量和每位价格(简称位价)。
(1)存取时间
要想衡量存储速度,最直观的指标就是完成一次存储器读/写操作所需要的时间,这叫做 存取时间,又称为 访问时间(Memory Access Time)。
存取时间又分为读出时间和写入时间。读出时间是从存储器接受到有效地址开始,到产生有效输出所需的全部时间;写入时间是从存储器接受到有效地址开始,到数据写入被选中存储单元为止的全部时间。
(2)存储器周期
存储器周期 (Memory Cycle Time)指连续进行两次独立的存储器操作(读或者写)需要的最小时间间隔,也叫 存取周期。需要注意的是,存储器周期并不等同于完成一次读写操作的时间,而是要更大;因为存储器经过一次读写操作后,并不能立即进行下一次读写,中间还需要一段时间来恢复内部状态。所以,
存储器周期 = 存取时间 + 恢复时间
(3)存储器带宽
一般来说,存储器周期越短,存储器的速度就越快;这前提是存储器的每次读写操作处理的数据位数相同。通常情况下,存储器每次读写的位数是跟存储字长相关的,字长越长,一个存取周期处理的数据就越多。
所以可以用 数据传输率 来表示存储速度,而 存储器带宽 就是衡量数据传输率重要指标。存储器带宽指单位时间内存储器存取的数据量。单位为位/秒(b/s),或者字节/秒(B/s)、字/秒。
存储器带宽 = 数据宽度 / 存储周期
例如,存储器周期为 500 ns,每个存取周期可以访问 16 位,那么带宽就是:
16 bit ÷ 500 ns = 32 Mb/s
存储容量指存储器能存放的数据总量,一般用二进制代码的总位数(bit)来表示。
存储容量 = 存储字数 × 存储字长
存储字数代表了存储器地址空间的大小,由地址线的位数决定。容量一般也可以用字节总数(Byte)来表示,也就是:
存储容量(字节数) = 存储字数 × 存储字长 / 8
例如,某机器存储字长为 8 位,地址线有 28 位,那么它的主存最大存储容量为:
228 × 8 / 8 = 228 B = 256 MB
每位价格也就是存储器的单位成本。
位价 = 总成本 / 总容量
一般来说,速度越高,位价就越高;容量越大,位价就越低;而且容量越大,速度也会越低。
最理想的存储器应该同时满足大容量、高速度、低位价,可惜这是很难达到的。

上面列出了不同层级的存储器。由上至下, 位价越来越低,速度越来越慢,容量越来越大,CPU 访问的频度也越来越少。
寄存器通常都制作在 CPU 芯片内。寄存器中的数直接在 CPU 内部参与运算, CPU 内可以有十几个、几十个寄存器,它们的速度最快,位价最高,容量最小。
主存用来存放将要参与运行的程序和数据,它与 CPU 速度差距较大。
为了使主存和 CPU 之间速度更好地匹配, 需要在主存与 CPU 之间插入一种比主存速度更快、容量更小的高速缓冲存储器 Cache,其位价要高于主存。
以上三类存储器都是由速度不同、位价不等的半导体存储材料制成的,它们都设在主机内。现代计算机将 Cache 也制作在 CPU 内。
磁盘、磁带属于辅存,其容量比主存大得多,大都用来存放暂时未用到的程序和数据文件。CPU不能直接访问辅存, 辅存只能与主存交换信息, 因此辅存的速度可以比主存慢得多。
存储系统的层次结构主要体现在缓存-主存和主存-辅存这两个存储层次上。显然, CPU 和缓存、主存都能直接交换信息; 缓存能直接和 CPU、主存交换信息; 而主存可以和 CPU、缓存、辅存交换信息。

缓存-主存层次
这一层次主要解决 CPU 和主存速度不匹配的问题。由于缓存的速度比主存的速度高, 只要将CPU近期要用的信息调人缓存, CPU 便可以直接从缓存中获取信息, 从而提高访存速度。但由于缓存的容量小,因此需不断地将主存的内容调入缓存,使缓存中原来的信息被替换掉。主存和缓存之间的数据调动是由硬件自动完成的,对程序员是透明的。
主存-辅存层次
这一层次主要解决存储系统的容量问题。辅存的速度比主存的速度低,而且不能和 CPU 直接交换信息, 但它的容量比主存大得多, 可以存放大量暂时未用到的信息。当CPU需要用到这些信息时, 再将辅存的内容调人主存, 供CPU直接访问。主存和辅存之间的数据调动是由硬件和操作系统共同完成的。
在主存-辅存这一层次的不断发展中,逐渐形成了虚拟存储系统。主存和辅存共同构成了虚拟存储器,二者在硬件和系统软件的共同管理下工作。
从CPU角度来看, 缓存-主存这一层次的速度接近于缓存, 高于主存; 其容量和位价却接近于主存。主存-辅存这一层次,从整体分析,其速度接近于主存,容量接近于辅存,平均位价也接近于低速、廉价的辅存位价。这就解决了速度、容量、成本这三者的矛盾。现代的计算机系统几乎都具有这两个存储层次,构成了缓存、主存、辅存三级存储系统。
半导体存储器分为 随机存取存储器(RAM)和 只读存储器(ROM)。
RAM 是一种可读/写存储器,其特点是存储器的任何一个存储单元的内容都可以随机存取,而且存取时间与存储单元的物理位置无关。计算机系统中的主存都采用这种随机存储器。
RAM 按照存储信息的原理不同,又可以分为静态随机存取存储器(SRAM)和动态随机存取存储器(DRAM),主存储器主要由 DRAM 实现,靠近处理器的那一层缓存 (Cache)则由 SRAM 实现,它们都是易失性存储器。ROM 是非易失性存储器。
采用超大规模集成电路制造工艺,可以将半导体存储器集成在一个芯片上,一个芯片内主要包括了具有记忆功能的存储矩阵、译码驱动电路和读/写电路等。

主存中各个存储单元的空间位置,是由一个地址号来表示的;通过地址总线可以给定一个存储单元的地址号,从而根据地址读出或者写入一个存储字。
译码驱动:将地址总线送来的地址信号翻译成对应存储单元的选择信号,该信号在读/写电路的配合下完成对被选中单元的读/写操作。
读/写电路:包括读出放大器和写入电路,用来完成读/写操作。
存储芯片通过地址总线、数据总线和控制总线与外部连接。地址线和数据共同反映了芯片的存储容量。比如,10 根地址线,4 根数据线,表示芯片的存储容量为:210 × 4 = 4 Kb。
地址线是单向输入的,其位数与芯片存储容量有关。
数据线是双向输入的,其位数与芯片每次可读出或写入的数据位数有关,从而也影响到存储容量。
控制线包括了读/写控制线和片选线。读/写控制线决定芯片进行的具体操作,片选线用来选择芯片。不同的存储芯片的控制线可能是不同的,有的芯片读/写控制线是两根(引脚名称一般是
半导体存储芯片的译码驱动,主要有两种方式:线选法 和 重合法。
线选法:是用一根字选择线(字线),直接选中一个存储单元的各位。这种方式结构比较简单,不过只适合用于容量不大的存储芯片。
重合法:用两个方向的地址,共同决定选中存储矩阵中的一个存储单元。相比 “一维” 的线选法,重合法就升级到了 “二维”,可以用更少的选择线实现对所有存储单元的选择。

上面是一个采用线选法译码驱动的存储芯片结构示意图。这个芯片有 8 位地址线和 8 位数据线,所以有 28 = 256 个存储字,需要 256 根字线来实现选中每个存储字。
如果采用重合法,则可以使用 X、Y 两个方向的地址译码器分别对 4 位地址进行译码,只需要两个方向各 16 根选择线,就可以直接选中 16 × 16 存储矩阵中的每一位。

当然,在这个示例中,如果用重合法实现 256 个字节(256 × 8)的存储器,需要使用 8 片上面结构的芯片,这样一来总的选择线并没有更少。不过如果考虑更大容量的存储器,比如地址线有 32 位、数据线仍为 8 位时,则线选法需要 232 根字线;而重合法同样是需要 8 片芯片,每片芯片只需 216 × 2 = 217 根选择线就可以实现。
通常把存放一个二进制位的物理器件称为存储元,它是存储器最基本的构件。地址码相同的多个存储元构成一个存储单元。存储单元的集合构成存储体。
静态 RAM(Static RAM,SRAM)的存储元是用双稳态触发器(六晶体管MOS)来记忆信息的,因此信息被读出后,它仍保持其原状态而不需要刷新;这种读特性被称为“非破坏性读出”。

上图中,T~1~ ~ T~4~ 构成了 MOS 管双稳态触发器基本电路;而 T~5~、T~6~ 受行地址选择信号控制,T~7~、T~8~ 受列地址选择信号控制,它们就像开关,将位线 A’ 、A 与数据线连接起来。由 T~1~ ~ T~6~ 这 6 个 MOS 管构成了静态 RAM 的基本单元电路,T~7~、T~8~ 则不包含在内,它们是芯片内同一列的各个基本单元电路所共有的。
下面是 Intel 2114 RAM 芯片的存储矩阵结构示意图。2114 芯片有 10 根地址线,其中 6 根行地址线、4 根列地址线,存储矩阵由 64 × 64 个基本单元电路组成,总容量为 1K × 4 位。

SRAM 使用触发器工作原理存储信息,因此在读出信息后,它仍会保持原来的状态,不需要刷新。不过如果电源掉电,存储的信息就会丢失,所以它属于易失性半导体存储器。
SRAM 的存取速度快,但集成度低,功耗较大,价格昂贵,一般用于 Cache。
动态 RAM(Dynamic RAM,DRAM)是利用存储元电路中栅极电容上的电荷来存储信息的。若电容上存有足够多的电荷表示存 “1”,电容上无电荷则表示存 “0”。
常见的动态 RAM 基本单元电路有三管式和单管式两种。单管式只需要一个 MOS 管和一个电容,因此可以极大地提高集成度。

当读取数据时,字线上的高电平使 MOS 管 T 导通,如果电容 C~s~ 有电荷就会在数据线上产生电流,可以看作 “1”;反之如果没有电荷,数据线上就没有电流,看作 “0”。进行读操作之后,电容上的电荷就释放掉了,所以必须进行 “再生” 处理;这种读取方式为 破坏性读出。
对于写入操作,同样是字线高电平令 T 导通,如果数据线上为高电平则对电容充电,存入 “1”;如果为低电平则电容放电,存 “0”。
可以看到,DRAM 的基本存储元可以只使用一个晶体管, 所以它比 SRAM 的密度要高很多。为了进一步提高集成度,DRAM 采用 地址复用技术,地址信号分行、列两次传送,这样地址线是原来的一半,地址引脚数也可以减少一半,就能够进一步减小芯片的体积。
下面是 Intel 4116 RAM 芯片的整体结构和存储矩阵示意图。4116 芯片的存储矩阵为 128 × 128,共有 16 K 个单管 MOS 基本单元电路,容量为 16K × 1 位。本来芯片应该有 14 根地址线,不过为了减少芯片封装的引脚数,地址线只有 7 根。这就需要将完整的地址信息分成行地址、列地址两部分(各自 7 位),分两次传送。


相对 SRAM 来说,DRAM 具有容易集成、价位低、容量大和功耗低等优点,但 DRAM 的存取速度比 SRAM慢,一般用于大容量的主存系统。
由于电容上的电荷一般只能维持1 ~2ms,因此即使电源不掉电,信息也会自动消失。为此,必须在 2ms 内对所有存储单元恢复一次原状态,这个过程称为 再生 或者 刷新。
刷新的过程,实质上是先将原存信息读出,再由刷新放大器形成原信息并重新写入的再生过程。由于存储单元是被随机访问的,有些存储单元可能一直不会被访问,因此其存储的原信息将会慢慢消失。因此,必须进行定时刷新。一般要求在一定的时间内,对动态 RAM 的全部基本单元必须作一次刷新,这个时间称为 刷新周期,也叫 再生周期,一般取 2ms。
通常有三种刷新方式:集中刷新、分散刷新和异步刷新。
(1)集中刷新
在规定的一个刷新周期内,对全部存储单元集中一段时间进行逐行刷新;刷新时必须停止读/写操作。
例如,我们有一个芯片的存储矩阵为 128 × 128,它的存取周期为 0.5 μs,刷新周期为 2 ms(4000 个存取周期),那么对它的 128 行存储单元进行集中刷新需要:
0.5 μs × 128 = 64 μs
那剩余的 1936 μs(3872个存取周期)就可以用来读/写或者维持信息。由于在这 64 μs 内无法进行读/写操作,所以这段刷新时间被称为 “死时间”,也叫访存 “死区”。死时间占据存取周期的比例 64 μs / 2 ms × 100% = 3.2%,称为死时间率。

(2)分散刷新
对每行存储单元的刷新,分散到每个存取周期内完成。这样,每个存储周期 t~C~ 就分成了两段:前半段 t~M~ 用来读/写或者维持信息,后半段 t~R~ 用来刷新。所以:
t~C~ = t~M~ + t~R~
同样以 128 × 128 存储矩阵的芯片为例,读/写周期 t~M~ = t~R~ = 0.5 μs,那么存取周期 t~C~ = 1 μs。逐行进行刷新,每隔 128 μs 就可以将存储芯片全部刷新一遍。

这样的好处是不存在停止读/写操作的死时间,而且刷新间隔比要求的刷新周期 2ms 短得多;缺点在于存取周期 t~C~ 变长了,使得整个系统速度变慢。
(3)异步刷新
异步刷新是前两种方式的结合,它既可以缩短 “死时间”,又能充分利用最大的刷新间隔 2ms。
还是之前的例子,对于 128 × 128 存储矩阵的芯片,存取周期 t~C~ = 0.5 μs,可以让它把对 128 行的刷新平均分配到 2ms 的刷新周期内。也就是说,每隔 2ms ÷ 128 ≈ 15.6 μs 刷新一行,每次刷新的时间还是一个存取周期 t~R~ = 0.5 μs。

这样一来,2ms 内用于刷新的时间仍然是 128 t~R~ = 64 μs,而由于分散到了整个刷新周期内,每次刷新一行只停了一个存取周期;所以对于每行来说,刷新的间隔还是 2ms,而 “死时间” 缩短为 0.5 μs。
如果将 DRAM 的刷新安排在 CPU 对指令的译码阶段,由于这个阶段 CPU 不会访问存储器,所以这样就完全避免了 “死时间” 的问题,从根本上提高了机器效率。
目前,随着 DRAM 的容量不断扩大,速度不断提高,它的应用要比 SRAM 更加广泛。DRAM 主要用在计算机的主存中,而 SRAM 通常用于容量不大的高速缓存(Cache)中。
两者的特点可以比较如下:

ROM (Read Only Memory)最原始的定义是 “只读存储器”,一旦写入原始信息后就不能更改。所以ROM 通常用来存放固定不变的程序、常数和汉字字库,甚至用于操作系统的固化。它与随机存储器可共同作为主存的一部分,统一构成主存的地址域。
不过随着用户的需要和技术的发展,又出现了更多类型的 ROM,让用户拥有了修改数据的能力。
根据制造工艺不同,ROM 可分为固定掩模型 ROM(MROM)、一次可改写 ROM (PROM)、紫外线擦除电可编程 ROM(EPROM)、电擦除电可编程 ROM (EEPROM)、快擦写(Flash)存储器。
早期只读存储器的存储内容根据用户要求,厂家采用掩模工艺,把原始信息记录在芯片中,一旦制成后无法更改, 称为掩模型只读存储器(Masked ROM, MROM)。随着半导体技术的发展和用户需求的变化, 只读存储器先后派生出可编程只读存储器(Programmable ROM, PROM)、可擦除可编程只读存储器(Erasable Programmable ROM, EPROM)以及用电可擦除可编程只读存储器(Electrically Erasable Programmable ROM, EEPROM)。到 20 世纪 80 年代,又出现了闪速存储器 (Flash Memory), 它具有 EEPROM 的特点, 而速度比 EEPROM 快得多。
闪速存储器(闪存,Flash),又称快擦型存储器,是在 EEPROM 的工艺基础上发展而来的,性价比更好、可靠性更高。其主要特点有:
价格便宜、集成度高;
属非易失性存储器,适合长期保存信息;
能快速擦写(电可擦除),写入前必须先擦除,因此写比读要慢。
由于 Flash 的擦除、重写时间已经非常短,比一般的 EEPROM 要快得多,所以 Flash 已经具备了 RAM 的功能,可以与 CPU 直接相连。电脑的 BIOS 程序由于包含了开机后的自检程序和自举装载程序,一般都会固化到主板上的一个 ROM 芯片中;如今的电脑通常就会用 Flash 芯片来存放 BIOS 程序。
Flash 可以至少擦写 10000 次以上,而且是非易失性存储器,所以在需要周期性修改存储信息、并长期保存的场合,它是一个非常理想的存储器;比如工控系统、单片机中作为数据采集和存储器件,用于制作 U 盘和移动硬盘等。
目前随着闪存技术的发展,容量越来越大、价格越来越低,让大容量 Flash 取代磁盘成为了可能。用闪存技术做成 固态硬盘(SSD),可以代替传统的磁盘,速度更快,功耗更低,体积更小。如今很多笔记本电脑中都使用了 SSD,使得计算机平均无故障时间大大延长。
SRAM、DRAM 和 ROM 这 3 种存储器的特点可以总结如下。

主存储器简称主存或内存,是计算机中存储程序和数据的重要部件。主存内包含了存储体、各种逻辑部件以及控制电路等。
主存是通过按地址访问的方式,对存储体内的存储单元进行读写操作的。因此主存首先需要从 MAR 中获取地址,由译码器进行地址译码、再经过驱动电路,进而通过选择线选中所需访问的单元。读出时,需要经过读出放大器才能将被选中存储单元的内容送到 MDR;写入时,MDR 中的数据也需要经过写入电路才能真正存入被选中的单元。所以主存实际结构的基本组成如下:

译码器、驱动器和读写电路都集成在 DRAM 存储芯片中,而 MAR 和 MDR 则集成在 CPU 芯片内。存储芯片可以通过总线与 CPU 相连。

当要从主存中读某个数据字时,首先由 CPU 将字的地址送到 MAR,通过地址总线送至主存,然后发出读命令;主存的译码器将地址总线送来的地址译码,导通对应存储单元的选择线,收到读信号后,便将该单元的内容送到数据总线上,进而交给 MDR。
如果要向主存写入一个数据字,仍然需要 CPU 先把地址送到 MAR,并把要写的数据送到 MDR,然后发出写命令;主存译码器依然从地址总线读取地址进行译码,接到写命令后,就把数据线上的信息写入对应的存储单元。
译码器是一种具有 “翻译” 功能的组合逻辑电路器件,可以将以二进制码表示的输入状态,转换成对应的特定输出信号。“译码” 就是 “编码” 的逆过程。
对于 n 位信号输入,译码器对应有 2n 个输出。译码器可以用逻辑门电路很容易实现。

存储器中的地址译码器,就是以 n 位地址线作为输入,以 2n 根选择线作为输出的译码器。当输入一个地址信号时,地址可以看作一个二进制数,它对应的十进制数就是选择线的序号。
主存中各存储单元的空间位置,都是由存储单元的地址号表示的;地址总线的作用就是给出要访问的存储单元的地址。每次访问存储单元,可以读出或者写入一个存储字。
存储字长必须是字节(8位)的整数倍,不同机器的存储字长不同。计算机一般既可以按字来寻址,也可以按字节寻址。例如,一台机器的存储字长为 32 位,并且可以按字节寻址,那么它的每个存储字都包含了 4 个具有独立地址的字节,地址的分配方式如下:

字地址是用该字高位字节的地址来表示,所以字地址总是 4 的整数倍,即二进制末两位总是 0。这样,对于同一个字内的字节,可以用地址末两位来进行区分,高位则是完全相同的。
如果这台机器的地址线为 24 位,那么按字节寻址的范围是 224 = 16M,按字寻址的范围为 16M / 4 = 4 M。
现代计算机的主存都由半导体集成电路构成,一般使用 DRAM 芯片。单个芯片的容量不可能很大,通过存储器芯片扩展技术,将多个芯片集成在一个 内存条上,然后由多个内存条及主板上的 ROM 芯片组成计算机所需的主存空间,再通过总线与 CPU 相连。

早期的内存就只是一块芯片,直接焊接在主板上;这样很难拆卸、更换,无法轻易扩容升级。内存条 的出现使得内存的更换和升级更加方便。内存条的主体是一块印制电路板(PCB),上面一般焊有多个内存芯片。通过印制板边缘的一排金色引脚(“金手指”)可以很容易地插入主板上的内存插槽,与 CPU 进行连接和数据交换。
单片存储芯片的容量有限,很难满足我们实际应用的需要,所以主存一般不会直接使用单个芯片实现,而是需要将多个存储芯片连在一起扩展成更大的存储器。这称为 存储容量的扩展,主要的方法有 位扩展 和 字扩展。
(1)位扩展
位扩展是指对字长进行扩展,也就是增加存储字长。这种情况下,系统地址线位数等于芯片地址线位数,而系统数据线位数多于芯片数据线位数。
位扩展的连接方式:各芯片的地址线、片选线和读写控制线与系统总线相应 并联;各芯片的 数据线单独引出,分别连接系统数据线。各芯片同时工作。
以之前介绍过的 SRAM 芯片 2114 为例,它的存储容量为 1K × 4,那么用 2 片 2114 采用位扩展的方式可以组成 1K × 8 的存储器。如下所示:

(2)字扩展
字扩展是指对存储字的数量进行扩展,而存储字的位数满足系统要求。这种情况下,系统数据线位数等于芯片数据线位数,系统地址线位数多于芯片地址线位数。
字扩展的连接方式:各芯片的地址线与系统地址线的 低位对应相连,芯片的数据线和读写控制线与系统总线相应 并联;由系统地址线的 高位译码 得到各芯片的片选信号。各芯片分时工作,同一时间只能有一个芯片被选中。
例如,用 2 片容量为 1K × 4 的 2114 芯片,采用字扩展的方式可以组成 2K × 4 的存储器。如下所示:

扩展之后的地址线为 11 位,共有 211 = 2 K 个地址。其中:
第一片 2114 的地址范围为 000 0000 0000 ~ 011 1111 1111;
第二片 2114 的地址范围为 100 0000 0000 ~ 111 1111 1111。
(3)字和位同时扩展
字和位同时扩展是前两种扩展的组合,这种方式既增加存储字的数量,又增加存储字长。
字和位同时扩展的连接方式:将进行位扩展的芯片作为一组,各组的连接方式与位扩展相同;由系统地址线高位译码产生若干个片选信号,分别接到各组芯片的片选信号。
例如,用 8 片容量为 1K × 4 的 2114 芯片,字和位同时扩展之后可以组成 4K × 8 的存储器。如下所示:

扩展之后的地址线为 12 位,共有 212 = 4 K 个地址。其中:
第一、二片 2114 通过位扩展构成第一组,地址范围为 0000 0000 0000 ~ 0011 1111 1111;
第三、四片 2114 通过位扩展构成第二组,地址范围为 0100 0000 0000 ~ 0111 1111 1111;
第五、六片 2114 通过位扩展构成第三组,地址范围为 1000 0000 0000 ~ 1011 1111 1111;
第七、八片 2114 通过位扩展构成第四组,地址范围为 1100 0000 0000 ~ 1111 1111 1111。
(1)合理选择存储芯片。通常选用 ROM 存放系统程序,选用 RAM 组成用户区。
(2)地址线的连接。CPU 地址线的低位与存储芯片的地址线相连,以选择芯片中的某一单元(字选);CPU 地址线的高位在扩充存储芯片时用,以选择存储芯片(片选)。
(3)数据线的连接。比较CPU的数据线数与存储芯片的数据位数。如果相等可以直接相连;如果不等,必须对存储芯片进行扩位,使其数据位数与 CPU 的数据线数量相等。
(4)读/写命令线的连接。CPU 的读/写命令线一般可以直接与存储芯片的读/写控制端相连。
(5)片选线的连接。片选信号一般由系统地址线高位译码,它是主存与 CPU 连接的关键。
随着计算机技术的发展,处理的信息量越来越多,对存储器的速度和容量要求也越来越高;而且随着 CPU 性能的不断提升、I/O 设备数量不断增加,导致主存的存取速度已经成为了整个计算机系统的性能瓶颈。这就要求我们必须提高主存的访存速度。
基本的想法是寻找更加高速的元器件和存储芯片,或者采用层级结构、加入高速缓存;除此之外,调整主存的结构也可以提高访问速度。这就是所谓的 多模块存储器。
在主存中,程序和数据是连续存放的,所以 CPU 访存取出的信息也是连续的。如果将存储器的存储单元进行扩展,让它能够存储更多的字,那么就可以在一个存取周期内,从同一地址取出更多的指令。将多条指令逐条送至 CPU 执行,由于 CPU 的速度远高于主存,这样就相当于增大了主存的带宽,提高了速度。
这种方式是对单独的存储器进行了扩展,类似于位扩展的思路,不过是将一个地址对应的数据扩展到了多个存储字。所以这种结构的存储器称为 单体多字存储器。
例如,对于一个单体四字存储器,可以在一个存取周期取出四个字的信息。假设指令字长就是一个存储字,那么原先一个存取周期拿到一条指令,现在就可以拿到 4 条;逐条传给 CPU 进行处理,就相当于每隔 1/4 周期,主存就向 CPU 传送了一条指令,带宽变成了 4 倍。

结构特点:存储器中只有一个存储体,每个存储单元存储 m 个字,总线宽度也为 m 个字。
访问方式:一次并行读出 m 个字,地址必须顺序排列并处于同一存储单元。
优点:宽度为单体单字存储器的近 m 倍(访问的内容在同一行时)。
缺点:如果出现访问冲突(需要的内容不在同一行)或遇到转移指令,效率会显著降低。
另一种思路是采用多模块组成存储器,各个模块可以并行读写,这就是多体并行系统。每个模块有相同的容量和存取速度,各模块都有自己独立的地址寄存器(MAR)、数据寄存器(MDR)、地址译码、驱动电路和读/写电路,它们能并行工作,也能交叉工作。
所谓的 “并行工作”,就是 CPU 可以同时访问 N 个模块,同时启动,同时读出;当然,由于总线是公共的,同时读出的 N 个字需要在总线上分时传送。
根据对这 N 个模块中存储单元的不同编址方式,多体并行系统又可以分为 多体高位交叉存储器 和 多体低位交叉存储器。
(1)多体高位交叉存储器
多体高位交叉存储器中,各模块采用 高位交叉方式编址。
高位交叉方式编址时,地址分为两部分,高位地址表示体号,低位地址为体内地址。这种编址方式下,一个模块(也就是 “体”)内的地址是连续的,程序存储时会按照体内地址的顺序存放,也就是先存一个模块,存满之后再存下一个;所以这种方式也叫 “顺序存储”。

只要调动合理,使不同的请求源同时去访问不同的模块,就可以实现并行工作。比如,CPU 在访问一个模块的同时,外部设备可以以直接存储器访问(DMA)的方式访问另一个模块,这样两个体就是并行工作的。
(2)多体低位交叉存储器
多体低位交叉存储器中,各模块采用 低位交叉方式编址。
低位交叉编址是指用主存地址的低位来指明存储器模块,高位指明模块内的字地址。这种编址方式下,连续的地址分布在相邻的模块中,同一模块内的地址是不连续的,因此也叫做 “交叉存储”。有 M 个模块的低位交叉编址,又叫 模 M 编址。

上面是一个模 4 交叉编址的存储器,存储体模块个数为 4,所以第一个模块中所有存储单元的地址号,对 4 取模都为 0;同样道理,第二、三、四个模块的地址号,对 4 取模结果分别为 1、2、3。具体的编址地址号如下所示:

程序按照地址连续存放在相邻模块中,采用低位交叉编址后,可以在不改变每个模块存取周期的前提下,采用 流水线 方式并行存取,提高存储器的带宽。
在一个存取周期 T 内,m 个模块按一定的顺序分时启动;如果分时启动的时间间隔为 t = T/m,那么在一个存取周期内,CPU 交叉访问各个模块,从而使各模块的读/写操作交错重叠进行,最终向 CPU 可以传送 m 个字。这样,存储器的带宽提升为 m 倍。
由于各个模块传送取出的字共享总线,因此假设总线传输周期为 τ,当 t 小于等于 τ 时,就可以获得最大的存储器带宽。所以:
t = T/m ≤ τ
所以,对于一个存取周期为 T、总线传输周期为 τ 的机器,设计多体低位交叉存储器时应该有 m ≥ T / τ。一般取最小值即可,在采用流水线方式时应该满足
T = m τ
对于四体低位交叉编址存储器 T = 4 τ,按流水线方式工作时不同模块访问字的时间顺序如下:

可以看出,对于流水线工作的低位交叉存储器,连续读取 n 个字所需的时间为:
t~1~ = T + ( n - 1 ) τ
而如果是高位交叉存储器,对应的时间为:
t~2~ = n T
对于上面的四字低位交叉存储器,τ = T / 4,所以 t~1~ = ( n + 3 ) T / 4,明显要低于 t~2~;当 n 非常大时,t~1~ 趋近于 T / 4,即速度提升了 4 倍。
当然,由于多模块存储器需要同时管理多个存储模块,还要同时处理来自不同部件的访问请求,因此必须增加一个存储器控制部件(简称存控)来处理这些事情。存控可以合理安排各部件请求访问的顺序,并有控制主存读/写操作的功能。
因为多模块有额外的开销,所以尽管 m 体低位交叉存储器理论上速度可以达到原先的 m 倍,在实际运行中会有很大差距;而且随着模块数 m 的增大,速度的提升也会越来越不明显。因此实际应用中,最多的情况就是采用 2个模块的二体低位交叉存储器,这就是所谓的 “双通道内存” 技术。
此外,通过在 DRAM 中引入一个锁存器,可以将 CPU 给出的地址和控制信号锁存,然后在指定的时钟周期后再响应。这样一来,CPU 和主存的数据交互就可以 同步 于系统时钟信号,这种存储器就叫做 同步 DRAM(Synchronous DRAM,SDRAM)。CPU 在发出访问请求后不需要等待,可以在存储器的读取周期内去完成其它操作;等到读取周期(可能需要若干个时钟周期)结束后,CPU 就可以获得从存储器读出的数据了。
SDRAM 还支持猝发访问模式,CPU 只要发出一个地址就可以访问连续的一段数据块。SDRAM 芯片内也可以包含多个存储体,构成多体并行系统,提高访问速度。新一代的 SDRAM 可以每周期两次向 CPU 传输数据,因此称为 双倍数据速率 SDRAM(Double Data Rate SDRAM,DDR-SDRAM)。
外部存储器是主存的后援设备,也叫做辅助存储器,简称 外存 或 辅存,与主存一起构成了存储器系统的主存-辅存层次。与主存相比,外存容量大、速度慢、价格低,可以脱机保存信息,属于 非易失性存储器。
用于计算机系统的外存主要有磁盘、磁带、光盘;磁盘和磁带都属于 磁表面存储器。而目前广泛应用的 固态硬盘(SSD)主体由闪存芯片构成,属于半导体存储器。
磁盘是应用最为广泛的外存设备。磁盘根据结构和盘片材质的不同,可以分为 硬磁盘 和 软磁盘,如今随着存储技术的发展,软磁盘存储器已渐渐不再使用,而硬磁盘存储器依然在外存中占据着重要的比例。
磁盘存储器具有外存设备普遍的优缺点:
优点:存储容量大,位价低;记录介质可重复使用;记录信息可长期保存而不丢失, 甚至可脱机存档;非破坏性读出,读出时不需要再生。
缺点:存取速度慢,机械结构复杂。
磁表面存储器在不同形状(盘状、带状)的载体上涂有磁性材料层,这磁层就是记录信息的存储介质。存储器工作时,依靠载体的机械运动,由磁头在磁层上进行读/写操作;信息就记录在磁层上,这些信息的轨迹叫做 磁道。磁盘的磁道是一个个同心圆,磁带的磁道则是一条条直线。
磁记录原理:磁表面存储器在磁头和磁性记录介质做相对运动时,通过电磁转换完成读/写操作。


磁记录方式:又称为编码方法,就是按某种规律把一连串的二进制信息转换成磁表面相应的磁化状态。通常采用调频制(FM)和改进型调频制(MFM)的记录方式。
(1)硬磁盘存储器的类型
硬磁盘中的存储载体是盘片,它是由硬质铝合金材料制成的,其表面涂有一层硬磁特性材料,可以被磁化从而完成信息的存储。通过磁头和盘片的相对运动,就可以实现信息的读取和写入。

根据能否更换盘片,硬磁盘可以分为 可换盘磁盘 和 固定盘磁盘。
可换盘磁盘的盘片可以脱机保存,所以更换的时候可以只换单片,方便维护和扩容;固定盘磁盘的盘片则不能从驱动器中取下,更换的时候需要整体更换,可靠性更高。
按照磁头的工作方式,硬磁盘可以分为 固定磁头磁盘 和 移动磁头磁盘。

固定磁头的磁盘存储器,磁头位置是固定不动的,磁盘上的每一个磁道都对应着一个磁头,盘片也不可以更换;这样省去了磁头在盘片上移动寻找磁道的时间,存取速度更快。
移动磁头的磁盘存储器,存取数据时磁头需要在盘面上做径向运动;这类存储器可以只有一个盘片,也可以有多个盘片。多个盘片会装在一个同心主轴上,每个记录面各有一个磁头。所有这些磁头连成一体,固定在支架上移动;任何时刻所有磁头和主轴的距离都相等,它们位于和圆心相等距离的一组磁道上,这组磁道称为一个 柱面。
目前,移动磁头的多盘片磁盘存储器应用最广泛,典型代表是 温切斯特磁盘。温切斯特磁盘简称温盘,是一种可移动磁头、固定盘片的磁盘存储器。它采用密封组合的方式,将磁头、盘片、驱动部件以及读/写电路等做成一个不可拆卸的整体,称作 头盘组合体。所以它的特点是可靠性强,防尘性能好,对环境要求不高。
(2)硬磁盘存储器的组成
硬磁盘存储器由磁盘驱动器、磁盘控制器和盘片组成。

磁盘驱动器
磁盘驱动器是主机之外的一个独立装置,又称作 磁盘机。驱动器主要包括主轴、定位驱动和数据控制 3 个部分。

主轴受传动机构的控制,可以使磁盘高速旋转;磁头分装在读/写臂上,连接到一个小车,在音圈电机的控制下平行移动进行寻道;定位驱动系统是一个带有速度、位置反馈的闭环自动控制系统,根据磁头的即时位置和速度计算出接下来运动的方向和速度;数据控制部分主要对数据转换和读/写操作进行控制。首先接收选头选址信号,然后根据磁记录方式将数据脉冲和线圈的驱动电流进行转换。
磁盘控制器
磁盘控制器是磁盘存储器和主机的接口,通常就是一块电路板,插在主机总线插槽中。它的作用是接收由主机发来的命令,将其转换成磁盘驱动器的控制命令,实现主机和驱动器之间的数据格式转换和数据传送,并且控制驱动器的读/写操作。一个磁盘控制器可以控制多台驱动器。
将磁盘控制器的功能全部内置在磁盘设备中,主机和设备之间就可以采用标准的通用接口了。最初这种接口就称为 IDE(Integrated Drive Electronics)接口,同时期还有更高性能的 SCSI(Small Computer System Interface)接口;之后又发展出了采用串行传输技术的接口,这就是 SATA(Serial Advanced Technology Attachment)和 SAS(Serial Attached SCSI)。目前我们的个人电脑中,大多都是采用 SATA 接口的硬盘。
盘片
盘片是磁盘中存储信息的载体,由驱动器控制它的转动并读/写数据;有时也会直接把盘片当作驱动器的一部分。目前硬盘的盘片正朝着小体积大容量的方向发展,记录密度越来越高。
(1)磁盘存储区域
一块磁盘划分为若干个记录面,每个记录面划分为若干条 磁道,而每条磁道又划分为若干个 扇区,扇区(也称块、扇段)是磁盘读写的最小单位,即磁盘按块存取。一个具有多盘片的磁盘组,可将其 n 个面上所有同一半径的磁道看成一个圆柱面,称为 柱面;在移动磁头的组合盘中,多个磁头一次定位的磁道集合就是一个柱面。


磁头数:表示磁盘总共有几个磁头,一般来说一个记录面对应一个磁头,所以等于记录面数。
柱面数:表示磁盘中柱面的个数,等于每个记录面上的磁道数。
扇区数:表示每条磁道上有几个扇区。
(2)磁盘地址
一个磁盘存储器可以有多台驱动器,不同的驱动器可以用一个编号(驱动器号,或者台号)来区分。当驱动器号确定后,磁盘进行寻址定位时,首先需要整体移动磁头找到对应柱面(磁道)、再选定磁头,最后转动盘片找到扇区。所以寻址所需要的磁盘地址,一般由 驱动器号、柱面(磁道)号、盘面号、扇区号 组成。
磁盘的地址格式如下所示:
| 驱动器号 | 柱面(磁道)号 | 盘面号 | 扇区号 |
|---|
例如,系统中有 4 个驱动器,每个驱动器带一个磁盘组,其中有 11 个盘片(最外层上下侧为保护面),每个盘面有 203 个磁道、划分为 16 个扇区。则可以算出,驱动器号需要 2 位;柱面号需要 8 位( 27 < 203 < 28 );而 11 个盘片有 20 个盘面,所以盘面号需要 5 位;扇区号需要 4 位。最终每个磁盘地址要 19 位二进制代码。
| 驱动器号(2位) | 柱面(磁道)号(8位) | 盘面号(5位) | 扇区号(4位) |
|---|
(3)磁盘的工作过程
磁盘的主要操作是寻址、读盘、写盘。磁盘属于机械式部件,其读/写操作是串行的,不可能在同一 时刻既读又写,也不可能在同一时刻读两组数据或写两组数据。
(1)记录密度
记录密度通常是指单位长度内所存储的二进制信息量。磁盘存储器用 道密度、位密度 和 面密度 来表示。
道密度:沿磁盘半径方向单位长度上的磁道数;单位 tpi(Track Per Inch,道每英寸)或 tpm(道每毫米)。为避免电磁干扰,磁道之间会保持一定的距离,称为 道距;道密度就是 D~t~ 就是 道距 P 的倒数:
位密度:单位长度的磁道上能记录的二进制位数;单位 bpi(Bits Per Inch,位每英寸)。磁盘中每个磁道上记录的信息量是相同的,可以记为每道总位数 f~t~ ;由于各个磁道周长不同,因此位密度也不同。一般所说的磁盘位密度,指的就是最内圈上的位密度(最大位密度)。如果最内圈同心圆的直径为 d~min~,那么位密度 D~b~ 为:
面密度:位密度和道密度的乘积。
(2)存储容量
存储容量指磁盘能存储的二进制信息的总数量,一般以位或者字节为单位。磁盘存储容量 C 可以计算为:
其中 n 为 盘面数,k 为每个盘面的磁道数,s 为每条磁道上记录的二进制代码数。
磁盘有非格式化容量和格式化容量两个指标。非格式化容量是指磁表面可利用的磁化单元总数,可以由道密度和位密度计算得到;格式化容量是指按某种特定的记录格式所能存储信息的总量,即用户可以使用的容量,一般是非格式化容量的 60% ~ 70%。
(3)平均寻址时间
磁盘的存取方式是直接存取,它的寻址时间分为两个部分:磁头寻找目标磁道的时间 t~s~;和找到磁道后,磁头等待要读写的磁道区段(扇区)旋转到磁头下方的时间 t~w~ 。由于寻找相邻磁道和不相邻磁道的时间不同,磁头等待不同扇区的时间也不同,所以应该取平均值,称为 平均寻址时间;它是 平均寻道时间 t~sa~ 和 平均等待时间 t~wa~ 之和。
平均寻址时间再加上数据传输时间,就是磁盘的 平均访问时间。
(4)数据传输率
数据传输率是指单位时间内,磁盘向主机传送数据的位数或字节数。数据传输率 D~r~ 与记录位密度 D~b~ 和磁道运动速度 V 有关;
对于磁盘来说,“磁道运动速度” 一般用磁盘的转速 r (单位 转/s)表示,那么
(5)误码率
误码率是衡量磁盘出错概率的参数,等于从磁盘读出信息时,出错信息位数和读出信息总位数之比。为了减少出错率,磁盘一般采用循环冗余校验(CRC)码来发现和纠正错误。
冗余磁盘阵列(Redundant Array of Independent Disks,RAID)是将多个独立的物理磁盘组成一个磁盘阵列,引入并行处理技术,让数据在多个物理盘上分割交叉存储、并行访问。
根据不同的目的,可以采用不同的 RAID 方案;在 RAID1 ~ RAID5 的几种方案中,无论何时有磁盘损坏,都可以随时拔出受损的磁盘再插入好的磁盘,而数据不会损坏。RAID 的分级如下所示:
RAID0:无冗余和无校验的磁盘阵列。
RAID1:镜像磁盘阵列,无校验。
RAID2:采用纠错的海明码的磁盘阵列。
RAID3:位交叉奇偶校验的磁盘阵列。
RAID4:块交叉奇偶校验的磁盘阵列。
RAID5:无独立校验的奇偶校验磁盘阵列。
其中,RAID0 把连续多个数据块交替地存放在不同物理磁盘的扇区中,几个磁盘交叉并行读写,不仅扩大了存储容量,而且提高了磁盘数据存取速度,但 RAID0 没有容错能力。
RAID1 是为了提高可靠性,使两个磁盘同时进行读写,互为备份,如果一个磁盘出现故障,可从另 一磁盘中读出数据。两个磁盘当一个磁盘使用,意味着容量减少一半。
总之,RAID通过同时使用多个磁盘,提高了传输率;通过在多个磁盘上并行存取来大幅提高吞吐量;通过镜像功能,提高了安全性、可靠性;通过数据校验,提供容错能力。
固态硬盘(Solid State Disk,SSD)是基于闪存(Flash)技术的半导体存储器,它与 U 盘并没有本质差别。SSD 由闪存芯片和闪存翻译层组成,闪存芯片代替了传统磁盘中的磁盘驱动器,闪存翻译层则将来自 CPU 的读写请求翻译成对芯片的读写控制信号,相当于磁盘中的磁盘控制器。

固态硬盘有很多优点。它由半导体存储器构成,没有机械部件,所以随机访问速度比磁盘快很多,也没有任何机械噪声和震动。另外,SSD 还具有能耗低、抗震性好、安全性高等优点。
当然,固态硬盘也有缺点。它最大的问题是依然基于 EEPROM 的擦除原理,随机写入比较慢。
固态硬盘的数据都存放在闪存芯片中。一个闪存芯片内包含了多个 “块”,每个块又由若干 “页” 组成。数据以页为单位进行读写,但是需要以块为单位进行擦除;所以只有一页所属的块整个被擦除之后,才能重新写这一页。一旦一个块被擦除了,块中的每一页都可以再写一次。一般某个块进行了数千次重复写之后,就会损坏。

因此随机写很慢,有两个原因:首先,擦除块本身就比较慢;其次,如果试图写的页所在块已经有数据了,那么这个块中其它所有有数据的页都必须被复制到一个新块(擦除过的块),然后才能进行写操作。
因此,闪存的擦写寿命是有限的,读/写数据通常会集中在 SSD 的一部分闪存,这部分闪存就会损坏得特别快;在磨损不均衡的情况下,数个闪存块的损坏,会导致整个 SSD 损坏。为弥补 SSD 的寿命缺陷,引入了 磨损均衡技术,SSD 磨损均衡技术大致分为两种:
动态磨损均衡:写入数据时,自动选择较新的闪存块。
静态磨损均衡:监测并自动进行数据分配,让旧的闪存块承担无须写数据的储存任务,同时让较新的闪存块空出来;平常的读/写操作都在较新的闪存块中进行,这样就使各闪存块的损耗更为均衡。
有了磨损均衡技术,SSD 的寿命就比较可观了。例如,对于一个 256 GB 的 SSD,闪存的擦写寿命是 500 次的话,那么就需要写入125 TB 数据才可能损坏;而目前的 Flash 芯片已经做到至少可以擦写上万次了。
为了解决 CPU 和主存之间速度不匹配的问题,计算机系统中引入了高速缓存(Cache)的概念。基本想法就是使用速度更快但容量更小、价格更高的 SRAM 制作一个缓冲存储器,用来存放经常用到的信息;这样一来,CPU 就可以直接与 Cache 交换数据,而不用访问主存了。
这种方案之所以有效,是因为通过对大量典型程序分析发现,在一定时间内,CPU 要从主存取指令或者数据,只会访问主存局部的地址区域。这是由于指令和数据在内存中都是连续存放的,而且有些指令和数据会被多次调用(比如常用函数、循环代码段、数组和一些常数);也就是说,指令和数据在主存中地址分布不是随机的,而是相对的簇聚。这使得 CPU 执行程序时,访存具有相对的局部性;这称为程序访问的 局部性原理。
时间局部性:如果一个数据现在被访问了,那么以后很有可能也会被访问
空间局部性:如果一个数据现在被访问了,那么它周围的数据在以后可能也会被访问
局部性原理是 Cache 高效工作的理论基础。
为了便于 Cache 与主存交换信息,Cache 和主存都被划分为相等的块。Cache 块又称 Cache 行,每块由若干字节组成,块的长度称为块长。由于 Cache 的容量远小于主存的容量,所以 Cache 中的块数要远少于主存中的块数,Cache 中仅保存主存中最活跃的若干块的副本。
假设主存按字节编址,地址用 n 位二进制码表示,那么主存容量为 2n B;块的大小为 16 个字节,那么主存中块的个数为:2n / 16 = 2n-4。那么如果对每个块也做一个编号,其实就对应着地址中的前 n - 4 位。

这样,主存地址就分成了两部分:高 n - 4 位表示主存中的 “块地址”,低 4 位表示 “块内地址”,块内地址其实就是具体存储字在块内的 “偏移量”。类似,Cache 中地址也可以分成这样的两部分,由于 Cache 中块长与主存一致,所以低 4 位同样是块内地址;剩余的高位则为 Cache 的块号。Cache 的块号位数小于 n - 4。

所以,可按照某种策略预测 CPU 在 未来一段时间内要访存的数据,将其装入 Cache。当 CPU 要读取主存中的某个字时,分为两种情况:
Cache 命中:需要的字已经在缓存中,就将其地址转换为缓存地址,直接访问 Cache,与主存无关;
Cache 未命中:需要的字不在缓存中,仍需访问主存,并将该字所在的块一次性地从主存调入 Cache。
如果某个主存块已经调入 Cache,就称该主存块和 Cache 中的缓存块建立了对应关系。由于 Cache 容量有限,当 Cache 已满时,就需要根据某种替换算法,让需要调入 Cache 的块替换之前某个缓存块的内容。所以,一个缓存块不可能永远只对应一个主存块;需要给每个缓存块设置一个标记,写入当前对应的主存块号,表示它当前存放了哪个主存块。
CPU 与 Cache 之间的数据交换,通常是以字为单位;而 Cache 与主存之间的数据交换则以块为单位。
Cache 的效率,通常用 命中率 来衡量。命中率是指 CPU 要访问的信息已经在 Cache 中的比率。Cache 的容量和块长都是影响命中率的重要因素。
假设一个程序执行期间,访问 Cache 的总命中次数为 N~c~,访问主存的总次数为 N~m~,那么命中率为:
设 t~c~ 为命中时的 Cache 访问时间,t~m~ 为未命中时的主存访问时间,那么 Cache - 主存系统的平均访问时间 t~a~ 为:
由于 t~c~ 远小于 t~m~,因此平均访问时间 t~a~ 越接近 t~c~ 就说明 Cache 效率越高。用 e 表示访问效率,则有:
命中率 h 越接近 1,访问效率就高。一般来说,Cache 容量越大,命中率就越高;而块长与命中率的关系较为复杂,它取决于程序的局部特性,一般取每块 4 ~ 8 个可编址单位(字或字节)效果较好。
Cache 主要由 Cache 存储体、主存 - Cache 地址映射变换机构、Cache 替换机构几大模块组成。

(1)Cache 存储体
Cache 存储体以块为单位与主存交换信息,Cache 访存的优先级最高。
(2)主存 - Cache 地址映射变换机构
地址映射变换机构会将 CPU 送来的主存地址转换为 Cache 地址。由于主存和 Cache 块长相同,所以块内地址是不变的,地址变换主要就是主存的块号(高位地址)到 Cache 块号之间的转换。这涉及到一个函数的映射关系,被称为 地址映射。
(3)Cache 替换机构
地址转化之后,如果 Cache 命中,CPU 就直接访问 Cache 存储体;如果不命中,CPU 需要访问主存将需要的字取出,并把它所在的主存块调入 Cache。如果 Cache 已满,无法将主存块直接调入 Cache,就需要 Cache 内的替换机构执行替换策略。
所谓替换策略,就是按一定的替换算法,确定从 Cache 中移出哪个块返回主存,并把新的主存块调入 Cache 进行替换。
在执行写操作时,还需要考虑如何使 Cache 如何与主存的内容保持一致。这就需要用某种 Cache 写策略。
Cache 的改进,主要就是由一个缓存改为使用多个缓存。主要有两个方向:增加 Cache 级数;将统一的 Cache 变为分立的 Cache。
(1)两级缓存
最初在 CPU 和主存之间只设一个缓存,称为 单一缓存。随着集成电路密度的提高,这个缓存就直接与 CPU 集成在了一个芯片中,所以又称为 片内缓存(片载缓存)。
由于片内缓存容量无法做到很大,所以可以考虑在片内缓存和主存之间再加一级缓存,称为 片外缓存,也由 SRAM 组成。这种由片外缓存和片内缓存构成的 Cache 系统被称为 “两级缓存”,片内缓存作为第一级(L1 Cache),片外缓存作为第二级(L2 Cache)。
(2)分立缓存
指令和数据都存放在同一缓存内的 Cache,称为 统一缓存;而 分立缓存 则将指令和数据分别存放在两个缓存中,一个叫指令 Cache,另一个叫数据 Cache。这两种缓存的选择主要考虑两个因素:
主存结构。如果计算机主存中指令、数据是统一存储的,则相应的 Cache 采用统一缓存;如果主存指令、数据分开存储,则相应的 Cache 采用分立缓存。
机器对指令执行的控制方式。如果采用了超前控制或者流水线控制方式,一般都采用分立缓存。所谓超前控制,是指在当前指令执行尚未结束时就提前把下一条准备执行的指令取出;而所谓流水线控制,就是多条指令同时分阶段执行。
Cache 块中的信息是主存中某个块的副本,地址映射是指把主存地址空间映射到 Cache 地址空间,这相当于定义了一个函数:
Cache 地址 = f ( 主存地址 )
当然,由于 Cache 和主存块长一样,而块内地址只是字在当前块内的 “偏移量”,所以映射转换之后块内地址是不变的。我们需要的其实只是 Cache 块号和主存块号之间的函数关系:
Cache 块号 = f ( 主存块号 )
Cache 块远少于主存块,所以 Cache 块不可能永远对应唯一的主存块,需要在 Cache 中为每一个块加一个 标记,指明它是主存中哪一块的副本。这个标记的内容,应该能够唯一确定对应主存块的编号。另外,为了说明 Cache 行中的信息是否有效,每个 Cache 行还需要有一个 有效位,该位为 1 时,表示 Cache 中该映射的主存块数据有效;为 0 则无效。
地址映射的方法有以下 3 种。
直接映射 的思路非常简单,就是 “挨个对应”,主存中的每一块只能装入 Cache 中的唯一位置。由于 Cache 容量很小,当主存中的块已经 “遍历” 完所有 Cache 地址后,下一个主存块的对应位置就又成了 Cache 中的第一行(第一个块)。
很明显,这跟 “顺序存储” 的思路是一样的,用主存块号对 Cache 的总行数取模,就可以得到对应 Cache 的行号了:
Cache行号 = 主存块号 mod Cache总行数
例如,假设主存地址为 32 位,按字节编址,主存块大小为 64 B,所以主存块共有 232 / 64 = 226 个;如果 Cache 只有 4 行(4 个块),那么采用直接映射方式的对应关系如下:

更加一般化,假设 Cache 共有 2c 行,主存有 2m 个块,那么 Cache 行号有 c 位,主存块号有 m 位。在直接映射方式中,主存块号为 0、2c、2c+1... 的块,都映射到 Cache 的第 0 行;而主存中块号为 1、2c + 1、2c+1 + 1... 的块,映射到 Cache 的第 1 行;以此类推。
这样一来,主存块号的低 c 位就对应了 Cache 中的行号;当一个块存放在 Cache 中,只需要高 m - c 位就可以指明它对应的主存中的块号。给每个 Cache 行设置一个 t = m - c 位的标记,那么当主存某块调入 Cache 后,就将其块号的高 t 位设置在对应 Cache 行的标记中。
所以直接映射方式下,主存地址结构为:

访存过程:
① 根据访存地址中间的 c 位,找到对应的 Cache 行。
② 将该 Cache 行中的标记和主存地址的高 t 位标记进行比较。
③ 若相等且有效位为1,则 Cache 命中,此时根据主存地址中低位的块内地址,在对应的 Cache 行中存取信息;若不相等或有效位为 0,则 Cache 未命中,此时 CPU 从主存中读出该地址所在的一块信息,并送至对应的 Cache 行中,将有效位置 1,并置标记为地址中的高 t 位。

直接映射实现简单,但不够灵活,即使 Cache 的其他许多地址空着也不能占用,这使得直接映射的块冲突概率高,空间利用率低。
直接映射的问题在于,我们找到的是从主存块到缓存行的一种 “多对一” 的关系,每一个主存块只能对应唯一的缓存行,从而导致冲突概率高。如果让一个主存块,可以映射到多个缓存块上,变成 “多对多” 的关系,明显就可以减少冲突了。
最简单的情况,就是不加任何条件限制,让主存的每一个块都可以映射到 Cache 的任意位置;简单来说就是 “有空就填”,放在哪里都可以。这就是 全相联映射 方式。

由于没有任何规律,所以当一个块存放在 Cache 中,无法根据 Cache 行号推出它对应主存块的任何信息;因此必须在每行的标记中明确指出该行取自主存的哪一块,这样标记就需要完整的 m 位主存块号。CPU 访存时,需要与所有 Cache 行的标记进行比较。
全相联映射方式下,主存的地址结构为:

全相联映射方式的优点是灵活,Cache块的冲突概率低,空间利用率高,命中率也高;缺点是标记的速度较慢,实现成本较高,通常需采用昂贵的按内容寻址的相联存储器进行地址映射。
把直接映射和全相联映射两种方式结合起来,就是 组相联映射 方式。
组相联的思路是将 Cache 分成 Q 个大小相等的组,每个主存块可装入对应组的任意一行;它所在的组则按顺序依次排列得到。也就是 组间采用直接映射、而 组内采用全相联映射 的方式。当 Q=1 时,变为全相联映射;当 Q = Cache 行数时变为直接映射。
假设每组有 R 个 Cache 行,则称之为 R 路组相联;例如每组有 2 个 Cache 行时称为 2 路组相联。
类似的例子,假设主存地址为 32 位,按字节编址,主存块大小为 64 B,所以主存块共有 232 / 64 = 226 个;如果 Cache 有 8 行(8 个块),采用 2 路组相联映射方式,那么共有 Q = 8 / 2 = 4 组。对应关系如下:

可以看出,现在的 “组号” 就相当于直接映射方式下的行号,可以由主存块号对组数 Q 取模得到:
Cache组号 = 主存块号 mod Cache组数
更加一般化,假设 Cache 共有 2c 行,分为 Q = 2q 组,主存有 2m 个块;那么 Cache 行号有 c 位,其中高 q 位是组号,主存块号有 m 位。这时每组中的 Cache 行数为 R = 2c / Q = 2c-q ,行号的低 c - q 位就代表了 Cache 行在组内的序号。
在 R 路组相联映射方式中,主存块号为 0、2q、2q+1... 的块,都映射到 Cache 的第 0 组,可以选择组内 2c-q 行的任一行;而主存中块号为 1、2q + 1、2q+1 + 1... 的块,映射到 Cache 的第 1 组,同样可以任选组内的 Cache 行;以此类推。
这样一来,主存块号的低 q 位就对应了 Cache 中的组号;当一个块存放在 Cache 中,只需要高 m - q 位就可以指明它对应的主存中的块号。给每个 Cache 行设置一个 t = m - q 位的标记,那么当主存某块调入 Cache 后,就将其块号的高 t 位设置在对应 Cache 行的标记中。
所以组相联映射方式下,主存地址结构为:

访存过程:
① 先根据访存地址中间的 Cache 组号,找到对应的 Cache 组。
② 然后将该组中每个 Cache 行的标记与主存地址的高位标记进行比较。
③ 若有一个相等且有效位为1,则 Cache 命中,此时根据主存地址中的块内地址,在对应 Cache 行中存取信息;若都不相等,或虽相等但有效位为 0,则 Cache 未命中,此时 CPU 从主存中读出该地址所在的一块信息,并送至对应 Cache 组的任意一个空闲行,将有效位置 1,并设置标记。
组相联映射方式下,路数 R 越大,即每组 Cache 行的数量越多,发生块冲突的概率越低,但比较电路也越复杂。
可以将以上 3 中映射方式对比如下:

如果有新的主存块需要调入 Cache,而可用空间又已经占满,这时就需要替换掉某个旧块,这就产生了替换策略(替换算法)的问题。当采用直接映射时,替换的位置是固定的,无须考虑替换算法;而在采用全相联映射或组相联映射时,就需要使用替换算法来确定到底置换哪个 Cache 行。
常用的替换算法有 随机(RAND)算法、先进先出(FIFO)算法、最近最少使用(LRU)算法 和 最不经常使用(LFU)算法。其中最常考查的是 LRU 算法。
随机算法:随机地确定替换的 Cache 块。实现简单,但未依据局部性原理,命中率较低。
先进先出算法(Fisrt In First Out,FIFO):选择最早调入的行进行替换。实现简单,但也未依据局部性原理。
最近最少使用算法(Least Recently Used,LRU):依据局部性原理,选择近期最久未访问过的 Cache 行作为被替换的行。LRU 算法为每个 Cache 行设置一个计数器,用来记录每个块的使用情况,并根据计数值选择淘汰某个块。
最不经常使用算法(Least Frequently Used,LFU):将一段时间内访问次数最少的 Cache 行换出。与 LRU 类似,也设置一个计数器,Cache 行建立后从 0 开始计数,每访问一次计数器加 1,需要替换时将计数值最小的行换出。
例如,假设一台机器 Cache 有 8 个行,初始值为空,采用 4 路组相联映射方式和 LRU 替换策略,当顺序访问主存块号为 0,4,8,3,0,6,12,0,4,8 时,缓存的命中和替换情况如下:

LRU 算法中利用计数器来表示 Cache 行未被访问的时间。整体原则是:当 Cache 行有新的主存块调入时,计数器开始计数,初始值为 0,此后每遇到一次对 Cache(或 Cache 组)的访问就加 1;如果一次访问 Cache 命中了这一行,就将计数器清 0;每次有 Cache 行计数器清 0,其它行的计数器依然要加 1,不过只需要计数值比当前行更小的那些继续加 1 就可以了。
需要替换时,直接选择计数值最大的行,调入新的块并将计数器置 0。这是由于不同的 Cache 行不会同时开始计数,且每次都同步加 1,所以所有 Cache 行的计数值都不会相同,每次发生替换时必然能够找到一个最大值;而一旦有计数器清 0,比它计数值更大的那些也是都不加 1,依然保持着原有的大小顺序。
这样一来,如果当前 Cache 共有 2c 行,分为 Q = 2q 组,每组行数为 R = 2c / Q = 2c-q ,那么计数器的值就不会超过 R;只要用 c - q 位就可以表示计数器了,这被叫做 LRU 位。因此,计数值的位数与 Cache 组的大小有关。当为 2 路时有 1 位 LRU 位,4 路时有 2 位 LRU 位。LRU 位会同标记、有效位一同作为 Cache 的一部分。
因为 Cache 中的内容是主存块内容的副本,当对 Cache 中的内容进行更新时,就需选用写操作策略使 Cache内容和主存内容保持一致。此时分两种情况:
(1)Cache 写命中(要修改的单元在 Cache 中)
这种情况有两种处理方法:
写直达法
也叫全写法、写穿透法。将数据同时写入 Cache 和主存。这种方法实现简单,一致性好。缺点是降低了速度,时间开销为访存时间。为了减少写入主存的开销,可以在 Cache 和主存之间加一个写缓冲。
写回法
也叫回写法、写返回法。数据只写入 Cache,而不立即写入主存,只有当此块被换出时才写回主存。这种方法效率很高,但一致性较差。在每个 Cache 行中设置一个修改位(脏位),若修改位为 1(“脏”),则说明对应 Cache 行中的块被修改过,替换时须写回主存;若修改位为 0(“净”),则替换时无须写回主存。
(2) Cache 写未命中(要修改的单元不在 Cache中 )
这种情况也有两种处理方法:
写分配法
把数据写入主存,同时将该块调入Cache。这种方法依据了空间局部性原理。
非写分配法
只把数据写入主存,不进行调块。
非写分配法通常与全写法合用,写分配法通常与回写法合用。
这样,还是之前的机器,采用组相联映射的 Cache 共有 2c 行,分为 Q = 2q 组,主存有 2m 个块;那么 Cache 行号有 c 位,其中高 q 位是组号,主存块号有 m 位。这时每组中的 Cache 行数为 R = 2c / Q = 2c-q ,即采用 R 路组相联映射,假如还采用了 LRU 替换策略和回写法,那 Cache 行应该包含以下部分:

现代计算机通常设立多级 Cache,一般两级 Cache 按离 CPU 的远近分别命名为 L1 Cache、L2 Cache,离 CPU 越近则速度越快、容量越小。指令 Cache 与数据 Cache 分离一般在 L1 级,LI Cache 对 L2 Cache 使用全写法,L2 Cache 对主存使用回写法。由于L2 Cache的存在,避免了因频繁写而造成写缓冲溢出的情况。
早期的计算机,CPU 是直接操作主存的,也就是运行程序时,直接给出要访问的实际主存地址。这种方式简单直接,但是会有一些问题:
不同的程序之间需要共享内存,它们的内存地址空间很难隔离,从而导致程序运行的稳定性和安全性降低;
主存容量有限,如果同时执行的程序太多、使用内存太大容易超出容量限制而崩溃。

为了解决这些问题,在主存-辅存这一层次的不断发展中,逐渐形成了虚拟存储系统。
主存和辅存共同构成了虚拟存储器,二者在硬件和系统软件的共同管理下工作。对于应用程序员而言,虚拟存储器是透明的。虚拟存储器具有主存的速度和辅存的容量。
虚拟存储器将主存和辅存的地址空间统一编址,形成一个庞大的地址空间,在这个空间内,用户可以自由编程,而不必在乎实际的主存容量和程序在主存的实际存放位置。用户编程允许涉及的地址称为 虚地址 或 逻辑地址,虚地址对应的存储空间称为虚拟空间。实际的主存地址称为 实地址 或 物理地址,实地址对应的是主存地址空间。虚地址比实地址要大很多。
使用虚拟存储器之后,程序中看到的地址都是逻辑地址。在访存时,逻辑地址首先会被转换成物理地址,然后再访问实际物理内存。

这样一来,每一个程序都有独立的虚拟地址空间,不同进程的虚拟地址空间互相不干扰,提高了安全性。在每个进程看来,就像它自己独享了整个内存。当物理内存不够时,可以将一部分不常使用的内存块换出(Swap-out)到磁盘中,下次使用时再换入到内存中(Swap-in),这样程序就可以使用超过实际物理内存大小的地址空间了。

CPU 使用逻辑地址时,先判断这个逻辑地址对应的内容是否已装入主存。若已在主存中,则通过地址变换,CPU 可直接访问主存指示的实际单元;若不在主存中,则把包含这个字的一页或一段调入主存后再由 CPU 访问。若主存已满,则采用 替换算法 置换主存中的页。
虚拟存储器采用了和 Cache 类似的技术,将辅存中经常被访问的数据副本存放到主存中。但缺页 (或段)而访问辅存的代价很大,因此虚存机制采用 全相联映射,每个页可以存放到主存区域的任意一个空闲页位置。此外,当进行写操作时,不能每次写操作都同时写回磁盘,因而采用 回写法。
页式虚拟存储器 以页为基本单位。虚拟空间与主存空间都被划分成同样大小的页,主存的页称为 实页 或 页框,虚存的页称为 虚页。这样,一个逻辑地址可以分为两段:虚页号 和 页内地址。

虚页和实页之间采用全相联映射,所以从主存中依次查找要访问的虚页号比较困难。所以我们专门引入一个数据结构,用来保存虚页号和实页号的映射关系,这就是 页表。页表可以实现从逻辑地址到物理地址的转换。
页表是一张存放在主存中的虚页号和实页号的对照表,它记录程序的虚页调入主存时被安排在主存中的位置。每个程序都有自己的页表,页表一般长久地保存在内存中。
页表中的每一项,都包含以下几部分:
有效位:也称 装入位,用来表示对应页面是否在主存,若为 1,则表示该虚页已从外存调入主存,此时页表项存放该页的物理页号;若为 0,则表示页面没有调入主存,此时页表项可以存放该页的磁盘地址。
脏位:也称 修改位,用来表示页面是否被修改过,虚拟存储机制中采用回写策略,利用脏位可判断替换时是否需要写回磁盘。
引用位:也称 使用位,用来配合替换策略进行设置,例如是否使用先进先出(FIFO)或近期最少使用(LRU)策略等。

CPU 执行指令时,需要先将逻辑地址转换为主存物理地址。每个进程都有一个 页表基址寄存器,存放该进程的页表首地址,然后根据逻辑地址高位部分的虚页号找到对应的页表项。若装入位为 1,则取出物理页号,和逻辑地址低位部分的页内地址拼接,形成物理地址;若装入位为 0,则说明缺页,需要操作系统进行 缺页处理。缺页时会由 CPU 的内存管理单元(MMU)发出中断,操作系统需要将相应的页从磁盘取回调入主存,并将物理页的地址填入页表中。
页式虚拟存储器的优点是:页的长度固定,页表简单,调入方便。缺点是:最后一页的零头无法利用而造成浪费,并且页不是逻辑上独立的实体,所以处理、保护和共享都不及段式虚拟存储器方便。
有了虚拟存储器之后,CPU 在寻址时所生成的都是虚拟地址。于是 CPU 在取指或者执行访存指令的时候,都需要进行地址翻译,而每次地址翻译都要访问主存中的页表,会产生严重的开销。
依据程序执行的局部性原理,当 CPU 在一段时间内总是经常访问某些页时,若把这些页对应的页表项存放在 Cache 中,就可以不访问主存直接进行地址翻译了;这样明显能提高效率。
在 CPU 芯片中,加入一个专门存放最常访问的页表项的 Cache,就叫做 转址旁路缓存(Translation Lookaside Buffer,TLB),一般简称为 “快表”。TLB 实质上就是 “页表的 Cache”,其中存储了当前最可能被访问到的页表项,其内容是部分页表项的一个副本;所以 TLB 又被称为 页表缓存。

相应地,把放在主存中的页表称为 慢表(Page)。 在地址转换时,先查找快表,若命中,则无须再访问主存中的页表(慢表)。
TLB 通常采用 全相联映射。每个 TLB 项由页表表项内容加上一个 TLB 标记字段以及有效位等标志位组成,TLB 标记用来表示该表项取自页表中哪个虚页号对应的页表项,其内容就是该页表项对应的虚页号。

TLB 和 Cache 都属于缓存,不过它们的用途不同:
TLB 用来保存最近经常访问的页表项,是对 地址映射 的缓存。
Cache 用来保存最近经常访问的主存块,是对 数据内容 的缓存。
所以对于一个有虚拟存储器的计算机系统,可以先通过 TLB 对逻辑地址的翻译进行加速,快速得到一个物理地址;然后再通过 Cache 的地址转换判断是否 Cache 命中,从而对数据的访问进行加速。
这样就将 Cache 和 TLB 结合起来,构成了多级存储系统。下面就是一个具有 2 路组相联映射 Cache 和 TLB 的多级存储系统;CPU 给出的是一个 32 位的逻辑地址,TLB 采用全相联映射,每一项都有一个比较器。

查找时将虚页号与每个 TLB 标记同时进行比较,若有某一项相等且对应有效位为 1,则 TLB 命中,此时可直接通过TLB进行地址转换;若未命中,则 TLB 缺失,需要访问主存去査页表。
图中所示是 两级页表方式,虚页号被分成 页目录索引 和 页表索引 两部分,由这两部分得到对应的页表项,从而进行地址转换,并将相应表项调入TLB。若 TLB 已满,则还需要采用替换策略。
完成由逻辑地址到物理地址的转换后,Cache 机构根据映射方式将物理地址划分成多个字段,然后根据映射规则找到对应的 Cache 行或组,将对应 Cache 行中的标记与物理地址中的高位部分进行比较,若相等且对应有效位为1,则 Cache 命中,此时根据块内地址取岀对应的字送 CPU。
查找时,快表和慢表也可以同步进行。若快表中有此虚页号,则能很快地找到对应的实页号,并使慢表的查找作废,从而就能做到虽采用虚拟存储器,但访问主存速度几乎没有下降。
在一个具有 Cache 和 TLB 的虚拟存储系统中,CPU —次访存操作可能涉及对 TLB、页表(Page)、Cache、主存和磁盘的访问。CPU 在访存过程中存在 3 种缺失情况:
① TLB 缺失:要访问页面的页表项不在 TLB 中;
② Page 缺失:要访问的页面不在主存中。
③ Cache 缺失:要访问的主存块不在 Cache 中;
需要注意,如果 TLB 命中,那么 Page 一定命中;如果 Page 缺失,那么 Cache 一定缺失。所以有如下一些组合情况:

第 1 种情况下,无须访问主存,地址转换和访问数据都可以通过高速缓存完成;
第 2 种和第 3 种情况都 需要访问一次主存,第 2 种是访问主存取数据,第 3 种是访问页表转换物理地址;
第 4 种情况需要访问两次主存,访问页表转换物理地址一次、访存取数据一次;
第 5 种情况就是 “缺页异常”,需要访问磁盘,并且至少访问两次主存。
Cache 缺失处理由硬件完成;缺页处理由软件完成,操作系统通过 “缺页异常处理程序” 实现;而 TLB 缺失既可以用硬件也可以用软件来处理。
在段式虚拟存储器中,将虚拟空间用 “段” 进行分割;而段是按程序的逻辑结构划分的,各段的长度因程序而异。虚地址分为两部分:段号 和 段内地址。虚地址到实地址之间的变换是由 段表 来实现的。段表的每行记录与某个段对应的段号、 装入位和段长等信息。由于段的长度可变,所以段表中要给出各段的起始地址与段的长度。

CPU 用逻辑地址访存时,先根据段号与段表基地址拼接成对应的段表项,再根据该段表项的装入位判断该段是否已调入主存(装入位为 “1”,表示该段已调入主存)。当已调入主存时,从段表读岀该段在主存的起始地址,与段内地址相加,得到对应的主存物理地址。
段式虚拟存储器的优点是,段的分界与程序的逻辑分界相对应,这使得程序易于编译、修改和保护,也便于多道程序共享;缺点是因为段长度可变,分配空间不便,容易留下碎片,造成浪费。
把程序按逻辑块分段,段内再分页,主存空间也划分为大小相等的页,程序对主存的调入调出仍以 页 为基本单位,这样的虚拟存储器称为 段页式虚拟存储器。在段页式虚拟存储器中,每个程序对应一个 段表,每段对应一个 页表,段的长度必须是页长的整数倍,段的起点必须是某一页的起点。

虚地址分为 段号、段内页号、页内地址 3 部分。CPU 根据虚地址访存时,首先根据段号得到段表地址,然后从段表中取出该段的页表起始地址,与虚地址段内页号拼接,得到页表地址;最后从页表中取出实页号,与页内地址拼接成主存实地址。
段页式虚拟存储器的优点是,兼具页式和段式虚拟存储器的优点,可以按段实现共享和保护;缺点是在地址变换过程中需要两次查表,系统开销较大。
相同点:
目标都是为了提高系统性能,两者都有容量、速度、价格的梯度。
都把数据划分为信息块,作为基本的传送单位,虚拟存储器系统的信息块更大。
都有地址的映射算法、替换算法、更新策略等问题。
依据局部性原理,应用“快速缓存”思想,将活跃的数据放在相对高速的部件中。
不同点:
Cache主要是为了提高系统速度,而虚拟存储器是为了解决主存容量不足的问题。
Cache由硬件实现,对所有程序员透明;虚拟存储器由操作系统和硬件共同实现,对应用程序员透明。
在不命中时对性能的影响不同。因为 CPU 的速度约为 Cache 的 10 倍,而主存的速度为硬盘的 100 倍以上,因此虚拟存储器系统在不命中时对系统性能的影响更大。
CPU 与 Cache 和主存有直接通路,而辅存与 CPU 没有直接通路。在 Cache 不命中时,CPU 能和主存直接通信;而虚拟存储器系统在不命中时,须先将数据从硬盘调入主存,CPU 才能访问。
【2010真题】下列有关 RAM 和 ROM 的叙述中,正确的是 ( )。
Ⅰ . RAM 是易失性存储器, ROM 是非易失性存储器 Ⅱ . RAM 和 ROM 都采用随机存取方式进行信息访问 Ⅲ. RAM 和 ROM 都可用作 Cache Ⅳ. RAM 和 ROM 都需要进行刷新
A.仅Ⅰ 和Ⅱ B.仅Ⅱ 和Ⅲ C.仅Ⅰ 、 Ⅱ 和Ⅳ D.仅Ⅱ 、 Ⅲ和Ⅳ
答案:A
【2011真题】下列各类存储器中,不采用随机存取方式的是 ( )。
A. EPROM B. CDROM C. DRAM D. SRAM
答案:B
【2015真题】下列存储器中,在工作期间需要周期性刷新的是 ( )。
A. SRAM B. SDRAM C. ROM D. FLASH
答案:B
【2012真题】下列关于闪存(Flash Memory)的叙述中,错误的是 ( )。
A. 信息可读可写,并且读、写速度一样快
B. 存储元由 MOS 管组成,是一种半导体存储器
C. 掉电后信息不丢失,是一种非易失性存储器
D. 采用随机访问方式,可替代计算机外部存储器
答案:A
【2011真题】某计算机存储器按字节编址,主存地址空间大小为 64MB,现用 4MB×8 位的 RAM 芯片组成 32MB 的主存储器,则存储器地址寄存器 MAR 的位数至少是 ( )。
A. 22 位 B. 23 位 C. 25 位 D. 26 位
答案:D
要点:MAR 的位数跟主存地址空间有关,与主存实际容量无关。
【2010真题】假定用若干个 2K×4 位的芯片组成一个 8K×8 位的存储器,则地址 0B1FH 所在芯片 的最小地址是 ( )。
A. 0000H B. 0600H C. 0700H D. 0800H
答案:D
【2014真题】某容量为 256MB 的存储器由若干 4Mx8 位的 DRAM 芯片构成, 该 DRAM 芯片的地址引脚和数据引脚总数是 ( ) 。
A. 19 B. 22 C. 30 D. 36
答案:A
要点:DRAM 采用地址复用技术,地址线为正常的一半。
【2016真题】某存储器容量为 64KB,按字节编址,地址 4000H~5FFFH 为 ROM 区,其余为 RAM 区。若采用 8K× 4 位的 SRAM 芯片进行设计,则需要该芯片的数量是 ( )。
A. 7 B. 8 C. 14 D. 16
答案:C
【2018真题】假定 DRAM 芯片中存储阵列的行数为 r、列数为 c,对于一个2Kx1 位的 DRAM 芯片,为保证其地址引脚数最少,并尽量减少刷新开销,则 r、c的取值分别是 ( )。
A. 2048、1 B. 64、32 C. 32、64 D. 1、2048
答案:C
要点:DRAM 芯片采用地址复用技术,按行刷新。
【2017真题】某计算机主存按字节编址,由 4 个 64M × 8 位的 DRAM 芯片采用交叉编址方式构成,并与 宽度为 32 位的存储器总线相连,主存每次最多读写 32 位数据。若 double 型变量 x 的主存地址为 804 001AH,则读取 x 需要的存储周期数是 ( )。
A. 1 B. 2 C. 3 D. 4
答案:C
要点:多体低位交叉存储器可增大带宽,每个存储周期对所有芯片各读取一次;double 型变量占 8 个字节。
【2021真题】某计算机的存储器总线中有 24 位地址线和 32 位数据线,按字编址,字长为 32 位。若 00 0000H~3F FFFFH 为 RAM 区,则需要 512K x 8 位的 RAM 芯片数为 ( )。
A.8 B.16 C.32 D.64
答案:C
【2022真题】某内存条包含 8 个 8192 x 8192 x 8 位的 DRAM 芯片,按字节编址,支持突发 (burst) 传送方式,对应存储器总线宽度为 64 位,每个 DRAM 芯片内有一个行缓冲区 (row buffer)。 下列关于该内存条的叙述中,不正确的是 ( )。
A. 内存条的容量为 512 M B. 采用多模块交叉编址方式
C. 芯片的地址引脚为 26 位 D. 芯片内行缓冲有 8192 x 8 位
答案:C
要点:DRAM 芯片采用地址复用技术,地址引脚为正常的一半;行缓冲区的大小就是一行的大小。
【2013真题】下列选项中,用于提高 RAID 可靠性的措施有 ( )。
I.磁盘镜像 II. 条带化 III.奇偶校验 IV.增加 Cache机制
A.仅I、II B.仅I、III C.仅I、III和IV D.仅II、III和IV
答案:B
要点:RAID 通过条带化来实现并行读写;通过磁盘镜像和校验增加可靠性。
【2013真题】某磁盘的转速为 10000 转/分,平均寻道时间是 6ms,磁盘传输速率是 20MB/s,磁盘控 制器延迟为 0.2ms,读取一个 4KB 的扇区所需的平均时间约为 ( )。
A.9ms B.9.4 ms C.12 ms D. 12.4 ms
答案:B
要点:平均访问时间 = 平均寻道时间 + 平均等待时间 + 数据传输时间 + 控制器延迟 。
【2015真题】若磁盘转速为 7200 转/分,平均寻道时间为 8 ms,每个磁道包含 1000 个扇区,则访问一个扇区的平均存取时间大约是 ( )。
A. 8.1 ms B. 12.2 ms C. 16.3 ms D. 20.5 ms
答案: B
要点:平均访问时间 = 平均寻道时间 + 平均等待时间 + 数据传输时间 。
【2019真题】下列关于磁盘存储器的叙述中,错误的是 ( )。
A.磁盘的格式化容量比非格式化容量小 B.扇区中包含数据、地址和校验等信息
C.磁盘存储器的最小读写单位为一字节 D.磁盘存储器由磁盘控制器、磁盘驱动器和盘片组成
答案:C
要点:磁盘最小读写单位为一个扇区。
【2014真题】采用指令 Cache 与数据 Cache 分离的主要目的是 ( ) 。
A. 降低 Cache 的缺失损失 B. 提高 Cache 的命中率
C. 降低 CPU 平均访存时间 D. 减少指令流水线资源冲突
答案:D
【2017真题】某C语言程序段如下:
xxxxxxxxxx61for(i=0; i<=9; i++)2{ 3 temp=1;4 for(j=0; j<=i; j++)temp * =a[j];5 sum + =temp;6}下列关于数组 a 的访问局部性的描述中,正确的是 ( )。
A.时间局部性和空间局部性皆有
B.无时间局部性,有空间局部性
C.有时间局部性,无空间局部性
D.时间局部性和空间局部性皆无
答案:A
【2015真题】假定主存地址为 32 位,按字节编址,主存和 Cache 之间采用直接映射方式,主存块大小为 4 个字,每字 32 位,采用回写(Write Back)方式,则能存放 4K 字数据的 Cache 的总容量的位数至少是 ( )。
A. 146K B. 147K C. 148K D. 158K
答案:C
要点:直接映射方式下,主存块号位数 m = 标记位数 t + Cache 行号位数 c ;
回写策略下,每个 Cache 行需要另加 1 位修改位(脏位);
Cache 行总位数 = 1位有效位 + 1位修改位 +(LRU位)+ 标记 + 数据。
【2022真题】若计算机主存地址为 32 位,按字节编址,某 Cache 的数据区容量为 32KB, 主存块大小为 64B, 采用 8 路组相联映射方式,该 Cache 中比较器的个数和位数分别为 ( )。
A. 8, 20 B. 8, 23 C. 64, 20 D. 64, 23
答案:A
要点:Cache 中比较器的个数就是组相联的路数 R,比较器的位数就是标记 t 的位数。
组相联映射方式下,主存块号位数 m = 标记位数 t + Cache 组号位数 q
【2016真题】有如下 C 语言程序段:
xxxxxxxxxx21for(k=0; k<1000; k++)2 a[k] = a[k]+32;若数组 a 及变量 k 均为 int 型, int 型数据占 4B,数据 Cache 采用直接映射方式,数据区大小为 1KB、块大小为 16B,该程序段执行前 Cache 为空,则该程序段执行过程中访问数组 a 的 Cache 缺失率约为 ( )。
A. 1.25% B. 2.5% C. 12.5% D. 25%
答案:C
要点:循环内语句需要对 a[k] 访问两次,第一次未命中,并将其所在块调入主存;第二次命中;在该块中的后面三个元素的 6 次访问也都命中。
【2010真题】下列命中组合情况中,一次访存过程中不可能发生的是 ( )。
A. TLB 未命中, Cache 未命中, Page 未命中
B. TLB 未命中, Cache 命中, Page 命中
C. TLB 命中, Cache 未命中, Page 命中
D. TLB 命中, Cache 命中, Page 未命中
答案:D
要点:TLB 命中,Page 必命中;Page 缺失,Cache 必缺失。
【2019真题】下列关于缺页处理的叙述中,错误的是 ( )。 A.缺页是在地址转换时 CPU 检测到的一种异常 B.缺页处理由操作系统提供的缺页处理程序来完成 C.缺页处理程序根据页故障地址从外存读入所缺失的页 D.缺页处理完成后回到发生缺页的指令的下一条指令执行
答案:D
要点:缺页处理完成后回到发生缺页的指令继续执行。
【2020真题】下列关于 TLB 和 Cache 的叙述中,错误的是 ( )。
A.命中率都与程序局部性有关 B.缺失后都需要去访问主存
C.缺失处理都可以由硬件实现 D.都由 DRAM 存储器组成
答案:D
【2015真题】假定编译器将赋值语句 “ x=x+3; ” 转换为指令 “add xaddr, 3”,其中, xaddr 是 x 对应的存储单元地址。若执行该指令的计算机采用页式虚拟存储管理方式,并配有相应的 TLB,且 Cache 使用直写(Write Through)方式,则完成该指令功能需要访问主存的次数至少是 ( )。
A. 0 B. 1 C. 2 D. 3
答案:B
要点:直写方式下,每次写入都必须将数据同时写入 Cache 和主存。
【2013真题】某计算机主存地址空间大小为256 MB,按字节编址。虚拟地址空间大小为 4GB,采用页式存储管理,页面大小为 4KB,TLB (快表)采用全相联映射,有 4 个页表项,内容如下表所示

则对虚拟地址 03FF F180H 进行虚实地址变换的结果是 ( )。
A.015 3180H B.003 5180H C.TLB缺失 D.缺页
答案:A
要点:虚页号的位数,可以由虚页的个数推出;TLB 中保存的标记就是虚页号。
【2022真题】某计算机主存地址为 24 位,采用分页虚拟存储管理方式,虚拟地址空间大小为 4GB, 页大小为4KB, 按字节编址。 某进程的页表部分内容如下表所示。

当 CPU 访问虚拟地址 0008 2840H 时,虚-实地址转换的结果是 ( )。
A. 得到主存地址 02 4840H B. 得到主存地址 18 0840H
C. 得到主存地址 01 8840H D. 检测到缺页异常
答案:C
【2016真题】某计算机采用页式虚拟存储管理方式,按字节编址,虚拟地址为 32 位,物理地址为 24 位,页大小为 8KB;TLB 采用全相联映射; Cache 数据区大小为 64KB,按 2 路组相联方式组织,主存块大小为 64B。存储访问过程的示意图如下。

请回答下列问题。
(1)图中字段 A~G 的位数各是多少? TLB 标记字段 B 中存放的是什么信息?
(2)将块号为 4099 的主存块装入到 Cache 中时,所映射的 Cache 组号是多少?对应的 H 字段内容是什么?
(3) Cache 缺失处理的时间开销大还是缺页处理的时间开销大?为什么?
答案:
(1)页大小为 8KB,页内偏移地址为 13 位,故 A=B=32-13=19; D=13; C=24-13=11;
主存块大小为 64B,故 G=6。
2 路组相联,每组数据区容量有 64 B×2=128B,共有 64KB/128B=512 组,故 F=9;
E = 24-G-F = 24-6-9 = 9。
因而 A=19, B=19, C=11, D=13, E=9, F=9, G=6。
TLB 中标记字段 B 的内容是虚页号,表示该 TLB 项对应哪个虚页的页表项。
(2)块号 4099=00 0001 0000 0000 0011B,因此,所映射的 Cache 组号为 0 0000 0011B=3,对应的 H 字段内容为 0 0000 1000B。
(3) Cache 缺失带来的开销小,而处理缺页的开销大。 因为缺页处理需要访问磁盘,而 Cache 缺失只要访问主存。
【2018真题】某计算机采用页式虚拟存储管理方式,按字节编址。CPU 进行存储访问的过程如图所示。

根据上图回答下列问题。
(1) 主存物理地址占多少位?
(2) TLB 采用什么映射方式?TLB 用 SRAM 还是 DRAM 实现?
(3) Cache 采用什么映射方式?若 Cache 采用 LRU 替换算法和回写(Write Back)策略,则 Cache 每行中除数据(Data)、Tag 和有效位外,还应有哪些附加位?Cache 总容量是多少?Cache 中有效位的作用是什么?
(4) 若 CPU 给出的虚拟地址为 0008 C040H,则对应的物理地址是多少?是否在 Cache 中命中?说明理由,若 CPU 给出的虚拟地址为 0007 C260H,则该地址所在主存块映射到的 Cache 组号是多少?
答案:
(1)物理地址由实页号和页内地址拼接,因此其位数为 16+12 = 28;或直接可得 20+3+5 = 28。
(2) TLB 采用全相联映射,可以把页表内容调入任一块空 TLB 项中, TLB 中每项都有一个比较器,没有映射规则,只要空闲就行。 TLB 采用静态存储器 SRAM,读写速度快,但成本高,多用于容量较小的高速缓冲存器。
(3)图中可以看到, Cache 中每组有两行,故采用 2 路组相联映射方式。
因为是 2 路组相联并采用 LRU 替换算法,所以每行(或每组)需要 1 位 LRU 位;因为采用回写策略,所以每行有 1 位修改位(脏位),根据脏位判断数据是否被更新,如果脏位为 1 则需要写回内存。
28 位物理地址中 Tag 字段占 20 位,组索引字段占 3 位,块内偏移地址占 5 位,故 Cache 共有 23 = 8组,每组 2 行,每行有 25 = 32B;故 Cache 总容量为 8×2×(20+1+1+1+32×8) = 4464 位 = 558 字节。
Cache 中有效位用来指出所在 Cache 行中的信息是否有效。
(4)虚拟地址分为两部分:虚页号、页内地址;物理地址分为两部分:实页号、页内地址。
利用虚拟地址的虚页号部分去查找 TLB 表(缺失时从页表调入),将实页号取出后和虚拟地址的页内地址拼接,就形成了物理地址。
虚页号 008CH 恰好在 TLB 表中对应实页号 0040H(有效位为 1,说明存在),虚拟地址的后 3 位为页内地址 040H,则对应的物理地址是 0040040H。物理地址为 0040040H,其中高 20 位 00400H 为标志字段,低 5 位 00000B 为块内偏移量,中间 3 位 010B 为组号 2,因此将 00400H 与 Cache 中的第 2 组两行中的标志字段同时比较,可以看出,虽然有一个 Cache 行中的标志字段与 00400H 相等,但对应的有效位为 0,而另一 Cache 行的标志字段与 00400H 不相等,故访问 Cache 不命中。
因为物理地址的低 12 位与虚拟地址低 12 位相同,即为 0010 0110 0000B。根据物理地址的结构,物理地址的后八位 01100000B 的前三位 011B 是组号,因此该地址所在的主存映射到 Cache 组号为 3。
【2020真题】假定主存地址为 32 位,按字节编址,指令 Cache 和数据 Cache 与主存之间均采用 8 路组相联映射方式,直写(WriteThrough)写策略和 LRU 替换算法,主存块大小为 64B,数据区容量各为 32KB。开始时 Cache 均为空。请回答下列问题。 (1) Cache 每一行中标记(Tag)、LRU 位各占几位?是否有修改位? (2) 有如下 C 语言程序段:
xxxxxxxxxx21for (k=0; k<1024 ;k++)2 s[k]=2*s[k];若数组 s 及其变量 k 均为 int 型,int 型数据占 4B,变量 k 分配在寄存器中,数组 s 在主存中的起始地址为0080 00C0H,则该程序段执行过程中,访问数组 s 的数据 Cache 缺失次数为多少?
(3) 若 CPU 最先开始的访问操作是选取主存单元 0001 0003H 中的指令,简要说明从 Cache 中访问该指令的过程,包括 Cache 缺失处理过程。
答案:
(1)主存块大小为 64B=26 字节,故主存地址低 6 位为块内地址, Cache 组数为 32KB/(64B×8) = 64=26, 故主存地址中间 6 位为 Cache 组号, 主存地址中高 32-6-6=20 位为标记;
采用 8 路组相联映射, 故每行中 LRU 位占 3 位;
采用直写方式,故没有修改位。
(2)因为数组s的起始地址最后 6 位全为 0, 故 s 位于一个主存块开始处,共占 1024×4B/64B=64 个主存块;
执行程序段过程中,每个主存块中的 64B/4B=16 个数组元素依次读、写 1 次, 因而对于每个主存块,总是第一次访问缺失,以后每次命中。
综上, 数组 s 的数据 Cache 访问缺失次数为 64 次。 (3) 0001 0003H = 0000 0000 0000 0001 0000 000000 000011B, 根据主存地址划分可知,组索引为 0, 故该地址所在主存块被映射到指令 Cache 第 0 组;
因为 Cache 初始为空,所有 Cache 行的有效位均为 0, 所以 Cache 访问缺失。此时,将该主存块取出后存入指令 Cache 第 0 组的任意一行,并将主存地址高 20 位(00010H)填人该行标记字段,设置有效位,修改 LRU 位, 最后根据块内地址 000011B 从该行中取出相应内容。
【2021真题】假设计算机 M 的主存地址为 24 位,按字节编址;采用分页存储管理方式,虚拟地址为 30 位,页大小为 4 KB;TLB 采用 2 路组相联方式和 LRU 替换策略,共 8 组。请回答下列问题。
(1) 虚拟地址中哪几位表示虚页号?哪几位表示页内地址?
(2) 已知访问 TLB 时虚页号高位部分用作 TLB 标记,低位部分用作 TLB 组号,M 的虚拟地址中哪几位是 TLB 标记?哪几位是 TLB 组号?
(3) 假设 TLB 初始时为空,访问的虚页号依次为 10、12、16、7、26、4、12 和 20,在此过程中,哪一个虚页号对应的 TLB 表项被替换?说明理由。
(4) 若将 M 中的虚拟地址位数增加到 32 位,则 TLB 表项的位数增加几位?
答案:
注意:对于本题的 TLB,需要采用处理 Cache 的方式求解。
(1)按字节编址, 页面大小为 4 KB=212B,所以页内地址为 12 位。 虚拟地址中高 30-12=18 位 表示虚页号, 虚拟地址中低 12 位表示页内地址。
(2)TLB 采用 2 路组相联方式,共 8=23 组,用 3 位来 标记组号。 虚拟地址(或虚页号)中高 18-3=15 位为 TLB 标记, 虚拟地址中随后 3 位(或虚页号中低 3 位)为 TLB 组号。
(3)虚页号 4 对应的 TLB 表项被替换。 因为虚页号与 TLB 组号的映射关系为
TLB 组号=虚页号 mod TLB 组数=虚页号 mod 8,
因此,虚页号 10,12,16, 7,26,4,12,20 映射到的 TLB 组号依次为 2,4,0,7,2,4,4,4。
TLB 采用 2 路组相联方式, 从上述映射到的TLB 组号序列可以看出,只有映射到 4 号组的虚页号数量大于 2, 相应虚页号依次是 12,4,12 和 20。根据 LRU 替换策略, 当访问第 20 页时, 虚页号 4 对应的TLB 表项被替换出来。
(4)虚拟地址位数增加到 32 位时, 虚页号增加了 32-30=2 位, 使得每个TLB 表项中的标记字段增加 2 位, 因此,每个TLB 表项的位数增加 2 位。
(一)指令系统的基本概念
(二)指令格式
(三)寻址方式
(四)数据的对齐和大/小端存放方式
(五)CISC 和 RISC 的基本概念
(六)高级语言程序与机器级代码之间的对应
编译器、汇编器和链接器的基本概念
选择结构语句的机器级表示
循环结构语句的机器级表示
过程(函数)调用对应的机器级表示
本章内容是也是考研考察的一个重点,一般会与第二章、第三章和第五章进行结合,往往以综合应用题的形式出现。而常见的寻址方式很容易以单项选择题的形式进行考察。
需要重点掌握的内容包括:
指令的格式及相关概念,定长与扩展操作码格式。
常见的寻址方式、特点及有效地址的计算。
常用的汇编指令,过程调用、选择语句和循环语句的机器级表示,标志位及其使用。
CISC 和 RISC 的基本概念,CISC 和 RISC 的比较。
| 考点 | 考查次数 | |
|---|---|---|
| 单项选择题 | 综合应用题 | |
| 常见寻址方式 | 9 | 5 |
| 指令格式 | 4 | 6 |
| 程序的机器级代码表示 | 0 | 2 |
| CISC 和 RISC | 1 | 1 |
| 寻址方式 | 有效地址 EA | 访存次数 |
|---|---|---|
| 立即寻址 | 不需要 | 0 |
| 直接寻址 | EA = A | 1 |
| 间接寻址 | EA = (A) | 2(一次间址) |
| 隐含寻址 | 隐含在寄存器中 | 0 |
| 寄存器寻址 | EA = Ri | 0 |
| 寄存器间接寻址 | EA = (Ri) | 1 |
| 基址寻址 | EA = (BR) + A | 1 |
| 变址寻址 | EA = (IX) + A | 1 |
| 相对寻址 | EA = (PC) + A | 1 |
| 堆栈寻址 | EA = (SP) - 1 (入栈) 或 EA = (SP) (出栈) | 0(硬堆栈)或 1(软堆栈) |
指令集的不同会导致一个处理器的基础结构不同。
最早的 CPU 出现在 20 世纪 70 年代,当时的集成电路技术制约了一块芯片上能实现什么,所以它们的指令集都非常有限;以 8 位 CPU 为主,Intel 8080、MOS 6502、MC 6800 就是其中的代表。之后随着超大规模集成电路技术的发展,微处理器发展得非常迅速,指令集也越来越复杂;并且出现了将处理器、RAM、ROM 和 I/O接口等计算机基本部件集成到一个芯片上的微控制器(MCU),这就是 单片机,Intel 的 MCS-51 系列就是其中的代表。


现代计算机按照处理器的 指令集架构(Instruction Set Architecture,ISA)主要可以分为两种:
CISC(Complex Instruction Set Computer,复杂指令集计算机)
RISC(Reduced Instruction Set Computer,精简指令集计算机)
面对越来越多的需求,计算机需要完成的任务越来越重,对计算机性能的要求也越来越高。关于如何提升计算机性能,两种指令集架构代表了不同的思路。
CISC 通过设置更多、更复杂的指令来实现更多的功能,这样就可以减少运行程序所需的指令数,依靠硬件提升运行速度。
这种架构的代表公司就是 Intel,它在 1978 年推出了著名的 16 位微处理器 8086,此后又推出了 80286 和 32 位的 80386、80486 以及 奔腾(Pentium)处理器,因而这一系列的处理器都被称为 “ x86 架构 ”。如今已经进入 64 位时代,这一家族采用的指令集架构称为 “ x86-64 ” 或简称 “ x64 ”,代表就是酷睿(Core)处理器。AMD 公司生产的 CPU 主要也是 x86/64 架构。

CISC 的主要特点如下:
指令数目庞大且复杂。Intel 描述全套指令的文档有 1200 多页;
指令字长是可变的。x86-64 的指令长度可以是 1 ~ 15 个字节;
每个指令可以执行若干简单操作,例如存储器读取、存储、计算操作等,因此很多指令都能进行访存操作,可以对内存中的操作数直接进行算术和逻辑运算;
寻址方式丰富。内存中操作数的指示符可以有各种组合,包括偏移量、基址和变址寄存器以及伸缩因子;
逻辑控制电路复杂,寄存器较少,使用主存中的软堆栈来实现堆栈寻址;
对机器级程序来说,实现细节是不可见的,全部由硬件完成。
因此,CISC 有着非常明显的优缺点。
优点:
运行程序所需的指令数少,有效提升性能;
更加依赖硬件实现功能,编写软件代码较为简单。
缺点:
指令执行时间差异很大,复杂的指令需要若干时钟周期才可以实现;
指令的使用频率差异很大,很多复杂指令的使用率并不高;
有些指令非常复杂,以至于无法通过组合逻辑电路直接完成,所以需要采用 微程序控制;
编译器能做的优化有限。
庞大的指令系统设计起来非常复杂,研制周期变得很长,成本耗费巨大。而且对传统 CISC 的测试发现,典型程序中 80% 的语句只用到了系统中 20% 的指令,这被称为 80 - 20 规律。
于是人们开始了对指令系统合理性的研究,试图通过 “做减法” 来从另一个角度提升效率,这样就产生了 RISC。
RISC 的主要思路是减少指令种类、简化指令功能,通过降低单个指令的执行周期数(CPI)来提高 MIPS,从而提升运行速度。
由于简化了指令集,CPU 芯片就不需要太大的空间来制作逻辑控制电路,而可以加入更多的寄存器,这样就可以让数据运算更快。基于这样的想法,IBM 公司开发出了第一代 RISC 架构计算机;与此同时,斯坦福大学的 RISC 研究课题 MIPS,考虑到了对处理器流水线的优化,研究结果转化成了后来 MIPS 公司的 R 系列产品。
1985 年,Acom 公司设计出了基于 RISC 指令集的 32 位计算机,简称 ARM(Acorn RISC Machine)。1990 年,Acom 改组为 ARM 公司,专门研发芯片架构、出售芯片技术授权,它的指令集架构就被称为 “ ARM 架构 ”。由于 ARM 架构低功耗、低成本的特点,在嵌入式处理器中得到了非常广泛的应用:智能手机、智能汽车、智能家居等各种领域都可以看到 ARM 架构处理器的身影。

如今 RISC 架构已经发展到第五代,称为 RISC-V,这是一个完全开源的指令集架构,采用宽松的 BSD 协议,企业可以完全自由免费使用,同时也容许企业添加自有指令集进行拓展。
RISC 的主要特点有:
指令数量比 CISC 要少得多;只使用频度较高的简单指令,通过简单指令的组合实现复杂指令功能。早期的 RISC 指令通常少于 100 个;
指令字长是固定的。早期的 RISC 通常将所有的指令都编码为 4 个字节;
允许访存的指令只有 load 和 store,这被称为 load / store 体系结构。因此只能对寄存器中的操作数进行算术和逻辑运算,不能直接对内存中的操作数进行运算;
寻址方式简单,一般所有的 load / store 都通过寄存器中的内容和指令字段中的偏移量来实现;
逻辑控制电路比较简单,有大量的通用寄存器,可以使用硬堆栈来实现堆栈寻址。
对机器级程序来说,实现细节是可见的。因此编译器需要在一些约束条件下进行性能优化;
因此,RISC 的优缺点和 CISC 相比也是非常明显的。
优点:
CPI 大大降低,并且通过大量寄存器减少了访存次数,有效提升性能;
采用流水线技术,大部分指令在一个时钟周期完成;采用超标量和超流水线技术,可以使每条指令的平均执行时间小于一个时钟周期;
控制器便于设计和实现,采用组合逻辑控制(硬布线),不用微程序控制;
可以利用编译器对程序性能进行优化。
低功耗、低成本
缺点:
指令的种类和寻址方式都比较少,编写软件比较麻烦;有些早期的 RISC 机器甚至没有乘法指令,需要用一系列加法来实现;
对编译器的要求比较高,编译器采用不同的优化策略可以显著改变运行性能;
不同指令系统间兼容性差。
下表中详细列出了 CISC 和 RISC 的特点对比:
| 对比项目 | CISC | RISC |
|---|---|---|
| 指令数量 | 多 | 较少 |
| 指令字长 | 不固定 | 固定 |
| 可访存指令 | 无限制 | Load / Store |
| 各种指令使用频率 | 相差很大 | 相差不大 |
| 各种指令执行时间 | 相差较大 | 绝大多数在一个时钟周期内完成 |
| 寻址方式 | 多 | 少 |
| 通用寄存器数量 | 较少 | 多 |
| 堆栈寻址 | 软堆栈 | 硬堆栈 |
| 控制方式 | 微程序控制 | 组合逻辑控制(硬布线) |
| 机器级程序实现细节 | 不可见 | 可见 |
| 编译器 | 难以优化 | 需要优化 |
| 指令流水线 | 可以通过一定的方式实现 | 必须实现 |
| 功耗 | 较高 | 较低 |
| 兼容性 | 较好 | 较差 |
| 主要应用领域 | PC 和服务器 | 嵌入式设备 |
如今,RISC 机器在发展进化的过程中,逐渐引入了更多的指令;而 CISC 机器也会充分利用高性能的流水线结构。商品化的计算机一般都会将 RISC 和 CISC 结合起来,取长补短。
(1)通用寄存器
x86 架构的 CPU 中会设置一组 通用寄存器,用来存储整数数据和指针(地址)。
最初的 8086 有 8 个 16 位的寄存器,分别叫做 ax、bx、cx、dx、si、di、bp、sp,每个寄存器都有各自特殊的用途,这都体现在它们的名字中。当扩展到 32 位架构(标准名称为 IA32)时,这些寄存器也都扩展为 32 位,名称前加上了 ’‘e“ 表示扩展(extended)。
每个 32 位的通用寄存器,都可以将低 16 位当作一个 16 位寄存器独立使用,最低 8 位当作一个 8 位寄存器使用;而 ax、bx、cx、dx 的高低字节都可以分别作为两个 8 位寄存器,称为 ah、bh、ch、dh 和 al、bl、cl、dl。

这里用途最为特殊的,就是堆栈指针 ebp 和 esp,它们配合可以很容易地实现子过程的调用和返回;另外,一般也经常用 ebp + 偏移量 的形式来定位存放在栈中的局部变量。
扩展为 64 位的 x86-64 架构后,原先的 8 个 32 位寄存器全部扩展到 64 位,标号以 r 开头;此外还新增了 8 个通用寄存器,标号为 r8 ~ r15。
(2)指令指针寄存器
除通用寄存器外,x86 架构的 CPU 还会设置一系列特殊功能的寄存器。其中最为重要的就是 指令指针寄存器 IP(Instruction Pointer),它存放了下一条要执行的指令的地址;很明显,这其实就是我们之前介绍的 程序计数器 PC。
最初的 ip 也是 16 位的;到了 IA32 架构下,指令指针寄存器也扩展为 32 位,称为 eip;而到了 x86-64 时代,对应也扩展成了 64 位的指令寄存器,称为 rip。
(3)标志寄存器
标志寄存器 flags 里面有众多标志位,记录了 CPU 执行指令过程中的一系列状态,大都由 CPU 自动设置和修改。
ZF 零标志
CF 进位标志
SF 符号标志
OF 溢出标志
PF 奇偶标志
TF 跟踪标志
IF 中断标志
...
很显然,标志寄存器其实就是之前提到的 程序状态字 PSW。IA32 架构下标志寄存器为 32 位,称为 efl(eflags),除去一些不使用的保留位外,每一位都对应着一个状态标志;x86-64 架构下扩展为 64 位,不过扩展的高位都没有使用,相当于还是 32 位。
对于 x86 指令集的汇编代码,也有两种不同的指令格式。
ATT 格式:由 AT&T 公司而得名,这是 GCC 等常用工具的默认格式。
Intel 格式:Intel 文档中和 Microsoft 编程工具采用的汇编格式。
这两种格式整体风格相似,但也有很多不同:

x86 是复杂指令集架构,支持多种寻址方式,两种格式的各种寻址方式如下:

下面是 mov 指令的一些示例。mov 指令用于移动数据,可以将立即数、寄存器和内存中的操作数,移动到寄存器或者内存中。需要注意,ATT 格式的数据传输方向是从左向右,而 Intel 格式恰好相反。

mov 指令不能将一个内存中的操作数,移动到另一个内存地址。
下面我们主要以 Intel 格式为例,来详细介绍 C 语言和汇编指令的对应关系。
(1)数据传输指令
mov:在寄存器和内存之间移动数据;
lea:load effective address,加载有效地址,将一个内存地址加载到目的寄存器;
push:将数据压入栈,同时 esp 减去数据长度;
pop:将栈顶数据弹出栈,同时 esp 加上数据长度;
(2)算术和逻辑运算指令
主要可以按照操作数的个数分为两类:
双操作数 —— add(加)、sub(减)、mul(无符号乘)、imul(有符号乘)、and(逻辑与)、or(或)、xor(异或)、sal/shl(左移)、sar(算术右移)、shl(逻辑右移)
格式为: op D, S
表示计算 D (op) S 的值,结果存入 D 中。
单操作数 —— inc(自增)、dec(自减)、neg(取负)、not(取反)
计算的结果仍然存入操作数所在位置。

比较特别的是除法指令,它们都只有一个操作数,表示除数,被除数则放在 edx : eax 中;得到的结果商放在 eax 中,余数放在 edx 中:
div:无符号除
idiv:有符号除
(3)转移指令
无条件转移:jmp
jmp 指令后面一般跟一个 “标签”(label),用来指明可以直接跳转到的目的地。它的底层编码一般都是 PC 相对的,也就是相对寻址。
有条件转移
标志寄存器 efl 中有很多标志状态,也称为 “条件码”,它们记录了最近的算术逻辑操作的结果属性。通过检测这些寄存器中的条件码,就可以执行条件转移指令了。最常用的条件码有:ZF(零标志)、CF(进位标志)、SF(符号标志)、OF(溢出标志);一般会对它们进行组合,用来表示更加容易理解的控制条件。

上面的条件跳转指令,需要先做一个算术或逻辑运算、更改条件码;而很多时候我们只需要做一个简单比较即可,并不需要将运算结果保存。有两类特殊指令可以只改变条件码、而不改变其它任何的寄存器:

这两种指令,特别是 cmp 经常和条件转移指令配合使用,用来实现条件分支(选择)和循环结构的程序。
调用和返回
进行子过程(函数)调用时,使用 call 指令;返回原函数时使用 ret 指令。
用高级语言编写好一段程序之后,需要经过一系列“翻译“过程,才能得到计算机能够执行的机器代码。比如,我们用 C 语言写了一个简单的 hello world 程序,源程序文件命名为 hello.c,用 GCC 编译器可以将它翻译成一个可执行目标程序 hello。具体的过程如下图所示:

第二步编译的结果,生成了汇编程序 hello.s,这就是汇编语言描述的机器指令;汇编程序再经过汇编就可以得到二进制的机器语言程序。
在一些集成开发环境(比如 Visual Studio)中,可以在调试(Debug)模式下对机器码进行 ”反汇编“,得到相应的汇编语言代码。
C 语言程序运行之后,对应的进程都会有自己独立的地址空间;这就是操作系统为每个进程提供的 虚拟地址空间。对于 32 位系统,进程虚拟地址空间的大小就是 232 B = 4 GB。
整个虚拟空间需要操作系统统一管理,因此进程的虚拟地址空间中必须保留一部分给操作系统内核使用。对于 Linux 系统(32 位),内核区大小为 1GB,地址从 0xc0000000 ~ 0xffffffff;而 Windows 系统默认情况下内核区大小为 2GB,地址从 0x80000000 ~ 0xffffffff。其余低地址部分则为用户区。
下面是一个 x86 Linux 进程的虚拟地址空间。

用户区主要包括这样几部分:
程序代码和数据:主要包括 只读代码段 和 读写段(.data 和 .bss)。对所有进程来说,代码都是从固定地址开始,紧接着就是 C 语言中的全局和静态数据。其中 .data 中是已初始化的全局和静态 C 变量,而 .bss 中是未初始化的全局和静态变量。
堆(Heap):用于运行时的动态内存分配,向上(高地址)生长。代码和数据区,在进程开始运行时就被指定了大小;而通过调用 malloc 和 free 这样的 C 标准库函数,可以让堆区动态地扩展和收缩。
共享库的内存映射区:用户区的中间部分是一块内存映射区域,用来存放像 C 标准库这样的共享库。
用户栈(Stack):位于虚拟地址空间用户区顶部,向下(低地址)生长。一般用来存储局部变量和函数参数,结合堆栈指针可以方便地实现函数的调用和返回。
C 语言中的函数是一种重要的抽象,它将代码按功能封装起来,让程序结构更加清晰、可重用性更高。
每个函数内部可以定义局部变量,这些变量只具有局部作用域。所以,嵌套函数调用时(例如,在函数 P 中调用函数 Q),就可以利用栈数据结构 ” 后进先出 “(LIFO)的特点,在栈内依次保存函数 P 和 Q 的相关内容。
这样,进程中的每一个函数,都会在栈上有一块自己的空间,就叫做 ” 栈帧 “(stack frame);当前正在执行的函数的栈帧总是在栈顶。这样 esp 的内容就是栈顶地址,而 ebp 的内容就保存当前栈顶栈帧的 “底部” 地址。

于是,在调用一个函数 Q(子过程)时,可以在栈上继续给 Q 中的局部变量分配内存空间(入栈)。当 Q 调用结束,就将 Q 的所有局部变量释放(出栈)。
想要用机器级代码实现函数调用,还需要考虑下面几个问题:
① 参数传递:函数 Q 应该能获取到 P 传入的参数;这可以通过指定参数存放的位置(写入寄存器或者入栈)来实现。

② 转移控制:调用 Q 时,需要跳转到函数 Q 入口处执行指令,这可以用 call 指令实现;调用结束,还应返回到 P 中的调用点继续执行,这需要保存之前调用点的信息,将下一条指令地址入栈。

等到调用结束时,执行 ret 指令返回,就执行出栈操作,将栈中保存的地址交给 eip,继续执行 P 中的下一条指令。

③ 保存上下文:原函数 P 使用的寄存器的内容,应该进行保存;调用结束后,还应该进行恢复。
调用 Q 后,可以先将原函数 P 的上下文(寄存器值)做一个入栈保存;然后再分配内存给 Q 的局部变量。待 Q 调用结束后,先释放 Q 的局部变量,然后继续弹栈恢复 P 的上下文。

比较特殊的是栈基指针 ebp,在调用 Q 之后,它应该指向 Q 的栈帧的底部;所以应该先将之前的 ebp(P 的栈帧底部)入栈,然后将 ebp 移向 esp 的位置。之后再保存 P 其它寄存器的值、分配空间给 Q 的局部变量。
xxxxxxxxxx21push ebp2mov ebp, esp
当 Q 调用结束返回时,只要反向执行,让 esp 移向 ebp 的位置;再将原先保存的 ebp 的值弹出,并放入 ebp 中就可以了:
xxxxxxxxxx21mov esp, ebp2pop ebp
对于函数调用开始时(call 之后)两条对 ebp 的处理指令,可以用一条 enter 指令来代替;函数结束时(ret 之前)的两条指令,则可以用 leave 来代替。
④ 返回值传递:函数 Q 调用结束,执行 ret 指令,此时应该能将返回值传回原函数 P;可以通过指定某个寄存器(eax)接收返回值来实现。
这样,我们可以将完整的栈帧结果表示如下:

栈帧中主要由 4 部分构成:上一层函数的上下文(主要是寄存器的值)、当前函数的局部变量、调用下一层函数所需的参数,以及返回地址。对于当前执行的函数 Q,没有参数构造区和返回地址两部分。
除顺序结构外,高级语言程序中一般还会有选择结构和循环结构。
选择结构 又称为 分支(branch)结构。C 语言中的选择语句主要有 if ... else 和 switch ... case,此外三目运算符 ? : 也可以实现选择结构。
很显然,通过设置条件码(标志位)、结合各类转移指令,就可以很容易地实现程序中的选择语句。
下面一段 C 语言代码使用 if - else 语句实现了选择结构:
xxxxxxxxxx101int a = 23;2int b = 31;3if (a > b)4{5 a++;6}7else {8 a--;9}10printf(" a = %d\n", a);可以发现它等效于使用 2 个 goto,分别跳过 if 后面的分支和 else 后面的分支:
xxxxxxxxxx81 if (a <= b)2 goto L1;3 a++;4 goto L2;5L1:6 a--;7L2:8 printf(" a = %d\n", a);这样,很容易得到对应的汇编代码:

C 语言中的循环语句有 do - while、while 和 for 三种。汇编语言中,同样可以用条件测试和跳转指令的组合来实现循环的效果。在循环结构中,通常使用条件转移指令来判断循环的结束。
下面是一段使用了 do - while 循环的 C 语言代码:
xxxxxxxxxx51int a = 0;2do {3 a++;4} while (a < 5);5printf(" a = %d \n ", a);很明显,循环部分可以利用 if 判断和 goto 语言来实现:
xxxxxxxxxx61 int a = 0;2L1:3 a++;4 if (a < 5)5 goto L1;6 printf(" a = %d \n ", a);这样,类比之前选择结构,可以得到对应的汇编表示如下:

while 循环与 do - while 类似,区别在于第一次执行循环体之前就要先做条件判断。下面是一段 while 循环的 C 语言代码:
xxxxxxxxxx51int a = 0;2while (a < 5) {3 a++;4}5printf(" a = %d \n ", a);如果用 goto 进行改写,可以参考 if - else 的实现,使用两个 goto 分别进行满足、不满足循环条件时的跳转:
xxxxxxxxxx81 int a = 0;2L1:3 if (a >= 5)4 goto L2;5 a++;6 goto L1;7L2:8 printf(" a = %d \n ", a);于是对应的汇编程序如下:

for 循环可以将循环变量和循环条件统一列出,因此对程序员来说是最友好的;但它直接转换成汇编语言会有一定的难度。for 循环的基本形式如下:
xxxxxxxxxx21for( 初始化循环变量; 判断循环条件; 更新循环变量)2 循环体可以把它先改写成 while 循环的形式:
xxxxxxxxxx61初始化循环变量;2while( 判断循环条件 )3{4 循环体;5 更新循环变量;6}这样,就可以用 goto 改写如下:
xxxxxxxxxx91 初始化循环变量;2L1:3 if(循环条件不成立)4 goto L2;5 循环体;6 更新循环变量;7 goto L1;8L2:9 循环外语句;当然,如果不改写成 while、完全按照 for 循环的执行顺序来处理,就会麻烦很多。比如下面是一段 for 循环的 C 语言代码:
xxxxxxxxxx61int a = 10;2for (int i = 0; i < 5; i++)3{4 a--;5}6printf(" a = %d \n ", a);对应的汇编代码如下:

很明显,本质上三种循环都是等效的,而比较之下 do-while 循环的汇编指令最简洁。因此,大多数编译器会进行优化,将另两种循环语句转换为 do-while 语句形式来生成机器代码。
另外,x86 架构指令集还提供了一个 loop 指令专门用于循环的实现。它默认使用 ecx 寄存器作为循环计数器,每次执行到 loop 指令都会先对 ecx 做减 1 操作;然后判断 ecx 是否为 0,如果不为 0 则跳转到 loop 后面标号对应的位置,如果为 0 则循环结束继续向下执行。
xxxxxxxxxx61mov ecx, 102.L1:3mov eax, dword ptr [a]4sub eax, 15mov dword ptr [a], eax6loop .L1
上面的 loop 指令相当于:
xxxxxxxxxx31dec ecx2cmp ecx, 03jne .L1
这样减少了指令数,汇编代码的可读性更高了。除 loop 外,还有类似的 loopz 和 loopnz 指令,它们判断循环继续的条件除了 ecx != 0 外,还有对 ZF 的要求。
下面是一段 C 语言的函数调用过程。我们在 main 函数中调用了 add 函数,进行两个整数的求和。
xxxxxxxxxx151int add(int x, int y);2
3int main() {4 int a = 23;5 int b = 31;6
7 int sum = add(a, b);8
9 printf(" sum = %d\n", sum);10}11
12int add(int x, int y) {13 int sum = x + y;14 return sum;15}函数调用时,首先应该跳转之前,将需要的参数进行保存;然后执行 call 指令,同时将下一条指令地址入栈。主程序 main 对应的汇编代码如下:

调用函数 add 后,首先应该将原来的 ebp 入栈,并将 ebp 指向当前 esp 的位置(add 函数栈帧的底部);然后保存之前其它寄存器的值(入栈)。之后分配局部变量的空间,执行 add 内部指令。执行完毕后,做反向操作,弹栈恢复所有寄存器的值,返回 main 函数的调用处继续执行。
add 函数对应的汇编指令如下:

【2010真题】下列寄存器中,汇编语言程序员可见的是 ( ) A.存储器地址寄存器(MAR) B.程序计数器(PC) C.存储器数据寄存器(MDR) D.指令寄存器(IR)
答案:B
【2021真题】下列寄存器中,汇编语言程序员可见的是 ( )
Ⅰ.指令寄存器 Ⅱ.微指令寄存器 Ⅲ.基址寄存器 Ⅳ.标志/状态寄存器
A.仅Ⅰ、Ⅱ B.仅Ⅰ、Ⅳ C.仅Ⅱ、Ⅳ D.仅Ⅲ、Ⅳ
答案:D
要点:汇编程序员可见的寄存器有基址寄存器、变址寄存器、状态/标志寄存器(PSW)、程序计数器 PC 和通用寄存器组;而 MAR、MDR、IR 是 CPU 内部工作寄存器, 对汇编程序员不可见。微指令寄存器属于微程序控制器的组成部分, 它是硬件设计者的任务,对汇编程序员是透明的。
【2022真题】下列选项中,属于指令集体系结构(ISA)规定的内容是 ( )
I. 指令字格式和指令类型 II. CPU 的时钟周期 III. 通用寄存器个数和位数 IV. 加法器的进位方式
A. 仅 I、 II B. 仅I、 III C. 仅 II、 IV D. 仅 I、 III、 IV
答案:B
要点:指令集处于软硬件的交界面上。指令字和指令格式、通用寄存器个数和位数都与机器指令有关,由 ISA 规定。两个 CPU 可以有不同的时钟周期,但指令集可以相同,CPU 的时钟周期不由 ISA 规定。 加法器的进位方式涉及电路设计, 也不由指令集规定。
【2009真题】下列关于 RISC 的叙述中, 错误的是 ( ) A. RISC 普遍采用微程序控制器 B. RISC 大多数指令在一个时钟周期内完成 C. RISC 的内部通用寄存器数量相对 CISC 多 D. RISC 的指令数、寻址方式和指令格式种类相对 CISC 少
答案:A
要点:相对于 CISC , RISC 的特点是以硬布线逻辑为主,不用或者少用微程序控制。
【2011真题】下列给出的指令系统特点中,有利于实现指令流水线的是 ( )
Ⅰ .指令格式规整且长度一致
Ⅱ .指令和数据按边界对齐存放
Ⅲ.只有 Load/Store 指令才能对操作数进行存储访问
A.仅Ⅰ 、 Ⅱ B.仅Ⅱ 、 Ⅲ C.仅Ⅰ 、 Ⅲ D. Ⅰ 、 Ⅱ 、 Ⅲ
答案:D
要点:以上三点都是 RISC 的特点,都可以有效简化指令流水线的复杂度。
【2016真题】某计算机主存空间为 4GB,字长为 32 位,按字节编址,采用 32 位字长指令字格式。若指令按字边界对齐存放,则程序计数器(PC)和指令寄存器(IR)的位数至少分别是 ( )
A. 30、 30 B. 30、 32 C. 32、 30 D. 32、 32
答案:B
要点:程序计数器(PC)给出下一条指令字的访存地址,取决于存储器的字数;指令寄存器(IR)用于存放取得的指令,取决于指令字长。
【2017真题】某计算机按字节编址,指令字长固定且只有两种指令格式,其中三地址指令 29 条,二 地址指令 107 条,每个地址字段为 6 位,则指令字长至少应该是 ( )
A. 24位 B. 26位 C. 28位 D. 32位
答案:A
要点:指令字长应是字节的整数倍。
【2022真题】设计某指令系统时,假设采用 16 位定长指令字格式,操作码使用扩展编码方式,地址码为 6位,包含零地址、 一地址和二地址 3 种格式的指令。 若二地址指令有 12 条,一地址指令有254 条,则零地址指令的条数最多为 ( )
A. 0 B. 2 C. 64 D. 128
答案:D
【2011真题】偏移寻址通过将某个寄存器内容与一个形式地址相加而生成有效地址。下列寻址方式中, 不属于偏移寻址方式的是 ( )
A.间接寻址 B.基址寻址 C.相对寻址 D.变址寻址
答案:A
【2017真题】下列寻址方式中,最适合按下标顺序访问一维数组元素的是 ( )
A.相对寻址 B.寄存器寻址 C.直接寻址 D.变址寻址
答案:D
【2020真题】某计算机采用 16 位定长指令字格式,操作码位数和寻址方式位数固定,指令系统有 48 条指令,支持直接、间接、立即、相对 4 种寻址方式。单地址指令中,直接寻址方式的可寻址范围是 ( )
A. 0 ~ 255 B. 0 ~ 1023 C. -128 ~ 127 D. -512 ~ 511
答案:A
要点:4 种寻址方式需要有 2 位寻址特征位。
【2009真题】某机器字长为 16 位,主存按字节编址,转移指令采用相对寻址,由两个字节组成, 第一字节为操作码字段,第二字节为相对位移量字段。假定取指令时,每取一个字节 PC 自动加 1。若某转移指令所在主存地址为 2000H,相对位移量字段的内容为 06H,则该转移指令成功转移后的目标地址是 ( )
A. 2006H B. 2007H C. 2008H D. 2009H
答案:C
【2013真题】假设变址寄存器 R 的内容为 1000H,指令中的形式地址为 2000H;地址 1000H 中的内容为 2000H,地址 2000H 中的内容为 3000H,地址 3000H中的内容为 4000H,则变址寻址方式下访问到的操作数是 ( )。
A.1000H B.2000H C.3000H D.4000H
答案:D
【2014真题】某计算机有 16 个通用寄存器,采用 32 位定长指令字, 操作码字段(含寻址方式位)为 8 位,Store 指令的源操作数和目的操作数分别采用寄存器直接寻址和基址寻址方式。若基址寄存器可使用任一通用寄存器, 且偏移量用补码表示, 则 Store 指令中偏移量的取值范围是 ( )
A. -32768-+32767 B. -32767-+32768 C. -65536-+65535 D. -65535-+65536
答案:A
要点:16 个寄存器需要 4 位地址码表示。
【2016真题】某指令格式如下所示

其中 M 为寻址方式, I 为变址寄存器编号, D 为形式地址。若采用先变址后间址的寻址方式,则操作数的有效地址是 ( )
A. I + D B. ( I ) + D C. ( ( I ) + D ) D. ( ( I ) ) + D
答案:C
要点:变址寻址中 EA = ( I ) + D;间接寻址中 EA = ( D )。
【2018真题】按字节编址的计算机中,某 double 型数组 A 的首地址为 2000H,使用变址寻址和循环结构访问数组 A,保存数组下标的变址寄存器初值为 0,每次循环取一个数组元素,其偏移地址为变址值乘以 sizeof(double),取完后变址寄存器内容自动加 1 。若某次循环所取元素的地址为 2100H,则进入该次循环时变址寄存器的内容是 ( )。
A.25 B.32 C.64 D.100
答案:B
要点:数组首地址就是形式地址 D,变址寄存器中存放的是数组下标。
【2018真题】减法指令“sub R1,R2,R3”的功能为“(R1)-(R2)→R3”,该指令执行后将生成进位/借位标志CF 和溢出标志 OF。若(R1)=FFFF FFFFH,(R2)=FFFF FFF0H,则该减法指令执行后,CF 与 OF 分别为 ( )。
A. CF = 0, OF = 0 B. CF = 1 , OF = 0 C. CF = 0, OF = 1 D. CF = 1, OF = 1
答案:A
要点:减法操作只需判断借位标志;当最高位进位和符号位进位的值不相同时才产生溢出。
【2019真题】某计算机采用大端方式,按字节编址。某指令中操作数的机器数为 1234 FF00H,该操 作数采用基址寻址方式,形式地址(用补码表示)为 FF12H,基址寄存器的内容为 F000 0000H, 则该操作数的 LSB(最低有效字节)所在的地址是 ( )
A. F000 FF12H B. F000 FF15H C. EFFF FF12H D. EFFF FF15H
答案:D
要点:基址寻址方式, EA = (BR) + A;大端方式编址,低位字节存放在字的高地址处。
【2017真题】在按字节编址的计算机 M 上,上题中 f1 的部分源程序(阴影部分)与对应的机器级代码(包括指令的虚拟地址)如下:
xxxxxxxxxx81int f1(unsigned n){2 int sum=1, power=1;3 for(unsigned i=0;i<=n-1;i++){4 power *= 2;5 sum += power;6 }7 return sum;8} xxxxxxxxxx141int f1(unsigned n)21 00401020 55 push ebp3... ... ...4for(unsigned i=0;i<=n-1;i++)5... ... ...620 0040105E 39 4D F4 cmp dword ptr [ebp-0Ch], ecx7... ... ...8{ power *= 2;9... ... ...1023 00401066 D1 E2 shl edx, 111... ... ...12return sum;13... ... ...1435 0040107F C3 ret
其中,机器级代码行包括行号、虚拟地址、机器指令和汇编指令。请回答下列问题。
(1)计算机 M 是 RISC 还是 CISC? 为什么?
(2)f1 的机器指令代码共占多少字节?要求给出计算过程。
(3)第 20 条指令 cmp 通过 i 减 n-1 实现对 i 和 n-1 的比较。执行 f1(0) 过程中当 i=0 时, cmp 指令执行后,进/借位标志 CF 的内容是什么?要求给出计算过程。
(4)第 23 条指令 shl 通过左移操作实现了 power *2 运算,在 f2 中能否也用 shl 指令实现 power *2? 为什么? (注:将 f1 中的 int 都改为 float, 可得到计算 f(n) 的另一个函数 f2。)
答案:
(1)M 为 CISC。 因为 M 的指令长短不一,不符合 RISC 指令系统特点。
(2)f1 的机器代码占 96 B。
因为 f1 的第一条指令 “push ebp” 所在的虚拟地址为 0040 1020H,最后一条指令 “ret” 所在的虚拟地址为 0040 107FH,所以, f1 的机器指令代码长度为 0040 107FH - 0040 1020H + 1 = 60H = 96 个字节。
(3)CF = 1。
cmp 指令实现 i 与 n-1 的比较功能,进行的是减法运算。在执行 f1(0) 过程中, n=0,当 i=0 时,i = 0000 0000H,n-1 = FFFF FFFFH。因此,当执行第 20 条指令时,在补码加/减运算器中执行 “0 减 FFFF FFFFH” 的操作,即 0000 0000H+0000 0000H+1=0000 0001H,此时,进位输出 C=0,减法运算时的借位标志 CF=C⊕1=1。
(4)f2 中不能用 shl 指令实现 power*2。
因为 shl 指令用来将一个整数的所有有效数位作为一个整体左移;而 f2 中的变量 power 是 float 型,其机器数中不包含最高有效数位,但包含了阶码部分,将其作为一个整体左移时并不能实现 “乘 2” 的功能,因而 f2 中不能用 shl 指令实现 power*2。
【2019真题】已知

其中,机器级代码行包括行号、虚拟地址、机器指令和汇编指令,计算机 M 按字节编址, int 型数据占 32 位。请回答下列问题:
(1)计算 f(10) 需要调用函数 f1 多少次?执行哪条指令会递归调用 f1?
(2)上述代码中,哪条指令是条件转移指令?哪几条指令一定会使程序跳转执行?
(3)根据第 16 行的 call 指令,第 17 行指令的虚拟地址应是多少?已知第 16 行的 call 指令采用相对寻址方式,该指令中的偏移量应是多少(给出计算过程)?已知第 16 行的 call 指令的后 4 字节为偏移量, M 是采用大端方式还是采用小端方式?
(4) f(13) = 6227020800,但 f1(13) 的返回值为 1932053504,为什么两者不相等?要使 f1(13) 能返回正确的结果,应如何修改 f1 的源程序?
(5)第 19 行的 imul 指令(带符号整数乘)的功能是 R[eax]←R[eax]×R[ecx],当乘法器输出的高、低 32 位乘积之间满足什么条件时,溢出标志 OF = 1?要使 CPU 在发生溢出时转异常处理,编译器应在 imul 指令后应加一条什么指令?
答案:
(1)计算 f(10) 需要调用函数 f1 共 10 次,执行第 16 行的 call 指令会递归调用 f1。 (2)第 12 行的 jle 指令是条件转移指令,其含义为小于等于时转移,本行代码的意义为:当 n≤1 时,跳转至地址 0040 1035H。
第 16 行的 call 指令为函数调用指令,第 20 行的 jmp 指令为无条件转移指令,第 30 行的 ret 指令为子程序的返回指令,这三条指令一定会使程序跳转执行。
(3)其长度计算机 M 上按字节编址,第 16 行的 call 指令的虚拟地址为 0040 1025H,长度为 5 字节,故第 17 行的指令的虚拟地址为 0040 1025H + 5 = 0040 102AH 。第 16 行的 call 指令采用相对寻址方式,即目标地址= (PC) +偏移量, call 指令的目标地址为 0040 1000H,所以偏移量=目标地址- (PC) = 0040 1000H - 0040 102AH = FFFF FFD6H。
根据第 16 行的 call 指令的偏移量字段为 D6 FF FF FF,可以确定 M 采用小端方式。
(4)因为 f(13) = 6227020800,其结果超出了 32 位 int 型数据可表示的最大范围,因此 f(13) 的返回值是一个发生了溢出的错误结果。为使 f1(13) 能返回正确结果,可将函数 f1 的返回值类型改为 double(或 long long,或 long double,或 float)类型。
(5)imul 指令最终结果放在 32 位的 eax 中,所以若乘积的高 33 位为非全 0 或非全 1,则 OF = 1。编译器应在 imul 指令后加一条 “溢出自陷指令”,使得 CPU 自动查询溢出标志 OF,当 OF=1 时调出“溢出异常处理程序”。
【2021真题】假定计算机 M 字长为 16 位,按字节编址,连接 CPU 和主存的系统总线中地址线为 20 位、数据线为 8 位,采用 16 位定长指令字,指令格式及其说明如下:

其中,op1 ~ op3 为操作码,rs、rt 和 rd 为通用寄存器编号,R[r] 表示寄存器 r 的内容,imm 为立即数,target 为转移目标的形式地址。请回答下列问题。
(1)ALU 的宽度是多少位?可寻址主存空间大小为多少字节?指令寄存器、主存地址寄存器(MAR)和主存数据寄存器(MDR)分别应有多少位?
(2)R 型格式最多可定义多少种操作?I 型和 J 型格式总共最多可定义多少种操作?通用寄存器最多有多少个?
(3)假定 op1 为 0010 和 0011 时,分别表示带符号整数减法和带符号整数乘法指令,则指令 01B2H 的功能是什么(参考上述指令功能说明的格式进行描述)?
若 1、2、3 号通用寄存器当前内容分别为 B052H、 0008H、0020H,则分别执行指令 01B2H 和 01B3H 后,3 号通用寄存器内容各是什么?各自结果是否溢出?
(4)若采用 l 型格式的访存指令中 imm(偏移量)为带符号整数,则地址计算时应对 imm 进行零扩展还是符号扩展?
(5)无条件转移指令可以采用上述哪种指令格式?
答案:
(1)ALU 的宽度为 16 位,ALU 的宽度即 ALU 运算对象的宽度,通常与字长相同。
地址线为 20 位,按字节编址,可寻址主存空间大小为 220 字节(或 1MB)。
指令寄存器有 16 位, 和单条指令长度相同。
MAR 有 20 位,和地址线位数相同。MDR 有 8 位,和数据线宽度相同。
(2)R 型格式的操作码有 4 位, 最多有 24 = 16 种操作。
I 型和 J 型格式的操作码有 6 位,因为它们的操作码部分重叠,所以共享这 6 位的操作码空间,且前 6 位全 0 的编码已被 R 型格式占用, 因此 I 和 J 型格式最多有 26 -1 = 63 种操作。
从 R 型和 I 型格式的寄存器编号部分可知, 只用 2 位对寄存器编码, 因此通用寄存器最多有 22 = 4 个。
(3)指令 01B2H = 000000 01 10 11 0010B 为一条 R 型指令,操作码 0010 表示带符号整数减法指令,其功能为 R[3]
执行指令 01B2H 后,R[3] = B052H-0008H = B04AH,结果未溢出。
指令 01B3H = 000000 01 10 11 0011B,操作码 0011 表示带符号整数乘法指令。执行指令 01B3H 后,R[3] = R[1] x R(2] = B052H x 0008H = 8290H,结果溢出。
(4)在进行指令的跳转时,可能向前跳转,也可能向后跳转。偏移量是一个带符号整数, 因此在地址计算时,应对 imm 进行符号扩展。
(5)无条件转移指令可以采用 J 型格式, 将 taget 部分写入 PC 的低 10 位,完成跳转。
(一)CPU 的功能和基本结构
(二)指令执行过程
(三)数据通路的功能和基本结构
(四)控制器的功能和工作原理
(五)异常和中断机制
异常和中断的基本概念
异常和中断的分类
异常和中断的检测与响应
(六)指令流水线
指令流水线的基本概念
指令流水线的基本实现
结构冒险、数据冒险和控制冒险的处理
超标量和动态流水线的基本概念
(七)多处理器基本概念
SISD、SIMD、MIMD、向量处理器的基本概念
硬件多线程的基本概念
多核处理器(multi-core)的基本概念
共享内存多处理器(SMP)的基本概念
本章知识点较多,综合性比较强。其中,指令流水线是历年考查的一个重点,数据通路、控制器原理出现的频率也很高,单项选择和综合应用题都有可能出现。本章单独出题一般以概念和原理为主,计算较少;往往会与第四章结合进行综合考查,有时也会结合第二章、第三章的内容。
需要掌握的内容包括:
CPU 的基本结构,运算器和控制器的组成,各种寄存器的功能和特性。
指令周期的概念,指令执行的过程和方案。
数据通路的结构,数据通路中的数据传送流程和控制信号。
硬布线控制器的概念和原理;微程序控制器的概念和原理,微指令的编码方式及特点;两种控制器的比较。
异常和中断的概念和分类;异常和中断的响应过程。
指令流水线的概念、分类和原理;流水线冒险与处理方法;流水线的性能指标;超标量流水线。
多处理器的基本概念和分类。
| 考点 | 考查次数 | |
|---|---|---|
| 单项选择题 | 综合应用题 | |
| 指令流水线 | 10 | 2 |
| 控制器的功能和工作原理 | 6 | 5 |
| CPU的功能和基本结构 | 5 | 4 |
| 数据通路的功能和基本结构 | 3 | 4 |
| 指令执行过程 | 2 | 2 |
| 多处理器的基本概念 | 1 | 0 |
| 考点 | 考查次数 | |
|---|---|---|
| 单项选择题 | 综合应用题 | |
| 程序中断方式 | 15 | 3 |
| 总线概念和常见总线标准 | 9 | 2 |
| 外部设备和 I/O 接口 | 7 | 1 |
| 总线的性能指标 | 6 | 1 |
| DMA 方式 | 3 | 3 |
| 程序查询方式 | 1 | 1 |