global pointer

全局指针寄存器(gp)是MIPS的通用寄存器$28。这个寄存器的用途主要有两种:

  1. 在PIC中,gp用来指向GOT(Global Offset Table)。注意,这里的PIC是指的Linux中共享库中的PIC,而在vxWorks的BSP中的PIC只是简单的代码和地址无关,并不涉及到共享库,所以BSP中的gp的用法并不属于此类。
  2. 在嵌入式开发中,gp用来指向链接时决定的静态数据的地址。这样,对在gp所指地址正负各32K范围内数据的load和store(其实就是ld和sw指令),就可使用gp作为基址寄存器。有关数据的load和store,请看这里。 在romInit()函数向C函数romStart()函数跳转,usrStart()函数最开始两处都有gp的初始化。代码如下所示。
    la	gp, _gp			# set global ptr from compiler
    

那么,_gp是什么呢?通过了解编译链接的过程,查看bootrom的符号表,可以看到,_gp就是链接器在链接时确定的一个静态数据的存放地址。在我们的代码中,大概是0x801656a0。

load&store

对内存单元的操作,使用的是load和store,对应的指令为ld和sw。注意,ld和sw只用唯一一种寻址模式,那就是base+offset的方式。基址base为寄存器,offset在ld和sw的机器码中是16位,转化为有符号数,就是正负32K。

stack pointer

堆栈指针寄存器(sp),MIPS使用的是直接的指令(例如addiu) 来升降堆栈,请注意区别在X86中使用指令POP和PUSH来升降堆栈的做法。在X86平台下,使用的是esp寄存器当做堆栈指针。

在子程序的入口,sp会被升到该子程序需要用到的最大堆栈的位置。在子程序中,堆栈的升降的汇编代码一般都大同小异,下面将romStart()函数开头和结尾的堆栈升降提取出来,代码如下所示:

addiu	$sp,$sp,-32
sw	$ra,28($sp)
sw	$s8,24($sp)
move	$s8,$sp
……
move	$sp,$s8
lw	$ra,28($sp)
lw	$s8,24($sp)
jr	$ra
addiu	$sp,$sp,32

第一句代码的-32就说明,romStart()函数最多用到的堆栈空间为32bytes。然后就是将返回地址ra和调用函数的堆栈位置(存放在\(s8中)放入堆栈中,然后将堆栈位置放入\)s8中move \(s8,\)sp。在函数返回就是一个逆操作,回复调用函数的堆栈现场。有关$s8(又叫做帧指针fp)的知识,请参考下一节。

$s8/frame pointer

第九个通用寄存器$8,又叫做帧指针(frame pointer,fp)。在X86中,使用的是ebp寄存器当做帧指针。有关fp/sp,ebp/esp的相关内容,请参考C语言-Stack的相关内容。涉及堆栈帧(stack frame),活动记录(active record),调用惯例(call convention)等相关概念。

分支延迟槽&加载延迟槽

在早期的MIPS CPU上,对于从内存加载数据的操作,CPU没有提供互锁功能,这导致加载延迟槽对于程序员是可见的,也就是说,汇编程序员必须仔细处理加载延迟槽,否则得到的结果就可能是错误的。

所谓互锁(interlock),是指CPU硬件提供的这样一种功能:如果指令的某个操作数尚未就绪,则推迟指令的执行,直到操作数就绪为止。在提供了互锁功能的CPU上,加载延迟槽对于程序员就是不可见的了,也就是说,即便在加载延迟槽中使用了加载指令操作的寄存器,得到的结果也是正确的(不过,这样做会牺牲一点性能)。早期的MIPS CPU没有提供互锁功能,但后来的MIPS CPU中都是加入了互锁功能的。那么,所谓早期MIPS CPU,到底是指哪些MIPS CPU呢,一般来讲,这是指MIPS I系列,MIPS I系列的典型代表就是MIPS R3000,这也正是我司路由器(大概也包括switch)上使用的大多数MIPS CPU所采用的处理器核。

作为MIPS I系列的代表,MIPS R3000与后续的MIPS CPU在体系结构上存在着诸多差异,对加载延迟槽的不同处理只是这诸多差异中的其中一个,在移植、调试软件时,需要多注意这些差异之处。