终于找到了一篇文章通俗地讲解计算机工作原理.docx
《终于找到了一篇文章通俗地讲解计算机工作原理.docx》由会员分享,可在线阅读,更多相关《终于找到了一篇文章通俗地讲解计算机工作原理.docx(16页珍藏版)》请在冰豆网上搜索。
终于找到了一篇文章通俗地讲解计算机工作原理
终于找到了一篇文章!
通俗地讲解计算机工作原理
任何技术,只要足够高深,都无法与魔法区分开来。
——亚瑟•克拉克
为解决各种问题,人们发明了不计其数的机器。
计算机种类繁多,从嵌入火星漫游机器人的计算机到为操纵核潜艇导航系统的计算机,不一而足。
冯•诺伊曼在1945年提出第一种计算模型,无论笔记本电脑还是电话,几乎所有计算机都遵循与这种模型相同的工作原理。
那么你们了解计算机是如何工作的吗?
本文将讨论这些内容:
◎理解计算机体系结构的基础知识
◎选择编译器将代码转换为计算机可以执行的指令
◎根据存储器层次结构提高数据的存储速度
毕竟,在非程序员看来,编程要像魔法一样神奇,我们程序员不会这么看。
体系结构
计算机是一种根据指令操作数据的机器,主要由处理器与存储器两部分组成。
存储器又称RAM(随机存取存储器),用于存储指令以及需要操作的数据。
处理器又称CPU(中央处理器),它从存储器获取指令与数据,并执行相应的计算。
接下来,我们将讨论这两部分的工作原理。
存储器
存储器被划分为许多单元,每个单元存储少量数据,通过一个数字地址加以标识。
在存储器中读取或写入数据时,每次对一个单元进行操作。
为读写特定的存储单元,必须找到该单元的数字地址。
由于存储器是一种电气元件,单元地址作为二进制数通过信号线传输。
二进制数以2为基数表示,其工作原理如下:
每条信号线传输一个比特,以高电压表示信号“1”,低电压表示信号“0”,如图7-1所示。
对于某个给定的单元地址,存储器可以进行两种操作:
获取其值或存储新值,如图7-2所示。
存储器包括一条用于设置操作模式的特殊信号线。
每个存储单元通常存储一个8位二进制数,它称为字节。
设置为“读”模式时,存储器检索保存在单元中的字节,并通过8条数据传输线输出,如图7-3所示。
设置为“写”模式时,存储器从数据传输线获取一个字节,并将其写入相应的单元,如图7-4所示。
传输相同数据的一组信号线称为总线。
用于传输地址的8条信号线构成地址总线,用于在存储单元之间传输数据的另外8条信号线构成数据总线。
地址总线是单向的(仅用于接收数据),而数据总线是双向的(用于发送和接收数据)。
在所有计算机中,CPU与RAM无时无刻不在交换数据:
CPU不断从RAM获取指令与数据,偶尔也会将输出与部分计算存储在RAM中,如图7-5所示。
CPU
CPU包括若干称为寄存器的内部存储单元,它能对存储在这些寄存器中的数字执行简单的数学运算,也能在RAM与寄存器之间传输数据。
可以指示CPU执行以下典型的操作:
◎将数据从存储位置220复制到寄存器3;
◎将寄存器3与寄存器1中的数字相加。
CPU可以执行的所有操作的集合称为指令集,指令集中的每项操作被分配一个数字。
计算机代码本质上是表示CPU操作的数字序列,这些操作以数字的形式存储在RAM中。
输入/输出数据、部分计算以及计算机代码都存储在RAM中。
通过在RAM中包含重写部分代码的指令,代码甚至可以对自身修改,这是计算机病毒逃避反病毒软件检测的惯用手法。
与之类似,生物病毒通过改变自身的DNA以躲避宿主免疫系统的打击。
图7-6取自Intel4004操作手册,显示了部分CPU指令映射为数字的方法。
随着制造工艺的发展,CPU支持的操作越来越多。
现代CPU的指令集极为庞大,但最重要的指令在几十年前就已存在。
CPU的运行永无休止,它不断从存储器获取并执行指令。
这个周期的核心是PC寄存器,PC(programcounter)是“程序计数器”的简称。
PC是一种特殊的寄存器,用于保存下一条待执行指令的存储地址。
CPU的工作流程如下:
(1)从PC指定的存储地址获取指令;
(2)PC自增;
(3)执行指令;
(4)返回步骤1。
PC在CPU上电时复位为默认值,它是计算机中第一条待执行指令的地址。
这条指令通常是一种不可变的内置程序,用于加载计算机的基本功能。
在许多个人计算机中,这种程序称为BIOS(基本输入输出系统)。
CPU上电后将继续执行这种“获取-执行”周期直至关机。
然而,如果CPU只能遵循有序、顺序的操作列表,那么它与一个花哨的计算器并无二致。
CPU的神奇之处在于可以指示它向PC中写入新值,从而实现执行过程的分支,或“跳转”到存储器的其他位置。
这种分支可以是有条件的。
以下面这条CPU指令为例:
“如果寄存器1等于0,将PC设置为地址200”。
该指令相当于:
ifx=0
compute_this()
else
compute_that()
仅此而已。
无论是打开网站、玩计算机游戏抑或编辑电子表格,所涉及的计算并无区别,都是一系列只能对存储器中的数据求和、比较或移动的简单操作。
大量简单的操作组合在一起,就能表达复杂的过程。
以经典的《太空侵略者》游戏为例,其代码包括大约3000条机器指令。
CPU时钟 早在20世纪80年代,《太空侵略者》就已风靡一时。
这个游戏在配备2MHzCPU的街机上运行。
“2MHz”表示CPU的时钟,即CPU每秒可以执行的基本操作数。
时钟频率为200万赫兹(2MHz)的CPU每秒大约可以执行200万次基本操作。
完成一条机器指令需要5到10次基本操作,因此老式街机每秒能运行数十万条机器指令。
随着现代科技的进步,普通的台式计算机与智能手机通常配备2GHzCPU,每秒可以执行数亿条机器指令。
时至今日,多核CPU已投入大规模应用,如四核2GHzCPU每秒能执行近10亿条机器指令。
展望未来,CPU配备的核心数量或许会越来越多。
CPU体系结构 读者是否思考过,PlayStation的游戏CD为何无法在台式计算机中运行?
iPhone应用为何无法在Mac中运行?
原因很简单,因为它们的CPU体系结构不同。
x86体系结构如今已成为行业标准,因此相同的代码可以在大部分个人计算机中执行。
但考虑到节电的要求,手机采用的CPU体系结构有所不同。
不同的CPU体系结构意味着不同的CPU指令集,也意味着将指令编码为数字的方式各不相同。
台式计算机CPU的指令并非手机CPU的有效指令,反之亦然。
32位与64位体系结构 第一种CPU是Intel4004,它采用4位体系架构。
换言之,这种CPU在一条机器指令中可以对最多4位二进制数执行求和、比较与移动操作。
Intel4004的数据总线与地址总线均只有4条。
不久之后,8位CPU开始广为流行,这种CPU用于运行DOS的早期个人计算机。
20世纪八九十年代,著名的便携式游戏机GameBoy就采用8位处理器。
这种CPU可以在一条指令中对8位二进制数进行操作。
技术的快速发展使16位以及之后的32位体系结构成为主导。
CPU寄存器随之增大,以容纳32位数字。
更大的寄存器自然催生出更大的数据总线与地址总线:
具有32条信号线的地址总线可以对232字节(4GB)的内存进行寻址。
人们对计算能力的渴求从未停止。
计算机程序越来越复杂,消耗的内存越来越多,4GB内存已无法满足需要。
使用适合32位寄存器的数字地址对超过4GB内存进行寻址颇为棘手,这成为64位体系结构兴起的动因,这种体系结构如今占据主导地位。
64位CPU可以在一条指令中对极大的数字进行操作,而64位寄存器将地址存储在海量的存储空间中:
264字节相当于超过170亿吉字节(GB)。
大端序与小端序 一些计算机设计师认为,应按从左至右的顺序在RAM与CPU中存储数字,这种模式称为小端序。
另一些计算机设计师则倾向于按从右至左的顺序在存储器中写入数据,这种模式称为大端序。
因此,根据“字节序”的不同,二进制序列1-0-0-0-0-0-1-1表示的数字也有所不同。
◎大端序:
27+21+20=131
◎小端序:
20+26+27=193
目前的大部分CPU采用小端序模式,但同样存在许多采用大端序模式的计算机。
如果大端序CPU需要解释由小端序CPU产生的数据,则必须采取措施以免出现字节序不匹配。
程序员直接对二进制数进行操作,在解析来自网络交换机的数据时尤其需要注意这个问题。
虽然目前多数计算机采用小端序模式,但由于大部分早期的网络路由器使用大端序CPU,所以因特网流量仍然以大端序为基础进行标准化。
以小端序模式读取大端序数据时将出现乱码,反之亦然。
模拟器 某些情况下,需要在计算机上运行某些为不同CPU设计的代码,以便在没有iPhone的情况下测试iPhone应用,或玩脍炙人口的老式超级任天堂游戏。
这是通过称为模拟器的软件来实现的。
模拟器用于模仿目标机器,它假定与其拥有相同的CPU、RAM以及其他硬件。
模拟器程序对指令进行解码,并在模拟机器中执行。
可以想见,如果两台机器的体系结构不同,那么在一台机器内部模拟另一台机器绝非易事。
好在现代计算机的速度远远超过之前的机器,因此模拟并非无法实现。
我们可以利用GameBoy模拟器在计算机中创建一个虚拟的GameBoy,然后就能像使用实际的GameBoy那样玩游戏。
编译器
通过对计算机进行编程,可以完成核磁共振成像、声音识别、行星探索以及其他许多复杂的任务。
值得注意的是,计算机执行的所有操作最终都要通过简单的CPU指令完成,即归结为对数字的求和与比较。
而Web浏览器等复杂的计算机程序需要数百万乃至数十亿条这样的机器指令。
但我们很少会直接使用CPU指令来编写程序,也无法采用这种方式开发一个逼真的三维计算机游戏。
为了以一种更“自然”且更紧凑的方式表达命令,人们创造了编程语言。
我们使用这些语言编写代码,然后通过一种称为编译器的程序将命令转换为CPU可以执行的机器指令。
我们用一个简单的数学类比来解释编译器的用途。
假设我们向某人提问,要求他计算5的阶乘。
5!
=?
但如果回答者不了解什么是阶乘,则这样提问并无意义。
我们必须采用更简单的操作来重新表述问题。
5×4×3×2×1=?
不过,如果回答者只会做加法怎么办?
我们必须进一步简化问题的表述。
5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5+5=?
可以看到,表达计算的形式越简单,所需的操作数量越多。
计算机代码同样如此。
编译器将编程语言中的复杂指令转换为等效的CPU指令。
结合功能强大的外部库,就能通过相对较少的几行代码表示包含数十亿条CPU指令的复杂程序,而这些代码易于理解和修改。
计算机之父艾伦•图灵发现,简单的机器有能力计算任何可计算的事物。
如果机器具有通用的计算能力,那么它必须能遵循包含指令的程序,以便:
◎对存储器中的数据进行读写;
◎执行条件分支:
如果存储地址具有给定的值,则跳转到程序的另一个点。
我们称具有这种通用计算能力的机器是图灵完备的。
无论计算的复杂性或难度如何,都可以采用简单的读取/写入/分支指令来表达。
只要分配足够的时间与存储空间,这些指令就能计算任何事物。
人们最近发现,一种称为MOV(数据传送)的CPU指令是图灵完备的。
这意味着仅能执行MOV指令的CPU与完整的CPU在功能上并无不同:
换言之,通过MOV指令可以严格地表达任何类型的代码。
这个重要概念在于,无论简单与否,如果程序能采用编程语言进行编码,就可以重写后在任何图灵完备的机器中运行。
编译器是一种神奇的程序,能自动将代码从复杂的语言转换为简单的语言。
操作系统
从本质上讲,编译后的计算机程序是CPU指令的序列。
如前所述,为台式计算机编译的代码无法在智能手机中运行,因为二者采用不同的CPU体系结构。
不过,由于程序必须与计算机的操作系统通信才能运行,编译后的程序也可能无法在共享相同CPU架构的两台计算机中使用。
为实现与外界的通信,程序必须进行输入与输出操作,如打开文件、在屏幕上显示消息、打开网络连接等。
但不同的计算机采用不同的硬件,因此程序不可能直接支持所有不同类型的屏幕、声卡或网卡。
这就是程序依赖于操作系统执行的原因所在。
借助操作系统的帮助,程序可以毫不费力地使用不同的硬件。
程序创建特殊的系统调用,请求操作系统执行所需的输入/输出操作。
编译器负责将输入/输出命令转换为合适的系统调用。
然而,不同的操作系统往往使用互不兼容的系统调用。
例如,与macOS或Linux相比,Windows在屏幕上打印信息所用的系统调用有所不同。
因此,在使用x86处理器的Windows中编译的程序,无法在使用x86处理器的Mac中运行。
除针对特定的CPU体系结构外,编译后的代码还会针对特定的操作系统。
编译优化
优秀的编译器致力于优化它们生成的机器码。
如果编译器认为可以通过修改部分代码来提高执行效率,则会处理。
在生成二进制输出之前,编译器可能尝试应用数百条优化规则。
因此,应使代码易于阅读以利于进行微优化。
编译器最终将完成所有细微的优化。
例如,一些人对以下代码颇有微词。
functionfactorial(n)
ifn>1
returnfactorial(n-1)*n
else
return1
他们认为应该进行以下修改:
functionfactorial(n)
result←1
whilen>1
result←result*n
n←n-1
returnresult
诚然,在不使用递归的情况下执行factorial函数将消耗较少的计算资源,但仍然没有理由因此而改变代码。
现代编译器将自动重写简单的递归函数,举例如下。
i←x+y+1
j←x+y
为避免进行两次x+y计算,编译器将上述代码重写为:
t1←x+y
i←t1+1
j←t1
应专注于编写清晰且自解释的代码。
如果性能出现问题,可以利用分析工具寻找代码中的瓶颈,并尝试改用更好的方法计算存在问题的代码。
此外,避免在不必要的微操作上浪费太多时间。
但在某些情况下,我们希望跳过编译,接下来将对此进行讨论。
脚本语言
某些语言在执行时并未被直接编译为机器码,这些语言称为脚本语言,包括JavaScript、Python以及Ruby。
在脚本语言中,代码由解释器而非CPU执行,解释器必须安装在运行代码的机器中。
解释器实时转译并执行代码,因此其运行速度通常比编译后的代码慢得多。
但另一方面,程序员随时都能立即运行代码而无须等待编译过程。
对于规模极大的项目,编译可能耗时数小时之久。
Google工程师必须不断编译大量代码,导致程序员“损失”了很多时间(图7-9)。
由于需要保证编译后的二进制文件有更好的性能,Google无法切换到脚本语言。
公司为此开发了Go语言,它的编译速度极快,同时仍然保持很高的性能。
反汇编与逆向工程
给定一个已编译的计算机程序,无法在编译之前恢复其源代码。
但我们可以对二进制程序解码,将用于编码CPU指令的数字转换为人类可读的指令序列。
这个过程称为反汇编。
接下来,可以查看这些CPU指令,并尝试分析它们的用途,这就是所谓的逆向工程。
某些反汇编程序对这一过程大有裨益,它们能自动检测并注释系统调用与常用函数。
借由反汇编工具,黑客对二进制代码的各个环节了如指掌。
我相信,许多顶尖的IT公司都设有秘密的逆向工程实验室,以便研究竞争对手的软件。
地下黑客经常分析Windows、Photoshop、《侠盗猎车手》等授权程序中的二进制代码,以确定哪部分代码负责验证软件许可证。
黑客将二进制代码修改,在其中加入一条指令,直接跳转到验证许可证后执行的代码部分。
运行修改后的二进制代码时,它在检查许可证前获取注入的JUMP命令,从而可以在没有付费的情况下运行非法的盗版副本。
在秘密的政府情报机构中,同样设有供安全研究人员与工程师研究iOS、Windows、IE浏览器等流行消费者软件的实验室。
他们寻找这些程序中可能存在的安全漏洞,以防御网络攻击或对高价值目标的入侵。
在这类攻击中,最知名的当属“震网”病毒,它是美国与以色列情报机构研制的一种网络武器。
通过感染控制地下聚变反应堆的计算机,“震网”延缓了伊朗核计划。
开源软件
如前所述,我们可以根据二进制可执行文件分析有关程序的原始指令,但无法恢复用于生成二进制文件的原始源代码。
在没有原始源代码的情况下,即使可以稍许修改二进制文件以便以较小的方式破解,实际上也无法对程序进行任何重大更改(如添加新功能)。
一些人推崇协作构建代码的方式,因此将自己的源代码开放供他人修改。
“开源”的主要概念就在于此:
所有人都能自由使用与修改的软件。
基于Linux的操作系统(如Ubuntu、Fedora与Debian)是开源的,而Windows与macOS是闭源的。
开源操作系统的一个有趣之处在于,任何人都可以检查源代码以寻找安全漏洞。
现已证实,政府机构通过日常消费者软件中未修补的安全漏洞,对数百万平民进行利用和监视。
但对开源软件而言,代码受到的关注度更高,因此恶意的第三方与政府机构很难植入监控后门程序。
使用macOS或Windows时,用户必须相信Apple或Microsoft对自己的安全不会构成危害,并尽最大努力防止任何严重的安全漏洞。
而开源系统置于公众的监督之下,因此安全漏洞被忽视的可能性大为降低。
存储器层次结构
我们知道,计算机的操作可以归结为使CPU执行简单的指令,这些指令只能对存储在CPU寄存器中的数据操作。
但寄存器的存储空间通常被限制在1000字节以内,这意味着CPU寄存器与RAM之间必须不断进行数据传输。
如果存储器访问速度过慢,CPU将被迫处于空闲状态,以等待RAM完成数据传输。
CPU读写存储器中数据所需的时间与计算机性能直接相关。
提高存储器速度有助于加快计算机运行,也可以提高CPU访问数据的速度。
CPU能以近乎实时的速度(一个周期以内)访问存储在寄存器中的数据,但访问RAM则慢得多。
对于时钟频率为1GHz的CPU,一个周期的持续时间约为十亿分之一秒,这是光线从本书进入读者眼中所需的时间。
处理器与存储器之间的鸿沟
近年来的技术发展使得CPU速度成倍增长。
虽然存储器速度同样有所提高,但却慢得多。
CPU与RAM之间的这种性能差距称为“处理器与存储器之间的鸿沟”。
我们可以执行大量CPU指令,因此它们很“廉价”;而从RAM获取数据所需的时间较长,因此它们很“昂贵”。
随着两者之间的差距逐渐增大,提高存储器访问效率的重要性越发明显。
现代计算机需要大约1000个CPU周期(1微秒左右)从RAM获取数据。
这种速度已很惊人,但与访问CPU寄存器的时间相比仍然较慢。
减少计算所需的RAM操作次数,是计算机科学家追求的目标。
在两个面对面的人之间,声波传播需要大约10微秒。
时间局部性与空间局部性
在尝试尽量减少对RAM的访问时,计算机科学家开始注意到两个事实。
◎时间局部性:
访问某个存储地址时,可能很快会再次访问该地址。
◎空间局部性:
访问某个存储地址时,可能很快会访问与之相邻的地址。
因此,将这些存储地址保存在CPU寄存器中,有助于避免大部分对RAM的“昂贵”操作。
不过在设计CPU芯片时,工业工程师并未找到可行的方法来容纳足够多的内部寄存器,但他们仍然发现了如何有效地利用时间局部性与空间局部性。
接下来将对此进行讨论。
一级缓存
可以构建一种集成在CPU内部且速度极快的辅助存储器,这就是一级缓存。
将数据从一级缓存读入寄存器,仅比直接从寄存器获取数据稍慢。
利用一级缓存,我们将可能访问的存储地址中的内容复制到CPU寄存器附近,借此以极快的速度将数据载入CPU寄存器。
将数据从一级缓存读入寄存器仅需大约10个CPU周期,速度是从RAM获取数据的近百倍。
借由10KB左右的一级缓存,并合理利用时间局部性与空间局部性,超过一半的RAM访问调用仅通过缓存就能实现。
这一创新使计算技术发生了翻天覆地的变化。
一级缓存可以极大缩短CPU的等待时间,使CPU将更多时间用于实际计算而非处于空闲状态。
二级缓存
提高一级缓存的容量有助于减少从RAM获取数据的操作,进而缩短CPU的等待时间。
但是,增大一级缓存的同时也会降低它的速度。
在一级缓存达到50KB左右时,继续增加其容量就要付出极高的成本。
更好的方案是构建一种称为二级缓存的缓存。
二级缓存的速度稍慢,但容量比一级缓存大得多。
现代CPU配备的二级缓存约为200KB,将数据从二级缓存读入CPU寄存器需要大约100个CPU周期。
我们将最有可能访问的地址复制到一级缓存,较有可能访问的地址复制到二级缓存。
如果CPU没有在一级缓存中找到某个存储地址,仍然可以尝试在二级缓存中搜索。
仅当该地址既不在一级缓存、也不在二级缓存中时,CPU才需要访问RAM。
目前,不少制造商推出了配备三级缓存的处理器。
三级缓存的容量比二级缓存大,虽然速度不及二级缓存,但仍然比RAM快得多。
一级/二级/三级缓存非常重要,它们占据了CPU芯片内部的大部分硅片空间。
见图7-11。
使用一级/二级/三级缓存能显著提高计算机的性能。
在配备200KB的二级缓存后,CPU发出的存储请求中仅有不到10%必须直接从RAM获取。
读者今后购买计算机时,对于所挑选的CPU,请记住比较一级/二级/三级缓存的容量。
CPU越好,缓存越大。
一般来说,建议选择一款时钟频率稍低但缓存容量较大的CPU。
第一级存储器与第二级存储器
如前所述,计算机配有不同类型的存储器,它们按层次结构排列。
性能最好的存储器容量有限且成本极高。
沿层次结构向下,可用的存储空间越来越多,但访问速度越来越慢。
在存储器层次结构中,位于CPU寄存器与缓存之下的是RAM,它负责存储当前运行的所有进程的数据与代码。
截至2017年,计算机配备的RAM容量通常为1GB到10GB。
但在许多情况下,RAM可能无法满足操作系统以及所有运行程序的需要。
因此,我们必须深入探究存储器层次结构,使用位于RAM之下的硬盘。
截至2017年,计算机配备的硬盘容量通常为数百吉字节,足以容纳当前运行的所有程序数据。
如果RAM已满,当前的空闲数据将被移至硬盘以释放部分内存空间。
问题在于,硬盘的速度非常慢,它一般需要100万个CPU周期(1毫秒)a在磁盘与RAM之间传输数据。
从磁盘访问数据看似很快,但不要忘记,访问RAM仅需1000个周期,而访问磁盘需要100万个周期。
RAM通常称为第一级存储器,而存储程序与数据的磁盘称为第二级存储器。
标准照片在大约4毫秒内捕捉光线。
CPU无法直接访问第二级存储器。
执行保存在第二级存储器中的程序之前,必须将其复制到第一级存储器。
实际上,每次启动计算机时,即便是操作系统也要从磁盘复制到RAM,否则CPU无法运行。
确保RAM永不枯竭 在典型活动期间,确保计算机处理的所有数据与程序都能载入RAM至关重要,否则计算机将不断在磁盘与RAM之间交换数据。
由于这项操作的速度极慢,计算机性能将严重下降,甚至无法使用。
这种情况下,计算机不得不花费更多时间等待数据传输,而无法进行实际的计算。
当计算机不断将数据从磁盘读入RAM时,则称计算机处于抖动模式。
必须对服务器进行持续监控,如果服务器开始处理无法载入RAM的数据,那么抖动可能会导致整个服务器崩溃。
银行或收银机前将因此排起长队,而服务员除了责怪发生抖动的计算机