计算机系统由硬件系统和软件系统组成,它们共同协作来完成执行程序的任务。作为20世纪(世界上第一台计算机ENIAC与1946年诞生于美国的宾夕法尼亚大学)最伟大的发明之一,计算机系统可以说是人类迄今为止创造的最复杂最精密的工具。今天,借助计算机界的圣经教材《深入理解计算机系统》,我来带领大家探索一下计算机系统的秘密。
1 信息就是位 + 上下文
计算机是信息学科的产物,其主要左右就是帮助人们处理各种各样的信息。而信息本身是一个比较抽象的概念,从百度百科给出的定义中我们可以知道,信息是用来传输和处理的。而且信息一定是有一个载体,也就是有一个表现形式。那么再计算机中,信息是如何存储并传输的呢?这就要说到我们这一节的主题了:信息就是位
+ 上下文。
计算机中所有信息都是以二进制位的形式存储的,理解这一点可以帮助你搞明白很多问题。比如很多新手搞不懂的文本文件和二进制文件去区别。其实,无论什么文件,在计算机中全部都是...01101100...这样的二进制码。所以所有文件都可以说是二进制文件,而文本文件不过是一种特殊的二进制文件罢了。特殊在哪儿呢?就特殊在计算机对其的“翻译”上。我们人类是不太能处理...01101100...这种东西的,所以计算机要将这些以二进制表示的信息翻译成我们人类所熟悉的文字、图片、声音等这些多媒体信息。当然,不同的多媒体信息与二级制之间的翻译规则是不同的,其中二进制与文字之间的一种翻译规则就称为ASCII编码。通过ASCII编码可以实现8位二进制位和各个字符之间的一一对应。而将文字信息利用ASCII编码转换成二进制在计算机系统中存储起来的那个文件就叫做文本文件。鉴于此,我们就可以将二进制文件分为文本文件、图片文件、声音文件等等。总之,在计算机中,我们熟悉的各种信息被编码成二进制的形式存储起来并进行传输。
就像上文提到的,不同类型的信息和二进制之间的转换规则是不同的。你不能把你喜欢的歌曲用图片的形式打开,也不能把你的照片转化成文字(当然强制转换是可以的,但结果肯定不是你想要的,回想一下,有没有经历过满屏乱码的经历)。也就是说,二进制信息也是有类型的,这些类型就表示在信息所在那个文件里,也就是那片二进制码中。这样就形成了前后相互关联的关系,一个个二进制位在特定的环境中被赋予了不同的含义。同样,我们要理解或者说翻译它们,肯定也要根据其前后的环境来理解,这就是我们所说的上下文。
信息被编码成一片相互关联的二进制代码存储在计算机系统中,它们以二进制的形式存储、传递、处理。只有在与人类交互时,比如显示在屏幕上或者打印输出时,才会被翻译成人们熟悉的形式展现出来。
2. 程序被其他程序翻译成不同的格式
前面我们说了计算机的主要作用是处理信息,也就是处理...01101100...这种二进制码。那显然就有一个问题,计算机作为一个没有生命没有智力的东西,它是怎么知道该怎么处理信息的呢?当然,那就是我们的软件或者说是程序了。软件系统只所以成为计算机系统不可缺少的部分,就是因为信息的处理过程必须由软件来完成,而一个不能处理信息的计算机系统是没有任何存在意义的。于是,人们发明了软件,赋予了计算机系统生命。
如何告诉计算机改怎么处理信息呢?既然计算机内只能存储二进制码,于是人们自然想到了用二进制码去控制计算机系统,没错,这就是机器语言的诞生!二进制码也变成了二进制代码。但前面我们也提到了,人类是不适应处理...01101100...这种东西的。所以在当时,人们和计算机的“交流”成本是非常大的,而且效率低下。人们为了克服这一困难,发明了另一种交流手段,即先用机器码写一个翻译软件,然后我们用一些助记符来表达想要执行的操作,再用前面那个翻译软件翻译成二进制机器码,最后让计算机系统去执行这些代码。借助这种“师夷长技以制夷”的策略,软件界向前迈出了一大步,而人们将这些助记符组成的语言称为汇编语言。接下来的事情就顺理成章了,人们又发现汇编语言还是太难用(人们总是感觉现有的东西难用,这也是人类文明向前发展的重要动力),于是便又发明了C语言,C语言更贴近自然语言,编码风格也更加灵活,深的用户喜爱。不过人类是不会满足的,后来又发明了C++、Java等各种语言。这些语言更加贴近现实世界,而远离底层那些烦人的二进制代码。
回到这一小节的标题,既然现在程序员写程序用的是所谓的高级语言,那搞明白计算机是如何把我们写的代码翻译成二级制代码的就很有必要了。这里我们以编译型语言为例,来说明一下这其中经历的几个步骤:首先是我们写的文本文件(为什么是文本文件?因为我们对文字更熟悉),这些文件被预处理器“物理”的处理了一下,产生一个中间产物(还是文本文件),然后是我们的重头戏,编译器把文本文件“智能”地翻译成汇编文件,接着是汇编器,它把汇编文件翻译成机器熟悉的二进制文件,最后是连接器,将多个文件链接起来,让计算机系统执行。
这里要着重说一下的是编译器的作用,前面的预处理器和后面的汇编器以及链接器做的都是固定的一对一的翻译工作,只有编译器是“智能”的,这种智能体现在优化能力上。首先要说明一下,在特定的硬件平台上,汇编代码和机器码是一一对应的,从汇编代码可以完全得到计算机系统的运行过程,所以程序调试的最底层是汇编语言而不是机器码(当然如果你喜欢看机器码也是可以的)。于此想对应的是编译器翻译的两端:源代码和汇编代码,并不是一一对应的,同样的源代码在不同的情况下(跟编译器的类型和优化级别有关)会被编译器翻译成不同的汇编语言。所以编译器是一个非常复杂且重要的软件系统,程序员的一生都在和它打交道!
这一小节讲的是我们平时使用的编程语言是为何以及如何在计算机中被转换的。编译系统为我们的代码和机器控制之间搭建了桥梁。
3. 了解编译系统如何工作是大有益处的
编译系统是一个智能的翻译系统,但其智能程度也很有限,它要求程序员必须按照既定的格式安排组织代码,否则,它可能会“误解”我们的意思。这些既定的规则就是某种语言的语法,我们必须严格按照语法来编写程序,否则编译器会报错说无法完成翻译。但这仅仅是个开始,写程序去控制计算机是一个复杂的过程,在这个过程中会出现各种各样的错误或者瑕疵影响我们的控制效果。要想更好的完成工作,我们有必要去了解编译器是如果工作的。
了解编译器的工作原理,至少是了解关键的几点,对程序员尤其是C程序员是非常有必要的,它可以让你和编译器合作的更加融洽。具体来说,有这样几个好处:优化程序性能,你可以从语言层面上提高程序的运行效率;理解链接时错误,相信大部分C/C++程序员都经历过无法解析的外部符号吧,等你理解了编译器的链接过程,你就能明白这到底是什么意思了;避免安全漏洞,缓冲区溢出是一个臭名昭著的程序bug,函数调用栈的知识有助于你理解并避免这种错误。
学习编译器的工作原理并不像学习其他技术一样,会对编程水平有很快的提高。这些知识更像武侠小说中的内功心法,是程序员进一步提高的基础。
4. 处理器读并解释存储在存储器中的命令
我们编写的源代码经过编译器的各种处理,最终转变成了二进制代码存储到了计算中内存中。内存可以看做是一个数据仓库,里面以二进制的形式存储着大量的数据。其中就有我们的程序代码,这些代码通过一个叫处理器的物理部件来执行,从而控制整个计算机系统的运行。
计算机系统整个运行过程就可以看做是处理器从内存读指令(代码的一个新名字),执行指令的过程。其效果就是处理器根据指令控制外围部件完成各种任务。这里就要讲到冯诺依曼的计算机体系结构了:存储器,就是我们的内存了,所有数据都放在这里;控制器,这是处理器的一部分,它利用时钟信号产生的“脉搏”来控制其他部分的运行;运算器,这也是处理器的一部分,用来完成简单的算术/逻辑运算,这些简单的运算构成了系统处理数据的基础;输入输出设备,这些东西是用来完成人机交互的,比如鼠标、键盘、显示器等等。
在整个系统运行过程中,数据被反复从一个位置移动到另一个位置。可以认为系统的运行过程就是搬运数据的过程。所以数据的传输效率是决定系统整体效率的关键因素,为了提高数据传输效率,计算机系统内采用了很多技术,了解这些技术并合理利用可以很大程度上提高程序性能。下面就来介绍一些这方面的知识。
5. 高速缓存至关重要
对于特定的存储器件来说,其存取速度和容量是成反相关的。也就是说,其存储量越大,其存取速度越慢。在计算机系统中存储速度最快的存储器件是寄存器,而其个数只有几个或者十几个。而存储量最大的硬盘,存取速度是最慢的。一个机器的硬盘容量可能比其内存容量大1000倍,但其存取速度可能要慢1000万倍,为了调节如此大的速度差距,计算机系统采用多级缓存的方法。
缓存的作用其实很简单,它只是把处理器可能要用到的数据提前从容量比较大的读取速度比较慢的存储器件中读出,以备处理器使用。为了调节效果,现在的新型系统中都采用三级缓存技术,即从内存到寄存器之间插入三层存储器件,它们容量越来越小,速度越来越快。
6. 存储设备形成层次结构
正如上面提到的,计算机系统中各种存储器件根据容量或者存取速度有一个排序,这样就形成了一个层次结构。最上面的是寄存器,下面是各级高速缓存,然后是内存,再下面是硬盘,最后可能还有网络资源。
存储器之所以是分结构,就是因为物理特性决定了其存储量和读取速度一定是成反比的。为了使快的设备和慢的设备更好的交换数据,就需要在中间插入一个中间层。相比于具体的实现细节,这种设计思想是更值得我们学习的。
7. 操作系统管理硬件
我们写的程序主要是逻辑的表达,但真正在计算机系统中运行,却要时时刻刻和具体的硬件打交道,我们不大可能每次写程序都花费心思就管理存储器的每个字节,也不应该思考屏幕每个像素点该怎么控制。为了使硬件系统对普通程序员透明化,计算机系统中采用操作系统来统一管理硬件,我们的应用程序只需要和操作系统交流就可以了。
有了操作系统的帮助,我们就可以把各种硬件设备抽象成一个统一的模型来管理使用了,而不用去理会具体的实现细节。具体来说,I/O设备被抽象成文件,内存和I/O设备被抽象成虚拟内存,处理器、内存和I/O设备一起被抽象成进程。让我们从大到小,分别做一个简单介绍。
进程,当一个程序被执行时,我们可以认为这个程序独占整个计算机系统,这种一个程序独自占用CPU、内存、I/O设备的“假象”是通过进程的概念实现的。操作系统控制整个计算机的过程就可以看做是管理进程表的过程,操作系统按照某种规则再不同的进程中间来回切换,从而完成并行的任务。我们执行一个程序,在计算机系统中就多了一个进程。
虚拟存储器,是对内存区域和I/O设备的抽象。程序员平时说的全局变量区、堆区、栈区等等都是虚拟内存中抽象的概念,而真实的内存是不分这些东西的,它们的硬件基础是完全相同的。之所以做这种抽象,就是为了程序员使用内存方便,而将程序员看到的内存和硬件内存相对应是一个相当复杂的过程。
文件,文件对于每个使用过计算机的人来说都不陌生,其实文件的本质就是一些字节序列。所有可读写的硬件设备都被抽象成文件,程序员只需要去读文件或者写文件就可以了,而不用关系具体硬件是怎么完成读写的。
对硬件的模型化的目的就是为了方便人们使用它们。正是有了种种抽象概念,程序员才得以从繁琐的硬件细节中解脱出来。这种解决问题的方法在整个计算机乃至信息领域都非常常见,是人类聪明才智发挥的完美体现。
8. 系统之间利用网络通信
计算机系统之所以如此强大,是因为它不仅自身可以处理数据,而且还可以进行跨越空间进行通信。计算机网络是比计算机系统本身跟重要的产物,正是有了这个网络,人类社会才发生了翻天覆地的变化。
虽然网络的概念非常重要,但这里并不打算介绍太多网络知识,对于一个计算机系统来说,网络上就相当于一个文件,我们可以从中读数据也可以往里面写数据,了解这一点对于程序员来说就可以了。
9. 重要主题
并发和并行是当前计算机系统速度提高的关键概念,他们都涉及同时处理多个事务的能力。就像人不能一心二用一样,计算机系统的并发也比较困难,几十年的研究现状才刚刚进入实践。这应该是以后计算机方面的研究热门之一。
抽象是人类思维的高级体现,在计算机系统中,人们用了很多抽象来帮助我们完成任务。而在其他领域,抽象的意义也非常重要,这在程序设计语言中也有很多体现,比如接口的概念,就是对一组功能的抽象。
10. 小结
计算机系统是被用来处理信息的,这些信息以二进制的形式存储在系统之中。为了实现计算机系统的控制,我们必须写代码完成。编译器是解释翻译我们代码的工具,理解翻译官的工作原理有助于我们写出更加安全高效的代码。处理器是执行代码的器件,在处理数据的过程中,数据传输在系统中非常常见,为了提高传输效率,采用快速缓存的思想来调节不同硬件的存取速度差别。抽象是非常重要的概念,无论是计算机系统中还是在其他领域,学习抽象思维比学习具体的技术更加重要。