天天看點

原子性操作

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. 總結

原子性操作的實作需要具體體系結構相關的指令集的支援。