第九章-自陷程序和子程序.doc_第1页
第九章-自陷程序和子程序.doc_第2页
第九章-自陷程序和子程序.doc_第3页
第九章-自陷程序和子程序.doc_第4页
第九章-自陷程序和子程序.doc_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第九章 自陷程序和子程序9.1 LC-3自陷程序9.1.1 介绍 回忆先前一章的图8.5的程序,为了成功地获得从键盘输入,程序员需要知道以下几件事情(第8章):1、键盘与显示器的硬件数据寄存器:显示器的数据寄存器是为了能够显示一个提示符,而键盘数据寄存器则是让程序知道到哪儿去寻找输入的字符。2、键盘与显示器的硬件状态寄存器:显示器的状态寄存器是为了让程序知道什么时候可以显示提示符中的下一个字符,而键盘状态寄存器则让程序知道什么时候有人键入了一个字符。3、键盘输入和执行程序之间的异步关系。 这是大多数应用程序员不知道的知识。实际上,在现实中,如果应用程序员(或有时称为“用户程序员”)必须在这个层面上理解输入与输出,那么在商业上将会很少运用输入/输出,程序员也会大大减少。 另外,如果允许用户程序员直接访问KBDR和KBSR等来实现输入/输出的行为,将会造成另外一个问题出现。输入/输出行为包含了被许多程序所共享的设备寄存器的使用,这就意味着,如果一个用户程序员被允许访问硬件寄存器,他/她没有谨慎处理,这会给其他用户程序制造混乱。这样,让用户程序员访问这些寄存器是不明智的。我们说硬件寄存器是有特权的,那些不拥有适当特权级别的程序是不能访问它们的。特权的概念带来了一大堆麻烦。不幸的是,我们在这里不能做更多的涉及,把它留给以后做更认真的处理。现在,我们只是注意到这里有用户程序不能访问的资源,只有那些被赋予足够特权的程序才可以控制它们,而没有特权的程序则不可以。说完这些,我们继续手头上的问题,如何“更好”的解决需要输入和/或输出的用户程序。一个更简单同时也是更安全的解决需要I/O的用户程序问题的方案包括自陷(TRAP)指令和操作系统。操作系统拥有适当的特权级别。我们已经在第5章介绍了TRAP指令。我们看到,在某些任务中,用户程序通过调用TRAP指令使操作系统做这个工作。这样,用户程序不必要知道前面提到的复杂的细节,并且其他用户程序也会被保护起来,避免用户程序员的不恰当行为的后果。图9.1显示了一个用户程序在到达地址x4000时,需要执行一个输入输出任务。用户程序请求操作系统代表它完成这个任务。操作系统控制机器,处理TRAP指令指定的请求,然后把控制权返还给用户程序。我们经常把这个用户程序的请求称为服务调用或系统调用。9.1.2 TRAP机制 TRAP机制包括一些要素,如下:1、 一组由操作系统代表用户程序去执行的服务程序。它们是操作系统的一部分,在存储器中的起始地址是任意的。LC-3被设计为总共可以识别256个服务程序。附录A中的表A.2包含了LC-3现有的操作系统服务程序的完整列表。2、 这256个服务程序的起始地址的一张表。这张表被存储在存储单元的x0000到x00FF中。不同的公司对这张表有不同的命名,一家公司叫它系统控制块,另一家公司叫它Trap向量表。图9.2提供了一个LC-3的Trap向量表的瞬态图。在这些起始地址中,字符输出服务程序(单元x0430)包含在单元x0021中;键盘输入服务程序(单元x04A0)包含在单元x0023中,还有,停机服务程序(单元xFD70)包含在单元x0025中。3、TRAP指令。当用户程序希望让操作系统代表用户程序执行某一个服务程序,然后把控制权交还给用户程序时,用户程序使用TRAP指令。4、返回用户程序的一个链接。服务程序必须有一种可以把控制权交还给用户程序的机制。9.1.3 TRAP指令TRAP指令通过做两件事实现服务程序的执行:l 它根据它的trap向量,改变PC的值为相应的服务程序的首地址。l 它提供了一个返回调用TRAP指令的程序的路径。这个返回路径指的是链接。TRAP指令说明如下。TRAP指令由两部分组成:TRAP的操作码1111和trap向量(7:0位)。11:8 位必须为0。trap向量标识了用户程序希望操作系统执行的程序。在下面的例子中,trap向量是x23。15141312111098765432101111000000100011TRAPTRAP向量在TRAP指令的指令周期的执行阶段,做4件事:1、 8位的trap向量通过零扩展到16位而形成一个地址,该地址被加载到MAR。对于trap向量x23,地址就是x0023,它是TRAP向量表中的一条记录的地址;2、 TRAP向量表位于存储单元x0000到x00FF中。位于x0023中的纪录被读取,它的内容是x04A0(如图9.2),被加载到MDR中;3、 通用寄存器R7被加载为PC中的当前内容。这会给用户程序提供一个返回路径,这一点马上就会变得更清楚;4、 MDR的内容被加载到PC中,完成这个指令周期。由于PC现在包含了x04A0,所以处理从存储单元x04A0继续下去。地址x04A0是从键盘输入一个字符的操作系统服务程序的起始地址。我们说trap向量指向TRAP程序的起始地址。因此,TRAP x23使操作系统开始执行键盘输入服务程序。为了返回到位于用户程序中的TRAP指令之后的指令(在执行完服务程序之后),必须有一个机制保存用户程序的下一条指令的地址。在上面列出的执行阶段的第3步提供了这个链接。在把服务程序的起始地址加载到PC之前,通过在R7中保存PC的值,TRAP指令为服务程序提供了将控制返回到用户程序的适当单元的全部信息。你知道,PC已经被更新(在TRAP指令的取指令阶段),指向下一条指令。这样,TRAP服务程序开始执行,R7包含了紧跟在TRAP指令之后的用户程序指令的地址。9.1.4 完整的机制我们已经详细的显示了TRAP指令如何调用服务程序来实现程序的命令,我们也显示了TRAP指令如何提供服务程序需要的将控制返回到用户程序的正确位置的信息。剩下的唯一的事就是显示将控制返回到用户程序的正确位置的服务程序中的真正的指令。回忆第5章的JMP指令。假设在TRAP服务程序执行的过程中,R7的内容未被改变。如果是那样的话,通过在TRAP服务程序的最后执行一条JMP R7指令,控制就可以返回到用户程序的正确位置。图9.3显示了LC-3使用TRAP和JMP指令来完成图9.1的例子。控制流从一个需要从键盘输入字符的用户程序开始(A),到代表用户程序执行此任务的操作系统服务程序(B),再返回到可能会使用在输入字符中包含的信息的用户程序(C)。回忆计算机继续执行指令周期(取指令,译码等),正如你所知道的,改变控制流的方式就是在当前指令的执行阶段,改变PC中的内容。这样,下一次取指令阶段读取的将是改变了方向的地址。因此,要调用字符输入服务程序,我们调用使用了trap向量x23的TRAP指令。该指令的执行使存储单元x0023的内容(在这个例子里包含了x04A0)被加载进PC,同时紧跟在TRAP指令之后的指令地址被加载到R7中。图9.3中的虚线显示了使用trap向量从trap向量表中获得TRAP服务程序的起始地址。下一个指令周期从取得x04A0的内容开始,也就是请求(接受)键盘输入的操作系统服务程序的第一条指令。我们马上就看到的那段服务程序,跟我们在8.4节中学过的键盘输入程序是一样的。回忆那一段完整的输入程序(图8.5),R0包含了那个键入的键的ASCII码。TRAP服务程序以JMP R7指令结束。JMP R7指令的执行,将PC加载为R7的内容。如果R7在执行服务程序期间没有改变,它将会包含原始的用户程序中紧跟着TRAP指令之后的那条指令的地址。因此,用户程序继续执行,且R0包含了从键盘输入的字符的ASCII码。JMP R7指令对于返回用户程序提供了如此的便利,以至LC-3汇编程序为这条指令提供了一个助记符RET,如下所示:15141312111098765432101100000111000000RET下面的程序被用来说明TRAP指令的使用。它也可以被用来逗一个4岁的孩子玩!例9.1写一个做如下事情的游戏程序:一个人坐在键盘前,每次这个人键入一个大写字符,程序输出该字符的小写。如果此人键入了一个7,程序结束。下面的LC-3汇编程序作了该工作。01 .ORIGx3000 02 LDR2, TERM; 加载 -703LDR3,ASC;加载ASCII 码的差值04AGAINTRAPx23;请求键盘输入05 ADDR1, R2, R0;测试是否是终止字符06 BRzEXIT 07 ADDR0, R0, R3;改变为小写字母08 TRAPx21;输出到显示器09BRnzpAGAIN; 再做一次0ATERM.FILLxFFC9; xFFC9是7的ASCII码的负数0BASC .FILLx0020 0CEXIT TRAPx25; 停机0D .END程序执行如下。首先它分别将xFFC9和x0020读入R2和R3。常数xFFC9是7的ASCII码的负数,它是用来测试键盘上输入的字符,看这个四岁的孩子是否想继续玩。常数x0020是一个大写字母与其小写表示的ASCII码之间的差的零扩展。例如,A的ASCII码是x41而a的ASCII码是x61。Z和z的ASCII码分别是x5A和x7A。然后执行TRAP x23,它调用键盘输入服务程序。当服务程序结束,控制返回应用程序(在05行)时,R0包含了键入字符的ASCII码。ADD和BRz指令来测试是否是终止字符7。如果键入的字符不是7,大小写的ASCII码的差(x0020)将被加到键入字符的ASCII码上,并将结果保存到R0。然后调用显示器输出服务程序,这使得同一字母的小写被显示到屏幕上。当控制返回应用程序(这次在09行)时,将执行无条件分支转移到AGAIN,请求另一个键盘输入。在这个例子中,程序的正确运行是假设坐在键盘前的人只输入大写字母和7。但如果他输入“$”呢?例子9.1的一个更好的解决方案是能够测试输入的字符,确定它是否是字母表中26个字母的大写形式,如果不是,则采取适当的操作。问题:将测试错误数据增加到上面的程序中。也就是说,写一段可以将键入的任意大写字母以小写表示,并且如果输入字符不是大写字母,则终止执行的程序(习题9.6)。9.1.5 处理I/O的TRAP程序利用上面提供的概念,图8.5描述的输入程序可以做稍微修改,成为图9.4的输入程序。需要两处改变:(1)我们添加了.ORIG和.END伪操作。.ORIG说明了输入服务程序的起始地址在trap向量表的地址x0023中发现的地址,(2)我们使用JMP R7(助记符RET)结束这个输入服务程序,而不是使用图8.5的20行的BR NEXT_TASK。因为这个服务程序通过trap x23调用,所以使用JMP R7。这不是用户程序的一部分,与图8.5的情况相同。8.3.2节的输出程序可被做类似的修改,如图9.5所示。结果就是使用恰当的TRAP向量的TRAP指令,可以简单、安全的调用输入(图9.4)和输出(图9.5)服务程序。在输入的情况下,在完成TRAP x23后, R0中包含了从键盘输入的字符的ASCII码。在输出的情况下,初始程序必须将要想在显示器上显示的字符的ASCII码加载入R0,然后再调用TRAP x21。9.1.6 停机的TRAP程序回想一下,在4.5节中,运行锁通过与石英振荡器做“与”操作,产生用来控制计算机运行的时种。我们注意到如果那个1位的锁被清空,那么与门的输出就是0,这样就停止了时钟。多年前,大多数的ISA都有一条HALT指令来停止时钟。已知此命令被执行的频率非常低,看起来为它提供一个操作码很浪费。在很多现代计算机中,运行锁的清空是用TRAP程序来实现的。在LC-3中,运行锁是机器控制寄存器的15位,机器控制寄存器被存储映射到单元xFFFE。图9.6显示了停止处理器的TRAP服务程序,也就是停止时钟的程序。首先(02,03和04行),寄存器R7、R1和R0都被保存。R0和R1被保存是因为它们在服务程序中要被用到,R7被保存是因为它的内容会在执行TRAP x21(09行)后被覆盖。然后(08行到0D行),“Halting the machine”(停止机器)这个标识被显示在显示器上。最后(11行到14行),运行锁(MCR15)通过对MCR与0111111111111111做“与”操作而被清空。也就是说MCR14:0保持不变,但是MCR15被清空。问题:什么指令(或是TRAP服务程序)可以使时钟开始?01.ORIGxFD70; 程序的位置02STR7, SaveR7 03ST R1, SaveR1; R1:临时存储MCR的值04STR0, SaveR0; R0用作工作空间05;06 ; 输出停机的消息07 ;08LDR0,ASCIINewLine09TRAPx210ALEAR0, Message0BTRAPx220CLDR0,ASCIINewLline0DTRAPx210E;0F; 清空xFFFE的15位,停机10;11LDIR1, MCR; 加载MCR的值到R1中12LDR0,MASK; R0=x7FFF13ANDR0, R1, R0; 用屏蔽清空最高位14STIR0, MCR; 将R0的值存储到MCR中15;16;从HALT程序返回17;18;19LDR1,SaveR1;恢复寄存器1A LDR0, SaveR01BLDR7, SaveR7 1CRET1D;1E; 一些常数1F;20ASCIINewLine.FILLx000A21SaveR0.BLKW122 SaveR1.BLKW123SaveR7.BLKW124Message.STRINGZ Halting the machine. 25MCR.FILLxFFFE; MCR的地址26MASK.FILLx7FFF; 清空最高位的屏蔽27.END图9.6 LC-3的停机服务程序9.1.7 寄存器的保存和恢复我们应该更加明确强调的我们曾经在过去提到的一点,就是需要保存寄存器中的值,1、 如果该值将被某个接下来的行为破坏,并且2、 如果当完成那个接下来的行为后,我们还需要用到那个值。假设我们想从键盘输入十个十进制数字,将它们的ASCII码转化成二进制表示,并且将这十个二进制数存进以地址“Binary”开头的十个连续的存储单元。下面的程序片段做个这个工作。01LEAR3,Binary;初始化为第一个单元02LDR6,ASCII;05行用到的模板03LDR7,COUNT;初始化为1004AGAINTRAPx23;获取键盘输入05ADDR0,R0,R6;去掉ASCII码模板06STRR0,R3,#0;存储二进制数07ADDR3,R3,#1;指针加108ADDR7,R7,#-1;计数器减109BRpAGAIN;还有更多字符?0ABRnzpNEXT_TASK;0BASCII.FILLxFFD0;x0030的负数0CCOUNT.FILL#100DBinary.BLKW #10这个程序片段的第一步是初始化。我们用为存储这10个十进制数而分配的存储器空间的起始地址来加载R3。我们将ASCII码模板的负数加载R6,这是为了从每一个ASCII码减去x0030。我们把计数器的初始值10存入R7。然后执行十次循环,每次从键盘上取一个字符,去除ASCII码模板,储存二进制结果,并检验我们是否做完。但是这段程序不能运行!为什么?答案:04行的TRAP指令将03行里加载到R7的值“10”替换为ADD R0,R0,R6指令的地址。所以08和09行的指令没有执行被安排做的循环控制功能。给我们的信息就是:如果一个寄存器内的值将在寄存器被存储了其他值之后再次用到,我们必须在其他事情发生之前将其保存,在我们接下来用它之前将其恢复。我们通过将寄存器存进存储器,而保存它的值;通过把它加载回寄存器而恢复它的值。在图9.6中,03行包含了用来保存R1的ST命令,11行包含了将实施TRAP服务程序工作的值加载到R1的LDI指令,19行包含了将R1恢复为在服务程序被调用之前的初始值的LD指令,22行则为保存R1,而在存储器中留出一个单元。保存/恢复问题可以通过:在TRAP执行之前由调用程序处理,或者在TRAP指令执行之后由被调用的程序(例如,服务程序)处理。我们将在9.2节中看到在另外一类调用/被调用程序中存在同样的问题,子程序机制。如果由调用程序处理这个问题,我们用术语caller-save(调用者保存)。如果由被调用的程序处理这个问题,我们用术语callee-save(被调用者保存)。哪个程序知道哪些寄存器将被接下来的操作所破坏,适合处理这个问题的就是哪一个。被调用程序知道需要哪些寄存器来完成它的工作。因而在它开始执行之前,它会用一系列存储指令保存这些寄存器中的值。在它完成以后,它又会用一系列的加载指令恢复那些寄存器的值。而且它还预留了一些存储单元来保存这些寄存器中的数据。在图9.6中,HALT程序需要使用R0和R1。因此它在03行和04行用ST指令将它们的值都保留下来,而在19行和1A行又用LD指令将它们的值恢复,它在21行和22行为这些值预留了存储单元。调用程序知道它控制下的指令将造成什么样的破坏。让我们再来看图9.6,调用程序知道每一种TRAP指令都会破坏R7中的数据。因此在HALT服务程序中的第一条TRAP指令被执行以前,它将保存R7,当HALT服务程序中的最后一条TRAP指令执行完毕后,R7被恢复。9.2 子程序 我们刚才已经看到了,如果程序员不需要去学习I/O 硬件的细节,而能够依赖于操作系统所提供的程序片段来完成这些任务,他们的效率将能够大大的得到提高。而且我们在过去也提到,使操作系统来访问那些设备寄存器,而使我们不必受其他程序员的支配是一件非常美妙的事情。我们看到在用户的程序中通过调用TRAP指令来请求服务程序,从而使操作系统来处理。通过JMP R7指令重新回到原来的程序。类似的,能够在同一程序内多次调用一个程序段,而每次需要时不必说明其源程序的全部细节,通常是很有用的。另外,有时一个人写一个需要程序片断的程序,而另一个人写那些程序片断。一个人也可能需要一段由厂商或某个独立的软件供应商提供的片断。通常那些提供的程序片断的集合让用户程序员不用自己去写。这个集合被称作库。例如数学库,包括执行诸如平方根、正弦、余切等函数的片断。因为这些原因,提供一个使用程序片断的方法是有益的。那些程序片断被称为子程序,或者换句话说是程序,或用C的术语是函数。使用它们的机制被称为调用/返回机制。9.2.1 调用/返回机制图9.4提供了一个在同一程序内多次执行一个程序片断的简单图解。注意起始于符号地址L1的3条指令,还要注意起始于地址L2、L3和L4的3条指令,这4个3条指令的序列,每一个都做如下工作:LABELLDIR3, DSRBRzpLABELSTIReg, DDR这4个程序片断中,有两个存储R0的内容,另外两个存储R2的内容,但是这点很容易解决,我们稍后就会看到。关键是,除了被用作STI指令的源寄存器是哪个有点小麻烦外,这4个程序片断做的是相同的工作。调用/返回机制允许我们把这个3条指令的序列作为一个子程序,子程序在我们的程序中只需包括一次,就多次执行这个指令序列。调用机制计算子程序的起始地址,加载到PC,保存返回地址以便返回调用程序的下一条指令。返回机制使用返回地址加载PC。图9.7显示了在程序中使用子程序和不使用子程序的指令执行流程。调用/返回机制与TRAP指令在将控制重定向到一个程序片断,同时保存返回到调用程序的链接这一点上,表现十分相似。二者都是,将PC加载为程序片断的起始地址,同时R7被加载为需要返回到调用程序的地址。程序片断的最后一条指令,无论是TRAP服务程序还是子程序程序片断,都是JMP R7指令,它将PC加载为R7的内容,因此将控制返回到跟在调用程序后面的一条指令。在子程序和被TRAP指令调用的服务程序之间有一点重要的不同之处。尽管这有点超出本课程的范围,我们将做个简单的介绍。这点与程序片断被要求做的工作的本质有关。在TRAP指令的情况下(我们曾经看到的),服务程序包括操作系统资源,它们通常需要访问计算机的底层硬件的特权;它们是由管理计算机资源的系统程序员写的。在子程序的情况下,它们要么是由写包含了调用指令的程序的相同程序员所写,要么是由某个同事所写,或者是被某个库的一部分所提供。而这两种情况所包括的资源都是不可以干扰其他人的程序的,所以它们可以成为某个用户程序的一部分。9.2.2 JSR(R) 指令LC-3规定了一个调用子程序的操作码,0100。该指令使用两种寻址模式,PC相对寻址,或基址寻址之一,来计算子程序的起始地址。取决于使用哪个寻址模式,LC-3汇编语言为此操作码提供了两个不同的助记符,JSR和JSRR。该指令做两件事:在R7中保存返回地址,计算子程序的起始地址并加载到PC。返回地址是增加了1的PC,它指向跟在调用程序中的JSR或JSRR指令后的一条指令。JSR(R)指令由三部分组成。1514131211109876543210操作码A计算地址位15:12位包含操作码,0100。11位表示寻址模式,如果寻址模式是PC相对寻址,其值为1;如果寻址模式是基址寻址,其值为0。10:0位包含被用作计算子程序的起始地址的信息。JSR和JSRR的唯一区别就是用作计算子程序的起始地址的寻址模式。JSRJSR指令通过将指令的11位的偏移量(10:0位)做符号扩展,并与增加了1的PC相加,从而计算出子程序的目标地址。这种寻址模式与LD和ST指令的寻址模式几乎相同,除了PC偏移量使用的是11位,而不是像LD和ST那样使用9位。如果下面这条JSR指令被存储在地址x4200,那它的执行会使PC加载为x3E05,R7中则加载Xx4201。15141312111098765432100100110000000100JSRAPCoffset11JSRRJSRR指令和JSR除了寻址模式不同外,其他完全相同。JSRR获得子程序的起始地址的方式与JMP指令完全相同,也就是说,它使用由指令的8:6位说明的寄存器中的内容。如果下面这条JSRR指令被存储在地址x420A,并且如果R5包含x3002,那么JSRR指令的执行会使R7加载为x420B,PC中则加载x3002。15141312111098765432100100000101000000JSRRABaseR 问题:什么是JMPR指令能够提供的而JSR指令无法提供的重要特点?9.2.3 重访字符输入的TRAP程序让我们再看一下图9.4的键盘输入服务程序。特别的,让我们看一下在符号地址L1,L2,L3和L4中都出现的三行的指令序列:LABELLDIR3, DSRBRzpLABELSTIReg, DDR JSR/RET机制能够让我们用一段简单的子程序来代替这四次出现的同一组指令序列吗?答案:是的,差不多可以。 在图9.8中,我们改进了键盘输入服务程序,在05,0B,11和14行包含:JSRWriteChar 在1D到20行是一段四条指令的子程序:WriteCharLDIR3,DSRBRzpWriteCharSTIR2, DDRRET 注意需要使用RET指令(实际上是JMP R7)结束子程序。 注意这个词:差不多。在原来的以L2和L3开头的原始指令序列中,STI指令将R0(并非R2)的内容传送给DDR。我们可以很容易的修改如下: 在图9.8的09行,我们使用LDRR2, R1, #0 代替LDRR0, R1, #0 这就使得提示符中的每个字符都被加载到R2。子程序WriteChar将每个字符从R2中传送给DDR。在图9.8的10行,我们插入一条指令ADDR2,R0,#0是为了将键盘的输入(在R0中)传送给R2。子程序WriteChar将它从R2传给DDR。注意,R0仍然包含着键盘的输入。此外,因为服务程序中后面的指令没有加载R0,所以当控制返回用户程序后,R0仍然包含键盘的输入。在图9.8的13行,我们插入指令LDR2,Newline是为了将字符“换新行”传送给R2。子程序WriteChar将它从R2传给DDR。 最后,我们注意到,与图9.4不同,这个TRAP服务程序包含了几条JSR指令。这样,包含在R7中的返回到调用程序的链接,在服务程序开始执行时,就早已被覆盖了(实际上在03行,被第一条JSR指令覆盖)。因此,我们在执行第一条JSR指令之前,在02行保存R7,并且在执行最后一条JSR指令之后,在16行恢复R7。图9.8是真正的用于键盘输入的LC-3的TRAP服务程序。01.ORIGx04A002STARTSTR7,SaveR7 03JSRSaveReg04LDR2,Newline05JSRWriteChar06LEAR1,PROMPT07;08; 09LOOPLDRR2,R1,#0; 得到下一个提示字符 0ABRzInput0BJSRWriteChar0C ADDR1,R1,#10DBRLOOP0E;0FInputJSRReadChar10ADDR2,R0,#0 ; 把字符送给R2准备回显到显示器11JSRWriteChar 12;13 LDR2,Newline14JSRWriteChar15JSRRestoreReg16LDR7,SaveR717RET;JMP R7结束TRAP程序18;19SaveR7.FILLx00001A Newline.FILLx000A1BPrompt.STRINGZInput a character1C;1DWriteCharLDIR3,DSR1EBRzpWriteChar1FSTIR2,DDR20RET;JMP R7结束子程序21DSR.FILLxFE0422DDR.FILLxFE0623;24ReadCharLDIR3,KBSR25BRzpReadChar26LDIR0,KBDR27RET28KBSR.FILLxFE0029KBDR.FILLxFE022A;2BSaveRegSTR1,SaveR12CSTR2,SaveR22DSTR3,SaveR32ESTR4,SaveR42FST R5,SaveR530 ST R6,SaveR631 RET32;33RestoreRegLD R1,SaveR134 LD R2,SaveR235 LD R3,SaveR336 LD R4,SaveR437 LD R5,SaveR538 LD R6,SaveR639RET3ASaveR1 .FILL x00003B SaveR2 .FILL x00003C SaveR3 .FILL x00003DSaveR4 .FILL x00003ESaveR5 .FILL x00003FSaveR6 .FILL x000040 .END图9.8 LC-3字符输入的TRAP服务程序9.2.4 PUTS:在显示器上输出一串字符在我们结束图9.8的例子之前,注意看09到0D行的编码。服务程序的这一片段是用来在显示器上显示一串字符:Input a character。一串字符通常被称为字符串。这一程序片段也在图9.6中出现,是用来在显示器上显示:Halting the machine.。实际上,一个用户程序要在显示器上显示一串字符是很常见的,所以在LC-3的操作系统中,为这种功能提供了的trap向量。因此,如果一个用户程序要求在显示器上显示一串字符,它只要提供一个(在R0中)该字符串的起始地址,然后调用TRAP x22指令。在LC-3汇编语言中这个TRAP指令被称为PUTS。这样,PUTS(或TRAP x22)把控制交给操作系统,图9.9所示的程序就被执行。注意PUTS就是把图9.8的09行到0D行的代码做了几个微小的调整。9.2.5 库程序在本节开始,我们注意到调用/返回机制有许多用途,其中包括用户程序能够调用通常作为计算机系统一部分的库子程序。库作为一种便利提供给用户程序员。它们允许用户程序员不需要了解其内部细节也可使用它们,因此被合理的宣传为“生产力提高者”。例如,某个用户程序员清楚平方根(我们简写为SQRT)的含义,需要用sqrt(x)求出某个值x平方根,但是他并不知道如何编写一段程序实现它,可能也不想知道怎么做。一个很简单的例子可以用来说明这点。我们丢失了钥匙但我们又想进入公寓。房间的窗户开着,它离地面有24英尺。我们可以斜放一个梯子靠在墙上,让它搭在窗户的下沿。沿着墙角有一个10英尺的花圃,所以我们必须让梯子的底部位于花圃的外面。我们需要多长的梯子斜靠在墙上,才能通过窗子爬进公寓呢?或者说得直白点,如果一个直角的两条边分别为24英尺和10英尺,则斜边为多长?(见图9.10)我们还记得在中学时毕达哥拉斯的答案是:知道了a和b,我们就可以很容易解出c,c为a2和b2的和的平方根。计算和并不难LC-3的ADD指令会做这个工作。平方也不难,通过做一系列加法,我们就可以乘两个数。但是我们如何算出平方根呢?我们的解决方案的结构如图9.11所示。子程序SQRT已被编写。如果没有数学库,程序员不得不重拾数学书(或者要求别人为他/她做),检查Newton-Raphson方法,写出缺失的子程序。然而,有了数学库,问题就不再是问题。数学库提供了许多子程序(包括SQRT),用户程序员就可以继续保持对Newton-Raphson之类方法的忽略。他需要知道是实现平方根功能的库程序的目标地址,在哪里输入参数x,在哪里可以得到结果SQRT(x)。而这些可以非常方便的从数学库附带的文档中获得。如果库程序起始于地址SQRT,提供给库程序的参数在R0中,库程序的结果在R0中,图9.11简化为图9.12。有两件事值得注意:第一件程序员不再担心如何计算平方根,库程序为我们做了这项工作。第二件伪操作.EXTERNAL,我们在7.4.2节中已经看到过,这个伪操作告诉汇编器,在19行的.FILL伪操作需要被汇编的标记(SQRT),是由其它程序片断(即模块)提供的,在生成可执行映像时将与这个程序片断(即模块)结合。可执行映像是实际执行的二进制模块,它是在链接时生成的。在链接时结合多个模块生成可执行映像的概念是很普遍的情况。图9.13图解了这个过程。你将在本课程的第二部分学习C语言程序设计时,看到具体的例子。大多数应用软件需要来自不同库的库程序。对于典型的程序员来说,假设他们首先可以写出那些程序的话,自己写这些程序不是非常高效的。我们已经提到过来自数学库的程序,也有许多生成“优美的”图形图像的预处理程序,还有其它的许多任务,对于程序员从头开始写这些程序是没有意义的。更容易的是仅仅需要(1)适当的文档,以便库程序和调用它的程序之间的接口是明确的,(2)在源程序中适当的使用伪操作,诸如.EXTERNAL。然后链接器在链接时,可以从被分别汇编的模块中生成可执行映像。习题5.26 重新定义LC-3的ISA,再增加10个新的操作码,改变存储器为字节可寻址,地址空间为64K字节;指令仍然采用16位,但是原来的15条指令的各字段的大小可能需要改变。c如果希望使用TRAP指令,能够访问128个操作系统程序,每一个程序的起始地址通过将TRAP向量向左移5位形成,那么,TRAP服务程序所需的存储单元至少有多少?d假设每个TRAP服务程序最多需要16条指令,请修改LC-3的TRAP指令的语义,使得TRAP向量提供的就是该服务程序的起始地址。6.16 一个LC-3程序存储于单元x3000至x3006处,从x3000开始执行。如果在程序执

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论