1_MIPS32架构与指令集初步

MIPS32 架构与指令集初步

因为 MIPS 处理器结构分为很多种, 有 16 位的 32 位的 64 位的, 我们主要针对 32 位进行讲解.

MIPS 架构---最经典的 RISC

处理器按照大类型来分的话可以分为 CISC 和 RISC, 那么我们以前讲的 X86 实际上就是 CISC 的一个典型.

一个复杂指令集, 一个是精简指令集, 那么何谓复杂, 何谓精简, 大家需要对比才知道. 所以我们讲完 X86 之后就来讲 MIPS, 这是一个最经典的 RISC 指令集, 非常优雅.

首先讲讲 MIPS 这个简写, 它的意思是, 我这个处理器啊, 没有一个互锁的这么一个流水线. 具体的意思是, 它尽量用软件方法来避免流水线中的数据相关的问题. 这里不深究了.

我们以后对比会发现它跟 X86 不同的地方, 我们会非常明显地看到所谓这精简的指令集, 它到底精简在什么地方.

然后 MIPS 这其实是一个双关语, 就是每秒钟能够执行多少百万指令, 这是一个重要的处理器评价指标之一.

  • MIPS 的由来与发展

    • Microprocessor without Interlocked Pipeline Stages (Millions of Instruction Per Second 的双关语)

    • 尽量利用软件办法避免流水线中的数据相关问题

    • 1981 年, 斯坦福大学教授 Hennessy 领导团队, 设计出第一个 MIPS 架构的处理器

  • MIPS 处理器是八十年代中期 RISC CPU 设计的一大热点 :

    • 在很多领域, 比如 Sony, Nintendo 的游戏机, Cisco 的路由器和 SGI 超级计算机中使用

我们在最开始就说过 CISC RISC 二者实际上在走向融合

  • 设计理念上 MIPS 强调软硬件协同提高性能, 同时简化硬件设计

  • 中国的龙芯采用的是 MIPS 指令架构

MIPS 的经典流水线结构

经典的 MIPS 架构分五段

  • Fetch instruction 指令抓取

  • Read registers 寄存器读取

  • Arithmetic operation 执行

  • Memory access 访存

  • Wirte back 寄存器内容的写回

这是教科书式的五级流水线的寄存器

但是五级流水是不是针对五个微处理器上的周期呢, 其实不是. 最经典的, 最早的 MIPS 架构五级流水没错, 但它是四个周期. 这个不用深究, 了解就好.

流水线五段这教科书式的说法没错, 如果你想继续往下了解的话, 有本书叫 \<see MIPS run>, 非常经典的一本讲 MIPS 架构的一本书, 里面就提到了最经典的 MIPS 流水线架构...

五级没错, 但它实际上是用四个周期来做完五段的流水. 也就是物理上是四段, 但逻辑上是五段. 具体的怎么五段法, 下面给画出来了 :

指令集特点 (与 X86 指令集对比)

首先说明一下, 我们讲 MIPS 架构, 主要是针对 MIPS 32, 也就是说它里面的指令长度是 32 位.

还有一点就是 MIPS 32 中的字的长度, 和 x86 有点不太一样.

我们说的 x86 由于后向兼容, 这里面 x86 所谓的字是 16 bit, 因为 80x86 里面就用了字这个单位, 所以我们说 x86 里面双字是 32 bit. 而在 MIPS 里面, 字的长度就是 32 bit. 这点不一样.

大家就对比这几个方面

  • 第一个是指令的长度怎么样.

那么 MIPS 32 的指令长度是固定的, 就是 32 bit; x86-32 是变长, 我们说过应该是 1~14 个字节可以变长.

定长指令的好处就是, 我流水线的取址段这个是固定的, 因为我每次取 32 位就可以了, 我不用去判断指令的边界在什么地方.

然后 x86 这个更麻烦一点, 因为你不知道下一条指令有多长, 你得取一段然后判断指令边界, 到底哪段到哪段才是有效的指令.

但是这个 MIPS 32 的固定字长有一个比较糟糕的缺点, 就是你想想看你总的字段长度, 指令长度是 32 位, 那么里面的常数字段肯定是小于 32 位. 比方说无条件跳转指令在 MIPS 里面, 立即数方式的目标地址是 26 位, 其它指令的一般为 16 位. 那么可能你这个常数作为运算用的一个立即数, 或者作为你取址的时候的地址的一个 offset.

这实际上是一个挺麻烦的事情, 因为如果你要装载一个立即数, 或者你去计算一个立即数, 或者你要访问一个地址里面的 offset, 它不能被 16 位长度来表示的话, 有一个问题就是你汇编指令不好写.

x86 里面是没有这个问题的, 反正我的字长可以变, 如果你 16 位里放得下, 那这个常数字段就是 16 位的; 如果放不下就 32 位; 再放不下我可以再变长一点. 但是 MIPS 32 这里面就不行了, 具体解决方案回头再说.

  • 再一个就是看一下指令当中的内存操作数

这个在我们刚开始的时候也讲过, 就是 RISC CISC 正在走向融合, 如果你一定要说它们有什么非常标志性的区别的话, 就是说 CISC 里面一些计算指令里面, 有的内存操作数是可以用作计算的, 而在 RISC 里面内存操作数是无法直接参与计算的, 你只能用 load store 指令来访问这些数据, 内存中的数据都搬到寄存器里面之后, 才能进行运算.

这一大原因就是 MIPS 32 的处理器, 它的流水线就是这么设计的, 你看 1 2 3 4 第 4 段, 才是 memory 段才能去访问内存. 所以这种情况下, 你取进来的数已经没有再作为运算的操作数之一了.

  • 再就是它们的计算操作指令的个数分别为多少

x86 我们讲过了, 多为两个, 相当于 A 加 B 到 A.

MIPS 的话就是 A 加 B 到 C.

这样的话, 编译器会比较高兴. 就是 A 加 B 到 C 对编译优化是有好处的, 不然你来回在里面老是倒腾这个数.

这么做的一大原因是 MIPS 32 它出来的时候, 里面通用寄存器的个数就是 32 个. 那 x86 的话, 我们知道, 32 位的情况下就是 8 个, 64 位下好不容易凑到 16 个, 很少. 这里说的寄存器是指令集可以看到的寄存器, 或者说你编译器可以看到的寄存器.

它一个少一个多嘛, 我寄存器个数比较多, 我就在指令里面多用一点, 这对于编译来说是有好处的.

  • 通用寄存器的个数

MIPS 32 是 32 个, x86 是 8 个, 64 位才是 16 个.

MIPS 里面有一个特殊的 0 号寄存器, 它的值永远为 0, 它就认为 0 是最常用的常数, 就直接用一个寄存器来代表了. 它可以省编码.

你甚至可以把 0 号寄存器作为你运算的一个目的寄存器, 你可以去改它, 但是改完之后它还是 0, 也不出错.

  • 条件码

x86 里面的条件码, 就是记录你最近执行一条指令的一些状态, 但是在 MIPS 里面就没有条件码, MIPS 所有信息都存于通用寄存器.

MIPS 条件判断通过检测通用寄存器的值来进行. 以后讲到条件跳转指令的时候大家会有一些直观的感受.

  • 访存操作

刚才提到过 RISC 代表就是 MIPS, 只能通过 load/store 指令, 当然这是两类指令, 只能通过这两类指令来进行内存的操作访问.

MIPS 一般情况下需要地址对齐, 地址对齐就是说你 load 一个 32 位字的话, 一个字的话就需要 4 byte 来对齐, 你 load 一个双字的话就需要 8 byte 对齐.

x86 为了提高效率, 软件反正给你做了对齐了, 但是 x86 本身硬件支持这种不对齐操作.

MIPS 32 不一样, 它硬件本身不支持这种不对齐的 store/load 操作

MIPS 的寻址方式只有一种, 基址是一个基址寄存器, 寄存器的值加上一个常数 offset 就完成了一个唯一的寻址.


总归就是 MIPS 硬件简单, x86 指令集支持的 MIPS 不支持, 不支持怎么办呢, 就只能靠软件解决了.

MIPS 传统上有一个通用寄存器也是可以作为栈顶寄存器的, 但这个事情完全是由软件来做的.

MIPS 有一条指令 jal, jal 指令把返回地址保存在 31 号寄存器, 不通过栈.

程序员可见的流水线效果

还有一点很有意思, 就是它精简到什么一个程度呢, 精简到你硬件上的硬件流水线, 就是处理器这个流水线, 流水线里面的效果, 可以被程序员看见.

硬件上的一些效果被程序员直接可见的话, 并不是一个非常好的设计. 当然的确提高了效能.

在 MIPS 情况下, 程序员可见的流水线效果叫作 Branch delay slot.

  • Branch Delay Slot (跳转延迟)

我们以前讲 x86 control flow 的时候也提到过. 如果你程序连续跑着, 碰到一个条件跳转指令, 那么这种情况下, 因为你硬件是通过流水线一条一条截取指令, 而你这条指令是跳转还是不跳转, 这个得等到你的流水线走到一定的程度上才能知道.

但是你理想情况下, 每一个周期, 你就得往里面提供一条指令, 顺序地去跑嘛. 那这样的话呢, 你碰到一条 Branch 指令的时候, 怎么去取址就成问题了, 就你是连续跑还是跳转过去, 不知道.

现在处理器很多都通过 Branch prediction 分支预测来解决这个问题, 从而提高效率.

而 MIPS 一开始是怎么做的呢, 因为它当时最早设计这个流水线的时候, 为了精简起见, 在流水线里面, 条件跳转指令的目标地址计算需要在 ALU 段 (图上箭头) 这个地方获得.

紧接着指令已经进入流水线了, 但这条指令应该进哪个, 这个时候还不知道. 看这个图, 是吧不知道. 那么它就把这条指令所在位置称之为延迟槽 (Branch delay slot), 延迟槽就是它需要有程序员获得编译器优化来填充这个 slot.

就是在 MIPS 里头, 一条条件跳转指令, 这条指令紧挨着它的后一条指令, 它们这两条指令是一体的. 什么是一体呢, 就是说它们都是同时执行的, 不管你这个指令是跳还是不跳, 你紧挨着的这个下一条指令, 这条指令的位置就是延迟槽.

这条指令一定是执行的, 因为如果按照原来的这种设计方法, 同时你又不想在硬件方面做得比较复杂, 进行预测什么的话, 那么你 branch 指令后面 delay slot 这条指令, 填和不填就成问题了.

有一个比较笨的方法是, 流水线程序停一下, 等你 ALU 算出这个结果之后, 我再去拿这个结果, 肯定不会错.

但是这样就相对损失的一个周期, 那 MIPS 为了弥补这一个周期, 就把这个问题开放出来, 开放给程序员或者编译器, 就是希望有一个程序员大神或者编译器来填这条 delay slot.

填进去的这条指令, 和它前面紧挨着的条件跳转指令, 是同时进行的. 不管是跳转还是不跳转, 都要执行完.

这个指令应该怎么填呢, 当然我可以添一条空指令进去, 什么都不做, 但这样就没有意义了. 一般情况下, 要添加一个无关指令, 就是说你把这个 branch 之前的一条指令, 跳转指令之前的一条指令填进去. 你这之前的一条指令和这个跳转没有什么关系, 跟它没有数据依赖这种关系, 就可以插在这个 delay slot 里面.

因为我这条指令和跳转没有关系的话, 我硬件就简单了, 不需要做什么预测或者是停止之类的. 我就是通过软件把这条无关的指令插到这里面去, 反正是一块执行, 这样我整体的效率又提高了.

个人认为这流水线里面的问题, 应该硬件来解决掉. 但是 MIPS 由于历史上的一些原因, 反正就是把 branch delay slot 这么一直规定下来了.

简单总结

RISC 的设计思想在于简化计算机指令功能、规格化指令设计, 使得各个指令的流水线分段较为均匀, 且操作相对简单规整, 从而提高主频

另外一个非常重要的特点就是说, 采用 load/store 结构, 只有 load/store 这两类指令能够访问内存, 把数据在内存和寄存器之间搬运. 搬运计算机内核之后, 其它指令才能够在计算机之间进行各种的数据处理, 提高处理速度.

还有就是它依赖软件, 主要就是编译了, 它依赖软件来实现优化以及完成复杂的功能, 尤其包括 branch delay slot.

MIPS 汇编指令初步

接下来讲 MIPS 初步, 给大家一些感性的认识.

第一条汇编指令 :

entrypoint: # That's a label
  addu  $1, $2, $3  # (register)  $1  $2  $3

上面这条 ADDU 是这个样子

它是这种三个操作数的指令形式, 目标寄存器在左侧 (与 AT&T 风格相反 !)

这里面 1 号是目标寄存器, 2 号寄存器加上 3 号寄存器的值, 存放到 1 号寄存器里面去.

下面是另外一个示例, 实际上是过程调用

...
jal printf
move  $4, $6  # 位于 delay slot
xxx # return here after call

jal 类似 x86 指令的 call, 这条指令 jump and link, 那么 link 是什么意思呢. 就是说你过程调用, 要返回, 它把这个返回地址存到 31 号寄存器里面去.

但是要注意, 这个 jal 是 jump 指令, 跳转, 刚才我们说到 branch delay slot, 就是说不管你跳不跳, jal 它后面的这条 move 指令肯定是要执行的. 也就是你执行 jal 之后, 紧接着执行了一下 move 指令.

完了你 jal 返回之后呢, 返回到 move 后面一条指令, 这才是你的返回地址.

访存指令

再往下看访存指令怎么个写法

lw $1, offset($2) 任何寄存器都可以作为地址或者目标寄存器, offset 为 16 位的带符号整数

load word, lw, 它的地址计算非常简单, 一个基址加上一个 offset.

这条指令就是我把 2 号寄存器里面的值加上 offset 这个常量, 计算出来一个地址, 然后去内存拿这个地址这个 32 位字, 存到 1 号寄存器里面去.

因为它指令字长度有限, 一共就是 32 位长, 所以 offset 是一个 16 位带符号整数.

但它里面也支持这个双字、字、半字以及 load byte 这些 load 类型. 但这里面提到了, 如果我们是 load 一个 byte 的话, 实际上它有两种写法, 因为你 load byte, 目标寄存器是 32 位的, 这样你把一个 byte 8 位的一个数据, load 到一个 32 位的一个寄存器里面去, 你得做一个扩充.

那怎么扩充呢, 两种扩充方式.

  • lb %1, offset($2) # mem[offset($2)] = 0xfe ; $1 = 0xfffffffe

就是 lb 后面没有任何后缀的话, 就做符号扩展

  • lbu $1, offset($2) # $1 = ???

u 就是 unsigned, 无符号扩展

就是把 byte 填进来之后, 高 24 bit 都填 0, 最前面那个就是填你的符号位.

C 名字 MIPS 名字 大小 (字节) 汇编助记符
long long dword 8 ld 中的 d
int word 4 lw 中的 w
long^2 word 4 lw 中的 w
short halfword 2 lh 中的 h
char byte 1 lb 中的 b

寄存器

总共是 32 个寄存器

0 号寄存器的值永远是 0; 31 号寄存器存放函数返回的地址

其他寄存器都是 "一样" 的. 在硬件上看, 它们的功能可以认为是等价的, 但是软件, 编译器或者汇编器使用的时候, 对它们有不同功能上的划分, 这仅仅是软件上的定义, 硬件上它们没有区别.

没有指令寄存器 (比如 x86 里面的 eip)

另外有意思的是, 它有整数乘除法的两个专用寄存器, 叫 Hi / Lo. 它里面的乘除法是一条单独的流水线, 它运算的结果都放到这两个寄存器里面去, 都是 32 位的.

另外如果存在浮点寄存器的话, 有 32 个浮点处理器.

MIPS 32 寄存器命名与使用惯例

寄存器编号 名字 习惯用途
0 zero 值永远是 0
1 at 保留给汇编器使用 (编程时避免使用)
2-3 v0, v1 过程调用返回值
4-7 a0-a3 传参用寄存器
8-15 t0-t7 调用者保存寄存器
24, 25 t8, t9 调用者保存寄存器
16-23 s0-s7 被调用者保存寄存器
26, 27 k0, k1 保留给异常处理使用
28 gp 全局指针 (Global Pointer) --- 因为 MIPS 指令的立即数域宽度有限, gp 寄存器可以作为基址寄存器进行 load/store 寻址
29 sp 栈顶指针
30 fp 栈帧寄存器
31 ra 保存过程调用的返回地址

1 号寄存器保留给汇编器使用, 就是你手动写这个汇编程序的时候, 别用这个 1 号寄存器.

v0 v1 就类似 x86 里面的 eax 和 edx, 你如果想返回 32 位的话, 就只用一个, 如果返回 64 位结果, 就用两个

4 - 7 号寄存器, 传参用寄存器, 当参数多余 4 个的时候, 就用栈来传递参数, 和 x64 一样

gp 寄存器, 有时候你的全局变量所在的地址比较大, 你 offset 的宽度可能不够用了, 一般我们保留 gp 寄存器, gp 寄存器作为一个基址寄存器, 相当于我所有的全局变量都放在 gp 寄存器这个值的周边, 这样你就可以用一条简单的 load/store 指令来对它们进行访问了, 只要你的 gp 取值合适. 这些当然都是软件做的事.

ra 就是刚才说过的 jal 指令默认保存过程调用返回地址

传统的 MIPS 32 传递过程参数方式 (不包括浮点数)

示例一 :

如果你使用寄存器传参, 前四个参数就是放到 a0 - a3 寄存器里面去, 从左往右放. 多于 4 个的部分, 从右往左压栈.

示例二 :

如果你穿参要传递一个结构体的话, 它应该怎么去压栈, 怎么进行传递, 咱们不细说了.

MIPS 32 体系结构下, C 过程的过程调用的栈帧 layout 示意图, 这个实际上和我们在 x86 里面是一样的.

你第 5 号开始的参数, 是从右往左压栈, 号越大也就压到越上面.

往下就是你本地存储的局部寄存器, 以及一些局部变量在里头.

我们提到里面没有返回地址, 因为返回地址默认放到 31 号寄存器里面. 其它的就和 32 位 layout 是一样的, 大家可以做一个对比.


下面再讲讲看, 整数乘法与相关的寄存器.

如果你 2 个 32 位整数相乘, 如果你只取低 32 位的话, unsigned 和 signed 的乘法的结果是一样的 ; 但它这里面如果取 64 位结果的话, 把 64 位结果放到 hi lo 寄存器里面去.

怎么去把 hi lo 寄存器里面的值给它挪出来呢. 就是 move from hi, move from lo 两个寄存器. 你也可以把数据填进去, 即 move to 也是可以的.

也可以存放整数相除的除法结果, 商和余数存到里面去. 同时乘除法操作不产生异常, 需要编译器这个软件来判断. 除 0 也不会产生异常.


完了再看程序地址空间布局了.

MIPS 里面稍微讲的细一点, 因为 MIPS 32 位地址, 总共你的访问内存的空间理论上上限是 4 个 G.

我们平时写程序, 一般就写这种 user level 的程序, 就是用户层面的程序. 你只能访问低两 G, 地址是从 0 到 0x8000 - 1. 就是 0x8000 本身是不能被访问的.

你不是驱动程序, 或者是系统程序的一部分的话, 只能访问低两 G 的空间, 高两 G 你一访问就出错.

所谓的虚实地址转化这块, 这边就先简单说说看.

任何的处理器地址的访问, 包括指令和数据, 都需要经过内存管理单元的地址转换.

在 MIPS 32 里, 程序的虚地址或者说逻辑地址, 需要转换成物理地址. 什么是虚地址, 你写程序的时候, 或者说你 CPU 内部执行命令的时候, 它里面处理器内部流水线里面的这些地址, 都是逻辑地址, 都是虚地址. 它并不是说你实际访问的内存芯片, 那个总线上的地址和它不一样, 它们之间有一个对应的转换关系, 通过 mmu 来转换, 也就是通过内存管理单元转换.

但是有一些非常简单的嵌入式处理器, 没有 mmu, 一般也经过一些固定的地址转换, 叫作 faced map 之类的.

你这个程序跑的时候, 也以依赖当前处理器是什么状态.

总共有三个状态, 一般我们写普通程序, 不是驱动不是操作系统的一部分, 就是用户态.用户态你只能访问 32 位地址空间的低一半, 就是低 2 G 的空间. 核心态才可以访问全部地址空间.

我们这个地址分成四段, 最下面的这一段是用户态可以访问到的地址, 经过 mmu 转换. 你写程序, 你写的那些地址都是虚的地址, 最后在你处理器执行过程当中, 你的这些指令地址或者数据地址都有经过内存管理单元转换, 虚实转换之后, 才能最终访问物理上的内存地址.

再往上走呢又分成三段, 注意这中间的两段, 也就是 kseg1 和 kseg0. 这两段实际上是不经过比较复杂的内存地址转换的, 也就是说你看到的地址, 比方说 0x8000000几, 这个地址拿来之后, 把最高位清 0 之后, 就是物理地址. 这个实际上叫作一个 faced map 的变换, 不是通过这种页表转化. 这一段是可以进行 cacheable 的.

再往下就是 kseg1, 就是相当于把你的虚拟地址或者程序地址, 最高三位清零. 这个清零的话指的是 32 位比特, 以 32 位二进制来看的话, 最高三位清零之后是物理地址. 而且这一段空间是 uncacheable 的.

为什么要这样区分呢, 我们知道处理器访问内存的时候, 你的内核和你的内存之间有一个缓存, 你的数据在 cache 里面能够命中的话, 它的访问速度就会大大加快, 但 cache 本身也是需要初始化的. 那么系统刚启动的时候, cache 没有初始化, 所以不能用, 不能用的话, 我就把我的启动代码写在 kseg1 里面, 这样刚启动的话我这段代码可以绕过缓存区访问内存, 来保证我启动过程是正确的. 所以说这里是 uncacheble 的.

cache 初始化完了之后, 你才可以跳到另一个段, 这个段是可以使用 cache 的.

协处理器 0 ---- CP0

MIPS 里面一个特点, 它里面有个协处理器, 叫 CP0 处理器.

协处理器是什么呢, 比如说浮点处理器是个协处理器. 一般来说协处理器是可有可无的, 假设我没有浮点处理器, 我用整数指令来模拟浮点指令也是可以的, 就是会非常慢.

但是 MIPS 里面的协处理器 CP0 是必不可少的, 它实际上是处理器上的一个控制单元, 但不是一个具体某个执行某个功能单元, 非常关键.

  • 支持虚拟存储、异常处理、运行状态切换等的系统控制协处理器

    所以这个 CP0 是必不可少的, 浮点处理器 CP1 可以没有, 但是 CP0 不能少. 它只是名字叫协处理器, 看起来像是协作的一个东西, 但实际上它是非常关键的.

    • 从程序员角度看, 就是一系列寄存器
      它专门有 MFCO move c0, MTC0 move to c0 两条指令对它进行读和写的操作. 但注意, 这两条指令不能在用户态下访问, 因为它是一些内核控制单元, 只有通过在核心态, 操作系统才能去访问它, 一般编译器不能用它.
    mfc0  t0, SR
    and t0, <要清零的位的反码>
    or  t0, <要设置 1 的位>
    mtc SR, t0
    • 与下列处理器功能密切相关
      处理器运行模式, 如大小端模式、当前运行态等 ;

    缓存控制

    异常/中断处理

    存储管理 (MMU)

    其它...

CP0 寄存器部分汇总

它里面有很多寄存器, 我们就讲一些比较常用的

寄存器名称 编号 功能描述
Status 12 状态寄存器, 包括处理器运行的状态、协处理器使能、某些中断使能以及一些处理器的配置信息
Cause 13 什么原因导致中断或者异常
EPC 14 中断/异常处理完成后从哪里开始恢复执行
Count 9 这一组寄存器形成了一个高分辨率定制器, 频率一般是处理器频率的 50%
Compare 11 这一组寄存器形成了一个高分辨率定制器, 频率一般是处理器频率的 50%
BadVaddr 8 引起地址相关异常的指令/数据地址
Context 4 对 MMU 编程的寄存器
EntryHi 10 对 MMU 编程的寄存器
EntryLo 0-1 2-3 对 MMU 编程的寄存器
Index 0 对 MMU 编程的寄存器
PageMask 5 对 MMU 编程的寄存器
Random 1 对 MMU 编程的寄存器
Wired 6 对 MMU 编程的寄存器
PRId 15 CPU 类型与版本号
Config 16.0 CPU 参数设置
TagLo 28.0 用于对处理器缓存 (cache) 编程的寄存器
DataLo 28.1 用于对处理器缓存 (cache) 编程的寄存器
TagHi 29.0 用于对处理器缓存 (cache) 编程的寄存器
DataHi 29.1 用于对处理器缓存 (cache) 编程的寄存器

其它还有一系列用于硬件调试、性能计数的寄存器等等


12 号的叫 status, 叫状态寄存器, 它里面存储处理器运行的状态, 协处理器到底能不能用, 某些中断使能以及一些处理器的配置信息. 这个非常关键, 你通过对它的配置就可以完成你运行状态的切换.

count 寄存器和 compare 寄存器, 蛮有意思的. 处理器运行的话, 很多时候都要依赖于外部的中断来完成, 比如某些任务的切换等等. 那么 MIPS 里面, 它有 count compare 这一堆寄存器, 形成了一个高分辨率的一个定时器, 频率一般是处理器频率的 50%. 什么意思呢, 就是说它首先在 compare 寄存器里面设置初值, count 寄存器一开始是 0, 它每两个周期加 1, 然后 count 寄存器值在加 1, 当加到与 compare 寄存器值一样的时候, 就触发一个时钟中断, 然后 count 寄存器清零, 从头再来. 这样, 它的时钟中断频率应该是处理器频率的 50%.

CP0 示例-1

CP0 示例-2