Linux內核部件分析 ——原子性操作atomic_t

在任何處理器平臺下,都會有一些原子性操作,供操作系統使用,我們這裏只講x86下面的。在單處理器情況下,每條指令的執行都是原子性的,但在多處理器情況下,只有那些單獨的讀操作或寫操作纔是原子性的。爲了彌補這一缺點,x86提供了附加的lock前綴,使帶lock前綴的讀修改寫指令也能原子性執行。帶lock前綴的指令在操作時會鎖住總線,使自身的執行即使在多處理器間也是原子性執行的。xchg指令不帶lock前綴也是原子性執行,也就是說xchg執行時默認會鎖內存總線。原子性操作是線程間同步的基礎,linux專門定義了一種只進行原子操作的類型atomic_t,並提供相關的原子讀寫調用API。本節就來分析這些原子操作在x86下的實現。
  1. typedef struct {  
  2.     volatile int counter;  
  3. } atomic_t;  

原子類型其實是int類型,只是禁止寄存器對其暫存。

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

原子類型的初始化。32位x86平臺下atomic API在arch/x86/include/asm/atomic_32.h中實現。

  1. static inline int atomic_read(const atomic_t *v)  
  2. {  
  3.     return v->counter;  
  4. }  
  5.   
  6. static inline void atomic_set(atomic_t *v, int i)  
  7. {  
  8.     v->counter = i;  
  9. }  

單獨的讀操作或者寫操作,在x86下都是原子性的。

  1. static inline void atomic_add(int i, atomic_t *v)  
  2. {  
  3.     asm volatile(LOCK_PREFIX "addl %1,%0"  
  4.              : "+m" (v->counter)  
  5.              : "ir" (i));  
  6. }  
  7.   
  8. static inline void atomic_sub(int i, atomic_t *v)  
  9. {  
  10.     asm volatile(LOCK_PREFIX "subl %1,%0"  
  11.              : "+m" (v->counter)  
  12.              : "ir" (i));  
  13. }  

atomic_add和atomic_sub屬於讀修改寫操作,實現時需要加lock前綴。

  1. static inline int atomic_sub_and_test(int i, atomic_t *v)  
  2. {  
  3.     unsigned char c;  
  4.   
  5.     asm volatile(LOCK_PREFIX "subl %2,%0; sete %1"  
  6.              : "+m" (v->counter), "=qm" (c)  
  7.              : "ir" (i) : "memory");  
  8.     return c;  
  9. }  

atomic_sub_and_test執行完減操作後檢查結果是否爲0。

  1. static inline void atomic_inc(atomic_t *v)  
  2. {  
  3.     asm volatile(LOCK_PREFIX "incl %0"  
  4.              : "+m" (v->counter));  
  5. }  
  6.   
  7. static inline void atomic_dec(atomic_t *v)  
  8. {  
  9.     asm volatile(LOCK_PREFIX "decl %0"  
  10.              : "+m" (v->counter));  
  11. }  

atomic_inc和atomic_dec是遞增遞減操作。

  1. static inline int atomic_dec_and_test(atomic_t *v)  
  2. {  
  3.     unsigned char c;  
  4.   
  5.     asm volatile(LOCK_PREFIX "decl %0; sete %1"  
  6.              : "+m" (v->counter), "=qm" (c)  
  7.              : : "memory");  
  8.     return c != 0;  
  9. }  

atomic_dec_and_test在遞減後檢查結果是否爲0。

  1. static inline int atomic_inc_and_test(atomic_t *v)  
  2. {  
  3.     unsigned char c;  
  4.   
  5.     asm volatile(LOCK_PREFIX "incl %0; sete %1"  
  6.              : "+m" (v->counter), "=qm" (c)  
  7.              : : "memory");  
  8.     return c != 0;  
  9. }  

atomic_inc_and_test在遞增後檢查結果是否爲0。

  1. static inline int atomic_add_negative(int i, atomic_t *v)  
  2. {  
  3.     unsigned char c;  
  4.   
  5.     asm volatile(LOCK_PREFIX "addl %2,%0; sets %1"  
  6.              : "+m" (v->counter), "=qm" (c)  
  7.              : "ir" (i) : "memory");  
  8.     return c;  
  9. }  

atomic_add_negative在加操作後檢查結果是否爲負數。

  1. static inline int atomic_add_return(int i, atomic_t *v)  
  2. {  
  3.     int __i;  
  4. #ifdef CONFIG_M386   
  5.     unsigned long flags;  
  6.     if (unlikely(boot_cpu_data.x86 <= 3))  
  7.         goto no_xadd;  
  8. #endif   
  9.     /* Modern 486+ processor */  
  10.     __i = i;  
  11.     asm volatile(LOCK_PREFIX "xaddl %0, %1"  
  12.              : "+r" (i), "+m" (v->counter)  
  13.              : : "memory");  
  14.     return i + __i;  
  15.   
  16. #ifdef CONFIG_M386   
  17. no_xadd: /* Legacy 386 processor */  
  18.     local_irq_save(flags);  
  19.     __i = atomic_read(v);  
  20.     atomic_set(v, i + __i);  
  21.     local_irq_restore(flags);  
  22.     return i + __i;  
  23. #endif   
  24. }  

atomic_add_return 不僅執行加操作,而且把相加的結果返回。它是通過xadd這一指令實現的。

  1. static inline int atomic_sub_return(int i, atomic_t *v)  
  2. {  
  3.     return atomic_add_return(-i, v);  
  4. }  

atomic_sub_return 不僅執行減操作,而且把相減的結果返回。它是通過atomic_add_return實現的。

  1. static inline int atomic_cmpxchg(atomic_t *v, int old, int new)  
  2. {  
  3.     return cmpxchg(&v->counter, old, new);  
  4. }  
  5.   
  6. #define cmpxchg(ptr, o, n)                      \   
  7.     ((__typeof__(*(ptr)))__cmpxchg((ptr), (unsigned long)(o),   \  
  8.                        (unsigned long)(n),      \  
  9.                        sizeof(*(ptr))))  
  10.   
  11. static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old,  
  12.                       unsigned long newint size)  
  13. {  
  14.     unsigned long prev;  
  15.     switch (size) {  
  16.     case 1:  
  17.         asm volatile(LOCK_PREFIX "cmpxchgb %b1,%2"  
  18.                  : "=a"(prev)  
  19.                  : "q"(new), "m"(*__xg(ptr)), "0"(old)  
  20.                  : "memory");  
  21.         return prev;  
  22.     case 2:  
  23.         asm volatile(LOCK_PREFIX "cmpxchgw %w1,%2"  
  24.                  : "=a"(prev)  
  25.                  : "r"(new), "m"(*__xg(ptr)), "0"(old)  
  26.                  : "memory");  
  27.         return prev;  
  28.     case 4:  
  29.         asm volatile(LOCK_PREFIX "cmpxchgl %k1,%2"  
  30.                  : "=a"(prev)  
  31.                  : "r"(new), "m"(*__xg(ptr)), "0"(old)  
  32.                  : "memory");  
  33.         return prev;  
  34.     case 8:  
  35.         asm volatile(LOCK_PREFIX "cmpxchgq %1,%2"  
  36.                  : "=a"(prev)  
  37.                  : "r"(new), "m"(*__xg(ptr)), "0"(old)  
  38.                  : "memory");  
  39.         return prev;  
  40.     }  
  41.     return old;  
  42. }  

atomic_cmpxchg是由cmpxchg指令完成的。它把舊值同atomic_t類型的值相比較,如果相同,就把新值存入atomic_t類型的值中,返回atomic_t類型變量中原有的值。

  1. static inline int atomic_xchg(atomic_t *v, int new)  
  2. {  
  3.     return xchg(&v->counter, new);  
  4. }  
  5.   
  6. #define xchg(ptr, v)                            \   
  7.     ((__typeof__(*(ptr)))__xchg((unsigned long)(v), (ptr), sizeof(*(ptr))))  
  8.   
  9. static inline unsigned long __xchg(unsigned long x, volatile void *ptr,  
  10.                    int size)  
  11. {  
  12.     switch (size) {  
  13.     case 1:  
  14.         asm volatile("xchgb %b0,%1"  
  15.                  : "=q" (x)  
  16.                  : "m" (*__xg(ptr)), "0" (x)  
  17.                  : "memory");  
  18.         break;  
  19.     case 2:  
  20.         asm volatile("xchgw %w0,%1"  
  21.                  : "=r" (x)  
  22.                  : "m" (*__xg(ptr)), "0" (x)  
  23.                  : "memory");  
  24.         break;  
  25.     case 4:  
  26.         asm volatile("xchgl %k0,%1"  
  27.                  : "=r" (x)  
  28.                  : "m" (*__xg(ptr)), "0" (x)  
  29.                  : "memory");  
  30.         break;  
  31.     case 8:  
  32.         asm volatile("xchgq %0,%1"  
  33.                  : "=r" (x)  
  34.                  : "m" (*__xg(ptr)), "0" (x)  
  35.                  : "memory");  
  36.         break;  
  37.     }  
  38.     return x;  
  39. }  

atomic_xchg則是將新值存入atomic_t類型的變量,並將變量的舊值返回。它使用xchg指令實現。

  1. /** 
  2.  * atomic_add_unless - add unless the number is already a given value 
  3.  * @v: pointer of type atomic_t 
  4.  * @a: the amount to add to v... 
  5.  * @u: ...unless v is equal to u. 
  6.  * 
  7.  * Atomically adds @a to @v, so long as @v was not already @u. 
  8.  * Returns non-zero if @v was not @u, and zero otherwise. 
  9.  */  
  10. static inline int atomic_add_unless(atomic_t *v, int a, int u)  
  11. {  
  12.     int c, old;  
  13.     c = atomic_read(v);  
  14.     for (;;) {  
  15.         if (unlikely(c == (u)))  
  16.             break;  
  17.         old = atomic_cmpxchg((v), c, c + (a));  
  18.         if (likely(old == c))  
  19.             break;  
  20.         c = old;  
  21.     }  
  22.     return c != (u);  
  23. }  

atomic_add_unless的功能比較特殊。它檢查v是否等於u,如果不是則把v的值加上a,返回值表示相加前v是否等於u。因爲在atomic_read和atomic_cmpxchg中間可能有其它的寫操作,所以要循環檢查自己的值是否被寫進去。

  1. #define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)   
  2.   
  3. #define atomic_inc_return(v)  (atomic_add_return(1, v))   
  4. #define atomic_dec_return(v)  (atomic_sub_return(1, v))  

atomic_inc_not_zero在v值不是0時加1。

atomic_inc_return對v值加1,並返回相加結果。

atomic_dec_return對v值減1,並返回相減結果。

  1. #define atomic_clear_mask(mask, addr)               \   
  2.     asm volatile(LOCK_PREFIX "andl %0,%1"           \  
  3.              : : "r" (~(mask)), "m" (*(addr)) : "memory")  

atomic_clear_mask清除變量某些位。

  1. #define atomic_set_mask(mask, addr)             \   
  2.     asm volatile(LOCK_PREFIX "orl %0,%1"                \  
  3.              : : "r" (mask), "m" (*(addr)) : "memory")  

atomic_set_mask將變量的某些位置位。

  1. /* Atomic operations are already serializing on x86 */  
  2. #define smp_mb__before_atomic_dec() barrier()   
  3. #define smp_mb__after_atomic_dec()  barrier()   
  4. #define smp_mb__before_atomic_inc() barrier()   
  5. #define smp_mb__after_atomic_inc()  barrier()  

因爲x86的atomic操作大多使用原子指令或者帶lock前綴的指令。帶lock前綴的指令執行前會完成之前的讀寫操作,對於原子操作來說不會受之前對同一位置的讀寫操作,所以這裏只是用空操作barrier()代替。barrier()的作用相當於告訴編譯器這裏有一個內存屏障,放棄在寄存器中的暫存值,重新從內存中讀入。

本節的atomic_t類型操作是最基礎的,爲了介紹下面的內容,必須先介紹它。如果可以使用atomic_t類型代替臨界區操作,也可以加快不少速度。

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