现在的位置: 首页 > 综合 > 正文

Linux内核同步原语之原子操作

2018年04月16日 ⁄ 综合 ⁄ 共 10217字 ⁄ 字号 评论关闭

避免对同一数据的并发访问(通常由中断、对称多处理器、内核抢占等引起)称为同步。 

——题记

    内核源码:linux-2.6.38.8.tar.bz2

    目标平台:ARM体系结构

 

    原子操作确保对同一数据的“读取-修改-写入”操作在它的执行期间不会被打断,要么全部执行完成,要么根本不会执行。例如在ARM上对全局变量的++运算至少要经历以下三步: 

  1. ldr r3, [r3, #0]  
  2. add r2, r3, #1  
  3. str r2, [r3, #0]  

    这就给并发访问制造了可能,所以在编写内核代码时需要给有可能被并发访问的变量加上原子操作。

    Linux内核提供了两组原子操作接口,一组用于整数,一组用于位操作。

    1、原子整数操作

    针对整数的原子操作只能对atomic_t类型的数据进行处理。atomic_t定义如下: 

  1. /* linux-2.6.38.8/include/linux/types.h */  
  2. typedef struct {  
  3.     int counter;  
  4. } atomic_t;  

    下面将详细分析所有原子整数操作函数在ARMv6之前各种CPU上的实现(定义在linux-2.6.38.8/arch/arm/include/asm/atomic.h文件中)。 

  1. #define ATOMIC_INIT(i)  { (i) }  

    ATOMIC_INIT用于把atomic_t变量初始化为i,如下例把a初始化为2: 

  1. atomic_t a = ATOMIC_INIT(2);  

 

  1. #define atomic_read(v)  (*(volatile int *)&(v)->counter)  
  2. #define atomic_set(v,i) (((v)->counter) = (i))  

    atomic_read(v)用于读取atomic_t变量*v的值,而atomic_set(v,i)用于把atomic_t变量*v设置为i。 

  1. static inline int atomic_add_return(int i, atomic_t *v)  
  2. {  
  3.     unsigned long flags;  
  4.     int val;  
  5.   
  6.     raw_local_irq_save(flags);  
  7.     val = v->counter;  
  8.     v->counter = val += i;  
  9.     raw_local_irq_restore(flags);  
  10.   
  11.     return val;  
  12. }  
  13. #define atomic_add(i, v)    (void) atomic_add_return(i, v)  

    atomic_add(i, v)用于把atomic_t变量*v加i。这里的原子性实现只是简单地通过raw_local_irq_save函数来禁止中断。 

  1. /* linux-2.6.38.8/include/linux/irqflags.h */  
  2. #define raw_local_irq_save(flags)           \  
  3.     do {                        \  
  4.         typecheck(unsigned long, flags);    \  
  5.         flags = arch_local_irq_save();      \  
  6.     } while (0)  
  7. /* linux-2.6.38.8/arch/arm/include/asm/irqflags.h */  
  8. static inline unsigned long arch_local_irq_save(void)  
  9. {  
  10.     unsigned long flags, temp;  
  11.   
  12.     asm volatile(  
  13.         "   mrs %0, cpsr    @ arch_local_irq_save\n"  
  14.         "   orr %1, %0, #128\n"  
  15.         "   msr cpsr_c, %1"  
  16.         : "=r" (flags), "=r" (temp)  
  17.         :  
  18.         : "memory""cc");  
  19.     return flags;  
  20. }  

    typecheck函数用来确保参数flags的数据类型为unsigned long,arch_local_irq_save函数通过内嵌汇编设置当前程序状态寄存器的I位为1,从而禁止IRQ。待加操作完成后,再通过raw_local_irq_restore函数使能中断。 

  1. static inline int atomic_sub_return(int i, atomic_t *v)  
  2. {  
  3.     unsigned long flags;  
  4.     int val;  
  5.   
  6.     raw_local_irq_save(flags);  
  7.     val = v->counter;  
  8.     v->counter = val -= i;  
  9.     raw_local_irq_restore(flags);  
  10.   
  11.     return val;  
  12. }  
  13. #define atomic_sub(i, v)    (void) atomic_sub_return(i, v)  

    atomic_sub(i, v)用于把atomic_t变量*v减i。 

  1. #define atomic_inc(v)       atomic_add(1, v)  
  2. #define atomic_dec(v)       atomic_sub(1, v)  
  3.   
  4. #define atomic_inc_and_test(v)  (atomic_add_return(1, v) == 0)  
  5. #define atomic_dec_and_test(v)  (atomic_sub_return(1, v) == 0)  
  6. #define atomic_inc_return(v)    (atomic_add_return(1, v))  
  7. #define atomic_dec_return(v)    (atomic_sub_return(1, v))  
  8. #define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)  
  9.   
  10. #define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)  

    这些函数只是上面所讲函数的封装。 

  1. static inline int atomic_cmpxchg(atomic_t *v, int old, int new)  
  2. {  
  3.     int ret;  
  4.     unsigned long flags;  
  5.   
  6.     raw_local_irq_save(flags);  
  7.     ret = v->counter;  
  8.     if (likely(ret == old))  
  9.         v->counter = new;  
  10.     raw_local_irq_restore(flags);  
  11.   
  12.     return ret;  
  13. }  
  14. static inline int atomic_add_unless(atomic_t *v, int a, int u)  
  15. {  
  16.     int c, old;  
  17.   
  18.     c = atomic_read(v);  
  19.     while (c != u && (old = atomic_cmpxchg((v), c, c + a)) != c)  
  20.         c = old;  
  21.     return c != u;  
  22. }  
  23. #define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)  

    atomic_inc_not_zero(v)用于将atomic_t变量*v加1,并测试加1后的*v是否不为零,如果不为零则返回真。

    注意,这里所讲函数的参数v都是atomic_t类型数据的地址。

    2、原子位操作

    在CONFIG_SMP和__ARMEB__都未定义的情况下,原子位操作相关函数的定义如下: 

  1. /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */  
  2. #define ATOMIC_BITOP_LE(name,nr,p)      \  
  3.     (__builtin_constant_p(nr) ?     \  
  4.      ____atomic_##name(nr, p) :     \  
  5.      _##name##_le(nr,p))  
  6.   
  7. #define set_bit(nr,p)           ATOMIC_BITOP_LE(set_bit,nr,p)  
  8. #define clear_bit(nr,p)         ATOMIC_BITOP_LE(clear_bit,nr,p)  
  9. #define change_bit(nr,p)        ATOMIC_BITOP_LE(change_bit,nr,p)  
  10. #define test_and_set_bit(nr,p)      ATOMIC_BITOP_LE(test_and_set_bit,nr,p)  
  11. #define test_and_clear_bit(nr,p)    ATOMIC_BITOP_LE(test_and_clear_bit,nr,p)  
  12. #define test_and_change_bit(nr,p)   ATOMIC_BITOP_LE(test_and_change_bit,nr,p)  

    相对应的非原子位操作函数定义在linux-2.6.38.8/include/asm-generic/bitops/non-atomic.h文件中,它们的名字前都多带有两个下划线,如set_bit的非原子操作函数为__set_bit。

     原子位操作函数根据参数nr是否为常量分为两种实现方式:

    (1)、nr为常量时 

  1. /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */  
  2. static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)  
  3. {  
  4.     unsigned long flags;  
  5.     unsigned long mask = 1UL << (bit & 31);  
  6.   
  7.     p += bit >> 5;  
  8.   
  9.     raw_local_irq_save(flags);  
  10.     *p |= mask;  
  11.     raw_local_irq_restore(flags);  
  12. }  
  13. static inline void ____atomic_clear_bit(unsigned int bit, volatile unsigned long *p)  
  14. {  
  15.     unsigned long flags;  
  16.     unsigned long mask = 1UL << (bit & 31);  
  17.   
  18.     p += bit >> 5;  
  19.   
  20.     raw_local_irq_save(flags);  
  21.     *p &= ~mask;  
  22.     raw_local_irq_restore(flags);  
  23. }  
  24. static inline void ____atomic_change_bit(unsigned int bit, volatile unsigned long *p)  
  25. {  
  26.     unsigned long flags;  
  27.     unsigned long mask = 1UL << (bit & 31);  
  28.   
  29.     p += bit >> 5;  
  30.   
  31.     raw_local_irq_save(flags);  
  32.     *p ^= mask;  
  33.     raw_local_irq_restore(flags);  
  34. }  

    在32位机上,参数bit的理想取值为0到31,例如取值为1,就表示对*p的bit[1]进行操作。

    当bit取值大于31时,函数操作的就不是你想要操作的那个变量*p了(通过p += bit>> 5;语句实现),所以在实际的应用中应该要确保bit的取值在合理的范围内。

    *p |= mask;语句实现bit位的置位。

    *p &= ~mask;语句实现bit位的清零。

    *p ^= mask;语句实现bit位的翻转,即*p的bit位为0时被置位,为1时则被清零。 

  1. static inline int  
  2. ____atomic_test_and_set_bit(unsigned int bit, volatile unsigned long *p)  
  3. {  
  4.     unsigned long flags;  
  5.     unsigned int res;  
  6.     unsigned long mask = 1UL << (bit & 31);  
  7.   
  8.     p += bit >> 5;  
  9.   
  10.     raw_local_irq_save(flags);  
  11.     res = *p;  
  12.     *p = res | mask;  
  13.     raw_local_irq_restore(flags);  
  14.   
  15.     return (res & mask) != 0;  
  16. }  
  17. static inline int  
  18. ____atomic_test_and_clear_bit(unsigned int bit, volatile unsigned long *p)  
  19. {  
  20.     unsigned long flags;  
  21.     unsigned int res;  
  22.     unsigned long mask = 1UL << (bit & 31);  
  23.   
  24.     p += bit >> 5;  
  25.   
  26.     raw_local_irq_save(flags);  
  27.     res = *p;  
  28.     *p = res & ~mask;  
  29.     raw_local_irq_restore(flags);  
  30.   
  31.     return (res & mask) != 0;  
  32. }  
  33. static inline int  
  34. ____atomic_test_and_change_bit(unsigned int bit, volatile unsigned long *p)  
  35. {  
  36.     unsigned long flags;  
  37.     unsigned int res;  
  38.     unsigned long mask = 1UL << (bit & 31);  
  39.   
  40.     p += bit >> 5;  
  41.   
  42.     raw_local_irq_save(flags);  
  43.     res = *p;  
  44.     *p = res ^ mask;  
  45.     raw_local_irq_restore(flags);  
  46.   
  47.     return (res & mask) != 0;  
  48. }  

    这三个函数增加了return (res & mask) != 0;语句,用来判断*p的bit位原值是否为1,如果为1则函数返回1,否则返回0。

    (2)、nr不为常量时

    当nr不为常量时,原子位操作函数的定义如下: 

  1. /* linux-2.6.38.8/arch/arm/include/asm/bitops.h */  
  2. extern void _set_bit_le(int nr, volatile unsigned long * p);  
  3. extern void _clear_bit_le(int nr, volatile unsigned long * p);  
  4. extern void _change_bit_le(int nr, volatile unsigned long * p);  
  5. extern int _test_and_set_bit_le(int nr, volatile unsigned long * p);  
  6. extern int _test_and_clear_bit_le(int nr, volatile unsigned long * p);  
  7. extern int _test_and_change_bit_le(int nr, volatile unsigned long * p);  

    它们都是通过汇编语言来实现的,定义如下: 

  1. /* linux-2.6.38.8/arch/arm/lib/setbit.S */  
  2. ENTRY(_set_bit_le)  
  3.     bitop   orr  
  4. ENDPROC(_set_bit_le)  
  5.   
  6. /* linux-2.6.38.8/arch/arm/lib/clearbit.S */  
  7. ENTRY(_clear_bit_le)  
  8.     bitop   bic  
  9. ENDPROC(_clear_bit_le)  
  10.   
  11. /* linux-2.6.38.8/arch/arm/lib/changebit.S */  
  12. ENTRY(_change_bit_le)  
  13.     bitop   eor  
  14. ENDPROC(_change_bit_le)  
  15.   
  16. /* linux-2.6.38.8/arch/arm/lib/testsetbit.S */  
  17. ENTRY(_test_and_set_bit_le)  
  18.     testop  orreq, streqb  
  19. ENDPROC(_test_and_set_bit_le)  
  20.   
  21. /* linux-2.6.38.8/arch/arm/lib/testclearbit.S */  
  22. ENTRY(_test_and_clear_bit_le)  
  23.     testop  bicne, strneb  
  24. ENDPROC(_test_and_clear_bit_le)  
  25.   
  26. /* linux-2.6.38.8/arch/arm/lib/testchangebit.S */  
  27. ENTRY(_test_and_change_bit_le)  
  28.     testop  eor, strb  
  29. ENDPROC(_test_and_change_bit_le)  

    使用ENTRY和ENDPROC两个宏来定义一个名为name的函数,如下所示: 

  1. /* linux-2.6.38.8/include/linux/linkage.h */  
  2. #define ENTRY(name) \  
  3.   .globl name; \  
  4.   ALIGN; \  
  5.   name:  
  6.   
  7. #define ALIGN __ALIGN  
  8.   
  9. #define END(name) \  
  10.   .size name, .-name  
  11.   
  12. /* linux-2.6.38.8/arch/arm/include/asm/linkage.h */  
  13. #define __ALIGN .align 0  
  14.   
  15. #define ENDPROC(name) \  
  16.   .type name, %function; \  
  17.   END(name)  

    而汇编代码实现的宏bitop和testop被相应函数所调用,并传递给它们相应的参数,代码如下所示: 

  1. /* linux-2.6.38.8/arch/arm/lib/bitops.h */  
  2.     .macro  bitop, instr  
  3.     and r2, r0, #7  
  4.     mov r3, #1  
  5.     mov r3, r3, lsl r2  
  6.     save_and_disable_irqs ip  
  7.     ldrb    r2, [r1, r0, lsr #3]  
  8.     \instr  r2, r2, r3  
  9.     strb    r2, [r1, r0, lsr #3]  
  10.     restore_irqs ip  
  11.     mov pc, lr  
  12.     .endm  
  13.   
  14.     .macro  testop, instr, store  
  15.     add r1, r1, r0, lsr #3  
  16.     and r3, r0, #7  
  17.     mov r0, #1  
  18.     save_and_disable_irqs ip  
  19.     ldrb    r2, [r1]  
  20.     tst r2, r0, lsl r3  
  21.     \instr  r2, r2, r0, lsl r3  
  22.     \store  r2, [r1]  
  23.     moveq   r0, #0  
  24.     restore_irqs ip  
  25.     mov pc, lr  
  26.     .endm  

而根据armv7(armv6)手册中关于Single-copy atomicity的定义来说,
以下几个操作是atomicity的:
• all byte accesses
• all halfword accesses to halfword-aligned locations
• all word accesses to word-aligned locations
• memory accesses caused by LDREXD and STREXD instructions to doubleword-aligned locations.

atomic_set(atomic_t *v, int i) 的语义是 保证下一级记忆体中会出现 *(&v->counter)==i 这个状态
而____atomic_set_bit(unsigned int bit, volatile unsigned long *p) 的语义是 保证下一级记忆体中会出现  *(&v->counter)的第bit位会为1 这个状态
那么在atomic_set中,如果想在下一级记忆体中出现 *(&v->counter)==i 这个状态,按照atomicity定义以及atomic_t 结构体的define,&v->counter必定会出现在word-aligned的地址上,
那么这个*(&v->counter)= i的操作本身就是被arm所支持的atomicity操作,在c语言级别上直接做赋值操作即可。

____atomic_set_bit的实现同理。



以thread 0 先到为例:

atomic操作是主要是为了避免“
交错读”的问题,

简单的说,避免


step 0:

thread 0: mov r0,[addr]

              .......

step 1:

thread 1: mov r1,[addr]

              .......

step 2:

thread 0: str r0,[addr]


也就是 类似 “读取----->写回“ 这种操作的这种问题

也就是在coder的想法中,要么
thread 1应该要么读到 step 0之前的值,要么读到step 2以后的值

so,step 0 和 step 2中间必须有某种锁机制存在

single core上,关中断就好
而dual core上,就必须 ldrex ----> strex组合来保证thread 1会读到step 2以后的值
而由于write buffer的存在,必须有smb的存在来强制将这个写操作对thread 1可观察,不然thread 1就得多等会儿

只是赋值操作的话,因为本身armv6,armv7对word aligned的地址的word写操作就是atomic的,所以不需要任何锁机制。

抱歉!评论已关闭.