自己动手写CPU之第九阶段(7)——MIPS32中的LL、SC指令说明

来源:互联网 发布:php curl抓取html代码 编辑:程序博客网 时间:2024/05/16 08:23

将陆续上传新书《自己动手写CPU》,今天是第46篇。


在MIPS32指令集中有两条特殊的存储加载指令:链接加载指令LL、条件存储指令SC,本次将介绍这两条指令,在后续将实现这两条指令。


9.6 链接加载指令ll、条件存储指令sc说明

      在本章前面的部分,笔者花费很多笔墨介绍了OpenMIPS中除llsc之外的加载、存储指令的实现过程,本节至9.9节将专门介绍链接加载指令ll、条件存储指令sc的实现过程。llsc指令是MIPS32指令集架构中比较特殊的加载存储指令,用来实现信号量机制。

      在多线程系统中,需要RMWRead-Modify-Write)操作序列保证对某个资源的独占性,RMW操作序列的含义是,读取内存某个地址的数据,读取的数据经过修改,然后再保存回内存原地址,这个过程不能有任何打扰,因此需要建立一个临界区域(Critical Region),临界区域中完成的操作通常称为原子操作,原子操作不被打扰。操作系统建立临界区域的方式通常是信号量机制,如下。

waitsemaphore;

原子操作;

 signalsemaphore;

      semaphore是一个信号量,为1表示信号量使用中,为0表示信号量空闲。进行原子操作前,使用wait函数查询semaphore的值,如果为1,则等待,否则,将其置为1,开始执行原子操作,操作结束后,signal函数将semaphore置为0,这样其它线程就可以执行原子操作了。

      需要注意的是,wait函数的执行也是一个原子操作,是一种“先检测后设置”操作(test-and-set operation),这种操作一般不希望被外部设备中断,也不希望被其它线程打断,很多处理器都有专门的指令用来实现“先检测后设置”操作,比如:680x0 CPUx86 CPU等。这也是一种信号量机制。

      MIPS32架构采用特殊的方式实现信号量机制,对于原子操作,MIPS32架构并不保证它一定是原子性的,也就是允许检测和设置在没有原子性保证的情况下运行,但只在它确实原子的运行了的时候才让“设置”生效。MIPS32架构采用链接加载指令ll、条件存储指令sc来实现这种信号量机制。

      ll指令同一般的加载指令一样,从内存中加载一个字,但是,有一点不同,ll指令还会将处理器内部的一个链接状态位LLbit置为1,表明发生了一个链接加载操作,并将链接加载的地址保存到一个特殊寄存器LLAddr中(这个寄存器在多处理器中有作用,OpenMIPS是单处理器,所以在OpenMIPS实现过程中并没有实现LLAddr寄存器)。

      ll指令执行完毕后,会进行一定的操作(如:修改加载得到的数据),然后执行sc指令,这可以认为是一个RMW序列。有如下两种情况干扰这个RMW序列,受到干扰后,处理器会设置链接状态位LLbit0

  •  在llsc指令之间产生异常,从而进入异常处理例程,或者发生线程切换,导致RMW序列受到干扰。
  •  多处理器的系统中,另一个CPU改写了RMW序列要操作的内存空间。

      对于OpenMIPS而言,只有第1种情况。

      执行sc指令时,会对从ll指令开始的RMW序列进行检查,判断是否受到干扰,实际就是判断LLbit是否为1,如果没有受到任何干扰,LLbit保持为1,那么操作是原子的,sc指令会对ll指令加载数据的地址进行写回操作,并设置一个通用寄存器的值为1,表示成功,反之不进行写回操作,并设置一个通用寄存器的值为0,表示失败。

      llsc指令的格式如图9-28所示。从图中可知,可以依据指令码对这2条指令进行区分。


  •  当指令中的指令码为6'b110000时,是ll指令,链接加载指令

      指令用法为:ll rt, offset(base)

      指令作用为:从内存中指定的加载地址处,读取一个字节,然后符号扩展至32位,保存到地址为rt的通用寄存器中。其中加载地址的计算方法如下。

      加载地址 = signed_extended(offset) + GPR[base]

      此外,还要设置链接状态位LLbit1

  •  当指令中的指令码为6'b111000时,是sc指令,条件存储指令

      指令用法为:sc rt, offset(base)

      指令作用为:如果RMW序列没有受到干扰,也就是LLbit1,那么将地址为rt的通用寄存器的值保存到内存中指定的存储地址处,同时设置地址为rt的通用寄存器的值为1,设置LLbit0。如果RMW序列受到了干扰,也就是LLbit0,那么不修改内存,同时设置地址为rt的通用寄存器的值为0。其中存储地址的计算方法如下。

      存储地址 = signed_extended(offset) + GPR[base]

      下面通过一个例子体会llsc指令的作用,这个例子实现了上面介绍的wait函数,不过此处是使用llsc指令实现的。


wait:ori $1, $0, sem         // sem是信号量的地址,将这个地址赋给寄存器$1TryAgain:ll  $2, 0($1)           // 获取信号量的值,保存到寄存器$2bne $2, $0, WaitForSem  // 如果信号量被占用(其值为1),那么转移到地址WaitForSem                        // 继续等待;如果信号量空闲(其值为0),那么执行下面的指令nopori $2, $0, 1sc  $2, 0($1)           // 如果没有被干扰,那么设置信号量被占用(将1保存到信号                        // 量中),同时,设置寄存器$2为1,反之,不修改信号量,                        // 设置寄存器$2为0beq $2, $0, TryAgain    // 如果寄存器$2为0,表示ll、sc指令没有成功,未获取到                        // 信号量,回到TryAgain继续尝试nopjr  $31                  // 反之, 表示ll、sc指令成功,获取到信号量,可以进入                         // “临界区域”了。调用wait函数时,会将返回地址放在                         // 寄存器$31,所以此处jr $31指令就是回到调用过程,                         // 进入临界区域


下一次将修改OpenMIPS以实现LL、SC指令

0 0
原创粉丝点击