这个小节我们要做的事情是来看一看我们主机内部的这三个硬件部件,它们的一些内部细节以及它们之间是如何协调着来工作的?那我们先看主存储器,之后看运算器,最后是控制器。
主存储器里边用于存放数据的东西叫做存储体。存储体是由一系列的存储元件来构成的,可以存放二进制0或者1。除了存储体之外,还有两个重要的寄存器(MAR、MDR),分别是存储地址寄存器、存储数据寄存器的一个英文缩写,寄存器是用来存放二进制数据的。只不过这两个寄存器,它们存放的数据各不相同,MAR是存放和地址相关的一些二进制数据,MDR是用于存放实际的数据的。 那主存储器里的这三个重要部件,它们分别有什么用呢?
菜鸟驿站的基本组成,和我们主存储器是有很多异曲同工的地方。对比着菜鸟驿站来说:今天早上到菜鸟驿站去取了一个包裹,发现每一个菜鸟驿站里面都会有一个货架,他们会给这个货架进行一个编号, 11 号货架,然后一层二层三层,这家菜鸟驿站的包裹编号,在货架里的第几层,这一层的第几个包裹这样的规则来给快递编号的。这家店这些一个一个的包裹货物,是有规律的,按照取件号摆放到货架上,去取包裹的时候,告诉前台的店员,取件号是多少,店员知道了取件号之后,他就可以知道这个包裹是存放在 11 号货架的第一层的第 42 个包裹,于是这个店员会告诉柜台的小哥,让他去这个位置找,于是在货架所对应的位置就找到了想要的包裹。找到之后,他们会把包裹放到柜台上,就可以从柜台上拿走包裹。因此菜鸟驿站的这个模型当中,货架、店员、柜台与我们主存储器的这三个部分都是一一对应的。菜鸟驿站的货架是用来存放货物的,一个一个的包裹。然后主存储器里的存储体是用来存放数据的,告诉店员取件号是多少,同时取件号也反映出了想要的货物,它在货架上的存放位置。而对于主存储器来说,CPU想要从里边取得一个数据,它会把他想要的数据的地址写到MAR这个寄存器当中,接下来主存储器就可以根据 MAR这儿接收到的地址信息,去存储体里边拿出 CPU 想要的数据。再回到菜鸟驿站,我想要的包裹从货架上找到之后会被放到柜台上,然后最终我是从柜台上取走这个包裹的。对于存储器来说也是一样,CPU提供一个想要的数据的存放地址,主存储器里边的一些控制逻辑会根据这个地址找到CPU想要的数据,并把数据先写到MDR这个寄存器当中,最后 CPU 就可以通过数据线路从MDR当中取走想要的数据。所以主存储器和菜鸟驿站的这个模型是有很多相通的地方的,当然也有不一样的地方,对于菜鸟驿站来说,我们通常只能从货架上取走的包裹。
对于存储器来说 ,CPU既可以从其中读出数据,同时也可以写入数据,写入数据的过程和读入数据是很类似的,CPU 会指明想要写入到哪个位置以及他想要写入的具体数据,最后 CPU 通过控制总线告诉主存储器说,这次我想要执行的操作是写操作,而不是读操作,主存储器根据 CPU 发出的这三个信息就可以往对应的位置写入CPU想要写的数据了。
接下来我们把注意力放到存储体上。存储体是什么原理?我们要的二进制数据是存放在存储体当中的,并且是按地址来存储,什么意思呢?就是说存储体会被分成一个一个的存储单元。每一个存储单元会存放一串二进制代码,每一个存储单元里边存储的这一串二进制代码,把它称为一个存储字,英文就是 word,每一个存储字包含多少个二进制位?我们把它称为存储字长,也就是每一个存储单元里边可以放得下多少个二进制位。通常每一个存储单元可以存放的二进制数都是八个比特 的整数倍,也就是8、16、或者32、64比特,这是比较常见的一些存储字长。每一个存储单元会对应一个地址信息,地址是从零开始的。这个地址信息就是刚才我们所说的MAR这里边应该指明的信息。如果要读取的是第二号的存储单元,CPU就会往MAR这个寄存器里边写入二这个信息。还有一个概念叫做存储元,也就是用于存储二进制数据的电子元件,这样的电子元件其实是用电容的原理来制造的,电容可以用来存储电荷,因此我们可以用一个电容来存放一个二进制的比特位。由多个存储元件,还有相应的线路,就构成了一个存储单元。关于存储元件的物理特性,不需要过度的深究,只需要知道每一个存储元可以存放一个比特就可以了。MAR指明了存储单元的地址,所以MAR这个寄存器的位数,它到底有几个比特位就直接的反映了我们的存储体里边到底有多少个存储单元。
另一个方面,我们从存储单元里边取出来的数据是要放到MDR当中的,所以MDR的二进制位数应该和存储单元是保持一致的,也就是要等于存储字长,比如
这个主存储器,它的 MAR地址寄存器只有四个比特位,就意味着它的存储体里边总共只有二的四次方个存储单元,因为四个二进制位最多也就只能表示这么多个数字,也就是二的四次方这么多个地址,如果它的MDR这个数据寄存器总共有16个比特位,那么就说明在这个主存储器当中,一个字的大小就是 16 个比特。换一个角度说,每一个存储单元可以存放 16 个二进制位,16 个比特的信息。这一点也是比较容易作为考题来考察的。 补充一个比较容易混淆的点。
用英文说的话就是一个 word,一个字到底有多少个比特?这个具体要看计算机的结构是怎么样的?也就是说一个字的大小,有可能是 16 个比特,有可能是8个比特,也有可能是 32 或者 64 个比特,具体要看我们计算机硬件是怎么设计的?除了字和字节之外,还有就是我们在描述一个字节的时候,经常会用1加B来表示。当我们在描述一个比特,一个二进制位的时候,是用1b来表示。B和b是不一样的,说一个大家都会有体会的生活经验。 当我们去办宽带的时候,会跟业务员说办一个 100M宽带,会发现这个 b 是小写的 b,它的这 100M 的宽带指的是每秒钟 persecond,每秒钟可以传送 100兆个比特位,是这样的一个速率。那当用 100M 的宽带来下载东西的时候,我们的迅雷之类的下载器一般是以B也就是字节作为这种计量单位的。所以会发现用 100M 的宽带来下载东西的话,最多就是 10M 多一点的速度,因为下载器里边用的是B也就是字节。并不是运营商骗了你,他承诺的理论峰值 100M,把它换算成B的话,应该是用 100 再除以一个八。这才是获得的最快的一个下载速度。 接下来进入下一个硬件部件就是运算器的基本组成。
我们先用大家最讨厌的念 PPT 的方式来简单的聊一下。之后再用一个具体的例子带大家来理解我们这儿给出的这些文字性的描述,一个运算器最主要的部件四个部件:ACC、MQ、x还有一个叫 ALU,运算器就是要来实现算术运算加、减、乘、除,还有逻辑运算的。 其中 ACC 是一个寄存器,翻译为中文一般叫做累加器,英文全称(Accumulator)。累加器可以用来存放进行加减乘除或者逻辑运算的操作数,也会用于存放运算的结果。第二个寄存器叫MQ(乘商寄存器),乘和商也就对应着乘法和除法。顾名思义,这个寄存器只会在乘法、除法运算的时候才会用得到。会用来存放乘法、除法的操作数或者运算的结果。第三个寄存器 x 叫通用的操作数寄存 器,可以把它简称为通用寄存器。这种通用寄存器在运算器里边可能会有多个,但理论上只需要有一个通用寄存器就可以实现想要的算术运算。大家如果看王道书给的运算器的结构的话,它里面就画了多个这种通用寄存器。通用寄存器的作用就是用来存放一些操作数。第四个寄存器ALU叫算术逻辑单元。ALU才是运算器的核心部件,里边集成了一些复杂的电路,用这些复杂的电路可以实现算术运算或者逻辑运算,前边三个寄存器其实就是用来存放一些数据的。前三个寄存器的硬件构造并不复杂,但是ALU的硬件构造就复杂了。所以运算器里边制造成本最高的一个部件就是ALU,前三个寄存器在进行加减乘除不同运算的时候,它们的作用会各不相同,这个我们一会儿结合具体的例子来理解。这是运算器的一个基本组成。有三个不可或缺的寄存器,另外还有一个核心部件ALU来实现算术运算和逻辑运算。
接下来看控制器:
控制器有三个必不可少的部件,一个叫CU(控制单元),控制单元是控制器里边最核心的部件,里边集成了很复杂的一系列的电路,可以完成分析指令,并且可以给其他的部件发出一些控制信号。我们把控制器比喻成霸道总裁,因为它会控制其他的部件,但是对于这个霸道总裁来说CU才是他真正的核心。就相当于CU是霸道总裁的老婆,总裁还是得听老婆的。除了CU之外,还有另外的两个寄存器。IR(指令寄存器),是用来存放当前执行的指令的,PC(程序计数器),是Program Counter的缩写。我们平时说的PC一般是个人电脑,不要和这里的 PC 混为一谈。这个寄存器的作用是用 来存放下一条指令的地址,并且有自动加1的功能。计算机最主要的工作就是执行代码,代码其实就是一条一条的指令。每完成一条指令,需要分为三个阶段,第一个阶段是取指令,会根据 PC,也就是程序计数器里边所记录的指令地址,根据这个地址,从内存里边取出那一条指令,取出的指令会被放在IR,也就是指令寄存器当中。然后CU这个部件就可以分析这条指令到底是要干什么的,当分析完了之后,CU部件就会控制着其他部件来配合着完成指令的具体执行,这是完成一条指令的三个阶段。很多地方也会把前边两个阶段统一的称为取指的阶段,最后的这个阶段把它称为执行的阶段。
运算器和控制器的功能都是很抽象的,要把抽象的知识变具体,最好的方法是让他们跑起来。接下来用一个具体的例子来看一下我们熟悉的一段代码是怎么执行的?比如我们可以用 C 语言写一段程序,定义a b c y 这样的四个变量。这个程序里面只是执行了一句简单的代码 y=a×b+c,这是我们高级语言程序员看到的一个视角。但事实上,我们的 CPU 不可能一次性完成这样的一个复合运算,它只能先进行乘法,然后再进行加法。所以这段程序在经过编译、链接一系列的操作之后,可以把这个高级语言翻译成机器能读懂的机器语言。同时会把这段程序装入主存,它在主存里的样子就是下面这样的:
最左边的一列指明了主存的地址,零号存储单元,一号存储单元,以此类推,一直到八号。a b c y 四个变量,a 这个变量存放在地址为5的存储单元当中的。在主存当中,是以二进制的形式来存放。b这个变量存放在6的存储单元当中,c存放在7的存储单元当中,最后要计算的这个 y是存放在8的存储单元当中。除了这几个数据之外,0到4这几个存储单元存储的是这一段程序所对应的机器指令。用高级语言写的短短的几行代码经过编译之后丢给计算机的,其实是这么多条复杂的指令。每一条指令会分为操作码和地址码两个部分,
每一个存储单元是 16 个比特,也就是存储字长为 16 个比特,然后每一条指令的两个部分,也是占 16 个比特,只不过在执行这些指令的时候 CPU 会自动的把它们拆解成操作码和地址码两个部分。
下面来看一下这个程序的运行,接下来的这段演示认真听,有助于大家理解各个硬件部件的作用,还有硬件部件之间是怎么配合工作的,能帮助形成一个对计算机运行原理的全局观。
运算器和控制器共同集成在 CPU 里面,还有一些 IO 设备,这个例子当中 IO设备不重要,首先指令还有变量的数据都是存放在存储体当中,这个程序要执行的第一个指令应该是存放在零号位置的
这条指令,在程序运行之前 PC会指向零位置。接下来,应该把
这个位置存放的指令取出来执行。首先 PC 存放的内容需要通过地址总线把它传送到 MAR这个地址寄存器当中。此时 PC 是零,这个操作就会导致MAR这个寄存器的值变为了零,也就是说控制器向主存指明了接下来要访问的是零号地址所对应的这一串数据
。同时,控制器会通过控制总线告诉主存储器,说这次要进行的是读操作,接下来主存储器会根据MAR记录的这个地址信息去存储体里边找出0号地址所对应的这些二进制数据,并且把这些二进制数据放到MDR,也就是这个数据寄存器当中。这就导致MDR这个寄存器当中里边存放的就是我们想要读取的第一条指令。这里解释一下,把寄存器的名字外面打一个括号,指的是这个寄存器里边的内容。然后M表示的是 memory,也就是主存储器,主存储器里边 MAR这个地址所指明的存储单元里边的数据放到MDR这个寄存器当中,是这样的一个意思。现在 CPU想要的货物已经放到MDR这寄存器当中了,接下来他就可以取走这个货物,这一次要取走的货物其实就是一条指令
。这条指令会通过数据总线把它放到IR,也就是指令寄存器当中,那这就导致控制器IR这个寄存器里边已经存放了此次要执行的指令。接下来这条指令的前边六个比特
,也就是操作码,这六个比特会被送到CU控制单元里边,CU 会分析
这个操作码,它就可以知道000001对应的是一条取数的指令。接下来要把这个地址码
所指明的内存单元里的
这个数据把它给取出来,并且放到 ACC这个寄存器当中,这是取数指令要做的事情。既然要读取主存的
这个地址的信息,就需要把这个地址码放到主存的 MAR当中。这就导致此时MAR是等于5,因为这个二进制数转换成十进制就是5。5号存储单元所指向的刚好就是 a 变量所存放的位置,接下来主存储器根据MAR指明的地址去存储体当中找出5号单元的数据,并且把这些数据放到MDR当中,也就是把 a 这个变量的值,放到MDR里, a 的值是2,最后在控制单元的指挥下,MDR里的数据会被传送到 ACC,也就是累加寄存器当中,到此为止,就完成了一个取数的指令,最终导致 a 变量的值已经被放到 ACC 这个寄存器当中了。图里边画的1,2,3,4这几步其实就是对应了我们之前说的取指令的过程。第五步对应了分析指令的过程,后边的6到9步才是实际执行这个取数指令的过程。虽然我们只是给出了
这样一条机器语言描述的指令,但是硬件要完成这条指令背后还是需要做很多很多事情。
我们说过程序计数器 PC有自动加1的功能,在取指令动作完成之后 PC的值就会自动的加1,以前是0,自动加1,它就会指向1,也就是下一条要执行的指令是在1这个单元,上一条指令执行之后,导致的结果是PC等于1, ACC=2,也就是等于 a 的值。接下来,根据程序计数器 PC 所指向的位置取得第二条指令。取指令的过程和之前是类似的。通过这样的四个步骤,把第二条指令放到 IR寄存器当中。取完指令之后就需要分析指令,和之前一样会把操作码送到CU控制单元,经过CU 分析之后,它可以知道
这个操作码对应的是一个乘法操作,所以这是一条乘法指令,接下来CPU会指挥其他部件来协调完成这个乘法操作。还记不记得我们写的程序 y=a×b+c 现在这一步,我们要完成的是 a×b 的操作。a 这个变量之前已经被送到 ACC寄存器里了,所以现在这条乘法指令它的地址码指明的是另一个乘数 b的存放位置, b 存放在6这个位置,刚好这个地址码把它翻译成十进制数也是等于六。所以这条指令的地址码会被送到 MAR当中,指明要取出的是六号存储单元里边的数据。接下来主存会根据MAR所指明的地址,取出相应的数据。也就是把 b 这个变量的值放到了MDR当中。接下来 b 的值会从MDR再通过数据总线送到MQ,也就是乘商寄存器当中,所以现在 b 的值已经放到 mq 里边了。接下来还需要把 a 的值先放到通用寄存器 x 当中,也就说当CPU执行乘法操作的时候,它会把被乘数。放到 x 这个通用寄存器里边。然后把乘数放到 MQ这个乘商寄存器里边。接下来CU会通过控制线告诉ALU算术逻辑单元,让它进行乘法运算。那 ALU会把 x 和MQ里边存储的这两个数,把它们进行一个相乘的操作,然后把最终乘得的结果放到 ACC 里边。那这个操作导致 ACC 里边得到了 a×b 的值6。在这个例子当中,进行相乘的这两个数很小,但是如果说这两个数很大的话,有可能 ACC 寄存器里边是存不下的。这种情况下就需要 MQ乘商寄存器的一个辅助存储,MQ里边会用来存放乘法运算结果的低位。这样就完成了第二 条指令,也就是乘法指令,和前边那条指令相比,第一步到第五步这几个步骤。每一步要做的事情都是一模一样的,只有当 CU控制单元分析出这条指令是乘法指令的时候。接下来的操作就会产生一些区别。和之前一样,当取指令结束之后, PC 的值也会自动加一,所以。这条指令执行结束之后,得到的效果就是 PC 指向了二,也就指向了下一条要执行的指令。
ACC 里边此时存放了我们上一个运算的结果。也就是 a×b 的等于6。接下来执行2地址存放的这条指令,前边的几个步骤都是一样的,通过五个步骤可以完成取指令和分析指令的操作,CU 发现,这次要执行的操作码应该是000011。这个操作码对应的是一个加法操作,因此这是一条加法指令,接下来 CU 会根据加法指令的执行步骤来指挥着其他部件协调工作。这次要计算的是 a×b+c,而 a×b 的结果,已经存放在 ACC 当中,所以这个加法指令的地址码
,指明的是 c 这个变量的存放地址,c 是存放在7这个位置的,刚好把这个二进制数转换成十进制就是7。因此,接下来要取出7这个地址所存放的数据,就需要把这个地址码把它放到MAR地址寄存器当中。接下来,主存根据 MAR指明的地址,取出相应的数据,也就是把 c 的值放到了MDR当中。接下来这一步会把MDR的值再传送到通用寄存器 x 当中,也就是当进行加法运算的时候,ACC 里边会先存入被加数,然后通用寄存器 x 当中会存放加数,也就是 c 的值。接下来控制单元会向ALU发送一个信号,告诉它此次要执行的是加法操作,那么 ALU算术逻辑单元就会把 ACC 和 x 里边存放的值进行相加,并且把加的结果再次存回 ACC 当中,因此这就导致了 ACC=7,也就是把 a×b+c 的值存放到了 ACC 里边。到此为止,完成了第三条指令,也就是这个加法指令。同样的,前边的五个步骤,其实和之前的两条指令没有任何区别,另外在取完指令之后,同样的也会自动的进行 PC 加一的操作。
也就是 PC 会指向下一条我们应该执行的指令。接下来这条指令的执行,前边的这些步骤都是一样的。根据 PC 记录的这个地址,取出这条指令,并且放到IR指令寄存器当中。第五个步骤会把这个指令的操作码把它送到CU当中进行分析,CU分析之后发现00010,这是一条存数的指令,也就是要把 ACC 里边的数据把它存到地址码所指明的存储单元当中,这个地址码转换成十进制,应该是8,而8这个存储单元刚好对应的是 y 这个变量的存储位置。现在 ACC 里边存的是7,也就是 a×b+c 的值。接下来执行存数指令,首先会把这个指令的地址码部分
把它送到MAR里边。用来指明,此次要存入的是哪一个存储单元,另外还需要把 ACC 里边的运算结果通过数据总线把它送到MDR当中。也就说,此时 mdr 里边保存了 a×b+c 的值。并且此时 mar 里边指明了这个值是要存到内存地址为八的这个地方。然后接下来 cu 控制单元会通过控制总线告诉主存储器,说我此次要进行的是一个存一个写的操作。于是主存储器会根据 mar 所指明的地址。把 mdr 当中的这个数据放到相应的位置当中,这就导致了8号存储单元的值变为了七,也就是我们最终想要得到的这个结果 y=a×b+c。同样的存数指令的执行取指和分析指令的阶段做的事情和前面那些指令没有区别,只有分析出了指令之后,才会在 cu 控制单元的控制之下执行不一样的一些操作。同样,此时PC 会加一指向下一条应该执行的指令。 那么接下来执行这条指令
,和之前一样,首先需要取出指令,把这个指令。把它取到IR指令寄存器当中,然后接下来要把操作码部分送到CU里边,然后CU分析发现000110,这个操作对应的是停机指令。所以执行到这一步就知道我们的这段程序运行结束了。停止一个进程的运行,需要通过系统调用,或者说通过中断机制来通知操作系统来终止这个进程。于是,接下来执行的指令就不是这一系列指令,而是操作系统相关的那些指令。后续的部分,我们就不需要再关心了。
那到此为止,我们就一步一步模拟了这个程序的执行过程,同时相信通过这个例子,大家就能够真正的理解运算器,控制器和主存储器,它们的各个部件分别有什么作用。另外,它们之间是如何相互协调着来工作的。
进行一个小小的总结,这是我们的第一条,也就是取数指令的一个执行过程。分为取指令,分析指令和执行指令这样的三个阶段,任何一条指令的执行一定都是这样的三个阶段。无论我们执行的是什么指令,前边取指令和分析指令的这些步骤其实都是一模一样的,只有分析完指令之后CU才可以知道这条指令到底是要做什么。于是接下来的这些步骤。对于不同的指令来说,就有可能不一样,那在取指令的阶段MDR里的数据肯定是要传送到IR指令寄存器当中。而在执行指令的阶段,CU会根据具体的这条指令来决定MDR里的数据应该是放到 ACC 还是放到MQ还是放到其他的通用寄存器里边。根据指令执行周期的不同,它处于不同的阶段。CPU 就可以区分出我们这次从内存里取出的,到底是指令?还是数据了。这个大家可以好好体会一下。
这一小节当中介绍了很多很硬核的东西,主存、运算器,控制器这三大部件构成了主机,那这三大部件的内部又分别有一些细分的小的部件,每一个部件完成了不一样的功能。但是相信通过之前的。那个程序运行的例子,大家能够体会到每一个部件的具体作用。运算器的核心部件是ALU,控制器的核心部件是CU,在 CPU 里边这两个部件的制造成本也是最高的。另外,关于存储体里边所涉及的这些概念,大家也一定要捋清楚,这些都是选择题的高频考点。 需要注意字和字节的区别。也需要注意,大 b 和小 b 的一个区别。这个小节的后半部分,用一个程序的执行来说明了计算机的一个工作过程。刚开始需要把指令和数据把它们存到主存里边,然后 PC 程序计数器会指向第一条指令在内存中的存放地址。接下来就是对一条一条指令的执行,首先需要从主存中取出一条指令,放到IR指令寄存器当中,同时 PC+1。指向下一条应该执行的指令。接下来CU会分析指令,根据指令的操作码来确定这条指令应该做一些什么具体的事情。那最后 CPU 会指挥其他的部件来共同的配合着完成这条指令的执行。这就是我们计算机的一个工作过程。再补充一点 MAR和MDR这两个寄存器,逻辑上是属于主存的,现代的计算机当中。这两个寄存器通常会被集成到 CPU 里边,所以如果接下来的学习当中大家看到了MAR和MDR这两个寄存器,被划到了 CPU 这个部件里边,也不要觉得奇怪。 那以上就是这一小节的全部内容,那在学习了这一小节之后,大家可以再回头看一下,上一小节提到的弗诺伊曼计算机的这几个特点。第二个特点,指令和数据以同等的地位存入存储器。刚才我们给的那个例题就是把指令和数据无差别的,以同等的地位放到了存储器里边,只不过存储的位置不一样而已。对于指令和数据,我们都可以按照地址来寻找和访问。无论我们是要读出一条指令,还是要读出一个数据,我们肯定都需要给出啊,它在内存里边的地址,所以这是按地址寻访的意思, 那第三个特点,指令和数据用二进制表示,这也不用说好第四个特点。指令由操作码和地址码组成。那这个肯定也能理解了,只不过我们这个小节给的例子当中,每一条指令只有一个操作码,一个地址码,但是有的计算机它所支持的指令。可能是有多个地址码的,比如说一条指令包含两个地址,那这种指令就称为 2d 指的指令。具体的我们会在之后的章节当中再遇到。第五个特点,存储程序在程序运行之前指令和数据都会被提前存到主存里边,这是存储程序,经过这个小节的学习,相信大家对冯诺依曼计算机的这些特点就有了更深的认识。