轉了一篇蝸牛關於原子操作的文章:https://blog.csdn.net/u010443710/article/details/103910077
個人再寫一寫個人理解的原子操作細節內容
首先來看下獨佔式訪問指令的介紹:
獨佔式訪問(local monitor和global monitor)
cpu支持獨佔式的內存訪問指令LDEX32.W和STEX32.W。用戶可以使用這兩條指令構成原子鎖等同步原語實現同一個核不同進程之間或者不同核之間的同步。通過LDEX指令標記需要獨佔訪問的地址,STEX指令判斷被標記的地址是否被其他進程搶佔。cpu爲每個核心上設置了一個位於L1數據高緩的局部監測器和一個位於二級高緩的全局監測器。每個監測器由一個狀態機和一個地址緩存器組成,其中,狀態機包含兩個狀態:IDLE和EXCLUSIVE。對於屬性設置爲cacheable的頁面,通過局部監測器就能夠實現獨佔式訪問。LDEX指令在執行過程中設置局部監測器的狀態機爲EXCLUSIVE態並將訪問的地址保存到地址緩存器中;STEX指令在執行過程中讀取局部監測器的狀態和地址,如果狀態爲EXCLUSIVE並且地址匹配,那麼執行該寫操作,返回寫成功,並清除狀態機回IDLE態;否則如果狀態或者地址有一項不滿足條件,或者數據高速緩存未使能時,不執行該寫操作,返回寫失敗,並清除狀態機回IDLE態。其他核的寫操作在地址匹配局部監測器時,也會將狀態機清回IDLE態;不同地址的獨佔訪問不影響局部監測器。此外,在進程切換時需要清除局部監測器。
對於屬性設置爲non-cacheable的頁面,需要局部監測器和全局監測器共同作用實現獨佔式訪問。LDEX在執行過程中不僅要設置局部監測器還需要設置全局監測器;STEX在局部監測器檢查通過後需要進一步檢查全局監測器,只有當全局監測器也通過檢查,才執行寫操作、返回寫成功,清除狀態機;否則不執行寫操作,返回寫失敗,清除狀態機。其他核的寫操作在地址匹配某個全局監測器時,會將該全局監測器的狀態清回IDLE態。
先不管局部監視器和全局監視器怎麼工作的,對軟件只需要關注兩點:
- ldex.w rz, (rx) 從存儲器地址RX加載字到通用寄存器RZ中,並標記RX地址爲獨佔。(注意ldex只標記,並不做獨佔判斷)
- stex.w rz, (rx) 將通用寄存器RZ中的字存儲到存儲器地址RX中,若獨佔存儲成功,則源寄存器RZ返回1;否則源寄存器返回0表示獨佔存儲失敗。(也就是stex會先判斷rx是不是已經標記了獨佔的地址,標記了獨佔指令才能成功執行)
下面解析一下atomic_add_unless函數的彙編內容
static inline int atomic_add_unless(atomic_t *v, int a, int u) | 只要原子變量v不等於u,那麼就執行原子變量v加a的操作。 如果v不等於u,返回非0值,否則返回0值 |
static inline int __atomic_add_unless(atomic_t *v, int a, int u)
{
unsigned long tmp, ret;
smp_mb();
asm volatile (
"1: ldex.w %0, (%3) \n" // 獨佔加載:tmp = v->counter, 並標記v->counter獨佔
" mov %1, %0 \n" // ret = tmp
" cmpne %0, %4 \n" // if (tmp == u)
" bf 2f \n" // goto 2: ##相當於退出;2f後面f的意思是,向下找第一個爲2的lab進行跳轉
" add %0, %2 \n" // tmp += a
" stex.w %0, (%3) \n" // 獨佔存儲: v->counter = tmp, 執行結果放在tmp中
" bez %0, 1b \n" // if (tmp == 0) goto 1: 也就是獨佔存儲失敗則重新進行獨佔加載
"2: \n" //##上一句1b後邊的b意思是向上找到第一個爲1的lab進行跳轉
: "=&r" (tmp), "=&r" (ret) //output: %0->tmp; %1->ret
: "r" (a), "r"(&v->counter), "r"(u)//input: %2->a; %3->&v->counter; %u->u
: "memory");
if (ret != u)
smp_mb();
return ret;
}
剛接觸GCC內嵌彙編語法有些頭疼,這裏不具體介紹,簡單說下這裏的代碼
彙編最後3行“:”開頭的語句是聲明輸入輸出和告訴編譯器memory的內容有修改,重點是前邊兩個:
: "=&r" (tmp), "=&r" (ret)
這一行是聲明輸出,r意思是用寄存器來代替tmp和ret,對應上面指令的%n,,後面輸入聲明類似,按照順序給局部變量聲明的寄存器編號。也就是說對於前面的彙編指令:%0就是tmp; %1就是ret,%2就是a; %3就是&v->counter; %4就是u
然後是關於跳轉指令lab位置的一點說明
" bf 2f \n"
這個2f,剛開始沒搞懂,下面有一個2:的lab ,但是爲啥是寫的2f呢?
這是因內嵌彙編所有的lab都是全局的,如果遇到lab重名,可能會跳轉到其他地方的lab上去。
這裏提供兩種方法:
1, lab後加b,默認向上找到第一個名字相同的lab進行跳轉
2,lab後加f,默認向下找到第一個名字相同的lab進行跳轉