分类: 嵌入式
2013-09-24 09:19:24
原文地址:mips 寄存器理解 作者:dogsun88
通用寄存器(GPR)
有32个通用寄存器,$0到$31:
$0:即$zero,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式。MIPS编译器使用slt,beq,bne等指令和由寄存器$0获得的0来
产生所有的比较条件:相等,不等,小于,小于等于,大于,大于等于。还可以用add指令创建move伪指令,即
move
$t0,$t1
实际为
add
$t0,$0,$t1
焦林前辈提到他移植fpc时move指令出错,转而使用add代替的。
使用伪指令可以简化任务,汇编程序提供了比硬件更丰富的指令集。
$1:即$at,该寄存器为汇编保留,刚才说到使用伪指令可以简化任务,但是代价就是要为汇编程序保留一个寄存器,就是$at。
由于I型指令的立即数字段只有16位,在加载大常数时,编译器或汇编程序需要把大常数拆开,然后重新组合到寄存器里。比如加载一个32位立即数需要
lui(装入高位立即数)和addi两条指令。像MIPS程序拆散和重装大常数由汇编程序来完成,汇编程序必需一个临时寄存器来重组大常数,这也是为汇编
保留$at的原因之一。
$2..$3:($v0-$v1)用于子程序的非浮点结果或返回值,对于子程序如何传递参数及如何返回,MIPS范围有一套约定,堆栈中少数几个位置处的内容装入CPU寄存器,其相应内存位置保留未做定义,当这两个寄存器不够存放返回值时,编译器通过内存来完成。
$4..$7:($a0-$a3)用来传递前四个参数给子程序,不够的用堆栈。a0-a3和v0-v1以及ra一起来支持子程序/过程调用,分别用以传递
参数,返回结果和存放返回地址。当需要使用更多的寄存器时,就需要堆栈(stack)了,MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。
$8..$15:($t0-$t7)临时寄存器,子程序可以使用它们而不用保留。
$16..$23:($s0-$s7)保存寄存器,在过程调用过程中需要保留(被调用者保存和恢复,还包括$fp和$ra),MIPS提供了临时寄存器和
保存寄存器,这样就减少了寄存器溢出(spilling,即将不常用的变量放到存储器的过程),编译器在编译一个叶(leaf)过程(不调用其它过程的过
程)的时候,总是在临时寄存器分配完了才使用需要保存的寄存器。
$24..$25:($t8-$t9)同($t0-$t7)
$26..$27:($k0,$k1)为操作系统/异常处理保留,至少要预留一个。异常(或中断)是一种不需要在程序中显示调用的过程。MIPS有个叫异常程序计数器(exception
program
counter,EPC)的寄存器,属于CP0寄存器,用于保存造成异常的那条指令的地址。查看控制寄存器的唯一方法是把它复制到通用寄存器里,指令mfc0(move
from system
control)可以将EPC中的地址复制到某个通用寄存器中,通过跳转语句(jr),程序可以返回到造成异常的那条指令处继续执行。仔细分析一下会发现个有意思的事情:
为了查看控制寄存器EPC的值并跳转到造成异常的那条指令(使用jr),必须把EPC的值到某个通用寄存器里,这样的话,程序返回到中断处时就无法将所有
的寄存器恢复原值。如果先恢复所有的寄存器,那么从EPC复制过来的值就会丢失,jr就无法返回中断处;如果我们只是恢复除有从EPC复制过来的返回地址
外的寄存器,但这意味着程序在异常情况后某个寄存器被无端改变了,这是不行的。为了摆脱这个两难境地,MIPS程序员都必须保留两个寄存器$k0
和$k1,供操作系统使用。发生异常时,这两个寄存器的值不会被恢复,编译器也不使用k0和k1,异常处理函数可以将返回地址放到这两个中的任何一个,然
后使用jr跳转到造成异常的指令处继续执行。
$28:($gp)C语言中有两种存储类型,自动型和静态型,自动变量是一个过程中的局部变量。静态变量是进入和退出一个过程时都是存在的。为了简化静态
数据的访问,MIPS软件保留了一个寄存器:全局指针gp(global
pointer,$gp),如果没有全局指针,从静态数据去装入数据需要两条指令:一条有编译器和连接器计算的32位地址常量中的有效位;令一条才真正装
入数据。全局指针只想静态数据区中的运行时决定的地址,在存取位于gp值上下32KB范围内的数据时,只需要一条以gp为基指针的指令即可。在编译时,数
据须在以gp为基指针的64KB范围内。
$29:($sp)MIPS硬件并不直接支持堆栈,例如,它没有x86的SS,SP,BP寄存器,MIPS虽然定义$29为栈指针,它还是通用寄存器,只
是用于特殊目的而已,你可以把它用于别的目的,但为了使用别人的程序或让别人使用你的程序,还是要遵守这个约定的,但这和硬件没有关系。x86有单独的
PUSH和POP指令,而MIPS没有,但这并不影响MIPS使用堆栈。在发生过程调用时,调用者把过程调用过后要用的寄存器压入堆栈,被调用者把返回地
址寄存器$ra和保留寄存器压入堆栈。同时调整堆栈指针,当返回时,从堆栈中恢复寄存器,同时调整堆栈指针。
$30:($fp)GNU MIPS C编译器使用了侦指针(frame
pointer),而SGI的C编译器没有使用,而把这个寄存器当作保存寄存器使用($s8),这节省了调用和返回开销,但增加了代码生成的复杂性。
$31:($ra)存放返回地址,MIPS有个jal(jump-and-link,跳转并链接)指令,在跳转到某个地址时,把下一条指令的地址放到$ra中。用于支持子程序,例如调用程序把参数放到$a0~$a3,然后jal
X跳到X过程,被调过程完成后把结果放到$v0,$v1,然后使用jr $ra返回。
在调用时需要保存的寄存器为$a0~$a3,$s0~$s7,$gp,$sp,$fp,$ra。
跳转范围
J指令的地址字段为26位,用于跳转目标。指令在内存中以4字节对齐,最低两个有效位不需要存储。在MIPS中,每个地址的最低两位指定了字的一个字
节,cache映射的下标是不使用这两位的,这样能表示28位的字节编址,允许的地址空间为256M。PC是32位的,那其它4位从何而来呢?MIPS的
跳转指令只替换PC的低28位,而高4位保留原值。因此,加载和链接程序必须避免跨越256MB,在256M的段内,分支跳转地址当作一个绝对地址,和
PC无关,如果超过256M(段外跳转)就要用跳转寄存器指令了。
同样,条件分支指令中的16位立即数如果不够用,可以使用PC相对寻址,即用分支指令中的分支地址与(PC+4)的和做分支目标。由于一般的循环和if语句都小于2^16个字(2的16次方),这样的方法是很理想的。