购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第2章
计算机和汇编语言

亲爱的朋友,从现在开始我们就要进入汇编语言的世界了。就像一个大学生刚到新单位报到,要想快速进入工作状态,首要的任务是熟悉单位的情况和工作环境。同样,在学习汇编语言时,要想快速上手,也必须先了解与汇编语言有关的计算机知识。

汇编语言和处理器是紧密联系的,学习汇编语言的过程,实际上也是洞悉处理器内部构造和工作方式的过程。用汇编语言编程,必须和处理器内部的寄存器打交道,但很多人(包括我本人)在第一次接触汇编语言时,对这些东西感到很迷惑,不知道什么是寄存器,不理解为什么要使用寄存器。因此,了解处理器的内部构造及其工作方式很重要。鉴于此,本章的目标是:

1.从如何用电来表示数字开始,对电子计算机(尤其是处理器)的工作原理和演进过程进行介绍,重点了解什么是寄存器、内存和指令,以及指令集、字节等基本概念;

2.在上述过程中,我们将了解到使用机器指令编程的缺点和复杂性,从而知道为什么要发明汇编语言,以及用汇编语言编程的好处。

顺便说一下,在写这本书之前,我写过另一本科普读物《穿越计算机的迷雾》,里面把计算机的原理讲清楚了,有兴趣的同学可以看看。

2.1 用电表示数字

电和数字有关系吗?原本是没有关系的,风马牛不相及。但是,要想发明电子计算机,我们必须让电和数字扯上关系。

如图2-1所示,这是两个用开关和导线组成的电路。在初中的时候我们都学过电路,所以,我相信你能够看懂这幅图的意思。线条表示导线。这里有两根导线,每根导线都连着一个开关。当开关断开时,没有电流通过导线;当开关闭合时,有电流通过导线。为了看的时候醒目,我们将有电流通过的导线画成黑色,将没有电流通过的导线画成灰色。

img

图2-1 用开关和导线组成的电路

在学这门课程之前,我要求你已经学过二进制,也知道如何用二进制来表示数字。二进制计数法采用0和1的组合来表示数字。0和1很适合用开关的闭合与断开,以及电流的有和无来体现。比如说,当开关断开时,导线上没有电流,表示0;当开关闭合时,导线上有电流,表示1。

现代的电子计算机用二进制来表示数字。这样做有一个好处,那就是,我们可以用一排导线来表示数字,如图2-2所示。如果我们依次记下每根导线的状态,没有电流通过记成0,有电流通过就记成1,就可以得到一个由0、1组成的二进制数。在这里,这个二进制数是01000100,换算成十进制就是68。

当然,如果你想用这排导线表示别的数字,只需要拨动开关,将它们设置成适当的状态就可以了。

img

图2-2 用一排导线上电流的有无来表示二进制数

在现实中,导线上有没有电,我们是看不见的。但是,如图2-3所示,我们可以在导线上安装灯泡。当导线上没有电流通过时,灯泡不发光,表示传送的是0;当导线上有电流通过时,灯泡发光,表示传送的是1。

我们把灯泡的状态记下来,组合成二进制数字,就知道这排导线上传输的数字是多少了。比如在这里,我们记下灯泡的状态是01000100,好,这就是它传输的二进制数字,换算成十进制,是68。

img

图2-3 用灯泡是否发光来判断导线上传送的是1还是0

2.2 二进制加法机

在二十世纪三四十年代,还没有计算机,人们更不可能想到计算机会这么有用,能上网、能听歌、能看视频、能聊天、能购物、能打游戏。在那个时候,人们想得很简单,只要能够发明一个简单的计算器,能算加减乘除,就十分满足了,就觉得已经很了不起了。

因此,世界上第一台电子计算机,严格来说是一个如图2-4所示的加法器,或者说是一个能做加法的电路。说它是计算机,现在看来挺可笑的,但当时已经是最先进的了,这就是我们人类社会的第一代电子计算机。

注意这个机器,它采用二进制工作。左边的这一部分,有8根导线,每根导线都通过开关把电流送到机器里。这8根导线通过拨动开关来组成并代表一个8位的二进制数。就当前的开关状态来说,它输入的是二进制数01000100,也就是十进制数68。

同样的道理,下面这一排带开关的导线也通过拨动开关来组成并代表另一个8位的二进制数。就当前的开关状态来说,它输入的是二进制数01100001,也就是十进制数97。

这个加法机器的作用是接受左边和下面的输入,把它们当成两个二进制数,并做加法操作,相加得出一个和数。

img

图2-4 能做加法的电路

相加的结果通过右边的那一排导线送出,当然是以二进制数的形式送出,每根导线都代表这个二进制数中的1比特。为了观察导线上是0还是1,我们为它接上了灯泡。从当前灯泡的发光情况来看,结果是二进制数10100101,也就是十进制数165。68加97是165,显然,这个机器工作正常,结果是对的。

注意,这个加法电路的工作是实时的,输入端的任何变化都将立即导致输出端的变化。当你拨动左边或者下边的任何一个开关时,右边的输出也将立即有所变化,某些灯泡会灭掉,而有些灯泡会亮起来。

这个加法电路的内部构造不是我们今天要关心的话题,我们只需要知道它的功能就可以了。如果你实在感到好奇,我推荐你读一读《穿越计算机的迷雾》这本书,里面有你想知道的答案。

2.3 具有记忆功能的器件——寄存器

一般的电路,它们的工作都是非常直接的。在前面的图2-3中,一旦我们拉起开关,切断电路,灯泡立马就不亮了,这表明电线上传送的是数字0;相反,一旦我们闭合开关,接通电路,灯泡立马就亮了,这表明电路上传送的是数字1。

后来,人们发明了一个装置,叫作触发器。如图2-5所示,一个特制的触发器有一个输入端D,以及一个输出端Q。触发器的特点是它可以把输入保存起来,这叫作锁存。如果你想用眼睛观察触发器锁存的内容,可以在输出端连接一个灯泡。

img

图2-5 具有记忆能力的触发器

那么,触发器什么时候锁存呢?这是可以控制的。注意下面有一根导线和一个按键开关,按键开关和我们前边讲的那些开关不一样。按键开关有个特点:当你按下它时,它会接通电路;当你松手后,它又会弹起来断开电路。

这个按键开关用于决定是否锁存。平时,按键开关处于断开状态,触发器不会执行锁存动作,无论从输入端D来的是0还是1,都不会进入触发器内部,都不会被触发器内部的电路保存,更不会出现在输出端Q,即不影响输出端Q原来的状态。

但是,一旦我们按下按键开关,则触发器会立即执行一个锁存动作,不管输入端是0还是1,都会被触发器锁存起来,并立即出现在输出端Q。锁存之后,无论输入端D再怎么变化,都不会影响到锁存的内容,也不会影响输出端Q原来的输出,除非再次按下按键开关发送锁存命令。

一个触发器只能保存1比特。为了保存一个比较大的二进制数,如图2-6所示,可以使用若干个触发器,将它们组合在一起,这样就形成了一个新的器件,叫作寄存器(Register),或者叫作锁存器。

img

图2-6 用多个触发器组成的寄存器

寄存器是一个多输入、多输出的器件,它的两边都连着一排导线,左边的导线用来提供输入,右边的导线用来提供输出;下面的按键开关用来向组成寄存器的所有触发器发送锁存命令。

在图2-6中,输入端是二进制数字11000101。当我们按下按键开关时,这个数字立即被锁存。一旦输入的数字或者说电平被锁存,那么,即使这些输入撤销了也没有关系,因为它们已经被锁存在了寄存器内部。与此同时,锁存的数据也会通过输出端送出去。

如果需要,寄存器可以随时锁存新的数字,以前锁存的数字会被新的数字冲掉。从这个意义上来说,任何数字都是临时被保存在这里的,不会长久,属于临时性寄存。这就是“寄存器”一词的由来。

2.4 带寄存器的加法机

人类喜欢简单的操作,他们会不停地改进设备。所以,如图2-7所示,这是前面那个加法电路的改进版本。

在这个新的加法电路里,我们加入了一个寄存器。为了方便,我们称之为寄存器R。加法电路的左侧是一排带有开关的导线,用于输入相加的数字;右边的一排导线用于输出计算结果。实际上,在机器内部,右边这排导线连接在寄存器的输出端上。因此,寄存器R当前锁存的内容可以通过灯泡观察到。

img

图2-7 带有寄存器的加法电路

加法电路的另一个变化是,它只有一组输入。这好像是个问题,但实际上这样做是很方便的。在这个电路的下面有两个按键开关,分别是“ 预置 ”和“ 相加 ”,它们就是用来解决这个问题的。比如说,如果我们要计算5加7加25,该怎么办呢?操作过程是这样的。

首先,拨动左边的一排开关,准备好第一个要相加的数字5,然后按一下“ 预置 ”按钮,将这个数字保存到寄存器R。

接着,再次拨动左侧的那排开关,准备好另一个要相加的数字7,然后按一下“ 相加 ”按钮。此时,左边的数字7和寄存器R里原有的数字5相加,相加的结果12依然保存在寄存器R中。

因为还有一个数字25需要相加,于是我们再次拨动左侧的那排开关,准备好要相加的数字25,准备好之后,按一下“ 相加 ”按钮,此时,左边的数字25和寄存器R里原有的数字12相加,相加的结果37依然保存在寄存器R中。

如果还有更多的数字要加,那么,操作过程和上面一样,反正就是准备数字,然后按一下“ 相加 ”开关。

2.5 能做四则运算的机器

前边我们一直在使用加法机做加法,有些人觉得,只做加法的话,功能太简单了。于是,如图2-8所示,他们改进了这个机器,为它增加了减法、乘法和除法功能。现在,我们称之为四则运算电路。

img

图2-8 四则运算电路

在这个四则运算电路的下边,有几个按键开关。这几个按键开关用来控制运算器内部的操作,下面我们分别进行说明。

如果按一下“ 预置 ”开关,那么,将执行锁存操作,左侧这排开关生成的二进制数被锁存到寄存器R。

如果按一下开关“ ”,那么,它所指定的操作是用寄存器R里原有的数字和左侧这排开关生成的数字相加,相加的结果位于寄存器R。

如果按一下开关“ ”,那么,它所指定的操作是用寄存器R里原有的数字和左侧这排开关所生成的数字相减,相减的结果位于寄存器R。

如果按一下开关“ ”,那么,它所指定的操作是用寄存器R里原有的数字和左侧这排开关所生成的数字相乘,相乘的结果位于寄存器R。

如果按一下开关“ ”,那么,它所指定的操作是用寄存器R里原有的数字和左侧这排开关生成的数字相除,相除的商位于寄存器R。

当然,你会觉得功能还是太少。但是你要知道,绝大多数问题都可以归结为基本的加减乘除运算。比如,3的2次方,可以用3乘以3来完成。其他数学问题也是如此。

这个机器用起来还是很方便的,可以做连续的加减乘除运算。这里有一个实际应用的例子,先给出或者说预置一个数字7,再加8,得到15,然后乘以3,得到结果45,最后除以5,得到9。

首先,我们先拨动左边的开关准备好第一个数字7,然后按一下“ 预置 ”按钮,将这个数字保存到寄存器R。

接着,再拨动左侧的开关,准备好另一个数字8,按一下“ ”按钮,则寄存器中原有的数字7和左边的数字8相加,相加的结果15依然保存在寄存器R中。

接着,再拨动左侧的开关,准备好另一个数字3,按一下“ ”按钮,则寄存器中原来的数字15和左边的数字3相乘,相乘的结果是45,依然保存在寄存器R中。

最后,再拨动左侧的开关,准备好数字5,按一下“ ”按钮,则寄存器中原来的数字45和左边的数字5相除,相除的结果9依然保存在寄存器R中。

寄存器的作用是参与运算,并临时保存运算结果。但是,如果只有一个寄存器,那么,在进行一些复杂的运算时,肯定是不够用的。比如这一道带括号的计算题:

(207+9)÷(56-48)

它很简单,但又有点复杂,因为我们必须先计算207+9和56-48的结果,再将这两个计算结果相除。我们来试试看。

首先拨动左侧的开关以生成数字207,然后按一下“ 预置 ”按钮,将207锁存到寄存器R中。接着,我们再拨动左侧的开关,生成数字9,然后按一下“ ”按钮,这将把寄存器R里的数字207和左侧输入的数字9相加,相加的结果216依然保存在寄存器R中。

现在的问题是,寄存器R被用来保存上一个计算结果,无法再用来计算56减去48。在这种情况下,我们只能把相加的结果216用脑子或者笔记下来,腾出寄存器R,用来计算56减48。

拨动左侧的开关以生成数字56,再按一下“ 预置 ”按钮,将56锁存到寄存器R中。接着,我们再拨动左侧的开关,生成数字48,然后按一下“ ”按钮,这将把寄存器R里的数字56和左侧的数字48相减,相减的结果8依然保存在寄存器R中。现在,用笔或者你的脑子把结果8记下来。

最后是把前面已经得到的两个中间结果216和8相除。拨动左侧的开关以生成数字216,再按一下“ 预置 ”按钮,将216锁存到寄存器R中。接着,我们再拨动左侧的开关,生成数字8,然后按一下“ ”按钮,这将把寄存器R里的数字216和左侧的数字8相除,相除的结果27依然保存在寄存器R中。

2.6 机器指令

从刚才的例子可以看出,因为只有一个寄存器,这使得运算器的功能受到限制,操作也很麻烦。

为此,如图2-9所示,我们可以在运算电路里多放几个寄存器,这样就能够倒腾得过来。为了方便说明问题,我们暂时再加入一个寄存器Z,这样我们就有了两个寄存器。

尽管只是增加了一个寄存器,但是这台机器的操作却复杂了很多。比如,可以将左边的数字传送或者预置到寄存器R中,也可以传送到寄存器Z中;可以将寄存器R中的数字和左边来的数字做加减乘除,也可以将寄存器Z中的数字和外来的数字做加减乘除;可以将寄存器R中的数字传送或者说复制到寄存器Z中,也可以将寄存器Z中的数字传送或者说复制到寄存器R中;可以用寄存器R中的数字和寄存器Z中的数字做加减乘除操作,而且可以选择运算的结果保存在哪一个寄存器。

img

图2-9 可以接受指令的四则运算电路

我粗略地估计了一下,这里共有大约20个动作。对于以上所列举的每个动作或者说每个操作,都需要一个按键开关来触发,所以至少需要20个按键开关。这还只是两个寄存器,如果以后再增加寄存器或者别的功能,开关就更多了。这不是长久之计,我们得另想办法。

考虑一下,既然我们可以用一排开关来生成参与加减乘除的数字,也可以用另一排开关来共同组合出我们要执行的操作。

为此,我们在运算电路的下面安装5个铡刀开关。和往常一样,开关的闭合代表这根线上是1,开关的断开代表这根线上是0,于是可以组合出一个5位的二进制数字。不同的二进制数字具有不同的含义,代表不同的操作。当我们拨动这一排开关时,就是指定这台机器所要执行的操作,因此,我们把这些开关所代表的数字叫作指令(Instruction)。指令就是给这台机器下达的操作命令。表2-1给出了这5个开关可以组合出的指令,以及它们所指定的操作。

表2-1 不同的指令及其所指定的操作

img

续表

img

那么,什么时候开始执行由开关所形成的指令呢?旁边还有一个按键开关,名字叫“ 执行 ”。当我们按下这个开关时,这台机器就按照指令的指示进行相应的操作。

比如,我们将这组开关设置成“ 开、开、开、关、关 ”的状态,当按一下“ 执行 ”开关时,将执行把寄存器R中的内容复制并传送到寄存器Z中的动作。

那么,现在我们就来用这台新机器计算数学题(207+9)÷(56-48),看看这个操作过程是怎样的。

首先,拨动左边的开关以生成数字207,接着,拨动下面的“ 指令 ”开关,将它们设置成00001,意思是将外数传送到寄存器R中。此时,按一下“ 执行 ”开关,这将把左边的207锁存到寄存器R中。

接下来,拨动左边的开关以生成数字9,接着,拨动下面的“ 指令 ”开关,将它们设置成00101,意思是,将寄存器R中的数字和外数相加。此时,按一下“ 执行 ”开关,这将把寄存器R中的数字207和左边的数字9相加,相加的结果216依然在寄存器R中。

接下来,拨动左边的开关以生成数字56,接着,拨动下面的“ 指令 ”开关,将它们设置成00010,意思是,将外数传送到寄存器Z。此时,按一下“ 执行 ”开关,这将把左边56锁存到寄存器Z中。

再往下看,我们拨动左边的开关以生成数字48,接着,拨动下面的“ 指令 ”开关,将它们设置成01010,意思是,将寄存器Z中的数字和外数相减。此时,按一下“ 执行 ”开关,这将把56和48相减,相减的结果8依然在寄存器Z中。

最后,我们拨动下面的“ 指令 ”开关,将它们设置成10000,意思是,将寄存器R里的数字和寄存器Z里的数字相除。此时,按一下“ 执行 ”开关,这将用寄存器R中的数字216除以寄存器Z中的数字8,相除的商27保存在寄存器R中。

2.7 内 存

通过拨动开关来形成指令,然后让运算器执行指令,这很有创意。但是,随着机器功能的增加,手工操作越来越烦琐,这是肯定的。

考虑一下,当我们拨动开关来组合指令时,和生成一个二进制数没有区别,只不过这些数字实际上是指令,用来指定某个操作。那么,能不能把这些代表指令的二进制数保存到某个容器里,让机器自动按顺序一条一条地取出来执行呢?没有问题,这完全可以。

如图2-10所示,在左边的容器里就保存着一堆代表指令的二进制数,右边的运算器可以一条一条地取出并加以执行。这样的容器,就是我们今天所要讲的内存。内存是由大量的内存单元堆叠而成的,在这里,组成内存的每一个方块都是一个内存单元。

img

图2-10 内存

和图2-10不同,在主流计算机的内存里,每个内存单元的长度是8比特,可以保存一个8位的二进制数。比如在图2-11中,最下面的那个内存单元,就存储了一个8比特的二进制数10000101。

内存单元很多,我们如何区分它们呢?答案是,每个内存单元都有一个唯一的编号。第一个单元的编号是0,第二个单元的编号是1,第三个单元的编号是2,后面的单元也依次编号。注意,单元的编号是这个单元在内存里的位置,通常称为地址(Address)。

img

图2-11 主流计算机上的内存

既然内存是由大量的内存单元组成的,那么,如何指定读写的是哪个单元呢?为此,内存使用一排电线,称为地址线,来指定单元的编号。当我们访问某个内存单元时,就通过这排地址线输入单元的编号。显然,地址线的数量决定了我们最多可以访问几个单元。

比如说,如果内存只有两根地址线,这两根线只能组合出4个二进制数,分别是00、01、10和11。这4个二进制数代表着4个地址,因此,只能访问到4个单元。如果用十进制数来表示单元的编号,这几个单元的编号分别是0、1、2和3。

再举个例子,如图2-12所示,如果有8根地址线,那么,这8根地址线可以组合出256个二进制数,分别是00000000、00000001、00000010、…、11111111。这256个二进制数代表着256个地址。所以,8根地址线只能访问256个内存单元。

内存单元的编号就是它的地址,习惯上,我们用十六进制标注在它的左侧。这里,第一个内存单元的地址是00H,最后一个内存单元的地址是FFH。

注意,为了整齐划一,地址0被标注为00,地址1被标注为01。这是可以的,在一个数字的前面加0,不会改变它的大小。

img

图2-12 具有256个单元的内存

推而广之,如果地址线的数量是 N ,那么,可以通过它访问的内存单元的数量是2的 N 次方,即2 N

在计算机领域,字节的概念被频繁地使用。习惯上,字节是用来描述二进制序列的长度单位,8比特组成1字节。字节的英语单词是Byte,简写为B。比如,二进制数10001101的长度是1字节;二进制数1101000101111110的长度是2字节。

在主流的计算机上,内存单元的长度是8比特。换句话说,每个内存单元的长度都是1字节。

内存的容量可以用内存单元的数量来统计。因为每个内存单元的长度是1字节,所以经常用字节数来衡量。根据内存的大小,内存的容量是以字节(B)、千字节(KB)、兆字节(MB)、吉字节(GB)和太字节(TB)来标称的,它们之间的换算关系是:

· 1 KB=1024 B;

· 1 MB=1024 KB;

· 1 GB=1024 MB;

· 1 TB=1024 GB。

内存用来保存或者读出数据。为此,如图2-13所示,内存上还需要另一排导线,这排导线叫作数据线。要写入的数据通过数据线进入内存;读出来的数据也通过数据线送到外面。

可以往内存里写数据,也可以从内存里读出数据,读和写统称为“访问”。为了访问内存,还需要一个读写控制线,用来指明是读操作还是写操作。举个例子来说,读写控制线平时没有输入,为0,表示处于随时可以读取的状态;如果它为1,则表明执行的是写入操作。

img

图2-13 内存都具有地址线、数据线和读写控制线

在写入的时候,我们先在地址线上给出一个地址,在数据线上给出一个要写入的数字,通过读写控制线发出写命令,内存就会把数据线上的数字写入指定的地址。

在读出时,先在地址线上给出一个地址,然后通过读写控制线发出读命令,那么,就会从指定的地址读出数据并送到数据线上。

举个例子来说,假定如图2-13所示的内存有16根地址线,那么,它可以访问65536个内存单元,地址范围是0000H~FFFFH。

如果发出的地址是二进制数0000000000000110,那么,由于它等于十六进制的6,所以将选中内存中地址为6的单元。

再假定这个内存有8根数据线,通过数据线输入的是二进制数10001101,并且读写控制线的状态是写入(1)。那么,数据线上的10001101会被写入这个地址为6的单元。

读的时候也是一样,如果地址是6,读写控制线的状态是读(0),那么,内存单元里的数字就会被送到数据线上。

内存是存储器(Storage或Memory)的一种,而存储器的种类实际上是很多的,包括大家都知道的硬盘和U盘等,甚至寄存器就是存储器的一种。如图2-14所示,我们这里所讲的内存也叫内存条。这个概念是这么来的:首先,它是计算机内部最主要的存储器,所以叫作内存储器或者主存储器,简称内存或主存;其次,它一般被设计成扁平的条状电路板,所以叫内存条。如果你曾经打开过家里的台式计算机,应该见过它。

img

图2-14 个人计算机里使用的内存条

在计算机发展的早期,也就是二十世纪五十年代,受技术限制,制造内存是非常不容易的事,人们使用了能够想到的各种方法,包括磁芯存储器,它用磁场来记录比特0和比特1。具体的原理,请参阅《穿越计算机的迷雾》这本书。

二十世纪七十年代,随着集成电路技术的发展,内存的制造技术也提高了,出现了集成电路存储器。这个时候的内存体积大大缩小,容量大大提高,但以现在的眼光来看还是很小,通常只有几千字节。

到了现在,随着大规模和超大规模集成电路的使用,内存在容量、体积方面都发生了翻天覆地的变化,可以提供几吉字节甚至几十吉字节的存储空间。

2.8 自动计算

在引入了内存之后,人们对运算器也做了改进。如图2-15所示,经过改进之后的运算器通过地址线、数据线和读/写控制线与内存相连,而且它现在的最大变化是可自主工作,可自动地从内存里面按顺序取指令并执行指令。

为了跟踪每条需要执行的指令,运算器内部有一个指令指针寄存器,这个寄存器保存着指令的地址。刚开始的时候,它的内容是第一条要执行的指令的内存地址。

img

图2-15 自动计算机器的组成

当运算器开始工作时,它先将指令指针寄存器的内容送到地址线上,这是要执行的第一条指令的地址。然后,运算器通过读/写控制线发出读内存的命令。之后,内存将该地址上的内容放到数据线上。因为现在是取指令阶段,所以,运算器收到数据后,把它当成指令进行译码,然后根据指令的内容做相应的操作,也就是执行指令。

与此同时,指令指针寄存器的内容被修改,修改为下一条指令的地址。问题是,处理器怎么知道下一条指令的地址呢?答案是,它可以根据当前这条指令的地址和长度来计算下一条指令的地址。它怎么知道当前这条指令的长度呢?不同的指令具有不同的功能,也具有固定的长度。最后,在当前指令执行完成后,接着重复以上过程。

来看一个具体的例子。如图2-16所示,内存里已经写入了很多指令,这些指令共同组成了完成(207+9)÷(56-48)这道算术题的步骤和过程,所以叫作“程序”。

第一条指令占用2字节的内存空间,第1字节01101001,被称为操作码,它指定了要进行什么操作。对于这个操作码来说,它指定了所要进行操作是将操作码后面的数字传送到寄存器R中。

img

图2-16 内存中的指令及其指定的操作

操作码后面的数字是11001111,也就是十进制的207。所以,这条指令执行时将207传送到寄存器R中。显然,在这条指令中,被操作的数字,也就是操作数,是直接包含在指令中的,是指令的组成部分。因此,这样的操作数被称为立即数(Immediate),意思是它是直接包含在指令中的,可以立即从指令中得到。

第二条指令也是2字节,操作码是01001100,指定的操作是将寄存器R中的内容和操作码后面的数字相加,结果依然在寄存器R中。操作码后面的数字00001001,也就是十进制的9。所以,这条指令执行时将寄存器R中的内容和指令中的立即数9相加,结果依然在寄存器R中。

第三条指令也是2字节,操作码是01101010,指定的操作是将操作码后面的数字传送到寄存器Z中。操作码后面的数字00111000,也就是十进制的56。所以,这条指令执行时将指令中立即数56传送到寄存器Z中。

第四条指令也是2字节,操作码是01000100,指定的操作是将寄存器Z中的内容和操作码后面的数字相减,结果依然在寄存器Z里。操作码后面的数字00110000,也就是十进制的48。所以,这条指令执行时将寄存器Z中的内容和指令中的立即数48相减,结果依然在寄存器Z中。

第五条指令只有1字节,操作码是11001010,指定的操作是将寄存器R中的内容和寄存器Z中内容相除,相除的结果依然在寄存器R里。

第六条指令也是2字节,操作码是01110000,指定的操作是将寄存器R中的内容传送到由操作码后面的操作数所指定的内存地址处。操作码后面的数字是00001100,也就是十进制的12。对于当前的操作码来说,这个操作数是一个内存地址。因此,这条指令是将寄存器R中的内容传送到地址为12的内存单元。

地址为12的内存单元是左侧标注为0C的内存单元,因为地址是采用十六进制的,十六进制数0C就是十进制数12。因此,这条指令在执行时,操作数12被当成地址,处理器通过地址线发送给内存,然后把寄存器R中的内容传送到这个地址上的内存单元。

通过和前面的第一条指令进行比较,很容易分清指令中的“立即数”是什么意思。指令执行和操作的对象是数。如果这个数已经在指令中给出了,不需要再次访问内存,那这个数就是立即数,比如第一条指令中的207;相反,如果指令中给出的是地址,真正的数还需要用这个地址访问内存才能得到,那它就不能称为立即数,比如这条指令中的12,它只是一个地址,并不是最终要操作的数字,最终要操作的数字还需要用这个地址再次访问内存才能得到。

运算器一旦开启,它就自动取指令和执行指令。在内存中,有些内容并不是指令。比如在这里,从内存地址0C开始,后面的内容都不是指令。但是,机器在工作时,你插不上手,不可能在它恰好执行到最后一条指令时让它停下来。

因此,最好的办法就是设计一条停机指令,让运算器执行这条指令后自动停止工作并保持停止前状态。在这里,我们的最后一条指令是停机指令,它只有1字节的长度,操作码是11110100。当运算器执行这条指令后,停止工作,我们可以从容地检查程序的执行结果。

2.9 处理器

以上,我们从加法机讲到全自动的运算器。运算器功能有限,经过一代又一代的反复改进后,它就变成我们现在所说的处理器(Processor),一些老的图书和教材把它叫作中央处理单元或者干脆称为CPU。

处理器是一台电子计算机的核心,它会在振荡器脉冲的激励下,从内存中获取指令,并发起一系列由该指令所定义的操作。当这些操作结束后,它接着再取下一条指令。在通常情况下,这个过程是连续不断、循环往复的。大体上,如图2-17所示,处理器由总线接口部件、控制部件和指令执行部件组成。

img

图2-17 处理器的内部组成

总线接口部件负责同外部的地址线和数据线进行连接,发送地址信号给内存或者其他外部设备,和内存或者其他外部设备交换(发送或者接受)数据,等等。

指令执行部件负责执行指令,它包含了很多寄存器,这些寄存器用于参与算术逻辑运算,并临时保存运算结果。指令执行部件的核心是算术逻辑部件(Arithmetic Logic Unit,ALU),算术运算和逻辑运算在这里进行。

控制部件负责协调和控制整个处理器的运行状态,什么时候取指令,什么时候输出地址,什么时候发送数据,什么时候接收数据,什么时候执行指令,都由它负责协调。

1947年,美国贝尔实验室的肖克利和同事们一起发明了晶体管。1958年,也许是受够了在一大堆晶体管里连接那些杂乱无章的导线,另一个美国人杰克·基尔比发明了集成电路。接着,1971年,在为日本人设计计算器过程中,INTEL的弗德里科·法金灵机一动,他想,能不能把运算功能和控制功能集成到一起,设计一款可以自动取指令并执行指令的芯片呢?于是他发明了第一款处理器INTEL 4004,如图2-18所示。

紧接着,INTEL又推出了8088和划时代的产品8086。4004是4位的处理器,8008是8位的处理器,而8086是16位的处理器。

img

图2-18 弗德里科·法金和他设计的INTEL 4004处理器

8086是一款划时代的产品,应用非常广泛。虽然INTEL的处理器越来越先进,但它的x86系列一直保持对8086的兼容性。在本书的前半部分,我们主要针对8086进行讲解。

那么,处理器的位数是什么意思呢?4位的处理器拥有4位的寄存器和算术逻辑部件;8位的处理器拥有8位的寄存器和算术逻辑部件;16位的处理器拥有16位的寄存器和算术逻辑部件;32位的处理器拥有32位的寄存器和算术逻辑部件;64位的处理器拥有64位的寄存器和算术逻辑部件。可以肯定的是,位数越多,寄存器就可以保存更大的数字,算术逻辑部件就可以在单次计算中使用更大的数字并产生更大的结果。

在8086之后,INTEL又生产了80286和80386。80386又是一款划时代的产品,深刻地影响了后续的处理器设计。本书的后半部分是以80386为基础讲解的。

在后来的岁月里,INTEL又推出了更多型号的处理器,这些处理器根据应用领域的不同,发展出多个分支来。图2-19中的这一款处理器名字叫i3-3220,左边是它的正面,右边是它的反面。这些密密麻麻的圆点是它的引脚,用来连接地址线、数据线和读/写控制线。

img

图2-19 i3-3220处理器的正面(左)和反面(右)

处理器的工作是自动取指令并执行指令。对于任何一款处理器来说,它可以识别哪些指令,是在设计和制造的时候就已经决定了的。任何一款处理器,它可以识别的所有指令的集合,叫作这款处理器的指令集。

几十年前,处理器的指令集很小,通常只有十几种或者几十种指令。随着技术的发展,处理器的功能大大增强了,指令集也扩展了。现在的处理器,指令集可以包含几百甚至上千种指令。

对于任何一款处理器来说,它所包含的指令都可以分为以下几种:算术运算指令、逻辑运算指令、数据传送指令和处理器状态控制指令。

算术运算指令和逻辑运算指令是最基本的,也最容易理解。数据传送指令在处理器内部的寄存器之间、处理器和内存之间、处理器和外围设备之间传送数据。这些外围设备包括我们常见的显示设备、存储设备(如硬盘)、打印机、鼠标、键盘等。通过和外部设备的数据交换,计算机的功能也变得丰富起来。比如,我们现在可以在显示器上显示文本和图形,于是产生了Windows和Linux这样的操作系统,可以使用键盘输入文字,进一步地,我们可以用计算机写文档、聊天、购物、玩游戏、看视频。

处理器状态控制指令用于控制处理器内部的工作模式和运行状态,如电源管理、程序的权限管理等。本书后面所要讲的保护模式,也是由这些指令来切换的。

2.10 汇编语言的诞生

我们说过,在内存里写入一些代表特定操作的二进制数(或者说指令),这个过程叫作编程(Programming)。为了给计算机编程,人们最早用的是开关和跳线。

如图2-20所示,(a)是用开关编程的机器;(b)是用跳线编程的机器。一排开关代表一个二进制数或者指令,每个开关代表这个二进制数或者指令的某比特,开关的断开与闭合代表着该比特是0还是1,跳线也是如此。

img

图2-20 用开关、跳线和纸带编程的例子

紧接着,为了方便,人们发明了纸带和纸带阅读机,图2-20(c)就是纸带的一个片段。纸带就是一卷长长的纸条,人们在纸带上打孔,有孔和无孔代表1和0。编写程序时,人们将指令的二进制形式打成孔,然后由纸带阅读机转换成二进制写入内存,最后由处理器执行。处理器执行的结果也可以在纸带上打孔来呈现。

现在,我们有了显示器和键盘,也有了操作系统。键盘可以打字,可以用来编程。操作系统为我们提供了一个好的环境,我们可以启动一个文本编辑器来编写程序,再也不用开关和纸带了。

图2-21显示了一个典型的场景:我们在Windows操作系统上的文本编辑器里用键盘输入文本,并且看起来好像在用二进制编程。如果这真的是在用二进制编程,那么,这将是非常抽象、非常痛苦的,难以理解,容易出错。

img

图2-21 在Windows操作系统上用文本编辑器输入文本

为了减轻程序员的负担,人们发明了汇编语言(Assembly Language)。汇编语言使用文本符号来代表处理器指令,由于和人类的自然语言比较接近,所以很容易看懂,也很容易书写。如图2-22所示,这是在Windows操作系统上用文本编辑器编写汇编语言程序。

img

图2-22 在Windows操作系统上用文本编辑器编写汇编语言程序

其中

img

意思是把指令中的立即数207传送到寄存器r中;

img

意思是用寄存器r中的数字和指令中的立即数9相加,结果回送到寄存器r中;

img

意思是把指令中的立即数56传送到寄存器z中;

img

意思是用寄存器z中的数字和指令中的立即数48相减,结果回送到寄存器z中;

img

意思是用寄存器r中的数字除以寄存器z中的数字,商回送到寄存器r;

img

意思是,将寄存器r中的数字传送到地址为12的内存单元里去;

img

意思是停机。

用汇编语言书写的程序只是一些文本和符号,我们人类能看懂,但处理器是不可能看懂的。为此,需要把汇编语言程序转换为包含了处理器指令的程序。

如图2-23所示,这个转换过程是由一个汇编程序来进行的。汇编程序也是人类编写的程序。可以想到,世界上第一个汇编程序肯定是用处理器指令编写的。

img

图2-23 汇编语言编程的基本步骤

汇编程序执行翻译过程,将汇编语言程序转换为包含了处理器指令的程序,也就是将文本符号转换为二进制的机器指令,转换后的结果是一个包含了处理器指令的程序,这个程序可以提交给处理器执行。

时至今日,计算机的性能已经非常强大,种类也很繁多。台式计算机、笔记本电脑我们就不用说了,手机是一部可以拿在手中的计算机,它的构造具有一台计算机的所有要素,而且性能不亚于你桌子上的计算机;一台车床,它的控制器也是计算机,用来控制车床的动作和加工的精度;智能冰箱里也有一个小小的计算机,用来控制冰箱的运行状态。可以说,在我们今天的生活中,计算机是无处不在的。

有赖于集成电路的发展,今天的计算机都是微型化的,封装得很好、很精致。但是,作为有用的机器,它们必须是可以用编程来控制的,而且必须是可以用汇编语言来控制的。

在后面的章节中,我们将看到这个从编写到翻译,再到执行的过程是怎样一步一步地进行的,当然,我们的重点依然是在汇编语言和指令上。

本章习题

如果地址线的数量是20,则可以表示的地址范围是(用十六进制表示)从________到________,最多可以访问的内存容量是________字节,折合________KB或者________MB。 vg8CEHgY/zdUBtw6DmDS5YaU0J3tjT1pq4XIf0mewW72fYTEBUANlHEolyXjvqFV

点击中间区域
呼出菜单
上一章
目录
下一章
×