• ARM体系架构下的同步操作


    http://blog.hamobai.com/2012/06/28/synchronization-on-ARM-one/

    处理器在访问共享资源时,必须对临界区进行同步,即保证同一时间内,只有一个对临界区的访问者。

    当共享资源为一内存地址时,原子操作是对该类型共享资源同步访问的最佳方式。

    随着应用的日益复杂和SMP的广泛使用,处理器都开始提供硬件同步原语以支持原子地更新内存地址。

    CISC处理器比如IA32,可以提供单独的多种原子指令完成复杂的原子操作,由处理器保证读-修改-写回过程的原子性。

    而RISC则不同,由于除Load和Store的所有操作都必须在寄存器中完成,

    如何保证从装载内存地址到寄存器,到修改寄存器中的值,再到将寄存器中的值写回内存中可以原子性的完成,便成为了处理器设计的关键。

    从ARMv6架构开始,ARM处理器提供了Exclusive accesses同步原语,包含两条指令:

    LDREX
    STREX

    LDREX和STREX指令,将对一个内存地址的原子操作拆分成两个步骤,

    同处理器内置的记录exclusive accesses的exclusive monitors一起,完成对内存的原子操作。

    LDREX

    LDREX与LDR指令类似,完成将内存中的数据加载进寄存器的操作。

    与LDR指令不同的是,该指令也会同时初始化exclusive monitor来记录对该地址的同步访问。例如

    LDREX R1, [R0]

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

    STREX

    该指令的格式为:

    STREX Rd, Rm, [Rn]

    STREX会根据exclusive monitor的指示决定是否将寄存器中的值写回内存中。

    如果exclusive monitor许可这次写入,则STREX会将寄存器Rm的值写回Rn所存储的内存地址中,并将Rd寄存器设置为0表示操作成功。

    如果exclusive monitor禁止这次写入,则STREX指令会将Rd寄存器的值设置为1表示操作失败并放弃这次写入。

    应用程序可以根据Rd中的值来判断写回是否成功。

    在这篇文章里,首先会以Linux Kernel中ARM架构的原子相加操作为例,介绍这两条指令的使用方法;

    之后,会介绍GCC提供的一些内置函数,这些同步函数使用这两条指令完成同步操作。

    Linux Kernel中的atomic_add函数

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

     1 static inline void atomic_add(int i, atomic_t *v)
     2 {
     3         unsigned long tmp;
     4         int result;
     5 
     6         __asm__ __volatile__("@ atomic_add
    "
     7 "1:     ldrex   %0, [%3]
    "
     8 "       add     %0, %0, %4
    "
     9 "       strex   %1, %0, [%3]
    "
    10 "       teq     %1, #0
    "
    11 "       bne     1b"
    12         : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
    13         : "r" (&v->counter), "Ir" (i)
    14         : "cc");
    15 }

    在第7行,使用LDREX指令将v->counter所指向的内存地址的值装入寄存器中,并初始化exclusive monitor。

    在第8行,将该寄存器中的值与i相加。

    在第9,10,11行,使用STREX指令尝试将修改后的值存入原来的地址,

    如果STREX写入%1寄存器的值为0,则认为原子更新成功,函数返回;

    如果%1寄存器的值不为0,则认为exclusive monitor拒绝了本次对内存地址的访问,

    则跳转回第7行重新进行以上所述的过程,直到成功将修改后的值写入内存为止。

    该过程可能多次反复进行,但可以保证,在最后一次的读-修改-写回的过程中,没有其他代码访问该内存地址。

    static inline void atomic_set(atomic_t *v, int i)
    {
        unsigned long tmp;
    
        __asm__ __volatile__("@ atomic_set/n"
    "1:    ldrex    %0, [%1]/n"
    "    strex    %0, %2, [%1]/n"
    "    teq    %0, #0/n"
    "    bne    1b"
        : "=&r" (tmp)
        : "r" (&v->counter), "r" (i)
        : "cc");
    }

    输入为v(原子变量),i(要设置的值),均存放在动态分配的寄存器中。tmp用来指示操作是否成功。

    GCC内置的原子操作函数

    看了上面的GCC内联汇编,是不是有点晕?

    在用户态下,GCC为我们提供了一系列内置函数,这些函数可以让我们既享受原子操作的好处,

    又免于编写复杂的内联汇编指令。这一系列的函数均以__sync开头,分为如下几类:

    type __sync_fetch_and_add (type *ptr, type value, ...)
    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指向地址交换前存放的值。

    LDREX 和 STREX

    独占加载和存储寄存器。

    语法

    LDREX{cond} Rt, [Rn {, #offset}]
    STREX{cond} Rd, Rt, [Rn {, #offset}]
    LDREXB{cond} Rt, [Rn]
    STREXB{cond} Rd, Rt, [Rn]
    LDREXH{cond} Rt, [Rn]
    STREXH{cond} Rd, Rt, [Rn]
    LDREXD{cond} Rt, Rt2, [Rn]
    STREXD{cond} Rd, Rt, Rt2, [Rn]

    其中:

    cond

    是一个可选的条件代码(请参阅条件执行)。

    Rd 

    是存放返回状态的目标寄存器。

    Rt

    是要加载或存储的寄存器。

    Rt2

    为进行双字加载或存储时要用到的第二个寄存器。

    Rn

    是内存地址所基于的寄存器。

    offset

    为应用于 Rn 中的值的可选偏移量。offset 只可用于 Thumb-2 指令中。 如果省略 offset,则认为偏移量为 0。

    LDREX

    LDREX 可从内存加载数据。

    • 如果物理地址有共享 TLB 属性,则 LDREX 会将该物理地址标记为由当前处理器独占访问,并且会清除该处理器对其他任何物理地址的任何独占访问标记。

    • 否则,会标记:执行处理器已经标记了一个物理地址,但访问尚未完毕。

    STREX

    STREX 可在一定条件下向内存存储数据。 条件具体如下:

    • 如果物理地址没有共享 TLB 属性,且执行处理器有一个已标记但尚未访问完毕的物理地址,那么将会进行存储,清除该标记,并在 Rd 中返回值 0。

    • 如果物理地址没有共享 TLB 属性,且执行处理器也没有已标记但尚未访问完毕的物理地址,那么将不会进行存储,而会在 Rd 中返回值 1。

    • 如果物理地址有共享 TLB 属性,且已被标记为由执行处理器独占访问,那么将进行存储,清除该标记,并在 Rd 中返回值 0。

    • 如果物理地址有共享 TLB 属性,但没有标记为由执行处理器独占访问,那么不会进行存储,且会在 Rd中返回值 1。

    限制

    r15 不可用于 RdRtRt2 或 Rn 中的任何一个。

    对于 STREXRd 一定不能与 RtRt2 或 Rn 为同一寄存器。

    对于 ARM 指令:

    • Rt 必须是一个编号为偶数的寄存器,且不能为 r14

    • Rt2 必须为 R(t+1)

    • 不允许使用 offset

    对于 Thumb 指令:

    • r13 不可用于 RdRt 或 Rt2 中的任何一个

    • 对于 LDREXDRt 和 Rt2 不可为同一个寄存器

    • offset 的值可为 0-1020 范围内 4 的任何倍数。

    用法

    利用 LDREX 和 STREX 可在多个处理器和共享内存系统之前实现进程间通信。

    出于性能方面的考虑,请将相应 LDREX 指令和 STREX 指令间的指令数控制到最少。

    Note

    STREX 指令中所用的地址必须要与近期执行次数最多的 LDREX 指令所用的地址相同。
    如果使用不同的地址,则 STREX 指令的执行结果将不可预知。

    体系结构

    ARM LDREX 和 STREX 可用于 ARMv6 及更高版本中。

    ARM LDREXBLDREXHLDREXDSTREXBSTREXD 和 STREXH 可用于 ARMv6K 及更高版本中。

    所有这些 32 位 Thumb 指令均可用于 ARMv6T2 及更高版本,但 LDREXD 和 STREXD 在 ARMv7-M 架构中不可用。

    这些指令均无 16 位版本。

    示例

        MOV r1, #0x1                ; load the ‘lock taken’ value
    try
        LDREX r0, [LockAddr]        ; load the lock value
        CMP r0, #0                  ; is the lock free?
        STREXEQ r0, r1, [LockAddr]  ; try and claim the lock
        CMPEQ r0, #0                ; did this succeed?
        BNE try                     ; no – try again
        ....                        ; yes – we have the lock

    http://lxr.free-electrons.com/source/arch/arm/include/asm/atomic.h?v=2.6.33 

    /*
    *  arch/arm/include/asm/atomic.h
    *
    *  Copyright (C) 1996 Russell King.
    *  Copyright (C) 2002 Deep Blue Solutions Ltd.
    *
    * This program is free software; you can redistribute it and/or modify
    * it under the terms of the GNU General Public License version 2 as
    * published by the Free Software Foundation.
    */
    #ifndef __ASM_ARM_ATOMIC_H
    #define __ASM_ARM_ATOMIC_H
    
    #include <linux/compiler.h>
    #include <linux/types.h>
    #include <asm/system.h>
    
    #define ATOMIC_INIT(i)  { (i) }
    
    #ifdef __KERNEL__
    
    /*
    * On ARM, ordinary assignment (str instruction) doesn't clear the local
    * strex/ldrex monitor on some implementations. The reason we can use it for
    * atomic_set() is the clrex or dummy strex done on every exception return.
    */
    #define atomic_read(v)  ((v)->counter)
    #define atomic_set(v,i) (((v)->counter) = (i))
    
    #if __LINUX_ARM_ARCH__ >= 6
    
    /*
    * ARMv6 UP and SMP safe atomic ops.  We use load exclusive and store exclusive to ensure that these are atomic.  
    * We may loop to ensure that the update happens.
    */
    static inline void atomic_add(int i, atomic_t *v)
    {
           unsigned long tmp;
           int result;
    
           __asm__ __volatile__("@ atomic_add
    "
    "1:     ldrex   %0, [%2]
    "
    "       add     %0, %0, %3
    "
    "       strex   %1, %0, [%2]
    "
    "       teq     %1, #0
    "
    "       bne     1b"
           : "=&r" (result), "=&r" (tmp)
           : "r" (&v->counter), "Ir" (i)
           : "cc");
    }
    
    static inline int atomic_add_return(int i, atomic_t *v)
    {
           unsigned long tmp;
           int result;
    
           smp_mb();
    
           __asm__ __volatile__("@ atomic_add_return
    "
    "1:     ldrex   %0, [%2]
    "
    "       add     %0, %0, %3
    "
    "       strex   %1, %0, [%2]
    "
    "       teq     %1, #0
    "
    "       bne     1b"
           : "=&r" (result), "=&r" (tmp)
           : "r" (&v->counter), "Ir" (i)
           : "cc");
    
           smp_mb();
    
           return result;
    }
    
    static inline void atomic_sub(int i, atomic_t *v)
    {
           unsigned long tmp;
           int result;
    
           __asm__ __volatile__("@ atomic_sub
    "
    "1:     ldrex   %0, [%2]
    "
    "       sub     %0, %0, %3
    "
    "       strex   %1, %0, [%2]
    "
    "       teq     %1, #0
    "
    "       bne     1b"
           : "=&r" (result), "=&r" (tmp)
           : "r" (&v->counter), "Ir" (i)
           : "cc");
    }
    
    static inline int atomic_sub_return(int i, atomic_t *v)
    {
           unsigned long tmp;
           int result;
    
           smp_mb();
    
           __asm__ __volatile__("@ atomic_sub_return
    "
    "1:     ldrex   %0, [%2]
    "
    "       sub     %0, %0, %3
    "
    "       strex   %1, %0, [%2]
    "
    "       teq     %1, #0
    "
    "       bne     1b"
           : "=&r" (result), "=&r" (tmp)
           : "r" (&v->counter), "Ir" (i)
           : "cc");
    
           smp_mb();
    
           return result;
    }
    
    static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new)
    {
           unsigned long oldval, res;
    
           smp_mb();
    
           do {
                   __asm__ __volatile__("@ atomic_cmpxchg
    "
                   "ldrex  %1, [%2]
    "
                   "mov    %0, #0
    "
                   "teq    %1, %3
    "
                   "strexeq %0, %4, [%2]
    "
                       : "=&r" (res), "=&r" (oldval)
                       : "r" (&ptr->counter), "Ir" (old), "r" (new)
                       : "cc");
           } while (res);
    
           smp_mb();
    
           return oldval;
    }
    
    static inline void atomic_clear_mask(unsigned long mask, unsigned long *addr)
    {
           unsigned long tmp, tmp2;
    
           __asm__ __volatile__("@ atomic_clear_mask
    "
    "1:     ldrex   %0, [%2]
    "
    "       bic     %0, %0, %3
    "
    "       strex   %1, %0, [%2]
    "
    "       teq     %1, #0
    "
    "       bne     1b"
           : "=&r" (tmp), "=&r" (tmp2)
           : "r" (addr), "Ir" (mask)
           : "cc");
    }
    
    #else /* ARM_ARCH_6 */
    
    #ifdef CONFIG_SMP
    #error SMP not supported on pre-ARMv6 CPUs
    #endif
    
    static inline int atomic_add_return(int i, atomic_t *v)
    {
           unsigned long flags;
           int val;
    
           raw_local_irq_save(flags);
           val = v->counter;
           v->counter = val += i;
           raw_local_irq_restore(flags);
    
           return val;
    }
    #define atomic_add(i, v)        (void) atomic_add_return(i, v)
    
    static inline int atomic_sub_return(int i, atomic_t *v)
    {
           unsigned long flags;
           int val;
    
           raw_local_irq_save(flags);
           val = v->counter;
           v->counter = val -= i;
           raw_local_irq_restore(flags);
    
           return val;
    }
    #define atomic_sub(i, v)        (void) atomic_sub_return(i, v)
    
    static inline int atomic_cmpxchg(atomic_t *v, int old, int new)
    {
           int ret;
           unsigned long flags;
    
           raw_local_irq_save(flags);
           ret = v->counter;
           if (likely(ret == old))
                   v->counter = new;
           raw_local_irq_restore(flags);
    
           return ret;
    }
    
    static inline void atomic_clear_mask(unsigned long mask, unsigned long *addr)
    {
           unsigned long flags;
    
           raw_local_irq_save(flags);
           *addr &= ~mask;
           raw_local_irq_restore(flags);
    }
    
    #endif /* __LINUX_ARM_ARCH__ */
    
    #define atomic_xchg(v, new) (xchg(&((v)->counter), new))
    
    static inline int atomic_add_unless(atomic_t *v, int a, int u)
    {
           int c, old;
    
           c = atomic_read(v);
           while (c != u && (old = atomic_cmpxchg((v), c, c + a)) != c)
                   c = old;
           return c != u;
    }
    #define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)
    
    #define atomic_inc(v)           atomic_add(1, v)
    #define atomic_dec(v)           atomic_sub(1, v)
    
    #define atomic_inc_and_test(v)  (atomic_add_return(1, v) == 0)
    #define atomic_dec_and_test(v)  (atomic_sub_return(1, v) == 0)
    #define atomic_inc_return(v)    (atomic_add_return(1, v))
    #define atomic_dec_return(v)    (atomic_sub_return(1, v))
    #define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)
    
    #define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)
    
    #define smp_mb__before_atomic_dec()     smp_mb()
    #define smp_mb__after_atomic_dec()      smp_mb()
    #define smp_mb__before_atomic_inc()     smp_mb()
    #define smp_mb__after_atomic_inc()      smp_mb()
    
    #include <asm-generic/atomic-long.h>
    #endif
    #endif
     
  • 相关阅读:
    为什么未来是全栈project师的世界?
    剑指Offer面试题10(Java版):二进制中的1的个数
    arm-linux内存管理学习笔记(1)-内存页表的硬件原理
    高速掌握Lua 5.3 —— 字符串库 (2)
    C语言实现使用动态数组来构造栈结构
    [自学AndroidStudio系列]第二篇章:高速上手AS小技巧其一
    苹果新的编程语言 Swift 语言进阶(十三)--类型检查与类型嵌套
    JAVA 安装JDK注意事项
    hdu 1398 Square Coins(母函数)
    百度有道雅虎的实习面试经历
  • 原文地址:https://www.cnblogs.com/shangdawei/p/3915735.html
Copyright © 2020-2023  润新知