⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter2.htm

📁 see mips run,mips学习最好的资料
💻 HTM
📖 第 1 页 / 共 4 页
字号:
<BR><BR>如果堆栈的底部在编译时刻不能被决定,你就不能通过sp来存取堆栈变量,因此fp被 
<BR>初始化为一个相对与该函数堆栈的一个常量的位置。这种用法对其他函数是不可见 <BR>的。 <BR><BR>* ra: 
当调用任何一个子函数时,返回地址存放在ra寄存器中,因此通常一个子程 <BR>序的最后一个指令是jr ra. 
<BR><BR>子函数如果还要调用其他的子函数,必须保存ra的值,通常通过堆栈。 
<BR><BR>对於浮点寄存器的用法,也有一个相应的标准的约定。我们将在7.5节。在这里,我 
<BR>们已经介绍了MIPS引入的寄存器的用法约定。最近在约定方面有一些演化,我们将 <BR>在10.8节中介绍这些变化,比如调用约定的一些新标准。 
<BR><BR>2.3 整数乘法部件与寄存器 <BR><BR>MIPS 体系结构认为整数乘法部件非常重要,需要一个单独的硬件指令。这一点在RISC芯 
<BR>片里不多见。一个另外做法是通过标准的整数运算流水线部件来实现一个乘法。这 
<BR>意味着对於每个乘法指令,需要一段软件过程(来模拟一个乘法指令)。早期的Spacr <BR>CPU就是这样做的。 
<BR><BR>另外一个用来避免设计一个整数乘法器的做法是通过浮点运算器来实现乘法。Motorola的 <BR>88000 
CPU家族就是提供了这样的解决方案。这样的缺点是损失了MIPS浮点运算器是 <BR>用来做浮点运算的设计初衷。 
<BR><BR>早期的MIPS乘法运算器不是特别快。它的基本功能是将两个寄存器大小的值做一个 <BR>乘法并将两个寄存器大小的结果存放在乘法部件里。mfhi, 
mflo指令用来将结果的 <BR>两部分分别放入指定的通用寄存器里。 
<BR><BR>与整数运算结果不一样的是,乘法结果寄存器是互锁的(inter-locked)。试图在乘 
<BR>法结束之前对结果寄存器的读操作将被暂停直到乘法运算结束。 <BR><BR>整数乘法器也可以执行两个通用寄存器的除法操作。lo寄存器用来存放结果(商), 
<BR>hi寄存器用来存放余数。 <BR><BR>MIPS CPU的整数乘法部件操作相对而言比较慢:乘法需要5-12个时钟周期,除法需 
<BR>要35-80个时钟周期(与具体CPU的实现有关,如操作数的大小)。相对一个同样的双 
<BR>精度浮点运算操作,乘法和除法操作是太慢了。乘/除法并且在内部不是靠流水线来 
<BR>实现的。可见相应的硬件实现是牺牲了速度以换取(指令)简单和节省芯片大小。 
<BR><BR>汇编器提供了一个合成的乘法指令用来执行乘法并将结果取出放回一个通用寄存器。 
<BR>MIPS公司的汇编器会通过一系列的移位和加法操作来替换(硬件)的乘法指令, 如果 
<BR>汇编器优化觉得这样更快的话。我对於这一点的意见是优化的工作应该有编译器来 <BR>完成,而不是有汇编器来做。 
<BR><BR>乘法部件不是流水线构造的。每一次只能执行一条指令。上一次的结果将丢失如果 
<BR>下一条乘法指令又开始了,上一次的结果不会象流水线结构那样被写到流水线的write-back阶 
<BR>段。(译者注:在流水线方式下,在write-back阶段,寄存器-寄存器指令的结果将 
<BR>被写回到结果寄存器)。这一点如果不注意的话,将导致一些非常难理解的问题,导 <BR>致你的程序的结果不对,比如中断的打扰使得你刚才的乘法结果被冲掉了。 
<BR><BR>如果一个mfhi或mflo指令在还没有走到流水线的write-back阶段而被中断或异常打 
<BR>断,系统将会重新启动上述读取操作,废掉上一次的读取。但是如果下一条指令是 
<BR>乘法指令并且完成了ALU阶段,该乘法指令会与异常处理并行的执行,并有可能覆盖 
<BR>掉hi和ho寄存器里的内容。那么上述mfhi或mflo的重新执行将会得到错误的结果。 
<BR>由於这个原因,乘法指令一般不要紧跟在mfhi/mflo指令后面,要隔开两条指令(译 <BR>者着:从而防止CPU的指令预取) 
<BR><BR><BR>2.4 加载与存储:寻址方式 <BR><BR>如前面所言,MIPS只有一种寻址方式。任何加载或存储机器指令可以写成 <BR>lw $1, 
offset($2) <BR>你可以使用任何寄存器来作为目标和源寄存器。offset偏移量是一个有符号的16位 
<BR>的数字(因此可以是在-32768与32767之间的任何一值)。用来加载的程序地址是源寄 
<BR>存器与偏移量的和所构成的地址。这种寻址方式一般已足够存取一个C语言的结构(偏 
<BR>移量是这个结构的起始地址到所要存取的结构成员之间的距离)。这种寻址方式实现 
<BR>了一个通过一个常量来索引的数组;并足够使得可以存取堆栈上的函数变量或桢指 
<BR>针;可以提供一个比较合适大小的以gp为基址的全局空间以存取静态和外部数据。 
<BR><BR><BR>汇编器提供一个简单直接存取方式的汇编格式从而可以加载一个在连接时刻才能决 <BR>定地址的变量的值。 
<BR><BR>许多更复杂的方式,如双寄存器或可伸缩的索引,都需要多个指令的组合。 <BR><BR>2.5 存储器与寄存器的数据类型 <BR><BR>MIPS 
CPU可以在一个单一操作中存储1到8个字节。文档中和用来组成指令助记符的 <BR>命名约定如下: <BR><BR>C名字 MIPS名字 大小(字节) 汇编助记符 
<BR>longlong dword 8 "d"代表ld <BR>int/long word 4 "w"代表lw <BR>short halfword 2 
"h"代表lh <BR>char byte 1 "b"代表lb <BR><BR>2.5.1 整数数据类型 
<BR><BR>byte和short的加载有两种方式。带符号扩展的lb和lh指令将数据值存放在32位寄存 
<BR>器的低位中并剩下的高位用符号位的值来扩充(位7如果是一个byte,位15如果是一 
<BR>个short)。这样就正确地将一个带符号整数放入一个32位的带符号的寄存器中。 
<BR><BR>不带符号指令lbu和lhu用0来扩充数据,将数据存放纵32位寄存器的低位中,并将高 <BR>位用零来填充。 
<BR><BR>例如,如果一个byte字节宽度的存储器地址为t1,其值为0xFE(-2或254如果是非符 
<BR>号数),那么将会在t2中放入0xFFFFFFFE(-2作为一个符号数)。t3的值会是0x000000FE(254作 <BR>为一个非符号数) 
<BR><BR>lb t2, 0(t1) <BR>lbu t3, 0(t1) <BR><BR>上述描述是假设一个32位的MIPS CPU。但是MIPS 
III或其上的体系结构实现了64位 <BR>寄存器。可见所有的部分word字的加载(包括非符号数)都带符号(包括0)扩充到高32位。 
<BR>这看上去很奇怪但却是很有用的。这将在2.7.3节中解释这一点。 
<BR><BR>这些较小长度的整数扩充到较长的整数的细微区别是由於C语言可移植性的历史原因 
<BR>造成的。现代C语言标准定义了非常明确的规则来避免可能的二义性。在不能直接作 
<BR>8位和16位精度的算术的机器中,如MIPS,编译器对任何包含short和char变量的表 
<BR>达式中需要插入额外的指令以确保数据该溢出时得溢出:这一点是不希望的,程序 <BR>效率非常差。当移植一个使用小整数变量的代码到MIPS 
CPU上的时候,你应该考虑 <BR>找出那些可以安全的转换成整数的变量。 <BR><BR>2.5.2 没对齐的加载和存储 
<BR><BR>MIPS体系结构中,正常的加载和存储必须对齐。半字(halfwords)必须从2个字节的 
<BR>边界加载;字(word)必须从4个字节的边界。一个加载没有对齐的地址的加载指令会 
<BR>导致CPU进入异常处理。因为CISC体系结构,例如MC680x0和Intel的x86确实能够处 
<BR>理非对齐的加载和存储,当移植软件到MIPS体系结构时,你可能会遇到这个问题。 
<BR>一个极端情况是你或许想安装一个异常处理程序来负责相应的加载操作从而使得地 
<BR>址对齐的操作对用户程序是透明的。但是这种做法使得程序效率非常慢,除非这样 <BR>的异常处理非常少。 
<BR><BR>所有C语言的数据类型将严格的按照其数据类型的大小对齐。 
<BR><BR>当你不知道你要操作的数据是对齐的或者说就是不对齐的,MIPS体系结构允许通过 
<BR>两条指令来完成这个非对齐的存取(比通过一些列的字节的存取然后移位,加法的效 
<BR>率高得多)。这些代理指令的操作很隐含,比较难以掌握,通常是有宏指令ulw的产 <BR>生的。详细可见8.4.1节。 
<BR><BR>MIPS另外还提供宏指令ulh(非对齐的加载半字)。这也是通过合成指令来完成的--两 <BR>个加载操作,一个移位和一个位或操作。 
<BR><BR>通常,C编译器负责将所有的数据进行正确的对齐。但是在有些情况下(但从一个文 
<BR>件中读取数据或与一个不同的CPU共享数据)能够处理非对齐的整数数据是必须的, 
<BR>一些编译器允许你设定一个数据类型是非对齐的,编译器将会产生相应的特殊代码 <BR>来处理。ANSI提供#progma align 
nn,GNU是通过更简洁packed结构属性类型来指定。 
<BR><BR><BR>即使你的编译器实现了packd数据类型,编译器并不保证会使用特殊的MIPS指令来实 <BR>现非对齐的存取。 <BR><BR>2.5.3 
内存中的浮点数据 <BR><BR>从内存中将数据加载到浮点寄存器中不会经过任何检查--你可以加载一个非法的浮 
<BR>点数据(实际上,你可以加载任意的数据模式),并不会得到浮点运算错误直到对这 <BR>些数据进行操作。 
<BR><BR>在32位处理器上,这允许你通过一个加载将一个单精度的数据放入一个偶数号的浮 
<BR>点寄存器中,你也可以通过一个宏指令加载一个双精度的数据,因此在一个32位的 <BR>CPU上,汇编指令 <BR><BR>l.d $f2, 24(t1) 
<BR><BR>被扩充为两个连续的寄存器加载: <BR><BR>lwc1 $f2, 24(t1) <BR>lwc1 $f3, 28(t1) 
<BR><BR>在一个64位CPU上, l.d是机器指令ldc1的别名。ldc1完成64位数据的加载工作。 
<BR><BR>任何一个遵循MIPS/SGI规则的C编译器都将8byte的long(长整数),双精度浮点变量 <BR>在8byte 
的地址边界上对齐。32位硬件不需要这个要求,对齐是为了向上的兼容性: 
<BR>64位CPU如果加载一个没有在8byte上对奇的double变量,CPU将进入错误处理,进入 <BR>异常。 <BR><BR>2.6 汇编语言的合成指令 
<BR><BR>虽然从体系结构的原因我们不能直接用一条指令来完成将一个32位的常量取入一个 <BR>寄存器中, 
但是写MIPS机器码或许太沉闷了。汇编语言程序员不想每次都得考虑这 <BR>些。因此MIPS公司的汇编器(和其他的MIPS汇编器)将会为你合成一些指令。你只需 
<BR>要写一个加载立即数指令,汇编器会知道什么时候通过两条机器指令来实现之。 
<BR><BR>显然这是很有用的,但是同时自从发明之后也就一直被乱用。许多MIPS汇编器通过 
<BR>将体系结构的特点掩盖起来从而使得不需要合成指令。在本书中,我们将试图尽量 
<BR>少用合成指令,当使用时,会给读者指出来。另外,在下面的指令列表中,我们将 <BR>会指出合成指令与机器指令的区别。 
<BR><BR>我的感觉是合成指令是用来帮助程序员的,严肃的编译器应该严格的一对一的产生 
<BR>机器指令代码。但是在这个不尽善尽美的世界里,还是有许多编译器产生合成指令。 <BR><BR><BR>汇编器提供的有用的方面包括下列: <BR><BR>* 
一个32位的立即数加载:你可以在数据码中加载任何数据(包括一个在连接阶段决 <BR>定的内存地址),汇编器将会把其拆开成为两个指令,加载这个数据的前半部分和后 
<BR>半部分。 <BR><BR>*从一个内存地址加载:你可以从一个内存变量来作一个加载。汇编器通常会将这个 
<BR>变量的高位地址放入一个暂时的寄存器中,然后将这个变量的低位作为一个加载的 
<BR>偏移量。当然这不包括C函数里的局部变量。局部变量通常定义在堆栈上或寄存器中。 
<BR><BR><BR>*对内存变量的快速存取:一些C程序包含了许多对static和extern变量的存取, 对 
<BR>它们加载与存储用load/store两条指令开销太大了。一些编译系统避开了这一点, 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -