计算机系统概论第七章.docx
- 文档编号:6427272
- 上传时间:2023-01-06
- 格式:DOCX
- 页数:14
- 大小:28.88KB
计算机系统概论第七章.docx
《计算机系统概论第七章.docx》由会员分享,可在线阅读,更多相关《计算机系统概论第七章.docx(14页珍藏版)》请在冰豆网上搜索。
计算机系统概论第七章
第七章汇编语言
到目前为止,我们已经有点厌倦了用1和0来编程,我们需要知道0001表示“加”;1001表示“非”。
如果我们采用一些有意义的符号表示“地址”,而不用去记忆它的16位地址,这岂不是更好吗?
并且如果我们能够用一些易于理解的方式来代替那些指令,而无需记住各个指令的二进制表示传达了什么信息,这该是多么好的一件事啊!
事实上,已经有了这样的机制。
本章里,我们将介绍汇编语言,它不仅实现了上述目标,还扩展了一些功能。
7.1汇编语言程序设计——向上一个层次
回忆一下我们在第一章的1.6节中提到的层次的转换。
我们把算法转化成用机械语言描述的程序。
这些机械语言,可以是像我们在第五章中所学习的特定计算机上的机器语言。
回忆一下,如果一个程序的每个指令都是来源于该计算机的指令集结构(ISA),那么这个程序就是用计算机的机器语言来编写的。
另一方面,机械语言的用户友好程度也可以更高一些。
我们通常把机械语言分成两个级别:
高级语言与低级语言。
二者之中,高级语言要比低级语言的用户友好程度更高。
例如C++、C、Fortran、COBOL、Pascal,加起来超过一千种之多。
在高级语言中几乎所有的指令(也不尽然)都类似于自然语言,如英语。
高级语言倾向于独立于指令集结构(ISA)。
这就是说,一旦你学会如何用针对某个ISA上的C语言(或Fortran或Pascal)编程,对于其他ISA上的C语言(或Fortran或Pascal)编程来说,你只需要迈出一小步就可以了。
一个用高级语言编写的程序执行之前,它首先必须被翻译成适合于期望执行的那台特定计算机的ISA的程序。
通常,高级语言的一条语句可以表示为ISA上的几条指令。
在第十一章,我们将给大家介绍一种高级语言——C语言,并且在第十二章到第十九章,我们将给大家介绍C语言中各种语句和它们转换成相应的LC-3代码之间的关系。
但是,在本章,我们只从第五章的ISA往上移动一小步。
这一小步是从一个机器的ISA到该ISA的汇编语言,汇编语言是一种低级语言。
请不要混淆英语中低级语言的指令和语句两个单词。
通常每个汇编语言的指令指的是一条ISA的指令。
与高级语言不同的是,高级语言是独立于ISA的,而低级语言是要依赖于ISA的。
事实上,每一个ISA通常只有一套汇编语言。
汇编语言的目的是使程序设计的用户友好性比机器语言(即我们正在涉及的计算机的ISA)更强,但是,它同样能使程序员详细的控制计算机能执行的指令。
也就是说,我们既能控制计算机能执行的详细指令,又不必记住1001表示什么操作码,0001又是代表的什么操作码,或者在地址0011111100001010中存储的是什么,什么内容会被存储到0011111100000101中。
汇编语言让我们可以用一些便于记忆的符号表示操作码,例如ADD和NOT;用一些有意义的符号表示存储单元,例如用SUM和PRODUCT,而不再是16位地址来表示,这将使我们更容易区别用SUM和用PRODUCT表示的地址。
我们称其为“符号地址”。
我们将会看到,从第十一章开始,当我们向上移动了一大步到一种高级语言(如C语言)时,编写程序对用户来说将会更加的友好,但是使用高级语言会让我们放弃了对准备执行的详细指令的精确的控制。
7.2一个汇编语言程序
我们使用一个例子来学习LC-3汇编语言。
图7.1中的这个程序用自加6次实现把一个整数乘以6。
举个例子,如果这个整数是123,程序将会通过完成123+123+123+123+123+123,计算出结果。
这个程序由21行代码构成。
我们已经给每行代码添加了“行号”来使我们能容易地找到每一行。
这是个普遍的做法。
这些行号不是程序的一部分。
以分号开头的共10行,表明它只是为了让人阅读的目的,稍后再讲。
06,07,08,0C,0D,0E和10这7行是汇编语言指令,将被翻译成LC-3的机器语言指令,是当程序运行时真正被执行的指令。
剩下的4行05,12,13和15包含了伪操作,是程序员为翻译程序给出的有助于翻译过程的消息。
翻译程序被称为汇编器(本书中称为LC-3汇编器),翻译过程被称为汇编。
01;
02;用6乘以一个整数的程序。
03;执行之前,该整数存储于NUMBER中。
04;
05.ORIGx3050
06LDR1,SIX
07LDR2,NUMBER
08ANDR3,R3,#0;R3清零,它将
09;包含乘积。
0A;内部循环
0B;
0CAGAINADDR3,R3,R2
0DADDR1,R1,#-1;R1跟踪
0EBRpAGAIN;循环次数
0F;
10HALT
11;
12NUMBER.BLKW1
13SIX.FILLx0006
14;
15.END
图7.1一个汇编语言程序
7.2.1指令
汇编语言的指令不再是LC-3的ISA中的0和1的组合,在汇编语言中,一条指令包括四个部分,如下所示:
标记(LABEL)操作码(OPCODE)操作数(OPERANDS);注释(COMMENTS)
其中的两个部分(标记和注释)是可选的,稍后会讲。
操作码和操作数
这两个部分是必不可少的。
一条指令必须有一个操作码(这条指令是做什么的),还要有适当数目的操作数(对谁进行操作)。
不必感到惊讶,这正是我们在第五章学习LC-3ISA所遇到的。
对于对应的LC-3指令的操作码来说,OPCODE只是一个符号名。
记住符号名ADD,AND和LDR所表示的操作要比记住4位的0001,0101或0110要容易的多。
图5.3(图A.2同样)列出了15种LC-3指令的OPCODE。
从526页到541页显示了15种指令所对应的汇编语言的表示。
操作数的数目取决于正在执行的操作。
例如,ADD指令(0C行)需要3个操作数(两个获取加数的源操作数,和一个被指定用来存放结果的目标操作数)。
3个操作数必须在该指令中被明确的标识出来。
AGAINADDR3,R3,R2
从寄存器2和寄存器3获得的操作数被相加,结果被放在寄存器3中。
我们用R0,R1,…,R7来表示寄存器0到7。
LD指令(07行)需要2个操作数(被读取的数值所在的存储单元,和指令执行结束后,包含那个数值的目标寄存器)。
我们看到该存储单元通过称为标记的符号地址给出。
在此,被读取的数值所在的存储单元被标记NUMBER给出。
那个数值的目标是被加载进寄存器2。
LDR2,NUMBER
正如我们在5.1.6节中讨论的,操作数可以通过寄存器、存储器或指令中的字面值(即立即数)获得。
在寄存器操作数的情况下,寄存器被明确的表示出来(如0C行中的R2和R3)。
在存储器操作数的情况下,存储器的符号名称被明确的表示出来(如07行中的NUMBER和06行中的SIX)。
在立即数操作数的情况下,实际的值被明确的表示出来(如08行中的数值0)。
ANDR3,R3,#0;R3清零,它将包含乘积。
一个字面值必须包含一个表明该数的基的符号。
我们用“#”表示十进制,用“x”表示十六进制,用“b”表示二进制。
有时,例如3F0A的值不存在含糊性,它表示十六进制。
虽然如此,我们仍把它写作x3F0A。
有时,例如1000的值存在含糊,x1000表示十进制数4096,b1000表示十进制数8,而#1000表示十进制数1000。
标记
标记是程序中用来明确的标识存储单元的符号名。
在LC-3汇编语言中,标记由1到20个字母、数字(即字母表中的大、小写字母,或十进制数位)组成,并且以字母表中的字母开头。
NOW,Under21,R2D2和C3PO都是LC-3汇编语言的标记的例子。
使用标记表示存储单元的原因有两个:
1、该单元为某个分支指令的目标(如0C行中的AGAIN);
2、该单元包含的是某个被加载或存储的值(如12行中的NUMBER,13行中的SIX)。
单元AGAIN被0E行的分支指令所引用。
BRpAGAIN
如果ADDR1,R1,#-1的结果是正数(通过P条件码被设为1而证实),那么程序跳转到被AGAIN明确标识的存储单元,从而执行又一次的循环。
单元NUMBER被07行的加载指令所引用。
存储在NUMBER被明确标识的存储单元中的值,被加载到R2中。
如果某个单元在程序中未被引用,那么就不需要为它设一个标记。
注释
注释只是给人看的信息。
对于翻译过程没有影响,事实上,LC-3汇编器对它不起作用。
在程序中,注释通过分号被标识出来。
分号表示该行的分号后面的部分是注释,被汇编器忽略。
如果分号是某行的第一个非空字符,那么整行都被忽略。
如果分号位于一条指令的操作数之后,那么,只有注释被汇编器忽略。
注释的目的是为了让程序更好的被读者理解。
它们帮助解释一条或一组指令的非直观上的表象。
在08行和09行,注释“R3清零,它将包含乘积”,让读者知道08行的指令是在计算两个数的乘积之前先对R3初始化。
08行的目的对于今天的程序员可能是明显的,但是如果两年后,在该程序员又写了3万行代码后,他/她不可能记得为什么写下ADDR3,R3,#0。
另一种可能的情况是,两年后,该程序员不在该公司工作了,而公司为了产品的升级,需要修改程序,如果这个任务被指派给一个以前没看过这段代码的人,注释对于提高理解力大有帮助。
做注释不是重申一个显而易见的表象,重要的是提供补充的理解。
这有2个原因,第一,重申一个显而易见的表象的注释,是浪费人的时间,第二,重申一个显而易见的表象,会给程序增加混乱,而使那些重要的注释不明显了。
例如,0D行,如果注释“R1减1”就是个坏主意。
它不能为指令提供补充的理解,只是增加的页面的混乱。
注释的另一个目的,也就是在一行中明智的使用空格,可以使程序的可视化的表现更易于被理解。
例如,注释可以被用来将程序分割为一个个片断,使程序更具可读性。
也就是说,为了计算出某一个结果而一起工作的代码行被放在连续的行中,而产生不同的结果的程序片断相互分隔开来。
例如,0C到0E行,被0B和0F两行与其他代码分隔开,在0B和0F两行除了分号什么都没有。
被汇编器忽略的空格也为程序对齐提供了机会,从而更易读。
例如,所有操作码都开始于页面中的相同的列。
7.2.2伪操作(汇编指令)
LC-3汇编器把输入的字符串,作为一个使用LC-3汇编语言写的计算机程序,并将其翻译为LC-3的ISA程序。
伪操作有助于汇编器实现其任务。
实际上,对于伪操作的更正式的名字是汇编指令。
之所以被称为伪操作,是因为它们不是在程序执行过程中被执行的操作,相反地,伪操作只是在汇编过程中,为了帮助汇编器而发给汇编器的信息。
一旦汇编器处理了这个信息,伪操作就被抛弃掉。
LC-3汇编器包括5个伪操作:
.ORIG,.FILL,.BLKW,.STRINGZ和.END。
所有的都把“点”作为第一个字符,可以容易的被识别出来。
.ORIG
.ORIG告诉汇编器将LC-3程序放在存储器的什么地方。
在05行,.ORIGx3050表示,开始于单元x3050。
结果,LDR1,SIX指令就被放进单元x3050。
.FILL
.FILL告诉汇编器在程序中留出一个单元,并将它初始化为操作数中的值。
在13行,结果是LC-3程序的第9个单元被初始化为值x0006。
.BLKW
.BLKW告诉汇编器在程序中留出一定数目的连续的存储单元(即字块)。
实际的数目就是.BLKW伪操作的操作数。
在12行,伪操作指示汇编器在存储器中留出一个单元(顺便提一下,这个单元被标记为NUMBER)。
伪操作.BLKW在操作数的实际值未知时特别有用。
例如,一个人想在存储器中留出一个单元,以便存储从键盘输入的某个字符。
直到程序运行之前,我们都不会知道按键的内容。
.STRINGZ
.STRINGZ告诉汇编器将一个连续的n+1个存储单元进行初始化。
参数是两个双引号之间的连续的n个字符。
存储器的前n个字被字符串中的字符对应的经零扩展的ASCII码初始化。
存储器的最后一个字被初始化为0。
最后一个字符是x0000,为处理ASCII码字符串提供了一个方便的标记。
例如,代码片断
.ORIGx3010
HELLO.STRING“Hello,World!
”
结果是汇编器把x3010到x301D初始化为以下的值:
x3010:
x0048
x3011:
x0065
x3012:
x006C
x3013:
x006C
x3014:
x006F
x3015:
x002C
x3016:
x0020
x3017:
x0057
x3018:
x006F
x3019:
x0072
x301A:
x006C
x301B:
x0064
x301C:
x0021
x301D:
x0000
.END
.END告诉汇编器程序在哪里终止。
在.END后出现的任何字符都不会被汇编器使用。
注意:
.END在执行时并不会停止程序。
事实上,在运行的时候它甚至都不出现。
它仅仅是一个简单的分界符——标志一个源程序的结束。
7.2.3例子:
回顾的5.5节的计算字符出现次数的例子
现在我们准备做一个完整的例子。
再次回顾在5.5节中出现的例子。
我们希望写一个程序,它能查找出在文档中的从键盘输入的字符出现的次数。
像以前一样,我们先通过构造流程图,研究出算法。
回忆在6.1节我们显示了如何系统的分解问题,从而创建一个如图5.16的流程图。
事实上在第六章的最后一个环节是图6.3e,其本质和图5.16相同。
下一步,我们用流程图来写真正的程序。
这次我们不用再担心0和1的繁琐而用LC-3汇编语言来写程序。
程序如图7.2所示。
有关程序的一些说明:
在这个程序中有三次需要操作系统的服务调用的协助。
每一次都使用trap命令。
TRAPx23从键盘输入一个值,并把该字符放到R0中(0D行)。
TRAPx21把在R0中的ASCII码显示到显示器上(在28行)。
TRAPx25使机器停止(在29行)。
我们曾经说过,我们将在第9章中详细介绍TRAP命令的实现。
01;
02;用于计算一个字符在文档中出现次数的程序。
03;字符由键盘输入。
04;结果显示在显示器上。
05;仅当一个字符的出现次数不多于9时,程序正常运行。
06;
07;
08;初始化
09;
0A.ORIGx3000
0BANDR2,R2,#0;R2是计数器,初始化为0
0C LDR3,PTR;R3是字符的指针
0DTRAPx23;R0获取输入的字符
0ELDRR1,R3,#0;R1取得下一个字符
0F;
10;检验字符,否到达文件的末尾
11;
13TESTADDR4,R1,#-4;检验EOT
14BTzOUTPUT;如果完成,准备输出
15;
16;检验字符是否匹配,如果匹配,计数器加1
17;
18NOTR1,R1
19ADDR1,R1,R0;如果匹配,R1=xFFFF
1ANOTR1,R1;如果匹配,R1=x0000
1BBRnpGETCHAR;如果不匹配,不加1
1CADDR2,R2,#1
1D ;
1E ;从文档中取得下一个字符.
1F ;
20GETCHARADDR3,R3,#1;指针加1
21LDRR1,R3,#0;R1取得下一个检验的字符
22BRnzpTEST
23;
24;输出结果
25;
26OUTPUTLDR0,ASCII;加载ASCII模板
27 ADDR0,R0,R2;将二进制转换为ASCII码
28TRAPx21;R0中的ASCII码被显示
29TRAPx25;停止机器
2A;
2B;存储指针和ASCII码模板
2C;
2DASCII.FILLx0030
2EPTR.FILLx4000
2F .END
图7.2计算字符出现次数的汇编语言程序
十进制数0到9(0000到1001)的ASCII码是x30到x39,从二进制转变为ASCII码只需将该十进制数的二进制表示加上x30即可。
2D行显示了标记ASCII被用来标识包含x0030的存储单元。
被测试的文档起始于地址x4000(2E行)。
通常,因为我们希望对文件操作的程序在将来可用,所以写这个程序员的程序员不会知道起始地址,这种情况将在7.4节中说明。
7.3汇编过程
7.3.1介绍
在一个LC-3汇编语言程序被执行前,它首先必须被翻译成机器语言程序,也就是说,每一个指令都是LC-3ISA中的指令。
LC-3汇编器的工作就是进行这样的翻译。
如果你有一个LC-3汇编器,你就能通过执行一个适当的命令,使它把你的汇编语言程序翻译成机器语言程序。
LC-3汇编器通常可通过网络获得,其命令是assemble,需要你编写的汇编语言程序的文件名作为参数之一。
举个例子,如果文件名是solution1.asm,那么
assemblesolusion1.asmoutfile
将生成LC-3ISA上的文件outfile。
有必要从你的老师处得到正确的命令,使LC-3汇编器能生成一个LC-3ISA中的0和1组成的文件。
7.3.2一个“两趟”的过程
在这一节,我们将看到汇编器是怎样把一个汇编语言程序翻译成机器语言程序的,我们使用图7.2的汇编语言程序作为处理过程的输入。
回想一下,在汇编语言程序的指令和机器语言程序的指令之间一般有一个“一一对应”的关系。
我们试图通过扫描汇编语言程序,实现这种翻译。
从图7.2的顶部开始,汇编器抛弃掉01到09行,因为它们只包含注释。
注释只是为人使用,与翻译过程无关。
然后,汇编器移到0A行,0A行是个伪操作,它告诉汇编器,该机器语言程序开始于地址x3000。
然后,汇编器移到0B行,它可以容易的翻译为LC-3机器指令。
到这儿,我们有
x3000:
010*********
LC-3汇编器继续移动,翻译下一个指令(0C行),不幸的是,因为它不明白符号地址PTR的意思,所以无法翻译。
在此,汇编器受到打击,汇编过程失败。
为了防止这种情况的发生,汇编过程需要“两趟”完整的对整个汇编语言程序的扫描(从开始到.END)。
“第一趟”扫描的目的是标识出符号名(或标记)对应的真实的二进制地址。
这个对应的集合就是符号表。
在“第一趟”扫描中,我们建立符号表,在“第二趟”扫描中,我们把各条汇编语言指令翻译成相应的机器语言指令。
这样,在“第二趟”扫描时,当汇编器检查0C行时,翻译
LDR3,PTR
它已经知道PTR和x3013之间存在对应关系。
这样,它可以容易的翻译0C行为
x3001:
0010011000010001
不知道PTR对应的16位地址的问题不再存在。
7.3.3第一趟:
构建符号表
对于我们的目的,符号表仅仅是符号名称和16位存储地址相对应的关系。
通过扫描汇编语言程序一趟,我们可获得这些对应关系:
标记哪个指令被分配到哪个地址,用分配的地址来标识每一个标记。
回忆我们在必须引用存储单元的地方提供标记的情况,要么是因为它是一个分支指令的目标,要么是因为它包含一个准备加载或存储的数据。
因此,如果我们没有任何程序错误,当我们能识别出所有的标记时,我们就能识别所有的在程序中用到的符号地址。
以上是假定我们的整个程序存在于.ORIG和.END这两个伪操作之间,这对于7.2节的程序是成立的。
在7.4节,我们将考虑包含多个部分的程序,每一个都有自己的.ORIG和.END,每一部分分别被汇编。
第一趟扫描开始,在抛弃掉01到09行的注释后,在0A行注意到第一条指令将被分配到地址x3000,我们用地址计数器LC记录分配给每一个指令的地址,地址计数器被初始化为.ORIG中说明的地址,即x3000。
汇编器按顺序检查每一条指令,每检查一条汇编语言指令,LC就增加一次。
如果检查的指令里包含标记,符号表中就会有一条针对该标记的记录,把LC里的内容作为它的地址。
当遇到.END指令时,第一趟扫描就结束了。
包含标记的第一条指令在13行。
因为它是程序中的第五条指令,LC在这一点包含x3004,所以一条符号表的纪录如下:
符号
地址
TEST
x3004
包含标记的第二条指令在20行,此时LC已经增加到x300B,所以产生符号表的一条纪录,如下:
符号
地址
GETCHAR
x300B
第一趟扫描后,符号表的记录如下:
符号
地址
TEST
x3004
GETCHAR
x300B
OUTPUT
x300E
ASCII
x3012
PTR
x3013
7.3.4第二趟:
生成机器语言程序
第二趟扫描是在符号表的帮助下,第二次一行行的遍历汇编语言程序。
在每一行,汇编语言指令都被翻译成一条LC-3机器语言指令。
再次从顶部开始,汇编器再次抛弃掉01到09行,因为它们只包含注释。
0A行是.ORIG伪操作,被汇编器用来初始化LC为x3000。
汇编器移到0B行,生成机器语言指令010*********,然后,汇编器移到0C行。
这时,当汇编器到达0C行,就可以汇编该指令了,因为它知道PTR对应于x3013。
这个指令是LD,它的操作码是0010,目标寄存器(DR)是R3,即011。
PCoffset计算如下:
我们知道PTR是地址x3013的标记,增加了1的PC是LC+1,在此是x3002。
因为PTR(x3013)一定是增加了1的PC(x3002)与经过符号扩展的PCoffset的和,所以PCoffset一定是x0011。
把这些放在一起,x3001就被设置成0010011000010001,LC被递加为x3002。
注意:
为了使用LD指令,加载的源地址,在这个例子中是标记为PTR的地址,必须不得超过当前LD指令的+256或-255个存储单元的地址。
如果PTR的地址比LC+1+255大,或比LC+1-256小,那么,偏移量无法在指令的[8:
0]位表示。
那样,一个汇编错误将会产生,阻止汇编过程成功的完成。
幸运的是,PTR足够的靠近LD指令,所以指令可以被准确的汇编。
第二趟扫描继续进行。
在每一步,LC加1,同时它所指的单元被分配为翻译的LC-3指令,或者,如果是.FILL的情况,则分配为标明的值。
当第二趟扫描遇到.END指令时,汇编结束。
最终的翻译得到的程序在图7.3中。
然而这个过程,即使在不错的一天里也让人感到厌烦。
很幸运,你不必亲自去做,留给LC-3汇编器去做。
现在,既然你已经了解了LC-3汇编语言,那就没有再用机器语言写程序的必要了。
我们可以把我们的程序用符号性的LC-3汇编语言写出来,然后求助于LC-3汇编器,让它把汇编语言程序转化成能在LC-3计算机上运行的机器语言即可。
7.4超过一个的汇编语言程序
我们学习这一章的目的是让你从计算机的ISA向上一层,介绍汇编语言。
尽管现在离C语言或C++还很远,然而汇编语言已减轻了我们不少的痛苦。
我们已经讲了一个基本的“两趟”汇编器如何把汇编语言程序转化成一个LC-3的ISA的机器语言程序。
复杂的汇编语言编程有很多方面是我们的介绍课程未涉及的。
然而,我们教授汇编语言的目的不是涉及它的复杂性,而是让大家看到它的简明易懂。
在我们结束这一章以前,还有一些附加的部分是我们要思考的。
7.4.1可执行映像
当计算机开始一个程序的执行时,被执行的实体叫做可执行映像。
可执行映像是被几个不同的程序员独立创造出来的模块形成的。
每一个模块
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 计算机系统 概论 第七