独占加载和存储指令与原子操作:LDEX STEX,gcc内嵌汇编语法

 转了一篇蜗牛关于原子操作的文章:https://blog.csdn.net/u010443710/article/details/103910077

个人再写一写个人理解的原子操作细节内容

首先来看下独占式访问指令的介绍:

独占式访问(local monitor和global monitor)
       cpu支持独占式的内存访问指令LDEX32.W和STEX32.W。用户可以使用这两条指令构成原子锁等同步原语实现同一个核不同进程之间或者不同核之间的同步。通过LDEX指令标记需要独占访问的地址,STEX指令判断被标记的地址是否被其他进程抢占。cpu为每个核心上设置了一个位于L1数据高缓的局部监测器和一个位于二级高缓的全局监测器。每个监测器由一个状态机和一个地址缓存器组成,其中,状态机包含两个状态:IDLE和EXCLUSIVE。

      对于属性设置为cacheable的页面,通过局部监测器就能够实现独占式访问。LDEX指令在执行过程中设置局部监测器的状态机为EXCLUSIVE态并将访问的地址保存到地址缓存器中;STEX指令在执行过程中读取局部监测器的状态和地址,如果状态为EXCLUSIVE并且地址匹配,那么执行该写操作,返回写成功,并清除状态机回IDLE态;否则如果状态或者地址有一项不满足条件,或者数据高速缓存未使能时,不执行该写操作,返回写失败,并清除状态机回IDLE态。其他核的写操作在地址匹配局部监测器时,也会将状态机清回IDLE态;不同地址的独占访问不影响局部监测器。此外,在进程切换时需要清除局部监测器。
       对于属性设置为non-cacheable的页面,需要局部监测器和全局监测器共同作用实现独占式访问。LDEX在执行过程中不仅要设置局部监测器还需要设置全局监测器;STEX在局部监测器检查通过后需要进一步检查全局监测器,只有当全局监测器也通过检查,才执行写操作、返回写成功,清除状态机;否则不执行写操作,返回写失败,清除状态机。其他核的写操作在地址匹配某个全局监测器时,会将该全局监测器的状态清回IDLE态。

先不管局部监视器和全局监视器怎么工作的,对软件只需要关注两点:

  1. ldex.w rz, (rx) 从存储器地址RX加载字到通用寄存器RZ中,并标记RX地址为独占。(注意ldex只标记,并不做独占判断)
  2. stex.w rz, (rx) 将通用寄存器RZ中的字存储到存储器地址RX中,若独占存储成功,则源寄存器RZ返回1;否则源寄存器返回0表示独占存储失败。(也就是stex会先判断rx是不是已经标记了独占的地址,标记了独占指令才能成功执行)

下面解析一下atomic_add_unless函数的汇编内容

static inline int atomic_add_unless(atomic_t *v, int a, int u) 只要原子变量v不等于u,那么就执行原子变量v加a的操作。
如果v不等于u,返回非0值,否则返回0值
static inline int __atomic_add_unless(atomic_t *v, int a, int u)
{
	unsigned long tmp, ret;

	smp_mb();

	asm volatile (
	"1:	ldex.w		%0, (%3) \n" // 独占加载:tmp = v->counter, 并标记v->counter独占
	"	mov		%1, %0   \n"     // ret = tmp
	"	cmpne		%0, %4   \n" // if (tmp == u)
	"	bf		2f	 \n"         // goto 2: ##相当于退出;2f后面f的意思是,向下找第一个为2的lab进行跳转
	"	add		%0, %2   \n"     // tmp += a
	"	stex.w		%0, (%3) \n" // 独占存储: v->counter = tmp, 执行结果放在tmp中
	"	bez		%0, 1b   \n"     // if (tmp == 0) goto 1: 也就是独占存储失败则重新进行独占加载
	"2:				 \n"         //##上一句1b后边的b意思是向上找到第一个为1的lab进行跳转
		: "=&r" (tmp), "=&r" (ret)         //output: %0->tmp; %1->ret
		: "r" (a), "r"(&v->counter), "r"(u)//input:  %2->a; %3->&v->counter; %u->u
		: "memory");

	if (ret != u)
		smp_mb();

	return ret;
}

刚接触GCC内嵌汇编语法有些头疼,这里不具体介绍,简单说下这里的代码

汇编最后3行“:”开头的语句是声明输入输出和告诉编译器memory的内容有修改,重点是前边两个:

: "=&r" (tmp), "=&r" (ret) 

 这一行是声明输出,r意思是用寄存器来代替tmp和ret,对应上面指令的%n,,后面输入声明类似,按照顺序给局部变量声明的寄存器编号。也就是说对于前面的汇编指令:%0就是tmp; %1就是ret,%2就是a; %3就是&v->counter; %4就是u

然后是关于跳转指令lab位置的一点说明

    "    bf        2f     \n"        

这个2f,刚开始没搞懂,下面有一个2:的lab ,但是为啥是写的2f呢?

这是因内嵌汇编所有的lab都是全局的,如果遇到lab重名,可能会跳转到其他地方的lab上去。

这里提供两种方法:

1, lab后加b,默认向上找到第一个名字相同的lab进行跳转

2,lab后加f,默认向下找到第一个名字相同的lab进行跳转

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章