将陆续上传新书《自己动手写CPU》,今天是第46篇。
在MIPS32指令集中有两条特殊的存储载入指令:链接载入指令LL、条件存储指令SC,本次将介绍这两条指令。在兴许将实现这两条指令。
9.6 链接载入指令ll、条件存储指令sc说明
在本章前面的部分,笔者花费非常多笔墨介绍了OpenMIPS中除ll、sc之外的载入、存储指令的实现过程,本节至9.9节将专门介绍链接载入指令ll、条件存储指令sc的实现过程。ll、sc指令是MIPS32指令集架构中比較特殊的载入存储指令。用来实现信号量机制。
在多线程系统中,须要RMW(Read-Modify-Write)操作序列保证对某个资源的独占性,RMW操作序列的含义是,读取内存某个地址的数据。读取的数据经过改动。然后再保存回内存原地址。这个过程不能有不论什么打搅,因此须要建立一个临界区域(Critical Region),临界区域中完毕的操作通常称为原子操作。原子操作不被打搅。操作系统建立临界区域的方式一般是信号量机制,例如以下。
wait(semaphore);
原子操作;
signal(semaphore);
semaphore是一个信号量。为1表示信号量使用中,为0表示信号量空暇。进行原子操作前,使用wait函数查询semaphore的值,假设为1,则等待,否则,将其置为1,開始运行原子操作,操作结束后,signal函数将semaphore置为0,这样其他线程就能够运行原子操作了。
须要注意的是,wait函数的运行也是一个原子操作。是一种“先检測后设置”操作(test-and-set operation),这样的操作一般不希望被外部设备中断,也不希望被其他线程打断。非常多处理器都有专门的指令用来实现“先检測后设置”操作,比方:680x0 CPU、x86 CPU等。这也是一种信号量机制。
MIPS32架构採用特殊的方式实现信号量机制。对于原子操作,MIPS32架构并不保证它一定是原子性的,也就是同意检測和设置在没有原子性保证的情况下执行。但仅仅在它确实原子的执行了的时候才让“设置”生效。MIPS32架构採用链接载入指令ll、条件存储指令sc来实现这样的信号量机制。
ll指令同一般的载入指令一样,从内存中载入一个字,可是,有一点不同。ll指令还会将处理器内部的一个链接状态位LLbit置为1,表明发生了一个链接载入操作。并将链接载入的地址保存到一个特殊寄存器LLAddr中(这个寄存器在多处理器中有作用,OpenMIPS是单处理器,所以在OpenMIPS实现过程中并没有实现LLAddr寄存器)。
ll指令运行完成后,会进行一定的操作(如:改动载入得到的数据)。然后运行sc指令,这能够觉得是一个RMW序列。有例如以下两种情况干扰这个RMW序列。受到干扰后。处理器会设置链接状态位LLbit为0。
- 在ll、sc指令之间产生异常。从而进入异常处理例程。或者发生线程切换,导致RMW序列受到干扰。
- 多处理器的系统中,还有一个CPU改写了RMW序列要操作的内存空间。
对于OpenMIPS而言,仅仅有第1种情况。
运行sc指令时。会对从ll指令開始的RMW序列进行检查。推断是否受到干扰,实际就是推断LLbit是否为1,假设没有受到不论什么干扰,LLbit保持为1。那么操作是原子的,sc指令会对ll指令载入数据的地址进行写回操作,并设置一个通用寄存器的值为1,表示成功,反之不进行写回操作。并设置一个通用寄存器的值为0,表示失败。
ll、sc指令的格式如图9-28所看到的。
从图中可知,能够根据指令码对这2条指令进行区分。
- 当指令中的指令码为6'b110000时。是ll指令,链接载入指令
指令使用方法为:ll rt, offset(base)
指令作用为:从内存中指定的载入地址处,读取一个字节。然后符号扩展至32位,保存到地址为rt的通用寄存器中。
当中载入地址的计算方法例如以下。
载入地址 = signed_extended(offset) + GPR[base]
此外。还要设置链接状态位LLbit为1。
- 当指令中的指令码为6'b111000时,是sc指令,条件存储指令
指令使用方法为:sc rt, offset(base)
指令作用为:假设RMW序列没有受到干扰,也就是LLbit为1,那么将地址为rt的通用寄存器的值保存到内存中指定的存储地址处,同一时候设置地址为rt的通用寄存器的值为1,设置LLbit为0。假设RMW序列受到了干扰,也就是LLbit为0,那么不改动内存,同一时候设置地址为rt的通用寄存器的值为0。当中存储地址的计算方法例如以下。
存储地址 = signed_extended(offset) + GPR[base]
以下通过一个样例体会ll、sc指令的作用,这个样例实现了上面介绍的wait函数。只是此处是使用ll、sc指令实现的。
wait:
ori $1, $0, sem // sem是信号量的地址,将这个地址赋给寄存器$1 TryAgain:
ll $2, 0($1) // 获取信号量的值,保存到寄存器$2
bne $2, $0, WaitForSem // 假设信号量被占用(其值为1),那么转移到地址WaitForSem
// 继续等待;假设信号量空暇(其值为0),那么运行以下的指令 nop
ori $2, $0, 1
sc $2, 0($1) // 假设没有被干扰,那么设置信号量被占用(将1保存到信号
// 量中)。同一时候,设置寄存器$2为1,反之,不改动信号量。
// 设置寄存器$2为0 beq $2, $0, TryAgain // 假设寄存器$2为0。表示ll、sc指令没有成功,未获取到
// 信号量。回到TryAgain继续尝试
nop jr $31 // 反之, 表示ll、sc指令成功,获取到信号量,能够进入
// “临界区域”了。调用wait函数时,会将返回地址放在
// 寄存器$31,所以此处jr $31指令就是回到调用过程。
// 进入临界区域
下一次将改动OpenMIPS以实现LL、SC指令