1、键盘输入一个数字计算机背后发生了什么?
2、计算机如何实现加法运算?
3、计算机如何在庞大的内存里面找数据?
4、计算机如何执行代码?
5、编程的本质是什么?
继电器,串联,并联,振荡器,计数器,地址,内存,RAM阵列,锁存器,累加器,触发器,开关
上一章我们介绍了计算机编码技术,从理论上论述了如何用最简单的0和1来表示数字,文本,甚至是图像和音频视频。但是如果仅仅只是表达信息还远远不够,本文将进一步通过使用一百年前就存在的工具手动构造一台“计算机”来揭示计算机运算的本质,从而说明了信息是如何被运算加工的。
我们已经掌握了采用二进制表示任何信息的技巧。可以通过二进制的一连串0和1来表示数字,文字,图像和声音。那么如果能找到合适的载体来实现我们的理论就能有效额表示信息。
我们知道长城由一个个的城堡和城墙连在一起。古时候有烽火戏诸侯的典故,那么长城也可以是我们要找的工具中的一种。我们约定城堡点火的时候表示1,没点火的城堡表示0,而且我们还规定每连在一起的8个城堡就算是一节,用来表示一个单位的信息。借用ASCII编码规则,只要你乐意就可以用长城的城堡点火与否来表达一首诗歌。当然,国家不会让你这么干,而且这样确实也比较麻烦。那就那我们身边容易找到的工具来表示。我们把时钟拨回到一个世纪之前,那个时候物质可没那么丰富,我们能找到最高级的就是电灯电池和电线磁铁了。
这就好办了,道理一样,点灯点亮了就表示1,灭的时候就表示0。我们做了一个长方形的木盒,上面插入了8个点灯排成一列,那么一个盒子就是一个字节了。这时候一个点灯连着一个开关,再接入电源,手动来控制这些点灯就可以表示不同的信息了。在ASCII编码中一个盒子可以表示一个字母(中文的话需要两个盒子来表示一个文字),那么只有你足够时间和金钱,你可以用这样的电灯盒子,构造出一本新华字典。
我们可以用电灯盒子来表示文字,但这只是你自己一个人在用,确实有点可惜,你自己对着一堆电灯玩久了也会腻吧。这个时候你肯定很想跟女朋友聊聊天吧。古有飞鸽传书,靠谱的鸽子速度还可以,但是如果遇到广东吃货半途被吃掉了,那你女朋友等半天没收到你的回信是要搞事情的。这个时候你突然灵机一动,你的那些电灯盒子是字典都能写出来,还怕写封信。但是怎么把写好的文字让你女朋友看到呢。因为你不差钱,所以你索性在你女朋友家也搞了一套一模一样的电灯盒子,可以把开关通过电线连到你家来,电线要很长很长。同样你家的那套灯具开关也连到你女朋友家。这么一来你们就可以在家轻松的聊天了。
这样我们就实现了信息的远程传输了。而且电路也很简单如下:
我们实现了远程的信息传输,采用了计算机编码的约定来检阅信息。成功的通过工具来传达我们所要表达的信息是一件很令人振奋的事情,但仅仅是传达信息还远远不够。人类借助语言进行高效的沟通协作,极大的提高了劳动生产力。语言最主要的核心就是上下文关联,也就是逻辑关系。那如何用我们现有的工具进一步来表示逻辑运算?
我们知道逻辑运算中最基础的三种运算时与,或,非,用我们的电灯来表示可以按如下方式来连接。
简化为与或非得符号,这将是构成我们下面要构建机器的基石,非常的重要。
本节构造加法机的步骤分为:
半加法机->全加法机->多位加法机->记忆加法机->选择加法机->自动加法机->自由加法机
既然我们可以用工具来表示数字,那肯定有人想着能不能让工具来帮我们运算数字。我们很快想到的是计算器,或者我们还能想到算盘,那也是运算的工具,即是它不是自动的。我们用ASCII编码规则通过组合数字0和1来表达文字。假如你女朋友需要你帮她买买买,你盘算着这点花你多少大洋的时候,这个时候如果能整出一台机器来帮你算就好了,你就不用拿着笔跟纸算半天还怕算错。加法可以说是最简单的运算了,既然我们能用电灯盒子来表达数字,那看看改进一下能不能来帮忙算算数字。
因为我们只能用灯泡的亮和灭来表示1和0。那我们先用十进制来看看加法是怎么实现的。回忆一下我们平时加两个数字的时候,比如1+1=2,这个是最简单的了,个位数加法不需赘述。
但如果是两位数的加法就有点麻烦了,比如8+7=15。这个时候就涉及到进位运算了。现在我们来看回二进制加法是如何实现的。下面两个表分别表示个位加数和进位加数的结果。
+和 | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 0 |
+进位 | 0 | 1 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
根据上表的计算结果,我们隐约感觉到跟我们的逻辑运算有点关联。
基本的逻辑关系运算有,与,或,非,如下表:
+ OR | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 1 |
+AND | 0 | 1 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
+NOT | ‘’ |
---|---|
1 | 0 |
0 | 1 |
对比一下加法进位表和逻辑运算表可以发现,数字位的右下角有点区别,进位表跟AND运算表是一样的。
那我们对AND和NOT进行一下组合,变换为NAND,表示进过AND逻辑运算后再进行NOT操作,得出下表:
+NAND | 0 | 1 |
---|---|---|
0 | 1 | 1 |
1 | 1 | 0 |
经过观察,我们发现OR的右下角跟“和”结果不一样,而NAND则是左上角不一样,那么通过将OR和NAND进行与操作就可以得出“和”。
这样我们就可以通过一个AND操作,一个OR操作和一个NAND操作和最终连接的AND来完成两个单位数的加法,由于NAND是组合操作,那么也可以任务是三次AND操作,一次OR操作和一次NOT操作。
这样一来就可以把加法拆分为数字位计算和进位计算,我们成为半加机。但是这个机器有两个输出,看来工作还没完成。
现在我们把一次NAND操作跟OR操作连在一起的AND操作定义为XOR操作,称为“异或”:
+XOR | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 0 |
那么一次加法操作就等同于一次AND操作加上一次XOR操作了。
现在我们已经用电线和灯泡,连接成如下电路:
A,B输入就是两个开关,开表示0,闭合表示1,和输出和进位输出则是灯泡,亮表示1,灭表示0。现在我们我们已经建造出一台半成品加法机了,我们称为半加机,起码可以计算 1+0=1 或者1+1=10了。但是如果你靠这台老爷机来算你女朋友的账单那还远远不够。毕竟你女朋友不可能只买个位数的东西。
现在我们把半加机装到一个木盒子,在盒子左边露出两个开关的小孔,右边装了两个灯泡,这就是一台固定的半加机器。
回忆一下我们平时在纸上算两个多位数加法的过程。每一次加法需要把当前两位的和再加上右边位置的两位数字和的进位。所以其实每一次加法需要有三个输入,也就是A输入,B输入和进位输入,输出则是和输出和进位输出。而进位输入是前一次加法的进位输出。
输入为A,B,CI(上一位置的进位输出),输出为S1(和输出),CO3(目标进位输出)。把这个过程分解为以下步骤:
A + B -> S,CO1
S + CI -> S1,CO2
CO1 OR CO2 -> CO3 (两次加法只有有一个进位就满足)
最终得到的S1,CO3就是加法机的结果,也就是把两个半加机连接起来,如下链路:
把两个半加机盒子装在一个更大的盒子里面,左边只留三个小孔输入,右边也是装两个灯泡表示和输出和进位输出。这样就构成真正意义上的加法机了,虽然这个加法盒子还是只能对单位数的数字进行加法运算。
既然我们都解决了个位数的加法运算,多位数加法其实就是多个单位数串联起来就可以。比如把8个加法机盒子排成一排,前一个加法机盒子的进位输出的灯泡拿掉换成继电器连着后一个加法机盒子的进位输入,以此列推,第一个加法机盒子的进位输入为0,也就是把开关打开,最后一个加法机盒子的进位输出用灯泡连接。这样我们有造出一个8位数的多位加法机,连接链路如下图:
8位加法机的盒子有16个开关,设置在盒子的正上方,侧面留了8个孔安装灯泡来显示和输出,如下:
8位二进制可以表示的范围为[0,256],现在你可以通过8位加法机来计算你女朋友的账单了,当然如果账单超过256,你完全可以再接一个8位加法机,就变成16位了,不够的话再加。。。
本来我们任务已经完成了,因为我们造的8位加法机基本已经够用了(如果你女朋友不是那么败家的话)。但是我们造出来的机器只能用于一次加法,这个就有点蛋疼了。比较你女朋友的账单可不止两件商品,用这台机器来做加法的话,每次加完就得抄在纸上,然后再输入,再加上第三个数字,相信这样的机器你不会有冲动去用多一次。
既然我们有的是时间,灯泡电线都够用。这个时候就得动动脑子想想有啥改进的办法,假如每次加完得出的结果能够保存起来,第二次计算的时候能加载进来,再手动输入第三个数字去加,这样就省事多了。我们初步的想法是再搞出个盒子来存放上一次加法运算的结果。那这个记忆盒子里面的灯泡和电线要如何连才能实现这个需求呢?
其实很简单,之前我们全加法机计算的结果无非就是用灯泡的明暗来表示1和0。我们在输出到灯泡的电线分一条拉进记忆盒子,记忆盒子留了8个小孔作为输入,每个小孔连着继电器,里面的输入跟小孔保持一致,当加法结果的灯泡亮的时候,里面的灯泡也是亮的,这样就是可以把计算结果锁存到记忆盒子里面。
接着我们还需要一个简单的选择盒子,来判断加法机的输入时手动输入还是来自记忆盒子里面的。整个流程如下图:
我们称这个机器为记忆加法机,但是这台加法机的记忆盒子在记忆结果的时候经常会出现接触抖动,这样会导致记忆结果的误差,所以需要改进记忆盒子的电线灯泡连接方式。优化后的电路称为边沿触发的D型触发器,由于篇幅问题,这里暂时不细讲。
有个记忆加法机,我们就可以愉快的玩耍了,你拿着女朋友的账单目不转睛的一次次输入,加法运算。等到最后面的时候突然发现你输错了一次数字,在你想重新输入的时候你发现好像不行,因为每一次输入都是上一次加法的结果,只能从头再来,这个时候你应该有砸了机器的冲动。但是转念之间你想到的是如何改进这个记忆加法机。假如我们能把所有要加的数字先输入到记忆盒子,再一个一个拿出来做加法,这样如果发现有错误的数字,大不了我们在记忆盒子里面修改完,重新加一次就行了。
说干就干,但是怎么样才能实现在记忆盒子里面存储多个数字呢?我们上一次构造的记忆盒子只有8位,只能存储一个加法结果的数字。因为我们不差钱,所以嘛,搞多几个记忆盒子不就行了,假如你女朋友账单有8笔数字要相加,那就加多是个记忆盒子。
现在有8个记忆盒子了,但是怎样其中一个用来存放加法的计算结果。在记忆加法机中有个2-1选择器,可以通过一个开关来选择第二个加数是来自手动输入还是加法的计算结果。2-1选择器其实就是一个开关就可以表示的两种状态,比如闭合时选择记忆盒子的数字,打开是选择手动输入的数字。那么现在有8个记忆盒子,就需要升级到8-1选择器,那如何构造这个选择器呢?
一个开关能表示两种状态,那8个盒子就需要三个开关了,如下图:
三个开关通过这样的链接方式,就可以选择唯一的一个记忆盒子来读取数字。同样我们输入数字到记忆盒子也用同样的方式来选取写入到哪个记忆盒子。连着8个记忆盒子的加法机我们称为选择加法机。这样一来我们就任意选择两个数字相加,并且可以将数字存储在记忆盒子里面,等着下次要用的时候再取出来。选择加法机的链接如下图:
现在对于这个选择加法机用来做账单的计算,貌似已经想不出还有啥需要改进。把账单上所有的数字都输入到记忆盒子里面,就可以由我们控制进行加法运算了,而且我们完全可以自由的选择加数的顺序,不必按照输入顺序,而且也不怕输入错误的数字。但是有一点还是避免不了的,就是每做完一次加法运算,我们就得守在旁边手动调整一下输入面板的三个开关来选择下一次计算的数字。有没有可能只做一个开关,按一下就自动把所有数字都加一遍。然后我们按一下开关就可以去喝个早茶,等吃完回来计算结果就摆在哪里,这样才够刺激。
说干就干,那我们就开始琢磨一下要实现这样的目标,得做什么改动呢?回想一下选择加法机的运算过程,我们通过控制面板手动输入数字,加完又手动把锁存器里面的数字输出到8-3选择器盒子里面。那么把目标拆分开来就是实现自动输入和自动输出。
首先要解决的是自动输入,怎样才能把手动切换控制面板开关的整个动作自动化呢。我们把8个记忆盒子的存储箱子称为8阵列存储器,需要通过3个开关来选择。这个时候需要有一个能模拟打开或者关闭一次开关的电压。那怎样才能用现有的电池,电线和灯泡来构造出类似可以计时的工具盒子呢?看看下面的连接方式:
我们可以看到当左边的开关闭合时,电磁继电器通电会产生磁性,从而把右边的弹簧片吸引下来,这个时候电路停电,电磁继电器失去磁性又返回使电路接触,这样反复循环的过程。如果把电路连上灯泡,那点灯就会周期行的闪亮,如果不是电线故障或者电灯泡寿命问题,这个电路就会一直的持续下去。我们称这样的电路为震荡电路,把它放到盒子里面就成功计数器,有了它就能自动发出0和1交替的信号了。
但是我们三个开关有2^3=8种状态,一个计数器只能表示两种状态,那么同样道理我们需要三个计算器,才能表示8种不同输入。问题来了,如何连接这三个计数器,才能稳定的表示8种不同输入呢?如果只是简单串起来,那只能表示111或者000,可达不到我们的要求。
之前我们构造记忆加法机的时候有提过用边沿触发的D型触发器来使记忆的数据更加稳定。这个触发器有个特点就是当输入由0->1的时候,输出不会跟着变化,而是保持着原有的0或者1。也就是输入的频率是输出的两倍。这样一来我们把振荡器连着3个边沿触发的D型触发器,就能完整的获取三位二进制数的八个状态,我们称为异步计数器。
现在有了异步计数器可以来代替掉手工的操作开关,只需要按一下振荡器的开关,异步计数器就会按顺序输出八个状态,把输出的信号接入到8个记忆盒子里面的接口,就可以顺序的取出每个记忆盒子里面的数字,传到累加器里面做运算,而输出我们是默认都存在锁存器里面参与下一步的运算。
大功告成,现在只需要按一下异步计数器的开关,自动加法机就会哗啦啦自己计算你女朋友的账单了,等你吃完早茶来,就可以看到总价了,是不是很爽。
最后有个问题,虽然自动计算很爽,但是好像这个自动只能把全部数字加起来,如果你女朋友突然跟你说有件衣服想退货了,不想买了,那怎么自动加其中的7个呢?如果只想加6个数字呢?这个时候我们要考虑一下灵活性,怎样改进自动加法机,让它变得更加灵活呢?
我们希望这台加法机能够自动的,又灵活的执行我们的意愿。我们想把其中的两个数字加完,再加另外的三个,顺序还要是可以调整的。自动加法机里面的异步计数器只能像计时器一样从000一直累加到111。计数器每一个数字对应唯一的一个记忆盒子,我们称为地址。这下我们知道自动加法器其实就是通过振荡器的时钟特性遍历了一遍记忆盒子,然后取出盒子里面的数据。
既然我们能遍历所有的记忆盒子的地址,那肯定有办法遍历其中的部分地址的的记忆盒子。异步计数器通过自身输出的计数来匹配记忆盒子的地址。那假如我们搞多一个记忆大箱子,里面装着8个8位的记忆盒子,每个盒子装着我们要加的数字所在盒子的地址,然后通过异步计数器来遍历这个箱子里面的盒子,再跟进读取的地址去数据区读取数据,最后传入累加器。
这样一个过程其实就是增加了一个大箱子,我们称为程序记忆盒子。这样以后你可以任意的把你要加的数字的顺序写到程序记忆盒子里面。然后启动开关,异步计数器就会去读取程序记忆盒子的数据(也就是地址),然后再通过地址去索引数据盒子的数据,如下图:
至此,我们从最初单纯的两个数相加,一步步升级成一个大箱子,里面装着数不清的电线,开关和灯泡,还有电磁铁。从开始的两个个位数相加,扩展的多位数相加;从开始的需要记住计算结果,到自动保存计算结果;从开始的单次输入相加,到可以一次性输入数字选择加数;从开始的手动操作开关输入数字,到通过计数器自动输入;从开始的固定操作数字顺序,到可以输入代码自由选择数字顺序。而这其中只是使用同样的工具做出不同的连线方式。
到此为止,这台自由加法机已经足够我们日常的运算需求了,但还是很多改进的空间,我们以后有机会再聊聊怎么改进。现在把时钟拨回来,21世纪计算机技术有了飞跃的提升。如今我们用的笔记本的性能已经可以做上10亿次的加法运算,当然甩我们一百年前构造的自由加法机n条街。
我们现在的计算机构造非常复杂,大概的组成分为:
存储器:实现记忆功能的部件用来存放计算程序及参与运算的各种数据
运算器:负责数据的算术运算和逻辑运算即数据的加工处理
控制器:负责对程序规定的控制信息进行分析,控制并协调输入,输出操作或内存访问
输入设备:实现计算程序和原始数据的输入
输出设备:实现计算结果输出
对比我们一百年前构造的自由加法机:
存储箱:用于存储输入的数字,要执行的代码,计算的结果
累加器:用于计算两个数字相加
控制板:切换开关
输入面板:输入开关
输出灯泡:灯泡显示输出数据
这样看来已经很相似了,现在我们来看看用现在计算机做加法和用我们的老爷加法机做加法过程有什么区别:
当我们在键盘敲入两个数字的时候,我们同时在加法机的输入面板开关设置了两个数字(不好意思,这个时候需要你在纸上算一下十进制转为二进制,因为输入面板的开关只能输入二进制数据)。这个时候计算机键盘产生电压,触发输入总线中断程序,将所键入数字输入内存区域,同时分配对应数字的内存地址。而我们加法机在操作输入面板的时候,每输入一个数字前都要手动通过选择器制定记忆盒子来存储数字。这个时候记忆盒子的编号对应了选择器的序号,也就是我们所说的内存地址。
当输完两个数字的时候,我们在计算机上面按了“+”的按键。计算机就会将之前两个数字所在内存地址加载到CPU进行加法计算。而我们自制的加法机还办法做得跟计算机一样那么智能,这个时候我们需要一点编程工作,也就是写点代码到代码记忆盒子里面(代码记忆盒子和数据记忆盒子的构造是一模一样的)。我们需要写入三行代码进去,第一行是加法操作代码,以Add为代号,第二第三行是我们刚才选择器选定的地址编码。编码完成后启动异步计数器,计数器就周期性的变量扫描代码记忆盒子的代码,逐行执行,异步计数器执行完,我们记忆盒子里面的数据就被加载到累加器了,这个时候通过译码器寻址数据记忆盒子的过程成为寻址过程。
最后计算机CPU接收到两个数字后就通过算术逻辑单位计算加法,得出结果后传输到内存,然后通过屏幕显示器显示计算结果。而数据通过寻址传输到累加器进行加法运算后就存储到锁存器盒子,最后通过电灯来显示计算结果。
我们描述计算做加法运算的时候是给出了大概的过程,实际上是远远要复杂得,而且大多数情况数字不经过内存就进入CPU进行计算,但这并不影响我们对计算机深层计算本质的理解。经过上述的比对,计算机核心的概念其实早在一百年前就可以用恰当的工具来实现了。我们亲手构造的自由加法机其实已经实现了冯·诺依曼计算机模型。在构建过程我们类似地实现了输入,输出,内存,寻址,译码,指令,执行,算术逻辑单元等核心概念,其实也深刻的理解了一遍计算机的本质。
理解计算机核心环节,就能做到了然于胸。我们构建过程用的工具是点灯,电线,开关和电磁铁。这只是辅助我们实现计算机模型的适当工具,但并不是唯一的,比如纸张也是可以构造出计算机的,上世纪中科学家就曾经用穿孔纸带来编码。其实就是代替了我们上文说的代码记忆盒子,然异步计数器去遍历纸带上的小孔,从而读取纸带的地址编号,实现取指功能。只要你有耐心,你有足够多的水管和一个大蓄水池,水压器和一些开关。水源产生动力,水管布线,开关控制,水压器显示表示0和1,也是可以构造出一台“计算机”的。
一台物理计算机已经被建造出来了,而且可以做加法运算,之前我们提到了优化空间。而编程就是把一系列的指令存入到内存,让计算机逐步去执行这些指令。我们肯定会想着把常用的一些操作封装起来,免得每次都要重复一遍,这样操作系统就应运而生了,下一篇我们将介绍,有了一台原始的计算机后,我们还需要做些什么?