指令也是数据?浅谈计算机体系结构

《指令也是数据?浅谈计算机体系结构》源站链接,阅读体验更佳

我从大学期间开始接触编程,接触的第一门编程语言是C语言,后来也进过实验室,玩过单片机,还接触过汇编,但是最终都没有走下去。到大四下学期的时候由于毕业设计和工作的需要,开始接触JavaWeb,从此踏上了一条不归路。

现在也已经工作两年多的时间了,接触的语言也越来越多,我也成功脱离的语言之争的低级趣味。学习更多的编程语言,熟练使用甚至精通多门编程语言已经成为了我长期的目标。

不知为何,我对编程有一种特别的执念,总想总结出一套自己的方法论和思维体系,在编码的时候总想以最优雅的方式完成逻辑,可能我本身就觉得写代码是件非常酷的事情吧。

因此我觉得有必要写一下我对编程语言的理解,为我以后学习多门语言打下思维基础。

在进入正题之前,我们有必要从故事开始的地方,捋一下软件开发是如何发展到今天的局面的——无论我们使用什么样的编程语言,也无论我们编写要在什么平台上运行的程序,代码的编写方式都是大差不差的。

这就不得不提计算机的体系结构。

最早的计算机仅内涵固定用途的程序,其运算逻辑被固化在了电路中,只能用于特定的用途,而不是像现在这样的通用计算机。也就是早期的计算机并没有那么“可编程”,当时所谓的“重写程序”很可能指的是纸笔设计程序步骤,接着制定工程细节,再施工将机器的电路配线或结构改变。

典型的就是早年的“Plugboard”这样的插线板式的计算机。整个计算机就是一个巨大的插线板,通过在板子上不同的插头或者接口的位置插入线路,来实现不同的功能。这样的计算机自然是“可编程”的,但是编写好的程序不能存储下来供下一次加载使用,不得不每次要用到和当前不同的“程序”的时候,重新插板子,重新“编程”。

Plugboard

现代的某些计算机仍然维持这样的设计方式,通常是为了简化或教育目的。例如一个计算器仅有固定的数学计算程序,它不能拿来做文字处理,更不能拿来玩游戏。如果想要改变此机器的程序,你必须更改线路、更改结构甚至是重新设计此机器。它的核心是运算电路,而控制电路则只是起到了辅助的作用,控制电路只是负责将电信号导向适合的运算电路,其运行流程基本是固化的,这样的计算机是不可编程的。

冯·诺依曼体系结构

可以看到,无论是“不可编程”还是“不可存储”,都会让使用计算机的效率大大下降,因为其大大降低了计算机的通用性。在今天,计算机设备可以说是无处不在的,其存在形式同样也是多种多样的,对于进行应用程序开发的程序员来说,接触的最多的无非就是服务器、个人PC和手机。但是,无论是我们编写运行在服务器上的应用程序后台,还是编写在PC上的客户端亦或者是编写手机上的APP,我们的编码方式其实都是大差不差的,这就意味着,不论是什么样形式的计算机,其都遵循了一个统一的逻辑模型,这就是计算机祖师爷之一冯·诺依曼所提出的冯·诺依曼体系结构,因为此结构隐约指导了将存储设备与中央处理器分开的概念,因此依据本结构设计出的计算机也叫存储程序计算机。

我们的冯祖师爷,基于当时在秘密开发的EDVAC写了一篇报告’First Draft of a Report on the EDVAC’,简称叫 First Draft,翻译成中文,其实就是《第一份草案》。在这篇文章中,冯·诺依曼描述了他心目中的一台计算机应该长什么样,我们一起来看看。

  • 首先是一个包含算术逻辑单元(Arithmetic Logic Unit,ALU)和处理器寄存器(Processor Register)的处理器单元(Processing Unit),用来完成各种算术和逻辑运算。

  • 然后是一个包含指令寄存器(Instruction Register)和程序计数器(Program Counter)的控制器单元(Control Unit/CU),用来控制程序的流程,通常就是不同条件下的分支和跳转。在现在的计算机里,上面的算术逻辑单元和这里的控制器单元,共同组成了我们说的 CPU。

  • 接着是用来存储数据(Data)和指令(Instruction)的内存。以及更大容量的外部存储,在过去,可能是磁带、磁鼓这样的设备,现在通常就是硬盘。

  • 最后就是各种输入和输出设备,以及对应的输入和输出机制。我们现在无论是使用什么样的计算机,其实都是和输入输出设备在打交道。个人电脑的鼠标键盘是输入设备,显示器是输出设备。我们用的智能手机,触摸屏既是输入设备,又是输出设备。而跑在各种云上的服务器,则是通过网络来进行输入和输出。这个时候,网卡既是输入设备又是输出设备。

任何一台计算机的任何一个部件都可以归到运算器、控制器、存储器、输入设备和输出设备中,所有的现代计算机也都是基于这个基础架构来设计开发的。而所有的计算机程序,也都可以抽象为从输入设备读取输入信息,通过运算器和控制器来执行存储在存储器里的程序,最终把结果输出到输出设备中。而我们编写的所有的无论高级还是低级语言的程序,也都是基于这样一个抽象框架来进行运作的。

冯·诺依曼体系结构概念图

冯·诺依曼体系结构的特点

冯·诺依曼体系结构有一个非常明显的特点——它一视同仁地对待指令和数据,它规定了指令和数据统一用二进制进行编码,而且指令和数据是存储在同一个存储器中(同一个内存空间中)的,借着将指令当成一种特别类型的静态数据,一台存储程序型电脑可轻易改变其程序,并在程控下改变其运算内容。

存储程序型概念也可让程序运行时自我修改程序的运算内容。本概念的设计动机之一就是可让程序自行增加内容或改变程序指令的存储器位置,因为早期的设计都要用户手动修改。但随着变址寄存器与间接位置访问变成硬件结构的必备机制后,本功能就不如以往重要了。而程序自我修改这项特色也被现代程序设计所弃扬,因为它会造成理解与调试的难度,且现代中央处理器的管线与缓存机制会让此功能效率降低。

---------维基百科

虽然指令和数据共用同一个内存空间,但是我们在代码中组织内存的时候指令和数据却不是一锅粥式地存储在一起,而是采用了分段的方式,最典型的就是将程序分为四段,分别是代码段、静态存储区、堆区(自由区,动态存储区)和栈。

虽然我们上文中引用的维基百科中的在程序运行过程中动态修改指令数据的方式已经基本被抛弃,但是在程序的运行过程中在堆区生成代表指令的数据同时将程序计数器重定向到堆区上的方式还是比较常用的(解释型语言的解释器、人工智能等场景),通过这样的方式我们同样可以实现在运行时生成指令的目的。

冯·诺依曼瓶颈

将CPU与存储器分开并非十全十美,反而会导致所谓的冯·诺伊曼瓶颈(von Neumann bottleneck):在CPU与存储器之间的流量(数据传输率)与存储器的容量相比起来相当小,在现代电脑中,流量与CPU的工作效率相比之下非常小,在某些情况下(当CPU需要在巨大的数据上运行一些简单指令时),数据流量就成了整体效率非常严重的限制。CPU将会在数据输入或输出存储器时闲置。由于CPU速度远大于存储器读写速率,因此瓶颈问题越来越严重。

------------维基百科

现代的计算机通常采用多级存储器(也就是缓存)来解决冯·诺依曼瓶颈,对CPU进行流水线设计以及加入分支预测功能也帮助缓和了此问题。同时引入缓存使得我们操作内存的粒度由逐字节扩大到了逐缓存行(比较大的缓存设计可能一个缓存行能容纳32个字节),这也在极大程度上减少了内存访问的次数,缓解了瓶颈。

冯·诺依曼体系结构还存在一个问题,因为指令和数据共享同一个内存空间,那么CPU的取指令操作和数据读写操作有可能就会发生资源竞争,这也在一定程度上加剧了瓶颈。

哈弗架构

从严格意义上说,哈弗架构并没有完全跳出冯·诺依曼体系结构,它也是一种程序存储计算机,只是其在指令和数据的存储方式上采用了和冯·诺依曼不一样的方式——把程序指令和数据分开存储。CPU首先到程序指令储存器中读取程序指令内容,解码后得到数据地址,再到相应的数据储存器中读取数据,并进行下一步的操作(通常是执行)。程序指令储存和数据储存分开,数据和指令的储存可以同时进行,可以使指令和数据有不同的数据宽度,哈佛架构的微处理器通常具有较高的执行效率。其程序指令和数据指令分开组织和储存的,执行时可以预先读取下一条指令。

哈弗结构逻辑图

这样的设计方式解决了CPU取指令和内存读写的资源竞争,但是电路设计上可能会更加复杂——因为可以使指令和数据有不同的数据宽度,可能要单独设计两套存储器访问的电路;也正是因为指令和数据的宽度可能不一致,所以指令和数据虽然在物理上还是同构的(都是用二进制编码存储的),但是在逻辑上已经是异构的了,那么如果要实现我们上文所说的在运行时动态生成代表指令的数据的功能就要花费额外的精力。

基于上面的限制,哈弗架构并没有得到非常广泛的应用,而为了解决CPU取指令和数据读写的资源竞争,现在大多数的计算机都采用了我们下文要介绍的改进的哈弗架构。

改进的哈弗架构

改进的哈弗结构其实是综合了哈弗结构和冯·诺依曼结构的特点,其还是把指令和数据进行统一的存储,但是在缓存的层面做了一些文章——把缓存拆分为了指令缓存和数据缓存,但它放松了指令和数据之间严格分离的这一特征,仍然允许CPU同时访问两个(或更多)内存总线。

下图是我截取的我的电脑的CPU信息,其中一级缓存(也就是最靠近CPU寄存器的那层缓存)已经分为了指令缓存和数据缓存,但是在下层的缓存中则没有这样的区分,当然了,内存也是没有这样的区分的。

image-20200524222017382

也就是说,改进的哈弗架构把哈弗架构的特性放到了更高层级的存储中,而在最底层的存储中仍然保留了冯·诺依曼体系结构的特点。

现代高性能CPU芯片在设计上包含了哈佛和冯诺依曼结构的特点。特别是,“拆分缓存”这种改进型的哈佛架构版本是很常见的。CPU的缓存分为指令缓存和数据缓存。CPU访问缓存时使用哈佛体系结构。然而当高速缓存未命中时,数据从主存储器中检索,却并不分为独立的指令和数据部分,虽然它有独立的内存控制器用于访问RAM,ROM和(NOR)闪存。

因此,在一些情况下可以看到冯诺依曼架构,比如当数据和代码通过相同的内存控制器时,这种硬件通过哈佛架构在缓存访问或至少主内存访问方面提高了执行效率。此外,在写非缓存区之后,CPU经常拥有写缓存使CPU可以继续执行。当指令被CPU当作数据写入,且软件必须确保在试图执行这些刚写入的指令之前,高速缓存(指令和数据)和写缓存是同步的,这时冯诺依曼结构的内存特点就出现了。

-------维基百科

指令也是数据

现代计算机体系结构,无论是冯·诺依曼体系还是哈弗架构,都把会把我们的程序存储起来,以此来实现计算机的“可编程”,其实这里面最本质的思维是把指令当做数据来对待。

指令和数据是同构的,理解这一点是非常重要的,因为现在又非常多的程序都是依赖于这个理论基础的,比如某些解释型语言的解释器就是在解释代码的时候生成可供计算机直接执行的机器码,然后把CPU的指令计数器指向刚刚生成的机器码进行执行。即使是以后出现了颠覆冯·诺依曼体系结构的新计算机体系结构,其也应该尽量实现这一点。即便是纯粹的哈弗架构,其也应该提供相应的指令来实现这样的同构。

上文中我们提到了机器码,也提到了现代计算机体系结构统一用二进制存储指令和数据,想必在你刚刚接触编程的时候就知道了,计算机只认0101这样的机器码,现在我们对支持我们使用高级语言编程的硬件环境有了基本的了解,那么编程语言又是如何一步一步从0101这样的机器码自举到今天高级语言遍地开花的局面的呢?下一篇文章,我们就来介绍一下高级语言的诞生。

感谢你耐心读完。本人深知技术水平和表达能力有限,如果文中有什么地方不合理或者你有其他不同的思考和看法,欢迎随时和我进行讨论(laomst@163.com)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

劳码识途

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值