📄 chapter2.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0036)http://www.xtrj.org/smr/chapter2.htm -->
<HTML><HEAD><TITLE>第二章 MIPS体系结构 </TITLE><!-- http://www.xtrj.org/smr/chapter2.htm -->
<META content="MSHTML 6.00.2900.2180" name=GENERATOR>
<META content=FrontPage.Editor.Document name=ProgId>
<META http-equiv=Content-Type content="text/html; charset=gb2312"></HEAD>
<BODY>
<P>第二章 MIPS体系结构 <BR><BR>在计算世界中, "体系结构"一词被用来描述一个抽象的机器,而不是一个具体的机器 <BR>实现.
这一点非常有用的, 用来区分在市场广告上已经被滥用的"体系结构"这个术 <BR>语. 读者有可能不熟悉"抽象描述",但其概念其实很简单.
<BR><BR>当然,如果你是一个喜欢在 滑的路上开快车的司机,前轮还是后轮驱动就很有所谓
<BR>了。计算机也是如此。如果你需要高性能计算,一个计算机的具体参数与实现对你 <BR>就很重要了。
<BR><BR>一般而言,一个CPU的体系结构有一个指令集加上一些寄存器而组成。“指令集”与”
<BR>体系结构“这两个术语是同义词。你经常会看见ISA(指令集体系结构--ISA)的缩写。
<BR><BR><BR>MIPS体系结构家族包含如下几代。每一代之间都有一些区别。 <BR><BR>MIPS 1:32位处理器使用的指令集。仍然被广泛使用着。
<BR>MIPS II:为R6000机器所定义的,包含了一些细微的改进。后来实现在1995年的32位 <BR>MIPS实现中。 <BR>MIPS
III:R4xxx的64位指令集。 <BR>MIPC IV: MIPS III的一个细微的升级。定义在R10000和R5000中。
<BR><BR>上述的MIPS体系结构等级与MIPS公司提供的文档中定义的是一致的。这些文档提供
<BR>了足够的信息,以使得同一个UNIX应用程序可以在不同的MIPS体系结构等级上运行,
<BR>但是在操作系统或底层相关的代码方面的移植方面,显得不足。MIPS CPU其他一些 <BR>软件可见的方面都是于具体CPU实清b相关的。
<BR><BR>在本书中,我将更加慷慨大方些。有时候,我会描述一个在MIPS 体系结构手册中找 <BR>不到的,但却在所有MIPS III
体系结构实现中能发现的并且你会遇到的功能。 <BR><BR>另外,除了ISA等级,大多数MIPS CPU在实现方法上分为两大类:早期的MIRS R3000和
<BR>其他所有的32位MIPS CPU;另外就是已MIPS R4000为代表的64位CPU。 <BR><BR>有不少MIPS
CPU的实现加入了一些新指令和其他一些有趣的功能。对於软件或工具 <BR>( 如编译器)而言,要利用这些非标准的,依赖于具体实现的功能是不容易的。
<BR><BR>我们可以在两种细节层次上来描述MIPS的体系结构。第一种描述(本章)是在汇编语
<BR>言的层次上看待你的程序,比如,你在工作站上写一个应用程序。这也意味着CPU的 <BR>所有一般的计算是可见的。
<BR><BR>在下面章节里,我们将介绍MIPS的各个方面,包括建构在CPU之上的操作系统所掩盖
<BR>的所有CPU的细节,CPU控制寄存器,中断,陷入,高速缓冲操作和内存管理。至少 <BR>我们会将一个CPU分成一些小部分来学习和介绍。
<BR><BR><BR><BR><BR>2.1 MIPS汇编语言的特点
<BR><BR>汇编语言是CPU二进制指令的可读写版本。我们在后面将有单独的一章来讲述汇编语
<BR>言。从来没有接触过汇编语言的读者在阅读本书时可能会有一些迷惑 。
<BR><BR>大多数MIPS汇编语言都是非常古板的,都是一些寄存器号码。但是工具链(toolchains)可
<BR>以使得使用微处理机语言变得简单。工具链至少允许程序员引用一些助记符,而严
<BR>格的汇编语言要求严格的数字编码。大多我们都是用比较熟悉的C预处理器。C预处 <BR>理器会把C风格的注解去掉,而得到一个可用的汇编代码。
<BR><BR>有C预处理器的帮助,MIPS汇编程序都是用助记符来表示寄存器。助记符同时也代表 <BR>了每个寄存器的用法(我们将在2.2节介绍这一点)
<BR><BR>对於熟悉汇编语言但不熟悉MIPS的读者,下面是一些例子。 <BR><BR>/* this is a comment */ <BR>#so is
this <BR><BR>entrypoint: #this's a label <BR><BR>addu $1, $2, $3 # (registers)
$1 = $2 + $3 <BR><BR>与大多数汇编语言一样, MIPS汇编语言也是以行为单位的。每一行的结束是一个指
<BR>令的结束,并且忽略任何“#”之后的内容,认为是注释。在一行里可以有多条指令。 <BR>指令之间要用分号“;”隔开。
<BR><BR>一个符号(label)是一个后面跟着冒号“:”的字。符号可以是任何字符串的组合。
<BR>符号被用来定义一段代码的入口和定义数据段的一个存储位置。
<BR><BR>如上所示,许多指令都是3个操作数/符(operand)。目标寄存器在左侧(注意,这一 <BR>点与Inetel x86
正相反)。一般而言,寄存器结果和操作符的顺序与C语言或其他符 <BR>号语言的方式是一致的。 例如: <BR><BR>subc $1, $2, $3
<BR><BR>意味着: <BR><BR>$1 = $2 - $3; <BR><BR>这方面我们就先讲这么多。 <BR><BR>2.2 寄存器
<BR><BR>对於一个程序,可以有32个通用寄存器,分别为:$0-$31。其中,两个,也只有两 <BR>个的使用不同于其他。
<BR><BR>$0:不管你存放什么值,其返回值永远是零。
<BR><BR>$31:永远存放着正常函数调用指令(jal)的返回地址。请注意call-by-registe的jalr指
<BR>令可以使用任何寄存器来存放其返回地址。当然,如不用$31,看起来程序会有点古 <BR>怪。
<BR><BR>其他方面,所有的寄存器都是一样的。可以被用在任何一个指令中(你也可以用$0作
<BR>为一个指令的目标寄存器。当然不管你存入什么数据,数据都消失了。)
<BR><BR>MIPS体系结构下,程序计数器不是一个寄存器,其实你最好不要去那样想。在一个
<BR>具有流水线的CPU中,程序计数器的值在一个给定的时刻有多个可选值。这一点有点 <BR>迷惑人。jal指令的返回地址跟随其后的第二条指令。
<BR><BR>... <BR>jal printf <BR>move $4, $6 <BR>xxx # return here after call
<BR><BR>上述的解释是有道理的,因为紧跟踪jal指令后面的指令,由於在delay slot(延迟
<BR>位置)上--请记住,关于延迟位置的规则是该指令将在转移目标(如上述的printf)之 <BR>前执行。延迟位置指令经常被用来传递函数调用的参数。
<BR><BR>MIPS里没有状态码。CPU状态寄存器或内部都不包含任何用户程序计算的结果状态信 <BR>息。
<BR><BR>hi和lo是与乘法运算器相关的两个寄存器大小的用来存放结果的地方。它们并不是
<BR>通用寄存器,除了用在乘除法之外,也不能有做其他用途。但是,MIPS里定义了一
<BR>些指令可以往hi和lo里存入任何值。想一想我们会发现,这是非常有必要的当你想 <BR>要恢复一个被打断的程序时。
<BR><BR>浮点运算协处理器(浮点加速器,FPA),如果存在的话,有32个浮点寄存器。按汇编 <BR>语言的简单约定讲,是从$f0到$31。
<BR><BR>实际上,对於MIPS I和MIPS II的机器,只有16个偶数号的寄存器可以用来做数学计
<BR>算。当然,它们可以既用来做单精度(32位)和双精度(64位)。当你做一个双精度的
<BR>运算时,寄存器$f1存放$f0的余数。奇数号的寄存器只用来作为寄存器与FPA之间的 <BR>数据传送。 <BR><BR>MIPS III
CPU有32个FP寄存器。但是为了保持软件与过去的兼容性,最好不要用奇 <BR>数号的寄存器。 <BR><BR>2.2.1 助记符与通用寄存器的用法
<BR><BR>我们已经描述了一些体系结构方面的内容,下面来介绍一些软件方面的内容。 <BR><BR>寄存器编号 助记符 用法 <BR>0 zero
永远返回值为0 <BR>1 at 用做汇编器的暂时变量 <BR>2-3 v0, v1 子函数调用返回结果 <BR>4-7 a0-a3 子函数调用的参数
<BR>8-15 t0-t7 暂时变量,子函数使用时不需要保存与恢复 <BR>24-25 t8-t9 <BR>16-25 s0-s7
子函数寄存器变量。子函数必须保存和恢复使用过的变量在函数返 <BR>回之前,从而调用函数知道这些寄存器的值没有变化。 <BR>26,27 k0,k1
通常被中断或异常处理程序使用作为保存一些系统参数 <BR>28 gp 全局指针。一些运行系统维护这个指针来更方便的存取“static“和”extern"
<BR>变量。 <BR>29 sp 堆栈指针 <BR>30 s8/fp 第9个寄存器变量。子函数可以用来做桢指针 <BR>31 ra 子函数的返回地□
<BR>'7d <BR><BR>虽然硬件没有强制性的指定寄存器使用规则,在实际使用中,这些寄存器的用法都
<BR>遵循一系列约定。这些约定与硬件确实无关,但如果你想使用别人的代码,编译器 <BR>和操作系统,你最好是遵循这些约定。
<BR><BR>寄存器约定用法引人了一系列的寄存器约定名。在使用寄存器的时候,要尽量用这 <BR>些约定名或助记符,而不直接引用寄存器编号。
<BR><BR>1996年左右,SGI开始在其提供的编译器中使用新的寄存器约定。这种新约定可以用 <BR>来建立使用32位地址或64位地址的程序,分别叫
"n32"和"n64"。我们暂时不讨论这 <BR>些,将会在第10章详细讨论。 <BR><BR>寄存器名约定与使用 <BR><BR>*at:
这个寄存器被汇编的一些合成指令使用。如果你要显示的使用这个寄存器(比 <BR>如在异常处理程序中保存和恢复寄存器),有一个汇编directive可被用来禁止汇编
<BR>器在directive之后再使用at寄存器(但是汇编的一些宏指令将因此不能再可用)。 <BR><BR>*v0, v1:
用来存放一个子程序(函数)的非浮点运算的结果或返回值。如果这两个寄 <BR>存器不够存放需要返回的值,编译器将会通过内存来完成。详细细节可见10.1节。
<BR><BR><BR>*a0-a3: 用来传递子函数调用时前4个非浮点参数。在有些情况下,这是不对的。请 <BR>参10.1细节。 <BR><BR>*
t0-t9: 依照约定,一个子函数可以不用保存并随便的使用这些寄存器。在作表达
<BR>式计算时,这些寄存器是非常好的暂时变量。编译器/程序员必须注意的是,当调用 <BR>一个子函数时,这些寄存器中的值有可能被子函数破坏掉。
<BR><BR>*s0-s8: 依照约定,子函数必须保证当函数返回时这些寄存器的内容必须恢复到函
<BR>数调用以前的值,或者在子函数里不用这些寄存器或把它们保存在堆栈上并在函数
<BR>退出时恢复。这种约定使得这些寄存器非常适合作为寄存器变量或存放一些在函数 <BR>调用期间必须保存原来值。 <BR><BR>* k0, k1:
被OS的异常或中断处理程序使用。被使用后将不会恢复原来的值。因此 <BR>它们很少在别的地方被使用。 <BR><BR>* gp:
如果存在一个全局指针,它将指向运行时决定的,你的静态数据(static data)区
<BR>域的一个位置。这意味着,利用gp作基指针,在gp指针32K左右的数据存取,系统只
<BR>需要一条指令就可完成。如果没有全局指针,存取一个静态数据区域的值需要两条
<BR>指令:一条是获取有编译器和loader决定好的32位的地址常量。另外一条是对数据 <BR>的真正存取。为了使用gp,
编译器在编译时刻必须知道一个数据是否在gp的64K范围 <BR>之内。通常这是不可能的,只能靠猜测。一般的做法是把small global data (小的
<BR>全局数据)放在gp覆盖的范围内(比如一个变量是8字节或更小),并且让linker报警
<BR>如果小的全局数据仍然太大从而超过gp作为一个基指针所能存取的范围。 <BR><BR>并不是所有的编译和运行系统支持gp的使用。 <BR><BR>*sp:
堆栈指针的上下需要显示的通过指令来实现。因此MIPS通常只在子函数进入和 <BR>退出的时刻才调整堆栈的指针。这通过被调用的子函数来实现。sp通常被调整到这
<BR>个被调用的子函数需要的堆栈的最低的地方,从而编译器可以通过相对於sp的偏移 <BR>量来存取堆栈上的堆栈变量。详细可参阅10.1节堆栈使用。
<BR><BR>* fp: fp的另外的约定名是s8。如果子函数想要在运行时动态扩展堆栈大小,fp作
<BR>为桢指针可以被子函数用来记录堆栈的情况。一些编程语言显示的支持这一点。汇
<BR>编编程员经常会利用fp的这个用法。C语言的库函数alloca()就是利用了fp来动态调 <BR>整堆栈的。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -