鴻蒙內核源碼分析(原子操作篇) | 誰在為原子操作保駕護航

基本概念

在支持多任務的操作系統中,修改一塊內存區域的數據需要“讀取-修改-寫入”三個步驟。然而同一內存區域的數據可能同時被多個任務訪問,如果在修改數據的過程中被其他任務打斷,就會造成該操作的執行結果無法預知。

使用開關中斷的方法固然可以保證多任務執行結果符合預期,但這種方法顯然會影響系統性能。

ARMv6架構引入了LDREXSTREX指令,以支持對共享存儲器更縝密的非阻塞同步。由此實現的原子操作能確保對同一數據的“讀取-修改-寫入”操作在它的執行期間不會被打斷,即操作的原子性。

有多個任務對同一個內存數據進行加減或交換操作時,使用原子操作保證結果的可預知性。

看過自旋鎖篇的應該對LDREX和STREX指令不陌生的,自旋鎖的本質就是對某個變量的原子操作,而且一定要通過匯編代碼實現,也就是說LDREXSTREX指令保證了原子操作的底層實現.

回顧下自旋鎖申請和釋放鎖的匯編代碼.

ArchSpinLock 申請鎖代碼

    FUNCTION(ArchSpinLock)  @死守,非要拿到鎖
        mov     r1, #1      @r1=1
    1:                      @循環的作用,因SEV是廣播事件.不一定lock->rawLock的值已經改變了
        ldrex   r2, [r0]    @r0 = &lock->rawLock, 即 r2 = lock->rawLock
        cmp     r2, #0      @r2和0比較
        wfene               @不相等時,說明資源被占用,CPU核進入睡眠狀態
        strexeq r2, r1, [r0]@此時CPU被重新喚醒,嘗試令lock->rawLock=1,成功寫入則r2=0
        cmpeq   r2, #0      @再來比較r2是否等于0,如果相等則獲取到了鎖
        bne     1b          @如果不相等,繼續進入循環
        dmb                 @用DMB指令來隔離,以保證緩沖中的數據已經落實到RAM中
        bx      lr          @此時是一定拿到鎖了,跳回調用ArchSpinLock函數

ArchSpinUnlock 釋放鎖代碼

    FUNCTION(ArchSpinUnlock)    @釋放鎖
        mov     r1, #0          @r1=0               
        dmb                     @數據存儲隔離,以保證緩沖中的數據已經落實到RAM中
        str     r1, [r0]        @令lock->rawLock = 0
        dsb                     @數據同步隔離
        sev                     @給各CPU廣播事件,喚醒沉睡的CPU們
        bx      lr              @跳回調用ArchSpinLock函數

運作機制

鴻蒙通過對ARMv6架構中的LDREXSTREX進行封裝,向用戶提供了一套原子操作接口。

  • LDREX Rx, [Ry]
    讀取內存中的值,并標記對該段內存為獨占訪問:

    • 讀取寄存器Ry指向的4字節內存數據,保存到Rx寄存器中。
    • 對Ry指向的內存區域添加獨占訪問標記。
  • STREX Rf, Rx, [Ry]
    檢查內存是否有獨占訪問標記,如果有則更新內存值并清空標記,否則不更新內存:

    • 有獨占訪問標記
      • 將寄存器Rx中的值更新到寄存器Ry指向的內存。
      • 標志寄存器Rf置為0。
    • 沒有獨占訪問標記
      • 不更新內存。
      • 標志寄存器Rf置為1。
  • 判斷標志寄存器
    標志寄存器為0時,退出循環,原子操作結束。
    標志寄存器為1時,繼續循環,重新進行原子操作。

功能列表

原子數據包含兩種類型Atomic(有符號32位數)與 Atomic64(有符號64位數)。原子操作模塊為用戶提供下面幾種功能,接口詳細信息可以查看源碼。

此處講述 LOS_AtomicAddLOS_AtomicSubLOS_AtomicReadLOS_AtomicSet
理解了函數的匯編代碼是理解的原子操作的關鍵.

LOS_AtomicAdd

//對內存數據做加法
STATIC INLINE INT32 LOS_AtomicAdd(Atomic *v, INT32 addVal)  
{
    INT32 val;
    UINT32 status;

    do {
        __asm__ __volatile__("ldrex   %1, [%2]\n"
                             "add   %1, %1, %3\n" 
                             "strex   %0, %1, [%2]"
                             : "=&r"(status), "=&r"(val)
                             : "r"(v), "r"(addVal)
                             : "cc");
    } while (__builtin_expect(status != 0, 0));

    return val;
}

這是一段C語言內嵌匯編,逐一解讀

    1. 先將 status val v addVal的值交由通用寄存器(R0~R3)接管.
    1. %2代表了入參v,[%2]代表的是參數v指向地址的值,也就是 *v ,函數要獨占的就是它
    1. %0 ~ %3 對應 status val v addVal
    1. ldrex %1, [%2] 表示 val = *v ;
    1. add %1, %1, %3 表示 val = val + addVal;
    1. strex %0, %1, [%2] 表示 *v = val;
    1. status 表示是否更新成功,成功了置0,不成功則為 1
    1. __builtin_expect是結束循環的判斷語句,將最有可能執行的分支告訴編譯器。
      這個指令的寫法為:__builtin_expect(EXP, N)。

      意思是:EXP==N 的概率很大。

      綜合理解__builtin_expect(status != 0, 0)

      說的是status = 0 的可能性很大,不成功就會重新來一遍,直到strex更新成(status == 0)為止.

    1. "=&r"(val) 被修飾的操作符作為輸出,即將寄存器的值回給val,val為函數的返回值
    1. "cc"向編譯器聲明以上信息.

LOS_AtomicSub

//對內存數據做減法
STATIC INLINE INT32 LOS_AtomicSub(Atomic *v, INT32 subVal)  
{
    INT32 val;
    UINT32 status;

    do {
        __asm__ __volatile__("ldrex   %1, [%2]\n"
                             "sub   %1, %1, %3\n"
                             "strex   %0, %1, [%2]"
                             : "=&r"(status), "=&r"(val)
                             : "r"(v), "r"(subVal)
                             : "cc");
    } while (__builtin_expect(status != 0, 0));

    return val;
}

解讀

  • LOS_AtomicAdd解讀

volatile

這里要重點說下volatilevolatile 提醒編譯器它后面所定義的變量隨時都有可能改變,因此編譯后的程序每次需要存儲或讀取這個變量的時候,都要直接從變量地址中讀取數據。如果沒有volatile關鍵字,則編譯器可能優化讀取和存儲,可能暫時使用寄存器中的值,如果這個變量由別的程序更新了的話,將出現不一致的現象。

//讀取內存數據
STATIC INLINE INT32 LOS_AtomicRead(const Atomic *v) 
{
    return *(volatile INT32 *)v;
}
//寫入內存數據
STATIC INLINE VOID LOS_AtomicSet(Atomic *v, INT32 setVal)   
{
    *(volatile INT32 *)v = setVal;
}

編程實例

調用原子操作相關接口,觀察結果:

1.創建兩個任務

  • 任務一用LOS_AtomicAdd對全局變量加100次。
  • 任務二用LOS_AtomicSub對全局變量減100次。

2.子任務結束后在主任務中打印全局變量的值。

#include "los_hwi.h"
#include "los_atomic.h"
#include "los_task.h"

UINT32 g_testTaskId01;
UINT32 g_testTaskId02;
Atomic g_sum;
Atomic g_count;

UINT32 Example_Atomic01(VOID)
{
    int i = 0;
    for(i = 0; i < 100; ++i) {
        LOS_AtomicAdd(&g_sum,1);
    }

    LOS_AtomicAdd(&g_count,1);
    return LOS_OK;
}

UINT32 Example_Atomic02(VOID)
{
    int i = 0;
    for(i = 0; i < 100; ++i) {
        LOS_AtomicSub(&g_sum,1);
    }

    LOS_AtomicAdd(&g_count,1);
    return LOS_OK;
}

UINT32 Example_TaskEntry(VOID)
{
    TSK_INIT_PARAM_S stTask1={0};
    stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic01;
    stTask1.pcName       = "TestAtomicTsk1";
    stTask1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    stTask1.usTaskPrio   = 4;
    stTask1.uwResved     = LOS_TASK_STATUS_DETACHED;

    TSK_INIT_PARAM_S stTask2={0};
    stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic02;
    stTask2.pcName       = "TestAtomicTsk2";
    stTask2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    stTask2.usTaskPrio   = 4;
    stTask2.uwResved     = LOS_TASK_STATUS_DETACHED;

    LOS_TaskLock();
    LOS_TaskCreate(&g_testTaskId01, &stTask1);
    LOS_TaskCreate(&g_testTaskId02, &stTask2);
    LOS_TaskUnlock();

    while(LOS_AtomicRead(&g_count) != 2);
    dprintf("g_sum = %d\n", g_sum);

    return LOS_OK;
}

結果驗證

g_sum = 0

寫在最后

  • 如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
  • 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
  • 關注小編,同時可以期待后續文章ing??,不定期分享原創知識。
  • 想要獲取更多完整鴻蒙最新學習知識點,請移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容