《编码》这本书被列了入我们团队技术方向培养方案的必读书目,可见这本书的地位。因此,我觉得有必要来撰写一篇导读,来帮助大家看透一些问题,把这本书上的知识和课本中的知识对应起来,并且形成一个体系。

《编码》讲的是什么呢?笼统的说,就是用自底向上的角度一层层的解析计算机的原理。比如第一章到第三章将的是编码的本质——信息和符号的对应。第四章到第六章讲的是电信号可以用来表示一种编码,从而传递信息。第七章到第九章介绍了二进制,也就是计算机系统中最基本的编码形式。第十章:逻辑与开关和第十一章:门,介绍了布尔代数,以及如何使用门电路进行布尔代数的运算。第十二章和第十三章介绍了二进制加法器和减法器,加法器已然是现代CPU中运算单元的雏形了。第十四章介绍的反馈与触发器是实现计算机中储存芯片的基础。第十五章字节与十六进制是为理解后续的存储器数据格式而准备的。第十六章讲了存储器的组织,这便是现代计算机系统中存储系统的原理。第十七章,是全书最难的一章,讲了如何制作一个CPU。CPU本质上就是一个可以编程的运算器。第十八章介绍了从算盘到机械结构到电子管再到晶体管的技术革命,晶体管、超大规模集成电路使得现代CPU的出现成为了可能。第十九章介绍了Intel 8080CPU的指令集。第二十章介绍了字符的存储格式,这可以帮助你理解计算机是如何存储文本的。第二十一章:总线,介绍了计算机系统中CPU、内存和I/O设备如何通信。第二十二章是一个简短的操作系统简史,注意到从这里我们涉及了软件层面的内容。第二十三章定点数和浮点数比较独立,主要讲了计算机是如何存储浮点数的。第二十四章和第二十五章则介绍了编程语言和图形学基础。

我们可以清晰的看到从硬件到软件,从具体到抽象,从底层到顶层的一个脉络。

这本书其实包含了我们计算机专业中《数字逻辑》、《计算机组成原理》、《微机系统与接口原理》三门课的主干内容。作者没有试图灌输大量的理论知识,而是试图将这些知识串联起来,包括最后的操作系统和编程语言,其实和计算机硬件是有着不可分割的关系的。在《编码》这本书中你就可以很自然的在章节的推进中体会到这种联系。

下面我会对一些章节进行详细的解读。主要是解释难点,拓展一些知识,并且写清这一章节的知识对应我们的哪门专业课,而专业课和这本书的要求有何不同等等。

我希望这本书可以让大家看清计算机的本质,计算机的本质其实就是一个处理信息的机器。你输入数据,计算机输出数据。首先我们需要一套编码系统来表示这些数据,而二进制的编码正好与半导体的性质相吻合,我们可以用布尔代数来对现实问题进行建模,通过门电路来表示这个布尔代数,从而制造出可以进行某种运算的电路。而如果这个电路可编程,再加上输入输出接口,那就是一个CPU了。至于是半导体和二进制之间孰先孰后呢,你可以做进一步的研究(从书中来看,第一台计算机ENIAC是采用十进制的,所以看来应该是半导体在先,二进制在后)(所以今后如果计算机的物理介质有了更新,我们也许就会换一种新的编码方案了)。

在书中大量讲到的门电路、继电器等等属于实现层面,大家了解一下就好。我们不是电气工程师,我们是软件工程师。当然理解硬件层面细节对于写好代码是很有帮助的。这主要解释的是一个“为什么”的问题。

你了解了CPU的指令集,和CPU的工作方式,你就理解为什么我们在程序运行时需要“栈”这个数据结构。

你了解了字符的编码以及浮点数的编码,你就不会奇怪于为什么0.1+0.2不等于0.3

我们要关注的主要是如何用编码来抽象问题。比如用二进制来表示数据,进行运算。比如用指令来对CPU进行编程。比如

第一章:至亲密友、第二章:编码与组合和第三章:布莱叶盲文与二进制码

前几章就举了几个编码的例子,让你知道——编码可以用来传递信息。然后布莱叶盲文是一种二进制的编码,因为和计算机的二进制特点符合,所以被拉出来讲了。接下去几章就是铺垫一些电学基础,让你知道我们可以用开关的开和闭,来表示1和0的编码。这样后面就可以借助这个特性,拿开关和电线来组装计算机了。

第四章:手电筒的剖析、第五章:绕过拐角的通信

第五章里最重要的概念就是接地。大地电阻很大,但大地是导体。电子只要流动就能形成电能,电子是哪里来的?是不是一个环路?这个不重要。接地处电子往大地那里移动,电势就低。电池那边的电子就可以往接地处移动了。 有人说,那我一个电路,中间拿掉一个导线,为什么不能接通?因为空气的电阻太大了··· 电子没法移动到空气中。

第十章:逻辑与开关与第十一章:门

第十章首先介绍的内容是:布尔代数。

布尔代数,其实在我们的《离散数学》课里就接触过。注:如果没学过离散数学,可以买《离散数学及其应用》这本书学习,这里说到的形式逻辑主要指命题逻辑《离散数学》里一开始讲的逻辑,比如这种形式的:

命题逻辑

我们知道一个命题可能是真(T),也可能是假(F)。那如果我们把T和F换成1和0,然后将命题逻辑中的合取,析取,否定等关系换了一种表示形式,这就成了布尔代数。

布尔代数中每个变量都是一个{0,1}的集合。命题逻辑中的合取在布尔代数中用·或者AND表示,而析取则用+或者OR表示。否定则用表示。

在第十一章中,有一个布尔表达式:

(M·N·(W+T)+(F·N·(1-W))+B)

我们代入每个变量之中,运用布尔代数运算符的规则,就可以计算出这布尔表达式的值是True或者是False。

这就是问题的关键了,布尔代数是一个抽象的计算模型。我们可以将现实世界的问题,抽象成布尔表达式(当然这个最终是因为我们可以用命题逻辑来表示现实世界的问题)。

讲到这里,似乎和计算机没有什么关系。但最精彩的部分就在下面,我们可以用布尔代数设计电路

一个机械开关,有开启和闭合两种状态,可以用来表示0或者1。这个是《编码》前几章大力铺垫的一个事实。既然这样,那我们就可以用两个开关的并联或者串联来表示AND和OR逻辑。更进一步,我们可以用开关和电线表示任何布尔表达式。

值得注意的是,用布尔代数可以用某种电路来等价的表示这个事实。从在19世纪50年代乔治·布尔发明布尔代数开始,一直到20世纪30年代才被信息论之父香农发现。尽管逻辑电路所需的开关和电线在19世纪都已经存在了。可见这种等价关系的革命性意义。

当然,反过来,布尔表达式也可以指导我们设计电路。事实上这个是目前所有逻辑电路设计的理论基础。

《数字逻辑》这门课讲布尔代数,主要是作为设计电路的一种工具。假如我们要设计电路,解决这个现实问题。这个问题被抽象成几个输入和输出。这些输入和输出是可能是0或者1。我们知道不同变量输入时对应的输出,也就是所谓的真值表。利用真值表,我们可以写出一个布尔表达式。但这个布尔表达式可以化简。我们可以用卡诺图等方式进行化简,化简的目的是简化电路。最终,化简的电路可以转化为门电路。

《编码》里只是简单的介绍了布尔代数和门电路,没有深入的介绍电路的设计。这是比较明智的,因为如何设计电路不是我们要关心的话题。《数字逻辑》中在电路设计这块涉及的还有功能表到布尔表达式的转换,布尔表达式的化简等等。有兴趣的同学可以了解一下。

总结一下,这两章对我们最大的启发就是:逻辑电路设计的理论基础是布尔代数。布尔代数可以用逻辑电路进行等价表示。而逻辑电路则是计算机所有硬件的基础。

第十二章:二进制加法器

这章一开始说:

如果我们可以造出加法器,同样地,就可以利用加法来实现减法、乘法和除法,计算按揭付款,引导火箭飞到火星、下棋,以及填写我们的话费账单

大家可能觉得这是夸张的说法,但其实加法器的确就是CPU中最重要的部分。实现了加法器,再加上存储系统,和控制器,就是一个基本的计算机了。

所以这章内容可以说是《编码》这本书介绍的如何制造一台计算机中的第一次飞跃(第二次飞跃是第十七章,自动操作)。

说的很神奇,其实加法器就是由简单的门电路构成的一个电路而已。

我们已经知道了各种门电路的输入输出,那问题就是,如何来利用门电路,设计一个相对复杂的电路呢?

这部分的知识对应的是大学中的《数字逻辑》课程。电路的设计有一套成熟的方法,当然我们先不考虑那么多,先来看看《编码》的作者是如何设计这个加法器电路的。

对于一个电路,我们首先要确定输入和输出,并写出一个真值表。

我们进行二进制数的相加时,会分别相加两个二进制数的每一位,如果有进位,则将进位加入下一位二进制数的相加中。

这样,我们就可以将两个二进制数的加法,转化为多次的一位二进制数加法。一位二进制数加法的输入是两个个加数。输出是一位的二进制数,以及一个进位数。比如输入是1加1,输出结果是0,进位位是1。

现在我们就可以列出两个真值表,一个是加法的真值表,一个是进位的真值表。

+加法01
001
110
+进位01
000
101

好了,现在我们可以进入第二步,根据真值表,设计电路。

我们要设计这样一个电路,输入A和B,都是一位二进制数,输出结果C和进位数D,也是都是一位二进制数。

我们观察真值表的输入和输出,发现进位表的真值和与门是一样的。而加法表的真值和异或门是一样的。

P141页的半加器,就是简单地将一个异或门和一个与门连到相同的输入端。异或门输出的就是加法结果,与门输出的则是进位的结果。

为什么这个叫半加器呢,因为这个半加器还无法完成真正的加法,因为它的输入中没有考虑进位。真正的一位加法器,输入有两个加数,以及进位位,输出结果和进位位。

P142页顶部的图就是全加器。它的原理就是级联两个半加器,将第一个半加器的结果和输入的进位位相加,对于两个半加器输出的进位位,则用一个或门来处理。或门在这里其实也是起一个加法器的作用,只不过两个级联的半加器的进位输出不会同时为1*(如果前一个半加器产生进位,那结果必然是0,那后一个半加器就不可能产生进位了)*,因此不用考虑输入都为1的情况。

P144页底部的图,讲8个全加器连接起来,前一个全加器的进位位输出是后一个全加器的进位位输入,第一个全加器的进位输入是0。这样我们就得到了一个8位加法器。

这个加法器用开关的开和闭表示1或者0的输入,用灯泡的亮和灭表示1或者0的输出。这种方式很原始,但从原理上来说和现代的晶体管电路和LED显示器没有太大的不同。

这种加法器有一个问题,也就是书中P146页讲到的,在时间上必须串行工作,即后一个全机器要等前一个完成运算之后才能进行运算,而不是并行的工作。解决这个问题,可以用前置进位等方法来提高运算的速度。这个会在《数字逻辑》课中讲到。

我们要知道的一点是,这里的电路是比较naive的。因此作者可以一眼看穿真值表对应的电路。而实际中电子工程师设计的电路是非常复杂的。用专门的方法和设计语言。

《数字逻辑》课程中,有一套非常sophisticated的设计电路的方法。从真值表中得出布尔代数表达式,用卡诺图等工具对这个表达式化简,最后用基本的门电路来实现。我们作为软件工程师,对这套方法有着基本的掌握就可以了。

这章的意义重大。我们用19世纪就被发明出来的电子器件,开关,灯泡和继电器,搭建了一个加法器。这可是一个能做加法的机器,这意味着我们可以用机器来做通用计算,而不是之前设计的只能帮你选择猫咪的死板电路(当然我觉得那个电路也很神奇),这在构建可编程计算机的道路上无疑是一个里程碑。

回首这个过程,最重要的理论上的突破,无疑是布尔代数的发明,以及香农将代数和电路对应起来,用电路实现布尔代数的发现。

第十八章:从算盘到芯片

第二十五章:图形化革命

这章与其说是在讲图形化革命,不如说是PC多媒体方向的发展史。

从电传打字机到视频显示器

当然首先提到的还是图形化革命。这里有一个很重要的概念就是人机交互界面(GUI)。在打孔机时代,人机交互的实现主要是,通过纸带或者各种开关进行输入,然后经过计算机批处理之后输出。这个输入-处理-输出的循环,就是一个人机交互的过程。

20世纪60年代,流行的时分操作系统,输入和输出设备是打字机。这就比较接近于现代计算机的交互了。

早期的显示器,其实就是“玻璃屏幕电传打字机”。这样的显示设备仅仅只能一行一行的输出字符,和打字机没有太大区别。想要显示图形,那就只能使用ASCII Art了。这种类似打字机的界面,比较接近于我们现在使用的终端。

C语言的标准I/O里的输出函数为什么叫print呢?这就和当年的输出设备是打字机有关系了。在当时看来,这个函数就是在纸上打印字符,所以叫print是很贴切的。 Linux中的一些命令,也和早期计算机的输入输出有关。比如早期计算机使用磁带作为存储设备。大家有兴趣的可以了解一下。

接下去就是一个关键的分界线了。首先,我们要知道显示设备的原理。光栅显示器的原理就是,用一个像素点矩阵来组成图像。比如分辨率是1920*1080,那就意味着你看到的图像由2073600个像素点组成。

我们知道红、绿、蓝。三原色可以混合出所有的颜色。那我们就可以通过某种编码来表示显示器像素的颜色。 做客户端的同学,会知道代码中有很多表示颜色的方法,比如rgb(255,255,255)或者#FFFFFF。这两种其实是一样的。都是用分别用8位二进制数来表示红、绿、蓝三原色的深度。范围是0-255,数值越大,表示这种颜色越深。

这种编码叫256色,可以表示256的三次方种颜色。这样的显示器已经足够一般人使用了。

那我们就可以用三个字节来表示一个像素点的颜色。给每个像素点都写入颜色信息,那显示器就可以用这个信息来绘制图像。

问题就来了,这些信息要放在哪呢(一个1920*1080的矩阵,每个项目占三个字节)。最自然的当然是放在内存中,但内存和显卡直接的通信成本比较高,因此我们可以在显卡上设置一个专用的存储器,也就是我们常说的显存。显存主要的作用就是存放图像渲染的信息。

施乐和Mac

OOP和图形界面

图像格式与压缩算法

模拟/数字转换和多媒体

互联网