哪些确定能够只用一条mipsmips汇编指令集完成对应的功能

当前位置: >>
在中高档服务器中采用 RISC 指令的 CPU 主要有 Compaq 即新惠普) 公司的 Alpha 、 HP 公司的 PA-RISC 、 (康柏, 康柏, 即新惠普) IBM 公司的 Power PC 、 MIPS 公司的 MIPS 和 SUN 公 司的 Spare 。 MIPS 汇编: 汇编:汇编语言是 CPU 二进制指令的可读写版本。大多数 MIPS 汇编语言都
是非常古 板的,都是一些寄存器号码。但是工具链(toolchains)可以使得使用微处理机语言 变得简单。工具链至少允许程序员引用一些助记符,而严格的汇编语言要求严 格的数字编码。大多我们都是用比较熟悉的 C 预处理器。C 预处理器会把 C 风 格的注解去掉,而得到一个可用的汇编代码。 有 C 预处理器的帮助,MIPS 汇编程序都是用助记符来表示寄存器。助记符同时 也代表了每个寄存器的用法 对於熟悉汇编语言但不熟悉 MIPS 的读者,下面是一些例子。 /* this is a comment */ #so is this entrypoint: #this''s a label addu $1, $2, $3 # (registers) $1 = $2 + $3 与大多数汇编语言一样, MIPS 汇编语言也是以行为单位的。每一行的结束是 一个指令的结束,并且忽略任何“#”之后的内容,认为是注释。在一行里可以 有多条指令。指令之间要用分号“; ”隔开。 一个符号(label)是一个后面跟着冒号“: ”的字。符号可以是任何字符串的组合。 符号被用来定义一段代码的入口和定义数据段的一个存储位置。 如上所示,许多指令都是 3 个操作数/符(operand)。目标寄存器在左侧(注意,这 一点与 Inetel x86 正相反)。一般而言,寄存器结果和操作符的顺序与 C 语言或 其他符号语言的方式是一致的。 例如: subc $1, $2, $3 意味着: $1 = $2 - $3; 这方面我们就先讲这么多。 对於一个程序,可以有 32 个通用寄存器,分别为:$0-$31。其中,两个,也只 有两个的使用不同于其他。 $0:不管你存放什么值,其返回值永远是零。 $31: 永远存放着正常函数调用指令(jal)的返回地址。 请注意 call-by-registe 的 jalr 指令可以使用任何寄存器来存放其返回地址。当然,如不用$31,看起来程序会 有点古怪。 其他方面,所有的寄存器都是一样的。可以被用在任何一个指令中(你也可以用 $0 作为一个指令的目标寄存器。当然不管你存入什么数据,数据都消失了。) MIPS 体系结构下,程序计数器不是一个寄存器,其实你最好不要去那样想。在 一个具有流水线的 CPU 中,程序计数器的值在一个给定的时刻有多个可选值。 这一点有点迷惑人。jal 指令的返回地址跟随其后的第二条指令。 .. jal printf move $4, $6 xxx # return here after call 上述的解释是有道理的,因为紧跟踪 jal 指令后面的指令,由於在 delay slot(延 迟位置)上--请记住, 关于延迟位置的规则是该指令将在转移目标(如上述的 printf) 之前执行。延迟位置指令经常被用来传递函数调用的参数。 MIPS 里没有状态码。 CPU 状态寄存器或内部都不包含任何用户程序计算的结果 状态信息。 hi 和 lo 是与乘法运算器相关的两个寄存器大小的用来存放结果的地方。它们并 不是通用寄存器,除了用在乘除法之外,也不能有做其他用途。但是,MIPS 里 定义了一些指令可以往 hi 和 lo 里存入任何值。想一想我们会发现,这是非常有 必要的当你想要恢复一个被打断的程序时。 浮点运算协处理器(浮点加速器,FPA),如果存在的话,有 32 个浮点寄存器。按 汇编语言的简单约定讲,是从$f0 到$31。 实际上,对於 MIPS I 和 MIPS II 的机器,只有 16 个偶数号的寄存器可以用来做 数学计算。当然,它们可以既用来做单精度(32 位)和双精度(64 位)。当你做一个 双精度的运算时,寄存器$f1 存放$f0 的余数。奇数号的寄存器只用来作为寄存 器与 FPA 之间的 数据传送。 MIPS III CPU 有 32 个 FP 寄存器。但是为了保持软件与过去的兼容性,最好不 要用奇 数号的寄存器。 助记符与通用寄存器的用法 我们已经描述了一些体系结构方面的内容,下面来介绍一些软件方面的内容。 寄存器编号 助记符 用法 0 zero 永远返回值为 0 1 at 用做汇编器的暂时变量 2-3 v0, v1 子函数调用返回结果 4-7 a0-a3 子函数调用的参数 8-15 t0-t7 暂时变量,子函数使用时不需要保存与恢复 24-25 t8-t9 16-25 s0-s7 子函数寄存器变量。子函数必须保存和恢复使用过的变量在函数返 回之前,从而调用函数知道这些寄存器的值没有变化。 26,27 k0,k1 通常被中断或异常处理程序使用作为保存一些系统参数 28 gp 全局指针。 一些运行系统维护这个指针来更方便的存取 “static “和” extern& 变量。 29 sp 堆栈指针 30 s8/fp 第 9 个寄存器变量。子函数可以用来做桢指针 31 ra 子函数的返回地□ ''7d 虽然硬件没有强制性的指定寄存器使用规则,在实际使用中,这些寄存器的用 法都遵循一系列约定。这些约定与硬件确实无关,但如果你想使用别人的代码, 编译器和操作系统,你最好是遵循这些约定。 寄存器约定用法引人了一系列的寄存器约定名。在使用寄存器的时候,要尽量 用这些约定名或助记符,而不直接引用寄存器编号。 1996 年左右,SGI 开始在其提供的编译器中使用新的寄存器约定。这种新约定 可以用来建立使用 32 位地址或 64 位地址的程序,分别叫 &n32&和&n64&。我们 暂时不讨论这些,将会在第 10 章详细讨论。 寄存器名约定与使用 *at: 这个寄存器被汇编的一些合成指令使用。如果你要显示的使用这个寄存器 (比如在异常处理程序中保存和恢复寄存器), 有一个汇编 directive 可被用来禁止 汇编器在 directive 之后再使用 at 寄存器(但是汇编的一些宏指令将因此不能再可 用)。 *v0, v1: 用来存放一个子程序(函数)的非浮点运算的结果或返回值。如果这两个 寄存器不够存放需要返回的值, 编译器将会通过内存来完成。 详细细节可见 10.1 节。 *a0-a3: 用来传递子函数调用时前 4 个非浮点参数。 在有些情况下, 这是不对的。 请参 10.1 细节。 * t0-t9: 依照约定,一个子函数可以不用保存并随便的使用这些寄存器。在作表 达式计算时,这些寄存器是非常好的暂时变量。编译器/程序员必须注意的是, 当调用一个子函数时,这些寄存器中的值有可能被子函数破坏掉。 *s0-s8: 依照约定,子函数必须保证当函数返回时这些寄存器的内容必须恢复到 函数调用以前的值,或者在子函数里不用这些寄存器或把它们保存在堆栈上并 在函数退出时恢复。这种约定使得这些寄存器非常适合作为寄存器变量或存放 一些在函数 调用期间必须保存原来值。 * k0, k1: 被 OS 的异常或中断处理程序使用。被使用后将不会恢复原来的值。因 此它们很少在别的地方被使用。 * gp: 如果存在一个全局指针, 它将指向运行时决定的, 你的静态数据(static data) 区域的一个位置。这意味着,利用 gp 作基指针,在 gp 指针 32K 左右的数据存 取,系统只需要一条指令就可完成。如果没有全局指针,存取一个静态数据区 域的值需要两条指令: 一条是获取有编译器和 loader 决定好的 32 位的地址常量。 另外一条是对数据的真正存取。为了使用 gp, 编译器在编译时刻必须知道一个 数据是否在 gp 的 64K 范围之内。通常这是不可能的,只能靠猜测。一般的做法 是把 small global data (小的全局数据)放在 gp 覆盖的范围内(比如一个变量是 8 字节或更小), 并且让 linker 报警如果小的全局数据仍然太大从而超过 gp 作为一 个基指针所能存取的范围。 并不是所有的编译和运行系统支持 gp 的使用。 *sp: 堆栈指针的上下需要显示的通过指令来实现。因此 MIPS 通常只在子函数 进入和退出的时刻才调整堆栈的指针。这通过被调用的子函数来实现。sp 通常 被调整到这个被调用的子函数需要的堆栈的最低的地方,从而编译器可以通过 相对於 sp 的偏移量来存取堆栈上的堆栈变量。详细可参阅 10.1 节堆栈使用。 * fp: fp 的另外的约定名是 s8。如果子函数想要在运行时动态扩展堆栈大小,fp 作为桢指针可以被子函数用来记录堆栈的情况。一些编程语言显示的支持这一 点。汇编编程员经常会利用 fp 的这个用法。C 语言的库函数 alloca()就是利用了 fp 来动态调整堆栈的。 如果堆栈的底部在编译时刻不能被决定,你就不能通过 sp 来存取堆栈变量,因 此 fp 被初始化为一个相对与该函数堆栈的一个常量的位置。这种用法对其他函 数是不可见的。 * ra: 当调用任何一个子函数时,返回地址存放在 ra 寄存器中,因此通常一个子 程序的最后一个指令是 jr ra. 子函数如果还要调用其他的子函数,必须保存 ra 的值,通常通过堆栈。 MIPS 简述: 简述:Million Instructions Per Second 的缩写,每秒处理的百万级的机器语言指令数。 这是衡量 CPU 速度的一个指标。 像是一个 Intel 80386 电脑可以每秒处理 3 百万 到 5 百万机器语言指令,既我们可以说 80386 是 3 到 5MIPS 的 CPU。MIPS 只 是衡量 CPU 性能的指标。 MIPS 技术公司是一家设计制造高性能、高档次及嵌入式 32 位和 64 位 处理器的厂商,在 RISC 处理器方面占有重要地位。1984 年,MIPS 计算机公司 成立。1992 年,SGI 收购了 MIPS 计算机公司。1998 年,MIPS 脱离 SGI,成为 MIPS 技术公司。 MIPS 公司设计 RISC 处理器始于二十世纪八十年代初,1986 年推出 R2000 处理器,1988 年推 R3000 处理器,1991 年推出第一款 64 位商用微处器 R4000。之后又陆续推出 R8000(于 1994 年) 、R10000(于 1996 年)和 R12000 (于 1997 年)等型号。 随后,MIPS 公司的战略发生变化,把重点放在嵌入式系统。1999 年, MIPS 公司发布 MIPS32 和 MIPS64 架构标准,为未来 MIPS 处理器的开发奠定 了基础。 新的架构集成了所有原来 NIPS 指令集, 并且增加了许多更强大的功能。 MIPS 公司陆续开发了高性能、低功耗的 32 位处理器内核(core)MIPS324Kc 与高性能 64 位处理器内核 MIPS64 5Kc。 2000 年, MIPS 公司发布了针对 MIPS32 4Kc 的版本以及 64 位 MIPS 64 20Kc 处理器内核。 MIPS 是世界上很流行的一种 RISC 处理器。 MIPS 的意思是 “无内部互 锁流水级的微处理器”(Microprocessor without interlocked piped stages),其机制 是尽量利用软件办法避免流水线中的数据相关问题。它最早是在 80 年代初期由 斯坦福(Stanford)大学 Hennessy 教授领导的研究小组研制出来的。 MIPS 公司的 R 系列就是在此基础上开发的 RISC 工业产品的微处理器。 这些系列产品为很多计 算机公司采用构成各种工作站和计算机系统。 MIPS 技术公司是美国著名的芯片设计公司,它采用精简指令系统计算结构 (RISC)来设计芯片。和英特尔采用的复杂指令系统计算结构(CISC)相比,RISC 具有设计更简单、设计周期更短等优点,并可以应用更多先进的技术,开发更 快的下一代处理器。MIPS 是出现最早的商业 RISC 架构芯片之一,新的架构集 成了所有原来 MIPS 指令集,并增加了许多更强大的功能。 1986 年推出 R2000 处理器,1988 年推出 R3000 处理器,1991 年推出第一 款 64 位商用微处理器 R4000。之后, 又陆续推出 R8000(于 1994 年)、R10000(于 1996 年)和 R12000(于 1997 年)等型号。 1999 年, MIPS 公司发布 MIPS 32 和 MIPS 64 架构标准。2000 年,MIPS 公司发布了针对 MIPS 32 4Kc 的新版本以及未来 64 位 MIPS 64 20Kc 处理器内核。 在 MIPS 芯片的发展过程中,SGI 公司在 1992 年收购了 MIPS 计算机公司, 1998 年,MIPS 公司又脱离了 SGI,成为 MIPS 技术公司; MIPS32 4KcTM 处理 器是采用 MIPS 技术特定为片上系统(System-On-a-Chip)而设计的高性能、低电 压 32 位 MIPS RISC 内核。采用 MIPS32TM 体系结构, 并且具有 R4000 存储器 管理单元(MMU)以及扩展的优先级模式,使得这个处理器与目前嵌入式领域广 泛应用的 R3000 和 R4000 系列(32 位)微处理器完全兼容。新的 64 位 MIPS 处 理器是 RM9000x2,从“x2”这个标记判断,它包含了不是一个而是两个均具有 集成二级高速缓存的 64 位处理器。RM9000x2 主要针对网络基础设施市场,具 有集成的 DDR 内存控制器和超高速的 HyperTransport I/O 链接。 处理器、内存和 I/O 均通过分组交叉连接起来的,可实现高性能、全面高 速缓存的统一芯片系统。除通过并行处理提高系统性能外,RM9000x2 还通过 将超标量与超流水线技术相结合来提高单个处理器的性能。 64 位处理器 MIPS 64 20Kc 的浮点能力强,可以组成不同的系统,从一个处 理器的 Octane 工作站到 64 个处理器的 Origin 2000 服务器;这种 CPU 更适合图形 工作站使用。MIPS 最新的 R12000 芯片已经在 SGI 的服务器中得到应用,目前 其主频最大可达 400MHz。 MIPS 处理器是八十年代中期 RISC CPU 设计的一大热点。MIPS 是卖的最 好的 RISC CPU,可以从任何地方,如 Sony, Nintendo 的游戏机,Cisco 的路 由器和 SGI 超级计算机,看见 MIPS 产品在销售。目前随着 RISC 体系结构遭到 x86 芯片的竞争,MIPS 有可能是起初 RISC CPU 设计中唯一的一个在本世纪盈 利的。和英特尔相比,MIPS 的授权费用比较低,也就为除英特尔外的大多数芯 片厂商所采用。 MIPS 的系统结构及设计理念比较先进,其指令系统经过通用处理器指令体 系 MIPS I、MIPS II、MIPS III、MIPS IV 到 MIPS V,嵌入式指令体系 MIPS16、 MIPS32 到 MIPS64 的发展已经十分成熟。在设计理念上 MIPS 强调软硬件协同 提高性能,同时简化硬件设计。MIPS 是世界上很流行的一种 RISC 处理器。 MIPS 的意思是 “无内部互锁流水级 的微处理器”(Microprocessor without interlocked piped stages),其机制是尽量利 用软件办法避免流水线中的数据相关问题。它最早是在 80 年代初期由斯坦福 (Stanford)大学 Hennessy 教授领导的研究小组研制出来的。MIPS 公司的 R 系列 就是在此基础上开发的 RISC 工业产品的微处理器。 这些系列产品为很多计算机 公司采用构成各种工作站和计算机系统。 MIPS 技术公司是美国著名的芯片设计公司,它采用精简指令系统计算结构 (RISC)来设计芯片。和英特尔采用的复杂指令系统计算结构(CISC)相比,RISC 具有设计更简单、设计周期更短等优点,并可以应用更多先进的技术,开发更 快的下一代处理器。MIPS 是出现最早的商业 RISC 架构芯片之一,新的架构集 成了所有原来 MIPS 指令集,并增加了许多更强大的功能。 新的 64 位 MIPS 处理器是 RM9000x2,从“x2”这个标记判断,它包含 了不是一个而是两个均具有集成二级高速缓存的 64 位处理器。RM9000x2 主要 针 对 网 络 基 础 设 施 市 场 , 具 有 集 成 的 DDR 内 存 控 制 器 和 超 高 速 的 HyperTransport I/O 链接。 处理器、内存和 I/O 均通过分组交叉连接起来的,可实现高性能、全面高 速缓存的统一芯片系统。除通过并行处理提高系统性能外,RM9000x2 还通过 将超标量与超流水线技术相结合来提高单个处理器的性能。 64 位处理器 MIPS 64 20Kc 的浮点能力强,可以组成不同的系统,从一个处 理器的 Octane 工作站到 64 个处理器的 Origin 2000 服务器;这种 CPU 更适合图形 工作站使用。MIPS 最新的 R12000 芯片已经在 SGI 的服务器中得到应用,目前 其主频最大可达 400MHz。 中国龙芯 2 和前代产品采用的都是 64 位 MIPS 指令架构,它与大家平常所 知道的 X86 指令架构互不兼容,MIPS 指令架构由 MIPS 公司所创,属于 RISC 体系。过去,MIPS 架构的产品多见于工作站领域,索尼 PS2 游戏机所用的 “Emotion Engine”也采用 MIPS 指令,这些 MIPS 处理器的性能都非常强劲, 而龙芯 2 也属于这个阵营,在软件方面与上述产品完全兼容。 作者点评:MIPS 技术公司则是一家设计制造高性能、高档次及嵌入式 32 位和 64 位处理器的厂商。在通用方面,MIPS R 系列微处理器用于构建 SGI 的 高性能工作站、服务器和超级计算机系统。在嵌入式方面,MIPS K 系列微处理 器是目前仅次于 ARM 的用得最多的处理器之一 (1999 年以前 MIPS 是世界上用 得最多的处理器) ,其应用领域覆盖游戏机、路由器、激光打印机、掌上电脑等 各个方面。 由于服务器 RISC 处理器市场的激烈竞争结果导致 HP 公司放弃它的 PA-RISC 和 “私生子” Alpha 两种类型服务器处理器, “Alpha 技术” 而 则被 Intel 和 AMD 吸收应用到他们自身的处理器中; MIPS 处理器应用范围则较广, 对于 作为服务器 RISC 处理器来说,主要是应用于专门的图形工作站/服务器上;相 对来说,应用面较专业,因而竞争较少。就目前的服务器 RISC 处理器来说,主 要是 IBM 的 POWER 和 SUN 的 UltraSPARC 两大处理器之间的竞争;相对而 言,IBM 在这场 RISC 处理器竞争中是个大赢家。 UltraSPARC 处理器是 Sun 的命脉,以 UltraSPARC 为基础的 Unix 服务器曾 为 Sun 带进大量营收,不过,经过.com 泡沫化的冲击,加上 Unix 服务器市场 渐趋平稳, 在营收下滑之际, UltraSPARC 庞大的研发费用转为 Sun 沉重的负担。 面对自己的良机顿挫,Sun 近来连续宣布 UltraSPARC 新策略,大幅改变 UltraSPARC 产品计划 (roadmap)以改变目前的不利局势。 , 例如取消了 UltraSparc V 与 Gemini 处理器,而将资源重点转向代号为 Niagara 与 Rock 的高吞吐量计 算处理器。并且 Sun 和富士通计划在 2006 年之前将它们基于 Sparc 处理器的服 务器产品合并在一起,共同来对付他们的竞争对手 IBM;到底鹿死谁手,人们 正拭目以待。MIPS 架构: 架构:MIPS 体系结构首先是一种 RISC 架构 1 MIPS32 架构中有 32 个通用寄存器,其中$0(无论你怎么设置,这个寄存器中 保存的数据都是 0)和$31(保存函数调用 jal 的返回地址)有着特殊的用途, 其它的 寄存器可作为通用寄存器用于任何一条指令中。 虽然硬件没有强制性的指定寄存器使用规则,在实际使用中,这些寄存器的用 法都遵循一系列约定。这些约定与硬件确实无关,但如果你想使用别人的代码, 编译器和操作系统,你最好是遵循这些约定。 寄存器编号 助记符 用法 0 zero 永远返回值为 0 1 at 用做汇编器的暂时变量 2-3 v0, v1 子函数调用返回结果 4-7 a0-a3 子函数调用的参数 8-15 t0-t7 24-25 t8-t9 暂时变量,子函数使用时不需要保存与恢复 16-25 s0-s7 子函数寄存器变量。 子函数必须保存和恢复使用过的变量在函数 返 回之前,从而调用函数知道这些寄存器的值没有变化。 26,27 k0,k1 通常被中断或异常处理程序使用作为保存一些系统参数 28 gp 全局指针。 一些运行系统维护这个指针来更方便的存取 “static “和” extern& 变量。 29 sp 堆栈指针 30 s8/fp 第 9 个寄存器变量。子函数可以用来做桢指针 31 ra 子函数的返回地 2 MIPS32 中如果有 FPA(浮点协处理器),将会有 32 个浮点寄存器,按汇编语言 的约定为$f0~$f31,MIPS32 中只能实用偶数号的浮点寄存器,奇数号的用途是: 在做双精度的浮点运算时,存放该奇数号之前的偶数号浮点寄存器的剩余无法 放下的 32 位。比如在做双精度的浮点运算时,$1 存放$0 的剩余的部分,所以 在 MIPS32 中可以通过加载偶数号的浮点寄存器而把 64 位的双精度数据加载到 两个浮点寄存器中,每个寄存器存放 32 位。 比如: l.d $02, 24(t1) 被扩充为两个连续的寄存器加载: lwc1 $f0, 24(t1) lwc1 $f1, 28(t1) 3 MIPS 架构中没有 X86 中的 PC(程序计数)寄存器, 它的程序计数器不是一个寄 存器。因为在 MIPS 这样具有流水线结构的 CPU 中,程序计数器在同一时刻可 以有多个给定的值,如 jal 指令的返回地址跟随其后的第二条指令。 ... jal printf move $4, $6 xxx # return here after call MIPS32 中也没有条件码,比如在 X86 中常见的状态寄存器中的 Z、C 标志位在 MIPS32 中是没有的,但是 MIPS32 中是有状态寄存器 4 MIPS32 中不同于其它的 RISC 架构的地方是其有整数乘法部件,这个部件使 用两个特殊的寄存器 HI、LO,并且提供相应的指令 mfhi/mthi,mthi/mtlo 来实现 整数乘法结果--hi/lo 寄存器与通用寄存器之间的数据交换 5 数据加载与存储.MIPS CPU 可以在一个单一操作中存储 1 到 8 个字节。文档 中和用来组成指令助记符的 命名约定如下: C 名字 MIPS 名字 大小(字节) 汇编助记符 long long dword 8 &d&代表 ld int/long word 4 &w&代表 lw short halfword 2 &h&代表 lh char byte 1 &b&代表 lb 5.1 数据加载.包括这样几条记载指令 LB/LBU、LH/LHU、LW byte 和 short 的加载有两种方式。带符号扩展的 lb 和 lh 指令将数据值存放在 32 位寄存器的低位中,剩下的高位用符号位的值来扩充(位 7 如果是一个 byte,位 15 如果是一个 short)。这样就正确地将一个带符号整数放入一个 32 位的带符号 的寄存器中。 不带符号指令 lbu 和 lhu 用 0 来扩充数据,将数据存放纵 32 位寄存器的低位中, 并将高位用零来填充。 例如,如果一个 byte 字节宽度的存储器地址为 t1,其值为 0xFE(-2 或 254 如果 是非符 号数),那么将会在 t2 中放入 0xFFFFFFFE(-2 作为一个符号数)。t3 的值会是 0x000000FE(254 作 为一个非符号数) lb t2, 0(t1) lbu t3, 0(t1) 5.2 数据存储.包括这样几条指令 SB、SH、SW 由于加载就是把寄存器中的数据加载到内存中,所以不存在位扩展的问题。 6 CP0 (协处理器 0)--MIPS 处理器控制.用于控制和设置 MIPS CPU,里面包含了 一些寄存器,同过对这些寄存器的不同的位的操作可以实现对处理器的设置 CP0 类似于 X 86 只能有内核 (高优先级权限)访问的一些处理器资源,而前面提 到的通用寄存器 GPR 和 FPR 则可以有由所有的优先级权限访问 CP0 提供了中 断异常处理、内存管理(包括 CACHE、TLB)、外设管理等途径(而这些只能由高 优先级的内核才能访问到)。 6.1 常见的 MIPS CPU 控制寄存器包括: SR( 状态寄存器) 12 Config (CPU 参数设置寄存器)-16 EPC (例外程序寄存器)13、 CAUSE(导致中断和异常的原因寄存器) 14、 BadVaddr(地址错误时存放地址的寄存器)8 Index-0、Random-1、EntryLo0-2、EntryLo1-3、EntryHi-10、PageMask Count-9、Compare-11 共同组成了高精度的时钟 6.2CP0 寄存器的访问指令 mtc0 ts, #把通用寄存器 ts 中的数据送到协处理器 0 中的寄存器 nn mfc0 ts, #把送到协处理器 0 中寄存器 nn 的值送到通用寄存器 ts dmtc0 ts, #把通用寄存器 ts 中的数据送到协处理器 0 中的寄存器 nn dmfc0 ts, #把送到协处理器 0 中寄存器 nn 的值送到通用寄存器 ts 6.3 起作用的寄存器及其作用时机 加电后:你需要设置 SR 和 Config 寄存器,以确保 CPU 进入正确的引导状态, 并且 SR 寄存器还设置了中断码。以决定系统响应哪些中断。 进入任何异常:处理任何例外都会调用一个“通用异常处理程序” 。MIPS 体系 并没有为进入异常作任何的寄存器保存,也没有关于堆栈方面的任何支持,进 入异常时唯一保存的就是异常返回地址保存在 EPC 中。所以这些都需要操作系 统的软件实现。操作系统可以使用 K0 或者 K1 寄存器作为堆栈指针,指向某个 位置,并且在需要保存的寄存器保存到这个栈上。然后通过 Cause 寄存器找到 发生异常的原因,这样才能跳转到对应的中断处理程序中。 从异常返回: 从异常返回到 EPC 制定的地址之前要把 CPU 的状态回复到异常之 前,好象什么事情都没有发生一样。在 R3000 中使用 rfe 指令作这样的事情,但 是着条指令仅仅恢复了一些寄存器中的内容,但是并没有转移控制指令,你需 要把 EPC 内容保存到一个通用寄存器中, 然后调用 jr 指令返回到 EPC 指向的地 址处, 7 MIPS 的存储管理模型.MIPS32 中的存储器模型被划分为四个大块,其中: 0xx7fff,ffff(0~2G-1) USEG must be mapped (set page table and TLB)and set cache before use 0xx9fff,ffff(2G~2.5G-1) KSEG0 directly mapped(no need to set page table and TLB) but need to set cache before use 0xa000,0000~0xbfff,ffff(2.5G~3G-1) KSEG1 directly mapped(no need to set page table and TLB) and never use cache 0xc000,0000~0xffff,ffff(3G~4G-1) KSEG2 muse be mapped(set page table and TLB) and set cache before use 这样的存储器管理模型和 X86 差距比较大,X86 有一个实模式,内核在启动保 护模式之前,运行在实模式之下,直到设定了保护模式之后才能运行在保护模 式下。在 MIPS32 中没有保护模式那么系统是如何启动的呢? MIPS32 中的系统启动向量位于 KSEG1 中 0xbf10,0000,由于 KSEG1 是 directly mapped 的,所以直接对应了物理地址 0x1fc0,0000,你可以在内核中一直使用 0xa000,0000~0xbfff,ffff 之间的虚拟地址来访问物理地址 0~512M-1,在设置了 KSEG0 的 cache 之后,也可一使用 0xx9fff,ffff 之间的虚拟地址来访 问 0~512M-1 之间的物理地址。对于 512M 以上的物理地址只能由 KSEG2 和 USEG 通过页表访问。 8 MIPS32 中的状态寄存器 SR.状态寄存器来设置处理器的一些功能集合,包括 设置协处理器 0~3 的可用性的位 CU0~CU3(28~31) 复位向量 BEV 中断屏蔽位 8~15 KUc、IEc0~1,基本的 CPU 保护单位 KUc 为 1 时表示运行在内核态,为 0 时运行在用户模式。在内核态下,可以访 问 所 有 的 地 址 空 间 和 协 处 理 器 0, 运 行 在 用 户 态 下 值 只 能 访 问 0xx7fff,ffff 之间的地址空间。 KUp、IEp2~3 当异常发生时,硬件把 KUc、IEc 的值保存到 KUp、IEp 中,并且将 KUc、IEc 设置为[1,0]--内核态、关中断。异常返回时 rfe 指令可以把 KUp、IEp 的内容复 制到 KUc、IEc 中 KUo、IEo 当异常发生时, 硬件把 KUp、IEp 的值保存到 KUo、 中;返回时把 KUo、IEo IEo 的内容复制到 KUp、IEp 中。 上面三对 KU/IE 位构成了深度 2 的栈,异常发生时,硬件自动压栈,rfe 指令从 异常返回时,从栈中恢复数值 还有一些其它的功能和状态位,可以参考相应的文档 9 MIPS32 中的 Cause 寄存器 BD 位:EPC 中正常情况下存放了发生异常的指令,但是当这条指令存放在调转 指令的延迟槽中时,那么 EPC 中存放的是这个跳转指令,否则这条跳转指令将 得不到执行。 IP 位:告诉用户来临的中断 ExcCode:这是一个 5 位的代码,告诉你哪一条异常发生了,可以根据这个从通 用异常处理程序跳装到特定异常处理程序中。 10 MIPS32 的 C 语言中参数传递和返回值的约定 caller 至少使用 16bytes 堆栈空间存放参数,然后把这 16 bytes 存放到通用寄存 器 a0~a3 中, called subroutine 直接使用寄存器中的参数,同时 caller 堆栈中的 16bytes 的数据可以不去理会了。 需要理解的是带有浮点参数和结构体的参数传递,对于带有浮点参数的传递需 要看第一个参数是否是浮点, 如果是浮点则将参数放到 $f12 和$f14 这两个浮点 寄存器中,如果第一个参数不是浮点数,则不用浮点寄存器存放参数。对于结 构体的参数传递和 x86 类似 对于整数和指针类型的参数返回值一般通过通用寄存器 v0($2)返回, 对于浮点返 回类型,一般存放在$f0 中返回。LINUX MIPS 启动分析: 启动分析:系统加电起动后,MIPS 处理器默认的程序入口是 0xBFC00000,此地址在无缓 存的 KSEG1 的地址区域内,对应的物理地址是 0x1FC00000,即 CPU 从 0x1FC00000 开始取第一条指令, 这个地址在硬件上已经确定为 FLASH 的位置, Bootloader 将 Linux 内核映像拷贝到 RAM 中某个空闲地址处,然后一般有个 内存移动操作,目的地址在 arch/mips/Makefile 内指定: core-$(CONFIG_MIPS_ADM5120)+= arch/mips/adm5120/ load-$(CONFIG_MIPS_ADM5120)+= 0xffffffff 则最终 bootloader 定会将内核移到物理地址 0x002000 处 上 面 Makefile 里 指 定 的 的 load 地 址 , 最 后 会 被 编 译 系 统 写 入 到 arch/mips/kernel/vmlinux.lds 中: OUTPUT_ARCH(mips) ENTRY(kernel_entry) jiffies = jiffies_64; SECTIONS { . = 0xFFFFFFFF; /* read-only */ _text = .; /* Text and read-only data */ .text : { *(.text) ... 这个文件最终会以参数-Xlinker --script -Xlinker vmlinux.lds 的形式传给 gcc,并 最 终 传 给 链 接 器 ld 来 控 制 其 行 为 。 ld 会 将 .text 节 的 地 址 链 接 到 0xFFFFFFFF 处。 关于内核 ELF 文件的入口地址(Entry point),即 bootloader 移动完内核后,直接 跳转到的地址, ld 写入 ELF 的头中, 由 其会依次用下面的方法尝试设置入口点, 当遇到成功时则停止: a.命令行选项-e entry b.脚本中的 ENTRY(symbol) c.如果有定义 start 符号,则使用 start 符号(symbol) d.如果存在.text 节,则使用第一个字节的地址。 e.地址 0 注意到上面的 ld script 中, ENTRY 宏设置了内核的 entry point 是 kernel_entry, 用 因此内核取得控制权后执行的第一条指令是在 kernel_entry 处。 linux 内核启动的第一个阶段是从 /arch/mips/kernel/head.s 文件开始的。而此处 正是内核入口函数 kernel_entry(),该函数定义在/arch/mips/kernel/head.s 文件里。 kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为 创建系统中的第一个进程进行准备,接着用一段循环将内核映像的未初始化数 据段(bss 段,在_edata 和_end 之间)清零,最后跳转到 /init/main.c 中的 start_kernel()初始化硬件平台相关的代码。 ********************************** *********** NESTED(kernel_entry, 16, sp) # kernel entry point 声明函数 kernel_entry,函数的堆栈为 16 byte,返回地址保存在 $sp 寄存器 中。 ----------------------------- 声明函数入口 #define NESTED(symbol, framesize, rpc) \ . \ .align 2; \ .type symbol,@ \ .ent symbol,0; \ symbol: .frame sp, framesize, rpc 汇编伪指令 frame 用来声明堆栈布局。 它有三个参数: 1)第一个参数 framereg:声明用于访问局部堆栈的寄存器,一般为 $sp。 2)第二个参数 framesize:申明该函数已分配堆栈的大小,应该符合 $sp+framesize= 原来的 $sp。 3)第三个参数 returnreg:这个寄存器用来保存返回地址。 ---------------------------- kernel_entry_setup # cpu specific setup ---------------------------- 这个宏一般为空的, include/asm-mips/mach-generic/kernel-entry-init.h 文件中定 在 义。 某些 MIPS CPU 需要额外的设置一些控制寄存器,和具体的平台相关,一般为 空宏;某些多核 MIPS,启动时所有的 core 的入口一起指向 kernel_entry,然 后在该宏里分叉,boot core 继续往下,其它的则不停的判断循环,直到 boot core 唤醒之。 ---------------------------- setup_c0_status_pri 设置 cp0_status 寄存器 ---------------------------- .macro setup_c0_status_pri #ifdef CONFIG_64BIT setup_c0_status ST0_KX 0 #else setup_c0_status 0 0 #endif .endm ---------------------------- ARC64_TWIDDLE_PC 除非 CONFIG_ARC64,否则为空操作 ----------------------------- #ifdef CONFIG_MIPS_MT_SMTC mtc0 zero, CP0_TCCONTEXT__bss_start mfc0 t0, CP0_STATUS ori t0, t0, 0xff1f xori t0, t0, 0x001e mtc0 t0, CP0_STATUS #endif /* CONFIG_MIPS_MT_SMTC */ 宏定义 CONFIG_MIPS_MT_SMTC 是使用多核的 SMTC Linux 时定义的。 一般情况下不考虑。 MIPS 已经开发出 SMP Linux 的改进版,叫做 SMTC(线程上下文对称多处理) Linux。 SMTC Linux 能理解轻量级 TC 的概念,并能因此减少某些与 SMP Linux 相关 的开销。 ---------------------------- PTR_LA t0, __bss_start # clear .bss LONG_S zero, (t0) PTR_LA t1, __bss_stop - LONGSIZE 1: PTR_ADDIU t0, LONGSIZE LONG_S zero, (t0) bne t0, t1, 1b 清除 BSS 段,清 0。 变量 __bss_start 和 __bss_stop 在连接文件 arch/mips/kernel/vmlinux.lds 中 定义。 -------------------------------- LONG_S a0, fw_arg0 # firmware arguments LONG_S a1, fw_arg1 LONG_S a2, fw_arg2 LONG_S a3, fw_arg3 把 bootloader 传递给内核的启动参数保存在 fw_arg0, fw_arg1, fw_arg2, fw_arg3 变量中。 变量 fw_arg0 为内核参数的个数, 其余分别为字符串指针, *** =XXXX 为 的格式。 ---------------------------------- MTC0 zero, CP0_CONTEXT # clear context register 清除 CP0 的 context register,这个寄存器用来保存页表的起始地址。 ---------------------------------- PTR_LA $28, init_thread_union 初始化 $gp 寄存器,这个寄存器的地址指向一个 union, THREAD_SIZE 大小,最低处是一个 thread_info 结构 --------------------------------- PTR_LI sp, _THREAD_SIZE - 32 PTR_ADDU sp, $28 设置 $sp 寄 存 器 , 堆 栈 指 针 。 $sp = (init_thread_union 的 地 址 ) + _THREAD_SIZE - 32 的得出 $sp 指向这个 union 结构的结尾地址 -32 字节地址。 ---------------------------------- - set_saved_sp sp, t0, t1 把 这个 CPU 核的堆栈地址 $sp 保存到 kernelsp[NR_CPUS]数组。 --------------------------------- 如果定义了 CONFIG_SMP 宏,即多 CPU 核。 .macro set_saved_sp stackp temp temp2 #ifdef CONFIG_MIPS_MT_SMTC mfc0 \temp, CP0_TCBIND #else MFC0 \temp, CP0_CONTEXT #endif LONG_SRL \temp, PTEBASE_SHIFT LONG_S \stackp, kernelsp(\temp) .endm 如果没有定义 CONFIG_SMP 宏,单 CPU 核。 .macro set_saved_sp stackp temp temp2 LONG_S \stackp, kernelsp .endm 变量 kernelsp 的定义,在 arch/mips/kernel/setup.c 文件中。 unsigned long kernelsp[NR_CPUS]; 把 这个 CPU 核的堆栈地址 $sp 保存到 kernelsp[NR_CPUS]数组。 --------------------------------- PTR_SUBU sp, 4 * SZREG # init stack pointer --------------------------------- j start_kernel END(kernel_entry) 最后跳转到 /init/main.c 中的 start_kernel()初始化硬件平台相关的代码。 ---------------------------------- ********************************** ************ 这个 init_thread_union 变量在 arch/mips/kernel/init_task.c 文件中定义。 union thread_union init_thread_union __attribute__((__section__(&.data.init_task&), __aligned__(THREAD_SIZE))) = { INIT_THREAD_INFO(init_task) }; linux 内核启动的第一个阶段是从 /arch/mips/kernel/head.s 文件开始的。 而此处正是内核入口函数 kernel_entry(),该函数定义在/arch/mips/kernel/head.s 文 件里。 kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为 创建系统中的第一个进程进行准备,接着用一段循环将内核映像的未初始化数 据段(bss 段,在_edata 和_end 之间)清零, 最后跳转到 /arch/mips/kernel/main.c 中的 start_kernel()初始化硬件平台相关的 代码。 下面讲述 start_kernel()函数。 ********************************** ********* asmlinkage void __init start_kernel(void) { --------------------------------- char * command_ extern struct kernel_param __start___param[], __stop___param[]; 定义了核的参数数据结构 --------------------------------- smp_setup_processor_id(); 设置 SMP 多核的 CPU 核的 ID 号,单核不进行任何操作,我们不关心。 --------------------------------- unwind_init(); 在 MIPS 体系结构中,这个函数是个空函数(可能调用 setup_arch,配置 核的相关函数) --------------------------------- lockdep_init(); 初始化核依赖关系哈希表。 --------------------------------- local_irq_disable(); 关闭当前 CPU 核的中断 --------------------------------- early_boot_irqs_off(); 通过一个静态全局变量 early_boot_irqs_enabled 来帮助我们调试代码, 通过这个标记可以帮助我们知道是否在“early bootup code” , 也可以通过这个标志警告是否有无效的中断打开。 和 early_boot_irqs_on()函数配置使用,参考下面。 --------------------------------- early_init_irq_lock_class(); 每一个中断都有一个 IRQ 描述符 (struct irq_desc)来进行描述。 这个函数的主要作用是设置所有的 IRQ 描述符 (struct irq_desc) 的锁是 统一的锁, 还是每一个 IRQ 描述符 (struct irq_desc)都有一个小锁。 --------------------------------- lock_kernel(); 获取大内核锁,这种大内核锁锁定整个内核。 --------------------------------- tick_init(); 如果没有定义 CONFIG_GENERIC_CLOCKEVENTS 宏定义,则这个函 数为空函数, 如果定义了这个宏,这执行初始化 tick 控制功能,注册 clockevents 的框 架。 --------------------------------- boot_cpu_init(); 对于 CPU 核的系统来说,设置第一个 CPU 核为活跃 CPU 核。 对于单 CPU 核系统来说,设置 CPU 核为活跃 CPU 核。 参考《linux-mips 启动分析(2-1)。 》 --------------------------------- page_address_init(); 当 定 义 了 CONFIG_HIGHMEM 宏 , 并 且 没 有 定 义 WANT_PAGE_VIRTUAL 宏时,非空函数。 其他情况为空函数。 --------------------------------- printk(KERN_NOTICE); printk(linux_banner); 输出打印版本信息。 --------------------------------- setup_arch(&command_line); 每种体系结构都有自己的 setup_arch()函数,这些是体系结构相关的。 如何确定编译那个体系结构的 setup_arch()函数呢? 主要由 linux 源码树顶层 Makefile 中 ARCH 变量来决定的。 例如: MIPS 体系结构的。 SUBARCH := mips ARCH ?= $(SUBARCH) --------------------------------- setup_command_line(command_line); 保存未改变的 comand_line 到字符数组 static_command_line[] 中。 保存 boot_command_line 到字符数组 saved_command_line[]中。 --------------------------------- unwind_setup(); 空函数。 --------------------------------- setup_per_cpu_areas(); 如果没有定义 CONFIG_SMP 宏,则这个函数为空函数。 如果定义了 CONFIG_SMP 宏, 则这个 setup_per_cpu_areas() 函 数 给 每 个 CPU 分 配 内 存 , 并 拷 贝 .data.percpu 段的数据。 --------------------------------- 如果没有定义 CONFIG_SMP 宏,则这个函数为空函数。 如果定义了 CONFIG_SMP 宏,这个函数 smp_prepare_boot_cpu(); --------------------------------- sched_init(); 核心进程调度器初始化,调度器的初始化优先于任何中断的建立(包括 timer 中断) 。 并 且 初 始 化 进 程 0 , 即 idle 进 程 , 但 是 并 没 有 设 置 idle 进 程 的 NEED_RESCHED 标志, 以完成内核剩余的启动部分。 --------------------------------- preempt_disable(); 进制内核的抢占。使当前进程的 struct thread_info 结构 preempt_count 成员的值增加 1。 --------------------------------- 建立各个节点的管理区的 zonelist,便于分配内存的 fallback 使用。 这个链表的作用: 这个链表是为了在一个分配不能够满足时可以考察下一 个管理区来设置了。 在考察结束时,分配将从 ZONE_HIGHMEM 回退到 ZONE_NORMAL, 在分配时从 ZONE_NORMAL 退回到 ZONE_DMA 就不会回退了。 build_all_zonelists(); --------------------------------- page_alloc_init(); --------------------------------- 在 MIPS 体系结构下,这个函数已经在 arch_mem_init() 函数中调用 了一次。 这个函数的具体分析详细分析,请看《linux-mips 启动分析(4)。 》 所以这个函数直接返回。 parse_early_param(); --------------------------------- 打印 linux 启动命令行参数。 printk (KERN_NOTICE &Kernel command line: %s\n&, boot_command_line); --------------------------------- 这个函数的意思对 linux 启动命令行参数进行再分析和处理。 这两个变量 __start___param 和 __stop___param 在 链接脚本 arch/mips/kernel/vmlinux.lds 中定义。 最后一个参数为,当不能够识别 linux 启动命令行参数时,调用的函数。 parse_args(&Booting kernel&, static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption); --------------------------------- 检查中断是否已经打开了,如果已将打开了,关闭中断。 if (!irqs_disabled()) { local_irq_disable(); } --------------------------------- sort_main_extable(); 这个函数对内核建立的异常处理调用函数表(exception table) 根据异常的向量号进行堆排序。 --------------------------------- 设置 CPU 的异常处理函数,TLB 重填,cache 出错,还有通用异常处理表 的初始化。 trap_init(); --------------------------------- 初始化 RCU 机制,这个步骤必须比本地 timer 的初始化早。 rcu_init(); --------------------------------- 用来初始化中断处理硬件相关的寄存器和中断描述符数组 irq_desc[] 数组, 每个中断号都有一个对应的中断描述符。 参考《linux-mips 启动分析(11)。 》 init_IRQ(); -------------------------------- 系统在初始化阶段动态的分配了 4 个 hashtable,并把它们的地址存入 pid_hash[] 数组。 便于从 PID 查找 进程描述符地址。MIPS 异常入口处理: 异常入口处理 处理:异常向量:异常处理开始的地方 大多数 CISC 理器由硬件(或微代码)来分析异常,根据发生的异常类烈把 CPU 发送到不同的入口点。甚至连中断都根据哪个中断输入信号激活而在不同的入 口点处理时,就叫做向量化中断(vectored interrupts) 。历史上,MIPS CPU 对此 做得很少。如果觉得这像是一个严重的疏忽,请考虑下面的因素。首先,在实 践中向量化中断并不像想象的那样有用。在大多数操作系统中,中断处理程序 共享代码(为了节省寄存器等原因) ,常见的是 CISC 微代码辛辛苦苦花费时间 将中断发送到不同的入口点,操作系统软件得到一个代号然后又花更多的时间 跳转到公共的处理程序。 其次,很难想象如果不用微代码而完全由硬件能做多少异常分析;在 RISC CPU 上,选用通常的代码就已经足够快了。 在此处和别处,你应该对 RISC 这代 CPU 相对其外围设备有多快心里有数。一 个有用的中断处理例程要读写一些外设寄存器,在 21 世纪初期的 CPU ,一个外 部总线周期可能要花费 50―200 个内部时钟周期。很容易在 MIPS CPU 上写出 比单个外设访问快的中断处理代码,所以这不大可能成为一个性能瓶颈。2003 年版的 MIPS32 标准中加入的向量化中断选项几乎没人用的事实, 进一步证明了 这一点。 然而,即使在 MIPS 中,也不是所有异常都是平等的。在体系结构发展过程中也 出现了一些差别。所以我们可以做些区分。 用户态地址的 TLB 重填充:在受保护的操作系统中有个与地址转换系统相关的 异常特别频繁。TLB 硬件只能保存适度数最的地址转换,在一个运行着虚拟存 储器的操作系统的负担很重的机器上,经常遇到应用程序运行到一个 TLB 未记 录其转换的地址――个称为 TLB 未命中的事件 (因为 TLB 被当作一个由软件管 理的高速缓存) 。 当 RISC CPU 刚刚推出的时候,用软件来处理这种情况引发了很多争议,MIPS CPU 为一个选中的 TLB 重填方案提供了重要的支持。硬件提供了足够的帮助使 得为选中的重填方案设计的异常处理程序能够在 13 个时钟周期完成。 作为该方案其中的一部分,常见的一类 TLB 重填被赋予了一个不同于其它异常 的入口点,这样精心优化过的重填代码不用再花费时间判断到底发生的是哪种 异常。 64 位地址空间的 TLB 重填:想要利用 64 位 CPU 上巨大的地址空间的任务,其 地址转换采用略有不同的寄存器布局和另外的 TLB 重填例程;MIPS 称其为 XTLB(我猜“X”代表扩展) 。与上面一样,为了保证高效率用了一个独立的入 口点。 不经过高速缓存的替代入口点:为了在异常处理时获得良好性能,中断入口点 必须位于使用高速缓存的存储区。但是在系统启动的时候不希望这样。复位或 者上电后,高速缓存在初始化之前不能使用。如果你需要一个健壮的和能够自 我诊断的启动代码,对于引导早期检测到的异常,你只得用不经过高速缓存的 只读存储器的入口点。在 MIPS CPU 里,没有不用高速缓存的“模式”D 只有 不经过高速缓存的程序地址区域――所以有一个模式位 SR ( BEV)用来把异常 入口点另行分配到不用高速缓存的、启动安全的 ksegl 区。 奇偶/ECC 出错:MIPS32 CPU 可以检测一个数据错误(通常在来自主存的数 据中,但是直到在高速缓存中用到才会发现)然后自陷。在高速缓存区来处理 高速缓存的错误显然很蠢,所以不管 SR (BEV)状态是什么,高速缓存出错异 常的入口点总是位于不经过高速缓存的地址空间。 复位:对很多目的来说,把复位看作另外一种异常是有道理的,尤其是当许多 CPU 对于冷复位 (此时 CPU 彻底重新配置; 和重新上电不可区分) 和热启动 (此 时软件彻底重新初始化)使用相同的入口点的时候.其实,不可屏蔽中断 NMI 的 结果相当于热复位的稍弱的版本,区别仅在于它要等待当前指令和尚未完成的 存取操作结束才生效。 中断: 作为 MIPS32 (还有 IDT PMC―Sierra 的早期的一些 CPU) 的一个可选项, 可以设置 CPU 把中断异常发送到单独的入口点。这很方便但很少人用:可能软 件作者不能让自己为一个并不普遍的特性而让操作系统作为特例进行处理。 进一步,在其中某些 CPU 上,你可以使能向量化的中断操作----不同的中断使用 多个不同的入口点。这是更具实质性的变化;在本章别的地方讲到过,MIPS 的 传统是中断只在软件处理时才有优先级。但当你有两个活动的中断而不得不选 择一个中断入口点时,硬件必须决定到底哪个优先级更高。因而这个变化对软 件的影响要大得多,因为软件丧失了对中断优先级的控制;操作系统维护人员 和硬件工程师得紧密配合才行。 所有的异常入口点都位于 MIPS 存储器映像中不作地址转换的区域, 不要高速缓 存的入口点位于 ksegl,需要高速缓存的位于 kseg0。不要高速缓存的入口点当 SR ( BEV)置位时是固定的,但是当 SR ( BEV)清零时,就可以对 EBase 寄存 器进行编程来平移所有入口点――一起----到别的内存块。当你的 CPU 是共享 kseg0 存储器的多处理器系统的一部分,但是想要和系统中其它 CPU 分开的单 独异常入口点的时候,可以移动中断基址的能力就特别有用。 在这些地址区,下表给出的 32 位地址通过符号扩展到 64 位内存映像。在 32 位 中的程序地址 0x 和 64 位中的 0xFFFF FFFF
相同。下表描 述了仅有 32 位地址时的入口点D 表中的 BASE 代表编程到 EBase 寄存器中的 异常基址。 最初的异常向量间的距离缺省为 128 ( 0x80) 字节空间, 可能是因为最初的 MIPS 体系结构师觉得 32 条指令足够编码基本的异常处理例程了,不浪费太多内存而 且省下了一条分支指令!现代的程序员很少有这么节省的。 表:异常入口点下面是 MIPS CPU 决定处理一个异常时所采取的步骤: 1.设置 EPC 指向重新开始的地址。 2.设置 SR ( EXL)位,强制 CPU 进入内核态(高特权级)并且禁止中断。 3.设置 Cause 寄存器这样软件可以看到发生异常的原因。在地址异常时,也要 设置 BadVAddr。存储器管理系统异常还要设置某些 MMU 寄存器; 4.然后 CPU 开始从异常入口点取指,此后一切交给软件处理。 很短的异常处理例程可以全程运行在 SR ( EXL)置位的状态(即我们所说的异 常模式) ,从来不会需要碰 SR 的其余部分。对于那些先保存状态然后将控制交 给复杂软件的较传统的异常处理程序,异常特权级提供一个保护伞让系统软件 可以在其下安全地保存必要的状态D 包括老的 SR 值。ARM 与 MIPS 比较: 比较:1.流水线结构 pipeline - MIPS 是最简单的体系结构之一, 所以使大学喜欢选择 MIPS 体系结构来介绍 计算体系结构课程。 - ARM has barrel shifter shifter 是两面性的,一方面它可以提高数学逻辑运算速度,另一方面它也增加了 硬件的复杂性。所以和可以完成同样功能的 adder/shift register 相比,效率更高, 但是也占用更多的芯片面积。 - MIPS have &branch delay slot& and &load delay slot& MIPS 使用编译器来解决上面的两个问题。因为 MIPS 最初的设计思想就是使用 简单的 RISC 硬体,然后靠编译器及其他软体技术,来达成 RISC 的完整概念。 2.指令结构 instruction - MIPS have 32bit and 64bit architecture,but ARM only have 32bit architecture ARM11 局部 64 位 - MIPS 是开放式的架构,用户可以在开发的内核中加入自己的指令, - ARM has 4-bit condition code in every instruction ARM 在这一点很像 x86。MIPS 在 MIPS IV 也加入&conditional move&指令,来 提高 pipeline 的效率。 - ARM has pre- and post-increment addressing modes auto-increment/decrement on load/store instructions - 在节省代码空间方面,MIPS16 很类似 ARM Thumb 3.寄存器 register -由于 MIPS 内核中有 32 个注册器(Register) ,而 ARM 只有 16 个,这种结构设 计上的先天优势,决定了在同等性能表现下,MIPS 的芯片面积和功耗会更小。 - ARM 有一组特殊用途寄存器 cp0-cp15,可以使用 MCR,MRC 等指令控制;相对 应的,MIPS 也有 cp0 0-30,使用 mfc0,mtc0 指令控制。 - Register banking in ARM. r8-r12 FIQr13:SP r14:LR 感觉不出 banked register 有什么好处。 - MIPS has a hard-wired-to-zero register ,but ARM not MIPS use register $0 for Zero 4.地址空间 address space - MIPS 起始地址是 0xbfc00000,会有 4Mbyte 的大小限制,但一般 MIPS 芯片都 会采取一些方法解决这个问题。 ARM 没有这种问题。 MIPS24K 起始地址改到了 0xbf000000,现在有 16Mbyte 的空间了。 - MIPS don't have to turn paging on to enable the cache. MIPS have the address space for both cache and un-cache but ARM need enable/disable cache 5.功能 function - Float point: MIPS64 has. ARM's support for FP is limited, and usually not included, and it is a 32 bit architecture - ARM use JTAG, MIPS use EJTAG。Debug 工具一般两种都支持。使用起来感觉 差不多。 6.性能 performance - 具体性能比较,因为差异性太大,所以很难分出谁好谁坏。从个人经验来讲 MIPS4k 和 ARM9 基本上是同一个级别的,但 ARM9 性能似乎要比 MIPS4K 好。 同样是 32bit 的 MIPS24K 性能上比 MIPS4K 有很大提升,也应该比 ARM9 要好 些。 因为没有用过 ARM11 和 MIPS34K 的芯片,没法比较,但感觉这两个似乎是一 个级别的。 7.应用 - 在 1000MHz 以上的应用,很难找到采用 ARM 架构的产品。 MIPS 架构用在 200MHz 或者是 266MHz 以下的应用比较少,而这恰恰是 ARM 的 主攻市场。 - ARM 在手机等便携式领域,MIPS 在住宅网关、线缆调制解调器、线缆机顶盒 等 - ARM 采用硬核授权;MIPS 采用软核授权, 用户可以自己配置, 做自己的产品。MIPS 多线程实现: 多线程实现:RISC CPU 喜欢把策略决定留给软件负责,而不是交给硬件。RISC 的原则倾向 于灵活通用的解决安案。这使得 MIPS MT 有以下几个特点: 1.互相可见线程寄存器:这给了操作系统对多线程的完全控制。mftr 和 mttr 是特 权指令允许操作系统软件掌握了解不同 TC 的寄存器。 这是所有跨线程初始化和 维护的基础。 2.启动时为单线程:这点遵循了引导软件最少差异的原则。操作系统或者引导软 件在准备好后可以唤醒更多线程。 3.同时提供两种形式的多线程:最小开销线程引擎和完全的“虚拟 CPU”可以 混合匹配。基本的 MT 特性就是最小线程引擎,称作 TC。如果我们就停留在这 一步,CPU 中除了直接的线程环境外其它所有的东西都是共享的。 其实我们允许 MT CPU 复制一个 Mips32/64 兼容的 CPU 所需要的一切。这些类 似 CPU 的寄存器组合其它资源的每一个叫做 VPE: 一个或多个 TC 和 VPE 相关 联,并共享他们的寄存器和资源,VPE 中的 TC 构成一个“虚拟处理单元, ”这 就是 VPE 缩写的来历。一个 CPU 可以实现多个 VPE 以生成看上去像多个 CPU 的东西。 MIPS 公司第一个 MT 产品,34-K CPU,最多可以有五个 TC 在两个 VPE 之间 任意分配。 VPE 间可以共享很多东西,当然每个 VPE 从软件上等价于一个 MIPS32 CPU: 这不是真的双 CPU,只是看上去很像。高速缓存、主流水线、控制逻辑、算术逻 辑单元和系统接口都是共享的。 TLB 在不同 VPE 间可以用硬件隔开, 或者共享。 机器运行的每个指令都有一个 TC 号,用来选择具体的环境。当该指令访问某些 状态的时候――例如读写通用寄存器的时候――就用其 TC 号来扩展已经定义 在指令内的寄存器号域。 一条指令根据不同的 TC 号看到的是一组不同的寄存器 集:非常简单但是的确有效。 由于上面提到的 TC/VPE 技巧,MIPS MT 不是那么简单。这条指令可能是 TC#5 的或者 VPE#1.这个应当没问题。当然更为复杂的是,让那些不能简单的归为寄 存器的 CPU 资源工作。但是那已经不属于体系结构,而是属于具体实现了;这些 材料你必须阅读 CPU 手册。 MT 规范没有硬件规定,但是实际的 MT CPU 需要能快速切换线程,否则线程 切换损失的时间可能会吃掉潜在的吞吐量增加。其它东西都一样,通常最好在 尽可能细的粒度上混合线程: 在单发射 CPU 中, 这等于说只要多个线程准备好, 每个时钟周期从不同线程各发射一条指令。 一、MT 新增的 CP0 寄存器 这些寄存器可以分为三组:提供给每个 TC 的、提供给每个 VPE 的、还有几个 是提供给每个 CPU 的。最后一个是提供 CPU 的资源清单的寄存器,可以共享; 这些不进一步说明。 每个 TC 的寄存器包括: 1.TCHalt:一位的寄存器, 写入 1 让目标停机。 目标 TC 停机时, 其状态是稳定的, 可以安全写入它的寄存器。 2.TCRestart: 线程“PC”――当线程停止时,这就是下次运行时第一条指令的地 址。当线程不是停止状态时,没有太多的意仪。 3.TCStatus:各 TC 的“遗留”域――内核/用户状态、ASID、以及指令集选项标 志。也有一个标志表明线程停止的指令位于分支延迟槽。 4.TCBind:包含与该 TC 关联的 VPE 的 ID,以及该 TC 的 ID,后者是只读的。 5.TCContext: 一个纯粹由软件读写的寄存器,典型的用法是存放特定操作系统 的线程 ID。 各 VPE 的寄存器 VPEControl 用于操作系统运行过程中可以合理改变的控制域, 而 VPEConf0-1 的配置域极可能是一次设置后就再也不变了。 二、异常和中断 单线程的 MIPS 体系结构的 CPU 的异常通常对流水线式极具破坏性的,常用的 实现方法丢弃了许多执行状态。MIPS MT 机器上的异常发生在线程环境中―― 其它的线程可望不受打扰继续运行。所以可以预计当我们在多线程机器上重新 定义线程时会有困难。 心中要记住操作系统是一个程序。具体哪个 TC 执行哪一部分可能关系不大。如 每个异常处理程序自身就构成一个线程。所以要运行一个异常处理程序,我们 需要借用一个 TC 来中断其原来的线程并运行这个异常处理线程。 异常分两种类型。中断是异步的――它们的发生原因与具体指令无关。但是大 多数其它异常是同步的――它们与特定的指令相联系。我们先看看后一种类型。 同步异常处理程序由导致异常的指令的 TC 运行。TC 立即停止正常线程上的工 作,开始从适当的异常处理程序取出指令执行。 MIPS MT ASE 规定一旦 TC 进入异常状态, 同一 VPE 内所有其它 TC 一律暂停。 直到异常处理程序离开异常模式,其他 TC 的指令才可以执行:就是说,要一直 等到 SR 位被异常处理程序结尾的 eret 指令清零, 或者因异常处理程序分支到操 作系统较少受限制的代码部分而清零。异常处理程序尽量减少花费在异常态的 时间本来就是良好的做法,大多数操作系统都是这样做的。 但是如果你的应用程序需要最大化并发性,你应当考虑最小化异常――你可以 用一个在 ITC 访问或者 yield 条件上阻塞的线程。当然安排异常处理程序保存必 要状态以便推出异常模式。 三、MIPS MT 和中断 在 MIPS 体系结构中,中断管理通过 CP0 寄存器进行。这些寄存器为每个 VPE 而不是每个 TC 复制一份,所以中断屏蔽和传递由每个 VPE 管理。甚至硬布线 连到核的中断信号也是由每个 VPE 处理。 每个中断输入可能只连到一个 VPE 上, 也可能连到全部 VPE 上, 所以哪个 VPE 接收中断涉及到系统的硬件布线方式,但也可能和软件配置有关。如果你不加 屏蔽地把一个中断连接到多个 VPE 上, 任意几个 VPE 都可能响应中断异常―― 你大概不希望发生这种情况,所以要么别连线要么别使某些中断。 中断异常可以由于 VPE 相联的任何可用的 TC 响应。 MIPS 体系结构已经提供了多种方式来拒绝中断异常。异常模式、全部中断使能 标志和单个终端的屏蔽位,即 SR(EXL)、SR(IE)、SR(IM)位――这里列出的不 是全部――都可以防止该 VPE 上的线程发生中断。 MIPS MT 规范增加了又一个不响应中断的理由。现在你可以设置一个新的每个 TC 的 CP0 寄存器域,该域可以防止特定 TC 被用于中断异常。 四、线程优先级提示 有些应用程序开发人员对于可以控制 CPU 带宽偏向一个线程而不是另一个线程 的能力表示感兴趣。 MIPS MT 规范提供了这个特性, 尽管并没有说明怎样实现。 一个调度策略管理器是 CPU 外部的一块逻辑,可以针对具体应用定制。PM 为 每个 TC 生成一个 2 位的优先级。早核里面,总是优先执行可运行的高优先级 TC,而不是可运行的低优先级 TC。 五、用户权限的动态线程创建――fork 指令 多线程硬件还有一个很有意思但还没有商用化探索的应用,就是提供另外一种 发现及利用串行算法的并行性的方法。例如:一个循环可以通过两个线程分别 运行其奇数和偶数次循环来优化。 如果这要在带保护的操作系统的支持下完成,就要求有一个非常高效的机制即 需发射新程。MIPS MT 的 fork 指令提供了这样一种机制。 这样 fork d,s,t 成了一条用户级指令。 如果一切正常。 该指令在准备就绪的 TC 上 开始一个新线程,从 s 所指向的指令处开始执行。子线程的 d 获得父亲线程的 t 值。 操作系统通过维护一个分支就绪到那时此外空闲的 TC 缓冲池通过一个标志位 来识别。当新线程完成应用程序的任务后,就终止执行,并用一条 yield#0 指令 把 TC 交还给缓冲池。

我要回帖

更多关于 mips li指令 的文章

 

随机推荐