1. 概念
原子操作是指不被打斷的操作,即它的最小的執行機關。最簡單的原子操作就是一條條的彙編指令(不包括一些僞指令,僞指令會被彙編器解釋成多條彙編指令)。在 linux 中原子操作對應的資料結構為 atomic_t,定義如下:
typedef struct {
int counter;
} atomic_t;
本質上就是一個整型變量,之是以定義這麼一個資料類型,是為了讓原子操作函數隻接受 atomic_t 類型的操作數,如果傳入的不是 atomic_t 類型資料,在程式編譯階段就不會通過;另一個原因就是確定編譯器不會對相應的值進行通路優化,確定對它的通路都是對記憶體的通路,而不是對寄存器的通路。
2. 指派操作
ARM 處理器有直接對記憶體位址進行指派的指令(STR)。
#define atomic_set(v,i) (((v)->counter) = (i))
3. 讀操作
用 volatile 來防止編譯器對變量通路的優化,確定是對記憶體的通路,而不是對寄存器的通路。
#define atomic_read(v) (*(volatile int *)&(v)->counter)
4. 加操作
使用獨占指令完成累加操作。
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");
}
5. 減操作
對比加操作和減操作的代碼可以看出,它們非常的相似,其實不同的地方就一句,是以現在最新的核心源碼中已經使用宏定義 ATOMIC_OP(op, c_op, asm_op) 來重寫了這部分代碼。
static inline void atomic_sub(int i, atomic_t *v)
{
unsigned long tmp;
int result;
// 使用獨占指令讀取,然後執行減操作,獨占寫失敗時就重新執行
__asm__ __volatile__("@ atomic_sub\n"
"1: ldrex %0, [%3]\n"
" sub %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");
}
6. 其他操作
類似的原子操作函數還有一些,比如 atomic_XXX_return、atomic_cmpxchg、atomic_clear_mask,以及在此基礎上實作的 atomic_inc、atomic_dec、atomic_XXX_and_test、atomic_XXX_return等。以上代碼都是針對 SMP 處理器的實作方式,針對非 SMP 處理器,由于不存在其他核心的搶占,是以隻需要防止其他程序搶占即可實作原子操作,例如加操作:
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;
}
7. 總結
原子性操作的實作需要具體體系結構相關的指令集的支援。