1、原子操作思想
原子操作(atomic operation)
,不可分割的操作。其通過原子變量來實現,以保證單個CPU
周期內,讀寫該變量,不能被打斷,進而判斷該變量的值,來解決并發引起的互斥。
Atomic
類型的函數可以在執行期間禁止中斷,并保證在訪問變量時的原子性。
同時,Linux
內核提供了兩類原子操作的接口,分別是針對位和整型變量的原子操作。
2、整型變量原子操作
2.1 API接口
對于整形變量的原子操作,內核提供了一系列的
API
接口
/*設置原子變量的值*/
atomic_t v = ATOMIC_INIT(0); /* 定義原子變量v并初始化為0 */
void atomic_set(atomic_t *v, int i); /* 設置原子變量的值為i */
/*獲取原子變量的值*/
atomic_read(atomic_t *v); /* 返回原子變量的值*/
/*原子變量的加減*/
void atomic_add(int i, atomic_t *v); /* 原子變量增加i */
void atomic_sub(int i, atomic_t *v); /* 原子變量減少i */
/*原子變量的自增,自減*/
void atomic_inc(atomic_t *v); /* 原子變量增加1 */
void atomic_dec(atomic_t *v); /* 原子變量減少1 */
/*原子變量的操作并測試*/
int atomic_inc_and_test(atomic_t *v); /*進行對應操作后,測試原子變量值是否為0*/
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
/*原子變量的操作并返回*/
int atomic_add_return(int i, atomic_t *v); /*進行對應操作后,返回新的值*/
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
2.2 API實現
我們下面就介紹幾個稍微有代表性的接口實現
以下基于
Linux
內核源碼4.19
,剛看是看的時候,有點摸不著頭腦,因為定義的地方和引用的地方較多,不太容易找到,后來才慢慢得窺門徑。
2.2.1 原子變量結構體
typedef struct {
int counter;
} atomic_t;
結構體名稱 :atomic_t
文件位置 :include/linux/types.h
主要作用 :原子變量結構體,該結構體只包含一個整型成員變量counter
,用于存儲原子變量的值。
2.2.2 設置原子變量操作
2.2.2.1 ATOMIC_INIT
#define ATOMIC_INIT(i) { (i) }
函數介紹 :定義了一個ATOMIC類型的變量,并初始化為給定的值。
文件位置 :arch/arm/include/asm/atomic.h
,由include/linux/atomic.h
引用
實現方法 :這個宏定義比較簡單,通過大括號將值包裹起來作為一個結構體,結構體的第一個成員就用就是給定的該值。
2.2.2.2 atomic_set
#define atomic_set(v,i) WRITE_ONCE(((v)- >counter), (i))
#define WRITE_ONCE(x, val) \\
({ \\
union { typeof(x) __val; char __c[1]; } __u = \\
{ .__val = (__force typeof(x)) (val) }; \\
__write_once_size(&(x), __u.__c, sizeof(x)); \\
__u.__val; \\
})
static __always_inline void __write_once_size(volatile void *p, void *res, int size)
{
switch (size) {
case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
default:
barrier();
__builtin_memcpy((void *)p, (const void *)res, size);
barrier();
}
}
函數介紹 :該函數也用作初始化原子變量
文件位置 :由include/linux/atomic.h
引用arch/arm/include/asm/atomic.h
,再引用include/linux/compiler.h
實現方式 :通過調用WRITE_ONCE
來實現,其中WRITE_ONCE
宏實現了一些屏蔽編譯器優化的技巧,確保寫入操作是原子的。
atomic_set
調用WRITE_ONCE
將i
的值寫入原子變量(v)->counter
中,WRITE_ONCE
以保證操作的原子性WRITE_ONCE
用來保證操作的原子性- 創建
union
聯合體,包括__val
和__C
成員變量 - 定義一個
__U
變量,使用強制轉換將參數__val
轉換為typeof(x)
類型,傳遞給聯合體變量__u.__val
- 調用
__write_once_size
函數,將__c
的值寫入到x
指向的內存地址中。 - 函數返回
__u.__val。
- 創建
union
聯合體- 它的特點是存儲多種數據類型的值,但是所有成員共享同一個內存空間,這樣可以節省內存空間。
- 主要作用是將一個非字符類型的數據
x
強制轉換為一個字符類型的數據,以字符類型數據來訪問該區塊的內存單元。
__write_once_size
函數實現了操作的原子性,核心有以下幾點:- 該函數在向內存寫入數據時使用了
volatile
關鍵字,告訴編譯器不要進行優化,每次操作都從內存中讀取最新的值。 - 函數中的
switch
語句保證了對不同大小的數據類型使用不同的存儲方式,可以保證內存訪問的原子性。 - 對于默認情況,則使用了
__builtin_memcpy
函數進行復制,而這個函數具有原子性。 barrier()
函數指示CPU
要完成所有之前的內存操作,以及確保執行順序與其他指令不發生重排。
- 該函數在向內存寫入數據時使用了
2.2.3 原子變量的加減
2.2.3.1 ATOMIC_OPS
/*
* 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.
*/
#define ATOMIC_OP(op, c_op, asm_op) \\
static inline void atomic_##op(int i, atomic_t *v) \\
{ \\
unsigned long tmp; \\
int result; \\
\\
prefetchw(&v- >counter); \\
__asm__ __volatile__("@ atomic_" #op "\\n" \\
"1: ldrex %0, [%3]\\n" \\
" " #asm_op " %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"); \\
} \\
#define ATOMIC_OP_RETURN(op, c_op, asm_op) \\
static inline int atomic_##op##_return_relaxed(int i, atomic_t *v) \\
{ \\
unsigned long tmp; \\
int result; \\
\\
prefetchw(&v- >counter); \\
\\
__asm__ __volatile__("@ atomic_" #op "_return\\n" \\
"1: ldrex %0, [%3]\\n" \\
" " #asm_op " %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"); \\
\\
return result; \\
}
#define ATOMIC_FETCH_OP(op, c_op, asm_op) \\
static inline int atomic_fetch_##op##_relaxed(int i, atomic_t *v) \\
{ \\
unsigned long tmp; \\
int result, val; \\
\\
prefetchw(&v- >counter); \\
\\
__asm__ __volatile__("@ atomic_fetch_" #op "\\n" \\
"1: ldrex %0, [%4]\\n" \\
" " #asm_op " %1, %0, %5\\n" \\
" strex %2, %1, [%4]\\n" \\
" teq %2, #0\\n" \\
" bne 1b" \\
: "=&r" (result), "=&r" (val), "=&r" (tmp), "+Qo" (v- >counter) \\
: "r" (&v- >counter), "Ir" (i) \\
: "cc"); \\
\\
return result; \\
}
#define ATOMIC_OPS(op, c_op, asm_op) \\
ATOMIC_OP(op, c_op, asm_op) \\
ATOMIC_OP_RETURN(op, c_op, asm_op) \\
ATOMIC_FETCH_OP(op, c_op, asm_op)