
原子操作(atomic operation),不可分割的操作。其通過原子變量來實現,以保證單個CPU周期內,讀寫該變量,不能被打斷,進而判斷該變量的值,來解決并發引起的互斥。
Atomic類型的函數可以在執行期間禁止中斷,并保證在訪問變量時的原子性。
同時,Linux內核提供了兩類原子操作的接口,分別是針對位和整型變量的原子操作。
?
?
對于整形變量的原子操作,內核提供了一系列的
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);
我們下面就介紹幾個稍微有代表性的接口實現
以下基于
Linux內核源碼4.19,剛看是看的時候,有點摸不著頭腦,因為定義的地方和引用的地方較多,不太容易找到,后來才慢慢得窺門徑。
typedef?struct?{
????int?counter;
}?atomic_t;
結構體名稱:atomic_t
文件位置:include/linux/types.h
主要作用:原子變量結構體,該結構體只包含一個整型成員變量counter,用于存儲原子變量的值。
#define?ATOMIC_INIT(i)?{?(i)?}
函數介紹:定義了一個ATOMIC類型的變量,并初始化為給定的值。
文件位置:arch/arm/include/asm/atomic.h,由include/linux/atomic.h引用
實現方法:這個宏定義比較簡單,通過大括號將值包裹起來作為一個結構體,結構體的第一個成員就用就是給定的該值。
#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要完成所有之前的內存操作,以及確保執行順序與其他指令不發生重排。/*
?*?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)
找
atomic_add找半天,還找到了不同的架構下面。:(原來內核通過各種宏定義將其操作全部管理起來,宏定義在內核中的使用也是非常廣泛了。
函數作用:通過一些列宏定義,來實現原子變量的add、sub、and、or等原子變量操作
文件位置:arch/arm/include/asm/atomic.h
實現方式:
我們以
atomic_##op為例來介紹,其他大同小異!
#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");???????\
}??
atomic_##op,通過##來實現字符串的拼接,使函數名稱可變,如atomic_add、atomic_sub等prefetchw函數,預取數據到L1緩存,方便操作,提高程序性能,但是不要濫用。__asm__ __volatile__:表示匯編指令"@ atomic_" #op "\n":為匯編注釋"1: ldrex %0, [%3]\n":將%3存儲地址的數據,讀入到%0地址中,ldrex為獨占式的讀取操作。" " #asm_op " %0, %0, %4\n":" #asm_op "表示作為宏定義傳進來的參數,表示不同的操作碼add、sub等,操作%0和%4對應的地址的值,并將結果返回到%0地址處" strex %1, %0, [%3]\n" :表示將%0地址處的值寫入%3地址處,strex為獨占式的寫操作,寫入的結果會返回到%1地址中" teq %1, #0\n":測試%1寄存器的值是否為0,如果不等于0,則執行下面的" bne 1b" 操作,跳轉到1代碼標簽的位置,也就是ldrex前面的1的位置: "=&r" (result), "=&r" (tmp), "+Qo" (v->counter):根據匯編語法,前兩個為輸出操作數,第三個為輸入輸出操作數: "r" (&v->counter), "Ir" (i):根據匯編語法,這兩個為輸入操作數: "cc":表示可能會修改條件碼寄存器,編譯期間需要優化。通過
ldrex和strex兩個獨占式的操作,保證了讀寫的原子性。
ATOMIC_OPS(add,?+=,?add)
ATOMIC_OPS(sub,?-=,?sub)
通過宏定義來實現
atomic_add和atomic_sub的定義,下面我們就不一一分析了,原理都是通過ARM提供的ldrexstrex也就是我們常說的Load和Store指令實現讀取操作,確保操作的原子性。
void?set_bit(nr,?void?*addr);??//?設置位:設置addr地址的第nr位,所謂設置位即是將位寫為1
void?clear_bit(nr,?void?*addr);??//?清除位:清除addr地址的第nr位,所謂清除位即是將位寫為0
void?change_bit(nr,?void?*addr);?//?改變位:對addr地址的第nr位進行反置。
test_bit(nr,?void?*addr);???//?測試位:返回addr地址的第nr位。
int?test_and_set_bit(nr,?void?*addr);//?測試并設置位
int?test_and_clear_bit(nr,?void?*addr);?//?測試并清除位
int?test_and_change_bit(nr,?void?*addr);//?測試并改變位
同樣,我們還是簡單介紹幾個接口,其他核心實現原理相同
#define?set_bit(nr,p)???ATOMIC_BITOP(set_bit,nr,p)
#define?ATOMIC_BITOP(name,nr,p)???\
????(__builtin_constant_p(nr)???____atomic_##name(nr,?p)?:?_##name(nr,p))
extern?void?_set_bit(int?nr,?volatile?unsigned?long?*?p);
/*
?*?These?functions?are?the?basis?of?our?bit?ops.
?*
?*?First,?the?atomic?bitops.?These?use?native?endian.
?*/
static?inline?void?____atomic_set_bit(unsigned?int?bit,?volatile?unsigned?long?*p)
{
????unsigned?long?flags;
????unsigned?long?mask?=?BIT_MASK(bit);
????p?+=?BIT_WORD(bit);
????raw_local_irq_save(flags);
????*p?|=?mask;
????raw_local_irq_restore(flags);
}
#define?BIT_MASK(nr)??(1UL?<((nr)?%?BITS_PER_LONG))
#define?BIT_WORD(nr)??((nr)?/?BITS_PER_LONG)
#ifdef?CONFIG_64BIT
#define?BITS_PER_LONG?64
#else
#define?BITS_PER_LONG?32
#endif?/*?CONFIG_64BIT?*/
函數介紹:該函數用于原子操作某個地址的某一位。
文件位置:/arch/arm/include/asm/bitops.h
實現方式:
__builtin_constant_p:GCC的一個內置函數,用來判斷表達式是否為常量,如果為常量,則返回值為1____atomic_set_bit函數中BIT_MASK,用于獲取操作位的掩碼,將要設置的位設置為1,其他為0BIT_WORD:確定要操作位的偏移,要偏移多少個字raw_local_irq_save和raw_local_irq_restore中斷屏蔽來保證位操作*p |= mask;的原子性該文章主要詳細了解了Linux內核鎖的原子操作,原子操作分為兩種:整型變量的原子操作和位原子操作。
ldrex和strex來實現
