目录
一 ll和sc指令说明
二 ll和sc指令的实现
1 llbit寄存器
2 译码阶段
3 访存阶段
4 Load相关问题
5 流水线在取指阶段暂停
本章介绍两个比较特殊的加载存储指令ll和sc,这两个指令的存在用于实现信号量机制。
信号量机制:在多线程中为了保证某个正在运行的线程不被其他线程打扰,需要遵循RMW操作序列,这是一个规范过程:读取,修改,返回,并且此过程不能有任何打扰即为一个原子操作(一定执行且不被打扰的操作为原子操作)。
原子操作的实现方式需要基于信号量机制,semaphore是一个信号量,用来表示当前是否有进程在运行,为1表示信号量使用中,为0表示信号量空闲。进行原子操作前,使用wait函数查询semaphore的值(读取),如果为1,则等待,否则,将其置为1,开始执行原子操作(修改)。操作结束后,signal 函数将semaphore置为0(返回),这样其他线程就可以执行原子操作了。
MIPS32架构采用ll和sc这两个指令实现信号量机制,具体步骤如下:
ll指令同一般的加载指令一样,从内存中加载一个字, 但是,有一点不同,ll指令还会将处理器内部的一个链接状态位LLbit置为1,表明发生了一个链接加载操作,并将链接加载的地址保存到一个特殊寄存器LLAddr中(这个寄存器在多处理器中有作用,OpenMIPS是单处理器,所以在OpenMIPS实现过程中并没有实现LLAddr寄存器)。ll指令执行完毕后,会进行一定定的操作(如:修改加载得到的数据),然后执行sc指令。
执行sc指令时,会对从II指令开始的RMW序列进行检查,判断是否受到干扰,实际就是判断LLbit是否为1,如果没有受到任何干扰,LLbit 保持为1,那么操作是原子的,sc指令会对II指令加载数据的地址进行写回操作,并设置一个通用寄存器的值为1,表示成功, 反之不进行写回操作,并设置一个通用寄存器的值为0,表示失败。
可以看到在这里LLbit就是一种信号量,ll和sc指令搭配实现了一种RWM操作序列,ll进行读取与修改,sc指令进行返回操作。在这个过程中通过对信号量LLbit的修改实现了原子操作。
一 ll和sc指令说明
ll指令作用为:从内存中指定的加载地址处,读取一个字节,然后符号扩展至32位,保存到地址为rt的通用寄存器中。并设置LLbit状态位为1.
sc指令作用为:如果RMW序列没有受到干扰,也就是LLbit为1,那么将地址为rt的通用寄存器的值保存到内存中指定的存储地址处,同时设置地址为rt的通用寄存器的值为1,设置LLbit为0。如果RMW序列受到了干扰,也就是LLbit为0,那么不修改内存,同时设置地址为rt的通用寄存器的值为0。
二 ll和sc指令的实现
我们可以把LLbit当做一个寄存器模块,ll指令在结束后会进行写操作,sc指令会先读它,然后再进行写rt寄存器。最后还会对LLbit寄存器进行写。LLbit寄存器同样放到回写阶段。
需要注意的是,由于对LLbit寄存器的修改是在回写阶段最后的时钟上升沿进行的,如果直接采用LLbit模块给出的LLbit寄存器的值,可能不是正确的值,因为此时处于回写阶段的上一条指令可能会需要修改LLbit寄存器,这一问题在第6章添加HI、LO寄存器时也遇到过,解决方法还是数据前推,将回写阶段指令对LLbit寄存器的操作信息(是否进行写LLbit操作以及写入的值)前推到访存阶段,访存阶段依据这些情况,确定正确的LLbit寄存器的值,所以MEM/WB模块的输出信号wb_LLbit_we、wb_LLbit_value也要送到MEM模块,就是用来解决数据相关问题的。
1 llbit寄存器
只需要对异常情况判断以及写使能,如果有异常情况信号量更新为0。至于具体的异常处理在后面才会介绍。
2 译码阶段
ll指令加载内存地址写入寄存器,需要写使能,需要读base地址,此外还需要修改LLbit的值,也就是要对LLbit寄存器进行写操作。
sc指令从寄存器读数据写入内存,最后还要修改LLbit寄存器和原通用寄存器,要读两个寄存器,一个base一个寄存器的值。
3 访存阶段
这是加载存储指令与特殊寄存器进行读写交互的地方,为了避免数据相关操作需要先获取一下LLbit寄存器最新值,即如果回写阶段也要写LLbit那就把写入的值给LLbit的最新值:
ll指令:需要读数据存储器一个字,需要写LLbit寄存器1
sc指令:因为是信号量机制所以要先检测信号量是否异常,无异常(为1)时再进行操作:需要写寄存器值到数据存储器,需要写寄存器为1,需要写LLbit寄存器为0
4 Load相关问题
加载操作需要写入值到寄存器,如果下一条指令就需要使用此寄存器的值会不会有影响?
答案是有的,因为load加载操作在访存阶段才获取到写入寄存器的新值,而下一条指令在译码阶段就已经获取了此寄存器的值,显然这个值是落后的。如下图:
那么是否可以使用数据前推来解决数据相关问题?
答案是不可以的。原因主要是因为load是在访存阶段才获取到新值,我们之前的数据数据相关问题在执行阶段就获取到了最新的值,所以落后了一个阶段后,就算把访存的值前推倒执行阶段,而下一条指令早在前一个译码阶段就获取了。
那么该如何解决?
既然相比之前的数据相关问题落后了一个阶段,如果我们可以让第二条指令在译码阶段如果识别到当前指令与上一条指令存在load关系就暂停一次,那么就相当于如下图:
此时访存阶段获取到新值,译码阶段正好需要取值,满足数据前推的解决条件,可以使用来解决问题。
5 流水线在取指阶段暂停
输出暂停信号之前还需要把上一条的指令回传过来好进行load相关的判定(如果上一条指令是load指令且当前指令操作的寄存器就是load写入的寄存器,那么就是load相关发生)
具体实现:
先判断上一条指令是不是加载指令:
为什么SC指令也在这里呢?因为sc指令在最后会回写寄存器,也会更改寄存器的值。
接下来判断加载指令的目的地址是否和当前指令读的寄存器一样:
如果满足load相关条件就请求流水线暂停:
至此,加载存储指令基本结束。