ARM体系架构下的同步操作

28 篇文章 11 订阅

处理器在访问共享资源时,必须对临界区进行同步,即保证同一时间内,只有一个对临界区的访问者。当共享资源为一内存地址时,原子操作是对该类型共享资源同步访问的最佳方式。随着应用的日益复杂和SMP的广泛使用,处理器都开始提供硬件同步原语以支持原子地更新内存地址。

CISC处理器比如IA32,可以提供单独的多种原子指令完成复杂的原子操作,由处理器保证读-修改-写回过程的原子性。而RISC则不同,由于除Load和Store的所有操作都必须在寄存器中完成,如何保证从装载内存地址到寄存器,到修改寄存器中的值,再到将寄存器中的值写回内存中可以原子性的完成,便成为了处理器设计的关键。

从ARMv6架构开始,ARM处理器提供了Exclusive accesses同步原语,包含两条指令: LDREX STREX LDREX和STREX指令,将对一个内存地址的原子操作拆分成两个步骤,同处理器内置的记录exclusive accesses的exclusive monitors一起,完成对内存的原子操作。

LDREX

LDREX与LDR指令类似,完成将内存中的数据加载进寄存器的操作。与LDR指令不同的是,该指令也会同时初始化exclusive monitor来记录对该地址的同步访问。例如

 

1
LDREX R1, [R0]

会将R0寄存器中内存地址的数据,加载进R1中并更新exclusive monitor。

STREX

该指令的格式为:

 

1
STREX Rd, Rm, [Rn]

STREX会根据exclusive monitor的指示决定是否将寄存器中的值写回内存中。如果exclusive monitor许可这次写入,则STREX会将寄存器Rm的值写回Rn所存储的内存地址中,并将Rd寄存器设置为0表示操作成功。如果exclusive monitor禁止这次写入,则STREX指令会将Rd寄存器的值设置为1表示操作失败并放弃这次写入。应用程序可以根据Rd中的值来判断写回是否成功。

在下面部分中,我会以Linux Kernel中如何编写原子操作代码具体介绍LDREX和STREX的使用方法,并介绍gcc提供的ARM架构下的关于这两条指令的C语言扩展。

为了实现对内存地址同步访问而引入的 LDREX STREX 两条指令。在这篇文章里,首先会以Linux Kernel中ARM架构的原子相加操作为例,介绍这两条指令的使用方法;之后,会介绍GCC提供的一些内置函数,这些同步函数使用这两条指令完成同步操作。

Linux Kernel中的atomic_add函数

如下是Linux Kernel中使用的atomic_add函数的定义,它实现原子的给v指向的atomic_t增加i的功能。

atomic_add

static inline void atomic_add(int i, atomic_t *v)
{
        unsigned long tmp;
        int result;

        __asm__ __volatile__("@ atomic_add\n"
"1:     ldrex   %0, [%3]\n"
"       add     %0, %0, %4\n"
"       strex   %1, %0, [%3]\n"
"       teq     %1, #0\n"
"       bne     1b"
        : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
        : "r" (&v->counter), "Ir" (i)
        : "cc");
}

在第7行,使用LDREX指令将v->counter所指向的内存地址的值装入寄存器中,并初始化exclusive monitor。 在第8行,将该寄存器中的值与i相加。 在第9,10,11行,使用STREX指令尝试将修改后的值存入原来的地址,如果STREX写入%1寄存器的值为0,则认为原子更新成功,函数返回;如果%1寄存器的值不为0,则认为exclusive monitor拒绝了本次对内存地址的访问,则跳转回第7行重新进行以上所述的过程,直到成功将修改后的值写入内存为止。该过程可能多次反复进行,但可以保证,在最后一次的读-修改-写回的过程中,没有其他代码访问该内存地址。

GCC内置的原子操作函数

看了上面的GCC内联汇编,是不是有点晕?在用户态下,GCC为我们提供了一系列内置函数,这些函数可以让我们既享受原子操作的好处,又免于编写复杂的内联汇编指令。这一系列的函数均以__sync开头,分为如下几类:

type __sync_fetch_and_sub (type *ptr, type value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)

 

这一系列函数完成对ptr所指向的内存地址的对应操作,并返回操作之前的值。

type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)
type __sync_or_and_fetch (type *ptr, type value, ...)
type __sync_and_and_fetch (type *ptr, type value, ...)
type __sync_xor_and_fetch (type *ptr, type value, ...)
type __sync_nand_and_fetch (type *ptr, type value, ...)

 

这一系列函数完成对ptr所指向的内存地址的对应操作,并返回操作之后的值。

bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)

 

这两个函数完成对变量的原子比较和交换。即如果ptr所指向的内存地址存放的值与oldval相同的话,则将其用newval的值替换。 返回bool类型的函数返回比较的结果,相同为true,不同为false;返回type的函数返回的是ptr指向地址交换前存放的值。

 

参考资料:

https://developer.arm.com/documentation/dht0008/a/arm-synchronization-primitives/exclusive-accesses/exclusive-monitors

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值