本文对MIPS的32个通用寄存器及它们的常用场合进行了小结.
看一张比较官方的截图。
![](https://img-my.csdn.net/uploads/201612/20/1482216155_2665.jpg)
zero它一般作为源寄存器,读它永远返回0,也可以将它作为目的寄存器写数据,但效果等于白写。为什么单独拉一个寄存器出来返回一个数字呢?答案是为了效率,MIPS的设计者只允许在寄存器内执行算术操作,而不允许直接操作立即数。所以对最常用的数字0单独留了一个寄存器,以提高效率
at该寄存器为给编译器保留,用于处理在加载16位以上的大常数时使用,编译器或汇编程序需要把大常数拆开,然后重新组合到寄存器里。系统程序员也可以显式的使用这个寄存器,有一个汇编directive可被用来禁止汇编器在directive之后再使用at寄存器。
v0, v1.这两个很简单,用做函数的返回值,大部分时候,使用v0就够了。如果返回值的大小超过8字节,那就需要分配使用堆栈,调用者在堆栈里分配一个匿名的结构,设置一个指向该参数的指针,返回时v0指向这个对应的结构,这些都是由编译器自动完成。
a0-a3. 用来传递函数入参给子函数。看一下这个例子:
ret = strncmp("bear","bearer",4);
参数少于16字节,可以放入寄存器中,在strncmp的函数里,a0存放的是"bear"这个字符串所在的只读区地址,a1是"bearer"的地址,a2是4.
t0-t9临时寄存器
s0-s8
保留寄存器
这两种寄存器需要放在一起说,它们是mips汇编里面代码里见到的最多的两种寄存器,它们的作用都是存取数据,做计算、移位、比较、加载、存储等等,区别在于,t0-t9在子程序中可以使用其中的值,并不必存储它们,它们很适合用来存放计算表达式时使用的“临时”变量。如果这些变量的使用要要跳转到子函数之前完成,因为子函数里很可能会使用相同的寄存器,而且不会有任何保护。如果子程序里不会调用其它函数那么建议尽量多的使用t0-t9,这样可以避免函数入口处的保存和结束时的恢复。
相反的,s0-s8在子程序的执行过程中,需要将它们存储在堆栈里,并在子程序结束前恢复。从而在调用函数看来这些寄存器的值没有变化。
k0, k1. 这两个寄存器是专门预留给异常处理流程中使用。异常处理流程中有什么特别的地方吗?当然。当MIPS CPU在任务里运行的时候,一旦有外部中断或者异常发生,CPU就会立刻跳转到一个固定地址的异常handler函数执行,并同时将异常结束后返回到任务的指令地址记录在EPC寄存器(Exception Program Counter)里。习惯性的,异常handler函数开头总是会保持现场即MIPS寄存器到中断栈空间里,而在异常返回前,再把这些寄存器的值恢复回去。那就存在一个问题,这个EPC里的值存放在哪里?异常handler函数的最后肯定是一句jr x,X是一个MIPS寄存器,如果存放在前面提到的t0,s0等等,那么PC跳回任务执行现场时,这个寄存器里的值就不再是异常发生之前的值。所以必须要有k0这样的寄存器,它在任务运行时不允许使用,只在异常handler里用来存储EPC,在异常结束时就可以一句jr k0指令返回了。
k1是另外一个专为异常而生的寄存器,它可以用来记录中断嵌套的深度。CPU在执行任务空间的代码时,k1就可以置为0,进入到中断空间,每进入一次就加1,退出一次相应减1,这样就可以记录中断嵌套的深度。这个深度在调试问题的时候经常会用到,同时应用程序在做一次事情的时候可能会需要知道当前是在任务还是中断上下文,这时,也可以通过k1寄存器是否为0来判断。
sp指向当前正在操作的堆栈顶部,
它指向堆栈中的下一个可写入的单元,如果从栈顶获取一个字节是sp-1地址的内容。在有RTOS的系统里,每个task都有自己的一个堆栈空间和实时sp副本,中断也有自己的堆栈空间和sp副本,它们会在上下文切换的过程中进行保存和恢复。
gp这是一个辅助型的寄存器,其含义较为模糊,MIPS官方为该寄存器提供了两个用法建议,一种是指向Linux应用中位置无关代码之外的数据引用的全局偏移量表;
在运行RTOS的小型嵌入式系统中,它可以指向一块访问较为频繁的全局数据区域,由于MIPS汇编指令长度都是32bit,指令内部的offset为16bit,且为有符号数,所以能用一条指令以gp为基地址访问正负15bit的地址空间,提高效率。那么编译器怎么知道gp初始化的值呢?只要在link文件中添加_gp符号,连接器就会认为这是gp的值。我们在上电时,将_gp的值赋给gp寄存器就行了。
话说回来,这都是MIPS设计者的建议,不是强制,楼主还见过一种gp寄存器的用法,来在中断和任务切换时做sp的存储过渡,也是可以的。
fp这个寄存器不同的编译器对其解释不同,GNU MIPS C编译器使用其作为帧指针,指向堆栈里的过程帧(一个子函数)的第一个字,子函数可以用其做一个偏移访问栈帧里的局部变量,sp也可以较为灵活的移动,因为在函数退出之前使用fp来恢复;还要一种而SGI的C编译器会将这个寄存器直接作为s8,扩展了一个保留寄存器给编译器使用。
ra在函数调用过程中,保持子函数返回后的指令地址。汇编语句里函数调用的形式为:
jal function_X
这条指令jal(jump-and-link,跳转并链接)指令会将当期执行运行指令的地址+4存储到ra寄存器里,然后跳转到function_X的地址处。相应的,子函数返回时,最常见的一条指令就是
jr ra
ra是一个对于调试很有用的寄存器,系统的运行的任何时刻都可以查看它的值以获取CPU的运行轨迹。
最后,楼主想说的是,如果纯写汇编语句的话,这些寄存器当中除了zero之外,其它的基本上都可以做普通寄存器存取数据使用(这也是它们为什么会定义为“通用寄存器”,而不像其它的协处理器、或者外设的都是专用寄存器,其在出厂时所有的功能都是定死的),那为什么要搞这么多规则呢?这些都MIPS开发者们为了让自己的处理器可以运行像C、Java这样的高级语言,以及让汇编语言和高级语言可以安全的混合编程而设计的一套ABI(应用编程接口),不同的编译器的设计者们就会有据可依,系统程序员们在阅读、修改汇编程序的时候也能根据这些约定而更为顺畅地理解汇编代码的含义。