购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.1 存储程序计算机

2.1.1 哈佛结构与冯·诺依曼结构

存储程序计算机的概念虽然简单,但在计算机发展史上具有革命性的意义,迄今为止仍是计算机发展史上非常有意义的发明。一台硬件有限的计算机或智能手机能安装各种各样的软件,执行各种各样的程序,这在人们看来都理所当然,其实背后是存储程序计算机的功劳。

存储程序计算机的主要思想是将程序存放在计算机存储器中,然后按存储器中存储的程序的首地址执行程序的第一条指令,以后就按照该程序中编写好的指令执行,直至程序执行结束。

相信很多人(特别是计算机专业的人)都听说过图灵机。图灵机关注计算的哲学定义,是一种虚拟的抽象机器,是对现代计算机的首次描述。只要提供合适的程序,图灵机就可以做任何运算。基于图灵机建造的早期计算机一般都是在存储器中存储数据,程序的逻辑都是嵌入硬件的。在图灵机之后,先后出现了哈佛结构和冯·诺依曼(Von Neumann)结构的计算机。

哈佛结构(如图2-1所示)起源于穿孔纸带存储程序指令,而数据则存储在存储器中,后来在嵌入式系统中沿用下来,将程序指令放在ROM(Read Only Memory,只读存储器)或Flash等存储器中,可以有效地保护程序指令在执行时不被改写;而数据则保存在内存中,可以读写。哈佛结构计算机将程序指令和数据分开的做法实际上是保护了程序指令。

图2-1 哈佛结构示意图

冯·诺依曼结构(如图2-2所示)比哈佛结构出现得稍晚一些,是在哈佛结构的基础上改进而来,它是将程序指令和数据存储在一起的存储器结构,简化和统一了程序和数据的总线存取,也称为存储程序计算机。冯·诺依曼结构具有简单、通用和低成本的优势,已经成为通用计算机领域广为人知的基本结构。

图2-2 冯·诺依曼结构示意图

在嵌入式专用计算机中,程序需要固化在硬件设备中,以硬件IC或固件的形式存在,产品出厂后程序几乎从不需要修改,但数据需要反复存取,因而程序和数据对存储器的类型要求是不同的,程序(固件)所需的存储器可以是一次或有限次烧写反复读取的存储器;数据所需的是需要反复读写的存储器,这样程序存储器和数据存储器是两个独立的存储器,即每个存储器独立编址、独立访问。哈佛结构将程序指令和数据分开的做法适合嵌入式设备的场景,而且这种分离的程序总线和数据总线允许在一个机器周期内同时获得指令(来自程序存储器)和操作数(来自数据存储器),通常具有较高的执行效率,可以保证嵌入式设备的实时性。因此哈佛结构在51单片机和ARM等嵌入式处理器中得以应用。

现代操作系统是以冯·诺依曼结构的通用计算机为基础开发的,比如Linux中进程地址空间就是将程序指令和数据统一编址存储的,如果把进程作为一个虚拟的计算机,那么它是冯·诺依曼结构的。问题来了,如果在ARM上运行Linux,那么这台计算机是冯·诺依曼结构还是哈佛结构呢?显然冯·诺依曼结构与哈佛结构融合起来了,可以把所有计算机都笼统、简化地理解为冯·诺依曼结构,而哈佛结构只在为了某些特殊目标而设计的硬件中存在,这大概是哈佛结构在智能手机中广为应用,却不如冯·诺依曼结构广为人知的原因吧。

2.1.2 复杂指令集和精简指令集

最初的计算机没有指令,是完全由硬件电路实现的专用计算机。硬件电路中反复使用的通用电路模块就是指令产生的基础,可以说指令最初是电路模块的编号,这些编号的集合就是指令集,可以认为这是最初的编程语言,对应机器语言和汇编语言。复杂指令集就是在经验积累的基础上产生的大量实用指令的集合。

CISC(Complex Instruction Set Computer),即“复杂指令集计算机”,从计算机诞生以来一直被沿用。CISC的指令比较丰富,有专用指令来完成特定的功能。因此,处理特殊任务效率较高。随着对指令集的反复应用和抽象,人们逐渐发现可以去除冗余指令或合并不同指令中相同的功能,用最精简的指令集来完成相同的工作,从而简化硬件芯片的复杂度,这就产生了精简指令集。

RISC(Reduced Instruction Set Computer),即“精简指令集计算机”,是一种执行较少类型计算机指令的微处理器,起源于20世纪80年代的MIPS。RISC有简单、高效的特色,对不常用的功能,常通过组合指令来完成,因此,在RISC上实现特殊功能时,效率可能较低,但可以利用流水线技术和超标量技术加以改进和弥补。

简单来说,复杂指令集相当于语言的词汇量丰富,精简指令集相当于语言的词汇量较少,但可以组合成的词组和短语较为丰富。

CISC处理器最具代表性的就是Intel和AMD的x86/x86-64指令集;RISC处理器有Power PC、MIPS、ARM/ARM64和RISC-V等指令集。

x86/x86-64是常见的个人计算机使用的指令集;ARM64,官方称为AArch64,也简称为A64,是ARM架构的64位扩展,在ARMv8-A架构中被首次提出。ARM64广泛应用于智能手机中,且逐渐向桌面和服务器领域拓展,比如基于苹果M1芯片的Mac计算机和MacBook笔记本电脑、基于华为鲲鹏处理器的台式机和服务器,以及树莓派等。

2.1.3 深入理解冯·诺依曼体系结构

我们都知道“庖丁解牛”这个成语,比喻经过反复实践,掌握了事物的客观规律,做事得心应手。冯·诺依曼体系结构就是各种计算机体系结构需要遵从的一个“客观规律”,了解它对于理解计算机和操作系统非常重要。下面介绍冯·诺依曼体系结构。

在1944—1945年,冯·诺依曼指出程序和数据在逻辑上是相同的,程序也可以存储在存储器中,以这个思路为基点形成了一种新的计算机体系结构。这种体系结构的主要特点是:CPU(Central Processing Unit,中央处理器,或简称处理器)和存储器(memory)是计算机的两个主要组成部分,存储器中保存着数据和程序指令,CPU从存储器中取指令执行,其中有些指令让CPU做运算,有些指令让CPU读写存储器中的数据。

冯·诺依曼体系结构的要点如下。

(1)冯·诺依曼体系结构如图2-3所示,其中运算器、存储器、控制器、输入设备和输出设备5大基本类型部件组成了计算机硬件。

图2-3 冯·诺依曼体系结构分解示意图

(2)计算机内部采用二进制来表示指令和数据。

(3)将编写好的程序和数据先存入存储器中,然后让计算机执行,这就是存储程序的基本含义。

计算机硬件的核心是CPU,它与存储器和输入/输出(I/O)设备进行交互,从输入设备接收数据,向输出设备发送数据。CPU由运算器(算术逻辑单元ALU)、控制器和一些寄存器组成。一个非常重要的寄存器是程序计数器(Program Counter,PC),在x86架构的CPU中称为指令指针(Instruction Pointer,IP)寄存器,即IP(16位)、EIP(32位)或RIP(64位)寄存器,而在ARM64中称为PC,它负责存储将要执行的下一条指令在存储器中的地址。C/C++程序员可以将PC看作一个指针,因为它总是指向某一条指令的地址(见图2-4)。CPU就是从PC指向的那个地址取一条指令执行,同时PC会自动加1指向下一条指令。CPU依次执行下一条指令,就像“贪吃蛇”一样。

图2-4 32位x86指令指针寄存器示意图

CPU、存储器和I/O设备通过总线连接。存储器中存放指令和数据。“计算机内部采用二进制来表示指令和数据”表明,指令和数据的功能和处理是不同的,但都可以用二进制的方式存储在存储器中。

上述第3个要点指出了冯·诺依曼体系结构的核心是存储程序计算机。用程序员的思维方式来对存储程序计算机进行抽象,如图2-5所示。

图2-5 存储程序计算机工作原理示意图

可以把CPU抽象成一个for循环,因为它总是从存储器里取下一条指令(next instruction)来执行。从这个角度来看,存储器保存指令和数据,CPU负责解释和执行这些指令,它们通过总线连接起来。这里揭示了计算机可以自动化执行程序的原理。

这里存在一个问题——CPU能识别什么样的指令,因此需要有一个定义。学过编程的读者基本都知道API(Application Program Interface,应用程序编程接口),而对于程序员来讲,还有一个ABI(Application Binary Interface),它主要是一些指令的编码。在指令编码方面,不会涉及具体的细节,只会涉及和汇编相关的内容。至于这些指令是如何编码成二进制机器指令的,有兴趣的读者可以查找指令编码的相关资料。

此外,这些指令会涉及一些寄存器,这些寄存器约定什么样的指令该用什么寄存器。同时,也需要了解寄存器的布局。对于x86架构的指令集来讲,大多数指令可以直接访问内存,而ARM64中只有str/ldr及其变种指令可以访问内存。对于x86架构中指令的编码不是固定长度的,指令指针寄存器自动加一,这里的“一”不是一个固定长度,而是智能地加一条指令的长度;而ARM64中指令编码的长度是固定的32位。

需要特别注意的是:指令指针寄存器在x86和ARM64中都不能被直接修改,它只可以被一些跳转指令修改,如x86中的call、ret、jmp等,ARM64中的b、bl、br、blr和ret等,编译器将C语言中的函数调用、return和if-else语句等映射为这些指令。

现在绝大多数具有计算功能的设备,小到微型嵌入式设备、智能手机,大到超级计算机,基本的核心部分都可以用冯·诺依曼体系结构(存储程序计算机)来理解,即便它的硬件结构可能是哈佛结构,但在其上运行的操作系统中抽象出的进程是存储程序计算机的模型。因此,存储程序计算机是一个非常基本的概念,是理解计算机系统工作原理的基础。

2.1.4 计算机的存储系统

计算机存储系统中最关键的是存储器或内部存储器(简称内存),它是存储程序计算机模型中两个关键部件之一。每个存储单元有一个地址(address),存储地址是从0开始编号的整数,CPU通过地址找到相应的存储单元,取其中的指令或者读写其中的数据。一个地址所对应的存储单元不能存储很多东西,只能存储一字节。指令或int、float等多字节的数据保存在内存中要占用连续的多字节地址,这种情况下指令或数据的地址是它所占存储单元的起始字节的地址。

计算机存储系统的层次结构复杂,除了内存,还有寄存器(register)、缓存(cache)、固态硬盘(solid-state disk)、硬盘(disk)和互联网(Internet)分布式存储。下面将计算机存储系统的层次结构进行简要总结,如图2-6所示。

图2-6 计算机存储系统的层次结构示意图

对不同的存储方式的访问速度的差别,我们往往没有具体概念,因为计算机相对于人类的感知能力来说实在太快了。将CPU内部的一个周期(cycle)扩大为人类能够感知的1秒钟,来直观对比一下寄存器、缓存、内存、固态硬盘、硬盘和互联网分布式存储之间的访问速度的差别。图2-7所示的4个方框分别表示寄存器、内存、硬盘和互联网分布式存储的访问速度。

图2-7 不同存储方式的访问速度

需要提及的是:5G网络的空口延迟可达1 ms,也就是网络访问速度可以提升到与本地硬盘访问速度大致相当,这意味着互联网云存储有逐步替代本地硬盘的潜力。

2.1.5 计算机的总线结构

CPU执行指令除了访问存储器还要访问很多设备(device),如键盘、鼠标、硬盘、显示器等,那么它们和CPU之间是如何连接的呢?下面以较为通用的冯·诺依曼结构为例来展示,如图2-8所示。

图2-8 计算机总线结构示意图

有些设备像存储器芯片一样连接到处理器接口上,正因为处理器接口上可以挂多个设备和存储器芯片所以才叫“总线”(bus)。总线内部又细分为地址总线、数据总线和控制总线。

以32位CPU和存储器之间用地址总线、数据总线和控制总线连接起来为例,每条线上有1和0两种状态。如果在执行指令过程中需要访问存储器,比如从存储器读一个数到寄存器,执行过程可以这样想象:首先,CPU通过控制总线RD发送一个读请求,并且将存储器地址通过地址总线A0~A31发送给存储器;然后,存储器芯片收到地址和读请求之后,将相应的存储单元对接到数据总线D0~D31;最后,存储单元每一位的1或0状态通过一条数据总线到达CPU寄存器中相应的位,就完成了数据传送。

计算机总线示意图如图2-9所示,其中画了32条地址总线、32条数据总线和1条控制总线,CPU寄存器也是32位,地址总线、数据总线和CPU寄存器的位数通常是一致的。32位计算机有32条地址总线,可寻址的逻辑地址空间(address space)从0x00000000到0xffffffff,共4 GB(2 32 ),而64位计算机一般使用48条地址总线,可寻址的逻辑地址空间为256 TB(2 48 ),而且这一逻辑地址空间在未来可能增加到16 EB(2 64 ,1 EB=1 024 PB,1 PB=1 024 TB,1 TB=1 024 GB)。

图2-9 计算机总线示意图

这里所说的地址总线、数据总线是指CPU内部逻辑上应该具有的总线条数,但由于MMU和总线接口的转换,实际的总线条数可能不同,例如,由于MMU和总线接口的转换,32位处理器的可寻址空间可以大于4 GB。

很多设备和存储器一样直接通过总线和CPU相连,称其为总线上的设备,总线上的设备和存储器芯片有不同的地址范围。访问它就像访问存储器一样,按地址读写即可,和访问存储器不同的是,向一个地址写数据只是给设备发送一个命令,数据不一定要保存,而从一个地址读数据也不一定是读先前保存在这个地址的数据,而是得到设备的当前状态。

总线上的设备往往也具有可供读写访问的存储单元,通常称为设备寄存器。注意设备寄存器和CPU寄存器不是一回事,但又是一回事,因为设备芯片是一个专用微型处理器,和通用处理器一样,内部可能包含一些存储单元,为了与CPU寄存器区分就称其为设备寄存器。操作设备的过程就是读写这些设备寄存器的过程。

还有一些设备集成在处理器芯片内部,但无论是在CPU外部接总线的设备还是在CPU内部接总线的设备都有各自的地址范围,都可以像访问存储器一样访问,很多体系结构(比如ARM)采用这种方式操作设备,称为内存映射I/O(memory-mapped I/O)。但是x86比较特殊,其对于设备有独立的端口地址空间,CPU核需要引出额外的地址线来连接处理器芯片内部集成的设备,访问设备寄存器时用特殊的in/out指令,这种方式称为端口I/O(port I/O)。

从CPU的角度来看,访问设备只有内存映射I/O和端口I/O两种,要么像存储器一样访问,要么用一种专用的指令访问。其实访问设备是相当复杂的,而且计算机的设备五花八门,各种设备的性能要求都不一样,如有的要求带宽大,有的要求响应快,有的要求热插拔,于是出现了各种适应不同要求的设备总线,比如PCI、AGP、USB、1394、SATA等,它们并不直接和CPU相连,CPU通过内存映射I/O或端口I/O访问相应的总线控制器,再通过总线控制器去访问挂在总线上的设备。所以图2-8中标有“设备”的框可能是实际的设备,也可能是设备总线控制器。

在x86平台上,硬盘是挂在IDE、SATA或SCSI上的,不是直接挂在总线上的,保存在硬盘上的程序是不能被CPU直接取指令执行的,操作系统在执行程序时会把它从硬盘复制到存储器,这样CPU才能取指令执行,这个过程称为加载(load)。程序加载到存储器之后,成为操作系统调度执行的一个任务,就称为进程(process)。操作系统(operating system)本身也是一段保存在硬盘上的程序,计算机在启动时执行一段固定的启动代码(称为bootloader)把操作系统从硬盘加载到存储器,然后执行操作系统中的代码把用户需要的其他程序加载到存储器。 xrs06AS0JtsY74HzqYwonNGg89dQ1lq0KWf4XMPbwkIv+ogj3d6a1XRSpcSiQ76R

点击中间区域
呼出菜单
上一章
目录
下一章
×