• ARM linux 的原子操作分析


    linux ARM的原子操作源文件位于 linux/arch/arm/include/asm/atomic.h

    linux源码宏展开

    最开始由如下宏定义,linux的各种宏太复杂了,分析起来有点费劲

    #define ATOMIC_OP(op, c_op, asm_op)					  <-------------------|
    static inline void atomic_##op(int i, atomic_t *v)			                      |
    {									                      |
    	unsigned long tmp;						                      |
    	int result;							                      |
    									                      |
    	prefetchw(&v->counter);						                      |
    	__asm__ __volatile__("@ atomic_" #op "
    "			                      |
    "1:	ldrex	%0, [%3]
    "						                      |
    "	" #asm_op "	%0, %0, %4
    "					                      |
    "	strex	%1, %0, [%3]
    "						                      |
    "	teq	%1, #0
    "						                      |
    "	bne	1b"							                      |
    	: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)		                      |
    	: "r" (&v->counter), "Ir" (i)					                      |
    	: "cc");							                      |
    }                                                                                              |
                                                                                                   |
    #define ATOMIC_OPS(op, c_op, asm_op)					 <-----|              |
    	ATOMIC_OP(op, c_op, asm_op)					       |--------------|
    	ATOMIC_OP_RETURN(op, c_op, asm_op)					|
    			                                                        |
    ATOMIC_OPS(add, +=, add)		-----------------------------------------
    

    最终最开始的函数static inline void atomic_##op(int i, atomic_t *v),替换op为add, asm_op为add被声明成:

    static inline void atomic_add(int i, atomic_t *v)
    {									
    	unsigned long tmp;	
    	int result;
    									
    	prefetchw(&v->counter);						
    	__asm__ __volatile__("@ atomic_" #op "
    "
    "1:	ldrex	%0, [%3]
    "						
    "	add     %0, %0, %4
    "					
    "	strex	%1, %0, [%3]
    "						
    "	teq	%1, #0
    "						
    "	bne	1b"							
    	: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
    	: "r" (&v->counter), "Ir" (i)					
    	: "cc");							
    }
    

    又根据GCC内联汇编的替换规则,result,tmp,v->counter,&v->counter,i按照出现的顺序分别替换%0-%4,操作数被逐个替换:

    static inline void atomic_add(int i, atomic_t *v)
    {									
    	unsigned long tmp;	
    	int result;
    									
    	prefetchw(&v->counter);						
    	__asm__ __volatile__(
    "1:	ldrex	result, [&v->counter]
    "						
    "	add	result, result, i
    "					
    "	strex	tmp, result, [&v->counter]
    "						
    "	teq	tmp, #0
    "						
    "	bne	1b"							
    	: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
    	: "r" (&v->counter), "Ir" (i)					
    	: "cc");							
    }
    

    单独提取汇编部分

    1:
    	ldrex	result, [&v->counter]
    	add	result, result, i
    	strex	tmp, result, [&v->counter]
    	teq	tmp, #0
    	bne	1b
    

    翻译成C语言:

    	do{
    		result = v->counter;				//ldrex	result, [&v->counter]
    		result = result + i;				//add	result, result, i
    		tmp = (v->counter = result赋值失败?1:0);	//strex	tmp, result, [&v->counter]
    	}while(tmp != 0);					//teq	tmp, #0;bne	1b
    

    比较怪的一句是tmp = (v->counter = result赋值失败?1:0);,当然这不是合法的C语句,但是它表达的就是这个意思。它会检测v->counter = result这句赋值操作,如果成功赋值则返回0给tmp,否则返回1。这里重点是要理解ldrexstrex这两条汇编是什么意思。

    LDREX和STREX指令

    从ARMv6架构开始,ARM处理器提供了Exclusive accesses同步原语,用于对内存访问的同步, 包含两条指令:

    LDREX Rd [Rs]
    STREX R0 Rd [Rs]
    

    LDREX和STREX指令,将对一个内存地址的原子操作拆分成两个步骤,一起完成对内存的原子操作。可以理解为执行LDREX Rd [Rs]指令会标记对[Rs]这个内存地址的访问是独占状态(exclusive state)。而执行STREX R0 Rd [Rs]指令会让先前处于独占状态的内存地址[Rs]转变为正常状态,并且设置R0为0。若执行STREX R0 Rd [Rs]指令时,内存地址[Rs]是正常状态,则指令的存储动作会失败,并且R0置为1。

    ARM内部为了实现这个功能,还有不少复杂的情况要处理。在ARM系统中,内存有两种不同且对立的属性,即共享(Shareable)和非共享(Non-shareable)。共享意味着该段内存可以被系统中不同处理器访问到,这些处理器可以是同构的也可以是异构的。而非共享,则相反,意味着该段内存只能被系统中的一个处理器所访问到,对别的处理器来说不可见。

    为了实现独占访问,ARM系统中还特别提供了所谓独占监视器(Exclusive Monitor)的东西,其结构大致如下:

    这里面包含两种类型的monitor,局部和全局的。对于非共享的内存,仅关心local monitor即可,而共享的内存则要local也global都需要关心。

    每个monitor是一个状态机,包含exclusive态和open态。LDREX操作会让对应的monitor变为exclusive态,而如果monitor现在为exclusive态,这时执行STREX操作会让monitor变为open态,并且存储内存这个动作执行成功,否则monitor状态不变,存储行为也失败。

    UP或SMP系统中非CPU间共享的的内存原子操作

    这个情况就是单个CPU上线程和线程间,或者线程和中断间的独占访问。这时关注local monitor即可。又根据文档:ARM同步原语提到:

    Resetting monitors
    When an operating system performs a context switch, it must reset the local monitor to open state, to prevent false positives occurring. ARMv6K introduced the Clear-Exclusive instruction, CLREX, to reset the local monitor.

    在上下文切换是会通过CLREX指令将monitor复位为open态。两个线程切换执行时:

    T1: Thread A调用LDREX,导致Local Monitor变为exclusive态。
    T2:系统调度触发,中断里面会调用CLREX复位Local Monitor为open态。
    T3:Thread B调用LDREX,导致Local Monitor变为exclusive态。
    T4:Thread B调用STREX,根据状态机的切换图,Local Monitor前一状态为exclusive,则STREX存储操作成功,并且Monitor变为open态。
    T5:系统调度触发,继续复位Monitor为open态。
    T6:Thead A调用STREX,根据状态机,open态时STREX动作会失败,存储操作失败,状态不变。

    CPU线程和中断间的原子操作

    T1: Thread A调用LDREX,导致Local Monitor变为exclusive态。
    T2:中断触发。
    T3:中断调用LDREX,exclusive态维持不变。
    T4:中断调用STREX,根据状态机的切换图,Local Monitor前一状态为exclusive,则STREX存储操作成功,并且Monitor变为open态。
    T5:中断返回。Thead A调用STREX,根据状态机,open态时STREX动作会失败,存储操作失败,状态不变。

    SMP CPU间的原子操作


    由于是CPU间的原子操作,那么本CPUSTREX能否成功既要关注这个CPU的local monitor还要关注global monitor。分析上面的过程:
    T1:CPU0执行LDREX,它的local monitor由open变为exclusive态,global monitor同样由open变为exclusive态。
    T2:CPU1执行LDREX,它的local monitor由open变为exclusive态,global monitor维持exclusive态。
    T3:CPU0执行STREX,它的local/global monitor均是由exclusive态变为open态,则这次store操作成功。
    T5:CPU1执行STREX,它的local monitor由exclusive态变为open态,这是没问题的,但是它的global monitor是由open变为open态,store操作会失败,则这次STREX的存储动作未成功。

    还有其他许多复杂的可能,也可以通过ldrex/strex指令的机制分析出来。

    参考资料
    http://blog.chinaunix.net/uid-20543672-id-3262230.html
    https://blog.csdn.net/juS3Ve/article/details/81784688
    http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf
    https://zhuanlan.zhihu.com/p/89299392
    http://blog.chinaaet.com/weiqi7777/p/5100060359

  • 相关阅读:
    这个开源组织里的项目都是精品
    动态代理大揭秘,带你彻底弄清楚动态代理!
    Python_对excel表格读写-openpyxl、xlrd&xlwt
    PostgreSQL 查看数据库,索引,表,表空间大小
    Mysql日志undo log、redo log、binlog、relay log
    python 高级
    win10 ssh 登录 ubuntu Access denied
    mysql5.7 ubuntu20.04
    pycharm 激活 成功
    Xmind:文件损坏怎么恢复
  • 原文地址:https://www.cnblogs.com/thammer/p/12596119.html
Copyright © 2020-2023  润新知