詩敏家具網(wǎng)站是誰做的官網(wǎng)seo
一、原子操作
原子操作保證指令以原子的方式執(zhí)行,執(zhí)行過程不被打斷。先看一個(gè)實(shí)例,如下所示,如果thread_func_a和thread_func_b同時(shí)運(yùn)行,執(zhí)行完成后,i的值是多少?
`//?test.c
static?int?i?=?0;void?thread_func_a()
{i++;
}
void?thread_func_b()
{i++;
}`
有的讀者認(rèn)為是2,也有的讀者認(rèn)為是1,在給出正確的結(jié)果之前,我們先看下這段代碼的匯編:
//?aarch64-linux-gnu-gcc?-S?test.c
//?vim?test.s
.LFB0:.cfi_startprocadrp????x0,?iadd?????x0,?x0,?:lo12:i?ldr?????w0,?[x0]????????//?加載內(nèi)存地址為x0寄存器的值,也就是i的值到w0寄存器add?????w1,?w0,?1???????//?將w0寄存器的值與1相加,結(jié)果存在w1寄存器adrp????x0,?iadd?????x0,?x0,?:lo12:istr?????w1,?[x0]????????//?把w1寄存器的值,加載到x0所在的地址nopret.cfi_endproc
...
可以看到雖然在我們寫的代碼中,i++只有一條指令,實(shí)際上匯編指令需要三條:
-
加載內(nèi)存地址的值
-
修改變量的值
-
將修改后的值寫回原先的地址
兩個(gè)cpu在執(zhí)行過程中,順序是隨機(jī)的,結(jié)果也是隨機(jī)的,這里為了更直觀,給大商家列一下實(shí)際可能的執(zhí)行順序,以及對應(yīng)的結(jié)果:
可能的結(jié)果:i = 2,執(zhí)行順序如下:
可能的結(jié)果:i = 1,執(zhí)行順序如下
針對上面的問題,linux提供了atomic_t類型的原子變量來解決,它可以保證對一個(gè)整形數(shù)據(jù)的原子性。
在內(nèi)核看來,原子操作函數(shù)就像一條匯編語句,保證了操作時(shí)不被打斷,如上述i++語句就可能被打斷,要保證操作的原子性,通常需要原子地(不間斷地)完成"讀-修改-回寫"機(jī)制,中間不能被打斷。
二、原子變量
linux提供了atomic_t類型的原子變量,它的實(shí)現(xiàn)依賴于不同的架構(gòu),不同處理器的實(shí)現(xiàn)方式不一樣。我們首先看下都有哪些原子操作可供使用,然后再針對arm64的實(shí)現(xiàn)方式進(jìn)行解讀(其他架構(gòu)原理都類似,大家自己揣摩)。
2.1 原子操作函數(shù)
linux內(nèi)核提供了很多操作原子變量的函數(shù),了解這些內(nèi)容,方便我們后續(xù)使用。我們以arm64為例進(jìn)行講解。
2.1.1 基本原子操作函數(shù)
接口:
ATOMIC_INIT(i)
atomic_read(const?atomic_t?*v)
atomic_set(atomic_t?*v,?int?i)
實(shí)現(xiàn):
//?linux-6.9.1/arch/arm64/include/asm/atomic.h#define?arch_atomic_read(v)?????????????__READ_ONCE((v)->counter)
#define?arch_atomic_set(v,?i)???????????__WRITE_ONCE(((v)->counter),?(i))
2.1.2 不帶返回值的原子操作函數(shù)
接口:
atomic_add(i,?v)
atomic_sub(i,?v)
atomic_and(i,?v)
atomic_or(i,?v)
atomic_xor(i,?v)
atomic_andnot(i,?v)
實(shí)現(xiàn)
//?linux-6.9.1/arch/arm64/include/asm/atomic.h#define?ATOMIC_OP(op)???????????????????????????\
static?__always_inline?void?arch_##op(int?i,?atomic_t?*v)???????\
{???????????????????????????????????\__lse_ll_sc_body(op,?i,?v);?????????????????\
}ATOMIC_OP(atomic_andnot)
ATOMIC_OP(atomic_or)
ATOMIC_OP(atomic_xor)
ATOMIC_OP(atomic_add)
ATOMIC_OP(atomic_and)
ATOMIC_OP(atomic_sub)
2.1.3 帶返回值的原子操作
linux內(nèi)核提供了兩類帶返回值的原子操作函數(shù),一類返回原子變量的新值,一類返回原子變量的舊值。 然會(huì)原子變量新值的原子操作函數(shù)如下。
接口:
atomic_add_return(i,?v)
atomic_sub_return(i,?v)
實(shí)現(xiàn);
//?linux-6.9.1/arch/arm64/include/asm/atomic.h#define?ATOMIC_FETCH_OP(name,?op)???????????????????\
static?__always_inline?int?arch_##op##name(int?i,?atomic_t?*v)??????\
{???????????????????????????????????\return?__lse_ll_sc_body(op##name,?i,?v);????????????\
}ATOMIC_FETCH_OPS(atomic_add_return)
ATOMIC_FETCH_OPS(atomic_sub_return)
返回原子變量舊值的原子操作函數(shù)如下:
接口:
atomic_fetch_add(i,?v)
atomic_fetch_sub(i,?v)
atomic_fetch_and(i,?v)
atomic_fetch_or(i,?v)
atomic_fetch_xor(i,?v)
atomic_fetch_andnot(i,?v)
實(shí)現(xiàn):
//?linux-6.9.1/arch/arm64/include/asm/atomic.h
#define?ATOMIC_FETCH_OP(name,?op)???????????????????\
static?__always_inline?int?arch_##op##name(int?i,?atomic_t?*v)??????\
{???????????????????????????????????\return?__lse_ll_sc_body(op##name,?i,?v);????????????\
}ATOMIC_FETCH_OPS(atomic_fetch_andnot)
ATOMIC_FETCH_OPS(atomic_fetch_or)
ATOMIC_FETCH_OPS(atomic_fetch_xor)
ATOMIC_FETCH_OPS(atomic_fetch_add)
ATOMIC_FETCH_OPS(atomic_fetch_and)
ATOMIC_FETCH_OPS(atomic_fetch_sub)
ATOMIC_FETCH_OPS(atomic_add_return)
ATOMIC_FETCH_OPS(atomic_sub_return)
3.1.4 內(nèi)嵌內(nèi)存屏障的原子操作函數(shù)
接口:
{}_relexd?????//?不內(nèi)嵌內(nèi)存屏障原語
{}_acquire????//?內(nèi)置加載-獲取內(nèi)存屏障原語
{}_release????//?內(nèi)置存儲(chǔ)-釋放內(nèi)存屏障原語
實(shí)現(xiàn):
//?linux-6.9.1/arch/arm64/include/asm/atomic.h#define?ATOMIC_FETCH_OP(name,?op)???????????????????\
static?__always_inline?int?arch_##op##name(int?i,?atomic_t?*v)??????\
{???????????????????????????????????\return?__lse_ll_sc_body(op##name,?i,?v);????????????\
}#define?ATOMIC_FETCH_OPS(op)????????????????????????\ATOMIC_FETCH_OP(_relaxed,?op)???????????????????\ATOMIC_FETCH_OP(_acquire,?op)???????????????????\ATOMIC_FETCH_OP(_release,?op)???????????????????\ATOMIC_FETCH_OP(????????,?op)ATOMIC_FETCH_OPS(atomic_fetch_andnot)
ATOMIC_FETCH_OPS(atomic_fetch_or)
ATOMIC_FETCH_OPS(atomic_fetch_xor)
ATOMIC_FETCH_OPS(atomic_fetch_add)
ATOMIC_FETCH_OPS(atomic_fetch_and)
ATOMIC_FETCH_OPS(atomic_fetch_sub)
ATOMIC_FETCH_OPS(atomic_add_return)
ATOMIC_FETCH_OPS(atomic_sub_return)
2.2 原子操作的實(shí)現(xiàn)
2.2.1 原子操作的實(shí)現(xiàn)
原子操作的實(shí)現(xiàn)依賴處理器硬件提供支持,在不同的處理器體系結(jié)構(gòu)上,原子操作會(huì)有不同的實(shí)現(xiàn),例如在x86體系結(jié)構(gòu)下,通常使用鎖緩存/總線的方式實(shí)現(xiàn)原子操作。目前在ARMv8體系結(jié)構(gòu)下支持兩種方式來實(shí)現(xiàn)原子操作:
-
一種是經(jīng)典的獨(dú)占內(nèi)存訪問機(jī)制,也叫做LL/SC(Load-Link/Store-Conditional),早期ARM體系結(jié)構(gòu)下的原子操作都是基于這種方式實(shí)現(xiàn);
-
另一種是ARMv8.1體系結(jié)構(gòu)上新增的LSE(Large System Extension)擴(kuò)展,LSE提供了多種原子內(nèi)存訪問操作指令。
具體選擇哪一種,CONFIG_ARM64_LSE_ATOMICS決定
//?linux-6.9.1/arch/arm64/include/asm/lse.h
#ifdef?CONFIG_ARM64_LSE_ATOMICS#define?__LSE_PREAMBLE??".arch_extension?lse\n"#include?<linux/compiler_types.h>
#include?<linux/export.h>
#include?<linux/stringify.h>
#include?<asm/alternative.h>
#include?<asm/alternative-macros.h>
#include?<asm/atomic_lse.h>
#include?<asm/cpucaps.h>#define?__lse_ll_sc_body(op,?...)???????????????????\
({??????????????????????????????????\alternative_has_cap_likely(ARM64_HAS_LSE_ATOMICS)???????\__lse_##op(__VA_ARGS__)?:???????????????\__ll_sc_##op(__VA_ARGS__);??????????????\
})/*?In-line?patching?at?runtime?*/
#define?ARM64_LSE_ATOMIC_INSN(llsc,?lse)????????????????\ALTERNATIVE(llsc,?__LSE_PREAMBLE?lse,?ARM64_HAS_LSE_ATOMICS)#else???/*?CONFIG_ARM64_LSE_ATOMICS?*/#define?__lse_ll_sc_body(op,?...)???????__ll_sc_##op(__VA_ARGS__)#define?ARM64_LSE_ATOMIC_INSN(llsc,?lse)????llsc#endif??/*?CONFIG_ARM64_LSE_ATOMICS?*/
#endif??/*?__ASM_LSE_H?*/
2.2.2 ll/sc方式
LL/SC機(jī)制使用多個(gè)指令,并且每個(gè)處理器都需要實(shí)現(xiàn)一個(gè)專有監(jiān)視器,LL/SC機(jī)制利用獨(dú)占內(nèi)存訪問指令和獨(dú)占監(jiān)視器共同實(shí)現(xiàn)原子操作。首先看下ARMv8體系結(jié)構(gòu)提供的獨(dú)占內(nèi)存訪問指令。
獨(dú)占內(nèi)存訪問指令
ARMv8體系結(jié)構(gòu)實(shí)現(xiàn)的獨(dú)占內(nèi)存訪問指令為LDXR/STXR:
-
LDXR:內(nèi)存獨(dú)占加載指令,它從內(nèi)存中以獨(dú)占方式加載內(nèi)存地址的值到寄存器中;
-
STXR:內(nèi)存獨(dú)占存儲(chǔ)指令,它以獨(dú)占的方式把數(shù)據(jù)存儲(chǔ)到內(nèi)存中。 LDXR/STXR的指令格式如下:
ldxr????<xt>,?[xn?|?sp]?
stxr????<ws>,?<xt>,?[xn?|?sp]
多字節(jié)獨(dú)占內(nèi)存訪問指令
LDXP和STXP指令是多字節(jié)獨(dú)占內(nèi)存訪問指令,一條指令可以獨(dú)占地加載和存儲(chǔ)16字節(jié)。
ldxp????<xt1>,?<xt2>,?[xn?|?sp]
stxp????<ws>,?<xt1>,?<xt2>,?[<xn?|?sp>]
獨(dú)占監(jiān)視器
獨(dú)占監(jiān)視器是一個(gè)硬件狀態(tài)機(jī),用于跟蹤讀-修改-寫序列,并支持Load和Store操作。當(dāng)CPU執(zhí)行LDXR指令時(shí),獨(dú)占監(jiān)視器會(huì)把對應(yīng)內(nèi)存地址標(biāo)記為獨(dú)占訪問模式,保證以獨(dú)占的方式來訪問這個(gè)內(nèi)存地址;而STXR是有條件的存儲(chǔ)指令,當(dāng)CPU執(zhí)行STRX指令將新數(shù)據(jù)寫入到LDXR指令標(biāo)記的獨(dú)占訪問內(nèi)存地址時(shí),會(huì)根據(jù)獨(dú)占監(jiān)視器的狀態(tài)來進(jìn)行處理:
-
若獨(dú)占監(jiān)視器為獨(dú)占訪問狀態(tài),那么STRX指令執(zhí)行成功,并且獨(dú)占監(jiān)視器會(huì)切換狀態(tài)到開放訪問狀態(tài);
-
若獨(dú)占監(jiān)視器為開放訪問狀態(tài),則STRX指令執(zhí)行失敗,數(shù)據(jù)無法存儲(chǔ)。
ARMv8體系提供了三類獨(dú)占監(jiān)視器:
-
本地獨(dú)占監(jiān)視器
-
內(nèi)部緩存一致性全局獨(dú)占監(jiān)視器
-
外部全局獨(dú)占監(jiān)視器
這些獨(dú)占監(jiān)視器分別位于系統(tǒng)存儲(chǔ)結(jié)構(gòu)的不同層次,如下
atomic_op實(shí)現(xiàn):
//?linux-6.9.1/arch/arm64/include/asm/atomic_ll_sc.h
#define?ATOMIC_OP(op,?asm_op,?constraint)???????????????\
static?__always_inline?void?????????????????????????????\
__ll_sc_atomic_##op(int?i,?atomic_t?*v)?????????????????\
{???????????????????????????????????????????????????????\unsigned?long?tmp;??????????????????????????????????\int?result;?????????????????????????????????????????\\asm?volatile("//?atomic_"?#op?"\n"??????????????????\"???prfm????pstl1strm,?%2\n"????????????????????????\"1:?ldxr????%w0,?%2\n"??????????????????????????????\"???"?#asm_op?"?%w0,?%w0,?%w3\n"????????????????????\"???stxr????%w1,?%w0,?%2\n"?????????????????????????\"???cbnz????%w1,?1b\n"??????????????????????????????\:?"=&r"?(result),?"=&r"?(tmp),?"+Q"?(v->counter)????\:?__stringify(constraint)?"r"?(i));?????????????????\
}
第11行:將v->counter的值以內(nèi)存獨(dú)占加載的方式存儲(chǔ)到w0寄存器,即result = v->counter
第12行:將w0的值和i的值操作(add/sub等)結(jié)果保存在w0,即result = result + i
第13行:將w0的值寫回v->counter,成功的為給w1賦0,否則等于1
第14行:判斷temp的值,為0代表成功;為1代表失敗,跳轉(zhuǎn)到ldxr。
說白了,這里也是一個(gè)自旋
2.2.3 lse方式
在ARMV8.1指令集中增加了一些新的原子操作指令,可以一個(gè)指令實(shí)現(xiàn)整形運(yùn)算。
新增的整形原子指令:
接口:
stclr
stset
steor
stadd實(shí)現(xiàn):
#define?ATOMIC_OP(op,?asm_op)???????????????????????\
static?__always_inline?void?????????????????????\
__lse_atomic_##op(int?i,?atomic_t?*v)???????????????????\
{???????????????????????????????????\asm?volatile(???????????????????????????\__LSE_PREAMBLE??????????????????????????\"???"?#asm_op?"?%w[i],?%[v]\n"??????????????\:?[v]?"+Q"?(v->counter)?????????????????????\:?[i]?"r"?(i));?????????????????????????\
}ATOMIC_OP(andnot,?stclr)
ATOMIC_OP(or,?stset)
ATOMIC_OP(xor,?steor)
ATOMIC_OP(add,?stadd)
三、總結(jié)
本篇文章首先根據(jù)一個(gè)真實(shí)的事例引出原子操作要解決的問題,然后對linux提供的原子操作的眾多接口進(jìn)行了解釋說明,最后對arm架構(gòu)上的兩種原子操作的實(shí)現(xiàn)方式原理LL/SC、LSE進(jìn)行了剖析。經(jīng)過上面的學(xué)習(xí),大家應(yīng)該已經(jīng)了解原子變量的使用場景以及內(nèi)部的實(shí)現(xiàn)機(jī)理。
參考: https://jishuzhan.net/article/1763876122459639809
《奔跑吧,linux內(nèi)核-卷一基礎(chǔ)架構(gòu)》
《奔跑吧,linux內(nèi)核-卷二調(diào)試與案例分析》
下篇文章,將經(jīng)典自旋鎖進(jìn)行解讀,敬請期待 。
一個(gè)專注于“嵌入式知識(shí)分享”、“DIY嵌入式產(chǎn)品”的技術(shù)開發(fā)人員,關(guān)注我,一起共創(chuàng)嵌入式聯(lián)盟。