0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

淺談鴻蒙內(nèi)核源碼的原子操作

鴻蒙系統(tǒng)HarmonyOS ? 來源:my.oschina ? 作者:鴻蒙內(nèi)核源碼分析 ? 2021-04-25 16:05 ? 次閱讀

在支持多任務(wù)的操作系統(tǒng)中,修改一塊內(nèi)存區(qū)域的數(shù)據(jù)需要“讀取-修改-寫入”三個步驟。然而同一內(nèi)存區(qū)域的數(shù)據(jù)可能同時被多個任務(wù)訪問,如果在修改數(shù)據(jù)的過程中被其他任務(wù)打斷,就會造成該操作的執(zhí)行結(jié)果無法預(yù)知。

使用開關(guān)中斷的方法固然可以保證多任務(wù)執(zhí)行結(jié)果符合預(yù)期,但這種方法顯然會影響系統(tǒng)性能。

ARMv6架構(gòu)引入了LDREX和STREX指令,以支持對共享存儲器更縝密的非阻塞同步。由此實(shí)現(xiàn)的原子操作能確保對同一數(shù)據(jù)的“讀取-修改-寫入”操作在它的執(zhí)行期間不會被打斷,即操作的原子性。

有多個任務(wù)對同一個內(nèi)存數(shù)據(jù)進(jìn)行加減或交換操作時,使用原子操作保證結(jié)果的可預(yù)知性。

看過鴻蒙內(nèi)核源碼分析(總目錄)自旋鎖篇的應(yīng)該對LDREX和STREX指令不陌生的,自旋鎖的本質(zhì)就是對某個變量的原子操作,而且一定要通過匯編代碼實(shí)現(xiàn),也就是說LDREX和STREX指令保證了原子操作的底層實(shí)現(xiàn). 回顧下自旋鎖申請和釋放鎖的匯編代碼.

ArchSpinLock 申請鎖代碼

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

ArchSpinUnlock 釋放鎖代碼

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

運(yùn)作機(jī)制

鴻蒙通過對ARMv6架構(gòu)中的LDREX和STREX進(jìn)行封裝,向用戶提供了一套原子操作接口

LDREX Rx, [Ry] 讀取內(nèi)存中的值,并標(biāo)記對該段內(nèi)存為獨(dú)占訪問:

讀取寄存器Ry指向的4字節(jié)內(nèi)存數(shù)據(jù),保存到Rx寄存器中。

對Ry指向的內(nèi)存區(qū)域添加獨(dú)占訪問標(biāo)記。

STREX Rf, Rx, [Ry] 檢查內(nèi)存是否有獨(dú)占訪問標(biāo)記,如果有則更新內(nèi)存值并清空標(biāo)記,否則不更新內(nèi)存:

有獨(dú)占訪問標(biāo)記

將寄存器Rx中的值更新到寄存器Ry指向的內(nèi)存。

標(biāo)志寄存器Rf置為0。

沒有獨(dú)占訪問標(biāo)記

不更新內(nèi)存。

標(biāo)志寄存器Rf置為1。

判斷標(biāo)志寄存器 標(biāo)志寄存器為0時,退出循環(huán),原子操作結(jié)束。 標(biāo)志寄存器為1時,繼續(xù)循環(huán),重新進(jìn)行原子操作。

功能列表

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

pIYBAGCFInSAHpgJAAG38gOWMDk715.png

此處講述LOS_AtomicAdd,LOS_AtomicSub,LOS_AtomicRead,LOS_AtomicSet理解了函數(shù)的匯編代碼是理解的原子操作的關(guān)鍵.

LOS_AtomicAdd

//對內(nèi)存數(shù)據(jù)做加法
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語言內(nèi)嵌匯編,逐一解讀

先將valstatusvaddVal的值交由通用寄存器(R0~R3)接管.

%2代表了入?yún),[%2]代表的是參數(shù)v指向地址的值,也就是 *v ,函數(shù)要獨(dú)占的就是它

%0 ~ %3 對應(yīng)valstatusvaddVal

ldrex %1, [%2] 表示 val = *v ;

add %1, %1, %3 表示 val = val + addVal;

strex %0, %1, [%2] 表示 *v = val;

status 表示是否更新成功,成功了置0,不成功則為 1

__builtin_expect是結(jié)束循環(huán)的判斷語句,將最有可能執(zhí)行的分支告訴編譯器。 這個指令的寫法為:__builtin_expect(EXP, N)。

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

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

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

"=&r"(val) 被修飾的操作符作為輸出,即將寄存器的值回給val,val為函數(shù)的返回值

"cc"向GCC編譯器聲明以上信息.

LOS_AtomicSub

//對內(nèi)存數(shù)據(jù)做減法
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

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

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

編程實(shí)例

調(diào)用原子操作相關(guān)接口,觀察結(jié)果:

1.創(chuàng)建兩個任務(wù)

任務(wù)一用LOS_AtomicAdd對全局變量加100次。

任務(wù)二用LOS_AtomicSub對全局變量減100次。

2.子任務(wù)結(jié)束后在主任務(wù)中打印全局變量的值。

#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;
}

結(jié)果驗(yàn)證

g_sum = 0

編輯:hfy

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • ARM
    ARM
    +關(guān)注

    關(guān)注

    134

    文章

    8967

    瀏覽量

    365017
  • 存儲器
    +關(guān)注

    關(guān)注

    38

    文章

    7366

    瀏覽量

    163090
收藏 人收藏

    評論

    相關(guān)推薦

    鴻蒙內(nèi)核源碼Task/線程技術(shù)分析

    前言 在鴻蒙內(nèi)核中,廣義上可理解為一個Task就是一個線程 一、怎么理解Task 1. 官方文檔是怎么描述線程 基本概念 從系統(tǒng)的角度看,線程是競爭系統(tǒng)資源的最小運(yùn)行單元。線程可以使用或等待CPU
    的頭像 發(fā)表于 10-18 10:42 ?2102次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>Task/線程技術(shù)分析

    鴻蒙內(nèi)核源碼分析:用通俗易懂的語言告訴你鴻蒙內(nèi)核發(fā)生了什么?

    鴻蒙虛擬內(nèi)存全景圖圖來自 鴻蒙內(nèi)核源碼注釋中文版 【Gitee倉 】再看鴻蒙用戶空間全景圖圖來自 鴻蒙
    發(fā)表于 11-19 10:14

    鴻蒙內(nèi)核源碼分析(源碼注釋篇):給HarmonyOS源碼逐行加上中文注釋

    月10日鴻蒙正式開源,重新激活了注者多年的心愿,就有那么點(diǎn)一發(fā)不可收拾了 :IP致敬鴻蒙內(nèi)核開發(fā)者感謝開放原子開源基金會,鴻蒙
    發(fā)表于 11-19 10:32

    鴻蒙內(nèi)核源碼分析:給HarmonyOS源碼逐行加上中文注釋

    開發(fā)者感謝開放原子開源基金會,鴻蒙內(nèi)核開發(fā)者提供了如此優(yōu)秀的源碼,一了多年的夙愿,津津樂道于此。越深入精讀內(nèi)核
    發(fā)表于 11-19 15:06

    鴻蒙源碼分析系列(總目錄) | 給HarmonyOS源碼逐行加上中文注釋

    鴻蒙內(nèi)核源碼注釋中文版 【Gitee倉】 給 HarmonyOS 源碼逐行加上中文注解,詳細(xì)闡述設(shè)計細(xì)節(jié), 助你快速精讀 HarmonyOS 內(nèi)核
    發(fā)表于 11-20 11:24

    鴻蒙內(nèi)核源碼分析(必讀篇):用故事說內(nèi)核

    的工作原理!操作系統(tǒng)就是管理場館和確保工作人員有序工作的系統(tǒng)解決方案商,外面公司只要提供個節(jié)目單,就能按節(jié)目單把這臺戲演好給廣大觀眾觀看。有了這個故事墊底,鴻蒙內(nèi)核源碼分析系列就有了一
    發(fā)表于 11-23 10:15

    鴻蒙內(nèi)核源碼分析(百篇博客分析.挖透鴻蒙內(nèi)核)

    致敬內(nèi)核開發(fā)者感謝開放原子開源基金會,致敬鴻蒙內(nèi)核開發(fā)者??梢院敛豢鋸埖恼f鴻蒙內(nèi)核
    發(fā)表于 07-04 17:16

    淺談鴻蒙操作系統(tǒng)的微內(nèi)核

    華為在松山湖的華為開發(fā)者大會上正式宣布了鴻蒙操作系統(tǒng),該系統(tǒng)其中一個亮點(diǎn)是 微內(nèi)核。華為聲稱,微內(nèi)核的啟用,使其速度大大提升,并且在安全性上產(chǎn)生變革性突破,微
    的頭像 發(fā)表于 10-13 12:36 ?6033次閱讀

    如何尋找鴻蒙源碼入口

    因?yàn)?b class='flag-5'>鴻蒙源碼剛開源,所以網(wǎng)上是不會搜到源碼講解的,搜到的基本都是鴻蒙OS應(yīng)用開發(fā)教程,這個和鴻蒙源碼
    的頭像 發(fā)表于 10-14 14:22 ?3887次閱讀
    如何尋找<b class='flag-5'>鴻蒙</b><b class='flag-5'>源碼</b>入口

    為何要精讀鴻蒙內(nèi)核源碼?

    一個沒學(xué)過計算機(jī)知識的賣菜大媽就不可能知道內(nèi)核的基本運(yùn)作了嗎? 不一定!在系列篇中試圖用 鴻蒙內(nèi)核源碼分析(總目錄)之故事篇 去引導(dǎo)這一層級的認(rèn)知,希望能卷入更多的人來關(guān)注基礎(chǔ)軟件,尤
    的頭像 發(fā)表于 04-26 15:00 ?1700次閱讀
    為何要精讀<b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>?

    淺談鴻蒙內(nèi)核源碼的棧

    上面的代碼和鴻蒙內(nèi)核用棧方式一樣,都采用了遞減滿棧的方式, 什么是遞減滿棧?
    的頭像 發(fā)表于 04-24 11:21 ?1331次閱讀
    <b class='flag-5'>淺談</b><b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>的棧

    鴻蒙內(nèi)核源碼分析:鴻蒙內(nèi)核的每段匯編代碼解析

    本篇說清楚CPU的工作模式 讀本篇之前建議先讀鴻蒙內(nèi)核源碼分析(總目錄)其他篇. 正如一個互聯(lián)網(wǎng)項(xiàng)目的后臺管理系統(tǒng)有權(quán)限管理一樣,CPU工作是否也有權(quán)限(模式)? 一個成熟的軟硬件架構(gòu),肯定會
    的頭像 發(fā)表于 03-02 09:56 ?4104次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>分析:<b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b>的每段匯編代碼解析

    鴻蒙內(nèi)核源碼分析: 虛擬內(nèi)存和物理內(nèi)存是怎么管理的

    有了上篇鴻蒙內(nèi)核源碼分析(內(nèi)存概念篇)的基礎(chǔ),本篇講內(nèi)存管理部分,本章源碼超級多,很燒腦,但筆者關(guān)鍵處都加了注釋。廢話不多說,開始吧。內(nèi)存一開始就是一張白紙,這些extern就是給它畫
    發(fā)表于 11-23 11:45 ?19次下載
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>分析: 虛擬內(nèi)存和物理內(nèi)存是怎么管理的

    鴻蒙內(nèi)核源碼分析 :內(nèi)核最重要結(jié)構(gòu)體

    為何鴻蒙內(nèi)核源碼分析系列開篇就說 LOS_DL_LIST ? 因?yàn)樗?b class='flag-5'>鴻蒙 LOS 內(nèi)核中無處不在,在整個
    發(fā)表于 11-24 17:54 ?35次下載
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>分析 :<b class='flag-5'>內(nèi)核</b>最重要結(jié)構(gòu)體

    華為鴻蒙系統(tǒng)內(nèi)核源碼分析上冊

    鴻蒙內(nèi)核源碼注釋中文版【 Gitee倉】給 Harmoηy○S源碼逐行加上中文注解,詳細(xì)闡述設(shè)計細(xì)節(jié),助你快速精讀 Harmonyos內(nèi)核源碼
    發(fā)表于 04-09 14:40 ?17次下載