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

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

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

鴻蒙系統(tǒng)內(nèi)核中哪些地方會用到自旋鎖?

電子設(shè)計 ? 來源:my.oschina ? 作者:鴻蒙內(nèi)核源碼分析 ? 2021-04-25 14:18 ? 次閱讀

內(nèi)核中哪些地方會用到自旋鎖?看圖:

pIYBAGCFCHaAKalfAAEbq5iNP4k545.png

自旋鎖顧名思義,是一把自動旋轉(zhuǎn)的鎖,這很像廁所里的鎖,進入前標記是綠色可用的,進入格子間后,手一帶,里面的鎖轉(zhuǎn)個圈,外面標記變成了紅色表示在使用,外面的只能等待.這是形象的比喻,但實際也是如此.

在多CPU核環(huán)境中,由于使用相同的內(nèi)存空間,存在對同一資源進行訪問的情況,所以需要互斥訪問機制來保證同一時刻只有一個核進行操作,自旋鎖就是這樣的一種機制。

自旋鎖是指當一個線程在獲取鎖時,如果鎖已經(jīng)被其它CPU中的線程獲取,那么該線程將循環(huán)等待,并不斷判斷是否能夠成功獲取鎖,直到其它CPU釋放鎖后,等鎖CPU才會退出循環(huán)。

自旋鎖的設(shè)計理念是它僅會被持有非常短的時間,鎖只能被一個任務(wù)持有,而且持有自旋鎖的CPU是不可以進入睡眠模式的,因為其他的CPU在等待鎖,為了防止死鎖上下文交換也是不允許的,是禁止發(fā)生調(diào)度的.

自旋鎖與互斥鎖比較類似,它們都是為了解決對共享資源的互斥使用問題。無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個持有者。但是兩者在調(diào)度機制上略有不同,對于互斥鎖,如果鎖已經(jīng)被占用,鎖申請者會被阻塞;但是自旋鎖不會引起調(diào)用者阻塞,會一直循環(huán)檢測自旋鎖是否已經(jīng)被釋放。

雖然都是共享資源競爭,但自旋鎖強調(diào)的是CPU核間的競爭,而互斥量強調(diào)的是任務(wù)(包括同一CPU核)之間的競爭.

自旋鎖長什么樣?

typedef struct Spinlock {//自旋鎖結(jié)構(gòu)體

        size_t      rawLock;//原始鎖
    #if (LOSCFG_KERNEL_SMP_LOCKDEP == YES) // 死鎖檢測模塊開關(guān)
        UINT32      cpuid; //持有鎖的CPU
        VOID        *owner; //持有鎖任務(wù)
        const CHAR  *name; //鎖名稱
    #endif
    } SPIN_LOCK_S;

結(jié)構(gòu)體很簡單,里面有個宏,用于死鎖檢測,默認情況下是關(guān)閉的.所以真正的被使用的變量只有rawLock一個.但C語言代碼中找不到變量的變化過程,而是通過一段匯編代碼來實現(xiàn).看完本篇會明白也只能通過匯編代碼來實現(xiàn)自旋鎖.

自旋鎖使用流程

自旋鎖用于多CPU核的情況,解決的是CPU之間競爭資源的問題.使用流程很簡單,三步走。

創(chuàng)建自旋鎖:使用LOS_SpinInit初始化自旋鎖,或者使用SPIN_LOCK_INIT初始化靜態(tài)內(nèi)存的自旋鎖。

申請自旋鎖:使用接口LOS_SpinLockLOS_SpinTrylockLOS_SpinLockSave申請指定的自旋鎖,申請成功就繼續(xù)往后執(zhí)行鎖保護的代碼;申請失敗在自旋鎖申請中忙等,直到申請到自旋鎖為止。

釋放自旋鎖:使用LOS_SpinUnlockLOS_SpinUnlockRestore接口釋放自旋鎖。鎖保護代碼執(zhí)行完畢后,釋放對應(yīng)的自旋鎖,以便其他核申請自旋鎖。

幾個關(guān)鍵函數(shù)

自旋鎖模塊由內(nèi)聯(lián)函數(shù)實現(xiàn),見于los_spinlock.h代碼不多,主要是三個函數(shù).

ArchSpinLock(&lock->rawLock);
ArchSpinTrylock(&lock->rawLock)
ArchSpinUnlock(&lock->rawLock);

可以說掌握了它們就掌握了自旋鎖,但這三個函數(shù)全由匯編實現(xiàn).見于los_dispatch.S文件 因為系列篇已有兩篇講過匯編代碼,所以很容易理解這三段代碼.函數(shù)的參數(shù)由r0記錄,即r0保存了lock->rawLock的地址,拿鎖/釋放鎖是讓lock->rawLock在0,1切換 下面逐一說明自旋鎖的匯編代碼.

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核進入睡眠狀態(tài)
        strexeq r2, r1, [r0]@此時CPU被重新喚醒,嘗試令lock->rawLock=1,成功寫入則r2=0
        cmpeq   r2, #0      @再來比較r2是否等于0,如果相等則獲取到了鎖
        bne     1b          @如果不相等,繼續(xù)進入循環(huán)
        dmb                 @用DMB指令來隔離,以保證緩沖中的數(shù)據(jù)已經(jīng)落實到RAM中
        bx      lr          @此時是一定拿到鎖了,跳回調(diào)用ArchSpinLock函數(shù)

看懂了這段匯編代碼就理解了自旋鎖實現(xiàn)的真正機制,為什么一定要用匯編來實現(xiàn). 因為CPU寧愿睡眠也非拿要到鎖不可的, 注意這里可不是讓線程睡眠,而是讓CPU進入睡眠狀態(tài),能讓CPU進入睡眠的只能通過匯編實現(xiàn).C語言根本就寫不出讓CPU真正睡眠的代碼.

ArchSpinTrylock 匯編代碼

如果不看下面這段匯編代碼,你根本不可能知道 ArchSpinTrylock 和 ArchSpinLock的真正區(qū)別是什么.

    FUNCTION(ArchSpinTrylock)   @嘗試拿鎖,拿不到就撤
        mov     r1, #1          @r1=1
        mov     r2, r0          @r2 = r0       
        ldrex   r0, [r2]        @r2 = &lock->rawLock, 即 r0 = lock->rawLock
        cmp     r0, #0          @r0和0比較
        strexeq r0, r1, [r2]    @嘗試令lock->rawLock=1,成功寫入則r0=0,否則 r0 =1
        dmb                     @數(shù)據(jù)存儲隔離,以保證緩沖中的數(shù)據(jù)已經(jīng)落實到RAM中
        bx      lr              @跳回調(diào)用ArchSpinLock函數(shù)

比較兩段匯編代碼可知,ArchSpinTrylock即沒有循環(huán)也不會讓CPU進入睡眠,直接返回了,而ArchSpinLock會睡了醒, 醒了睡,一直守到丈夫(lock->rawLock = 0的廣播事件發(fā)生)回來才肯罷休. 筆者代碼注釋到這里那真是心潮澎湃,心碎了老一地, 真想給ArchSpinLock立一個貞節(jié)牌坊!

ArchSpinUnlock 匯編代碼

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

代碼中涉及到幾個不常用的匯編指令,一一說明:

匯編指令之 WFI / WFE / SEV

WFI(Wait for interrupt):等待中斷到來指令.WFI一般用于cpuidle,WFI 指令是在處理器發(fā)生中斷或類似異常之前不需要做任何事情。

鴻蒙源碼分析系列篇(總目錄)線程篇中已說過,每個CPU都有自己的idle任務(wù),CPU沒事干的時候就待在里面,就一個死循環(huán)守著WFI指令,有中斷來了就觸發(fā)CPU起床干活. 中斷分硬中斷和軟中斷,系統(tǒng)調(diào)用就是通過軟中斷實現(xiàn)的,而設(shè)備類的就屬于硬中斷,都能觸發(fā)CPU干活. 具體看下CPU空閑的時候在干嘛,代碼超級簡單:

LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID) //CPU沒事干的時候待在這里
{
    while (1) {//只有一個死循環(huán)
        Wfi();//WFI指令:arm core 立即進入low-power standby state,等待中斷,進入休眠模式。
    }
}

WFE(Wait for event):等待事件的到來指令WFE指令是在SEV指令生成事件之前不需要執(zhí)行任何操作,所以用WFE的地方,后續(xù)一定會對應(yīng)一個SEV的指令去喚醒它. WFE的一個典型使用場景,是用在自旋鎖中,spinlock的功能,是在不同CPU core之間,保護共享資源。使用WFE的流程是:

開始之初資源空閑

CPU核1 訪問資源,持有鎖,獲得資源

CPU核2 訪問資源,此時資源不空閑,執(zhí)行WFE指令,讓core進入low-power state(睡眠)

CPU核1 釋放資源,釋放鎖,釋放資源,同時執(zhí)行SEV指令,喚醒CPU核2

CPU核2 獲得資源

另外說一下 以往的自旋鎖,在獲得不到資源時,讓CPU核進入死循環(huán),而通過插入WFE指令,則大大節(jié)省功耗.

SEV(send event):發(fā)送事件指令,SEV是一條廣播指令,它會將事件發(fā)送到多處理器系統(tǒng)中的所有處理器,以喚醒沉睡的CPU.

SEV和WFE的實現(xiàn)很像設(shè)計模式的觀察者模式.

匯編指令之 LDREX / STREX

LDREX用來讀取內(nèi)存中的值,并標記對該段內(nèi)存的獨占訪問:

LDREX Rx, [Ry]上面的指令意味著,讀取寄存器Ry指向的4字節(jié)內(nèi)存值,將其保存到Rx寄存器中,同時標記對Ry指向內(nèi)存區(qū)域的獨占訪問。

如果執(zhí)行LDREX指令的時候發(fā)現(xiàn)已經(jīng)被標記為獨占訪問了,并不會對指令的執(zhí)行產(chǎn)生影響。

而STREX在更新內(nèi)存數(shù)值時,會檢查該段內(nèi)存是否已經(jīng)被標記為獨占訪問,并以此來決定是否更新內(nèi)存中的值:

STREX Rx, Ry, [Rz]如果執(zhí)行這條指令的時候發(fā)現(xiàn)已經(jīng)被標記為獨占訪問了,則將寄存器Ry中的值更新到寄存器Rz指向的內(nèi)存,并將寄存器Rx設(shè)置成0。指令執(zhí)行成功后,會將獨占訪問標記位清除。

而如果執(zhí)行這條指令的時候發(fā)現(xiàn)沒有設(shè)置獨占標記,則不會更新內(nèi)存,且將寄存器Rx的值設(shè)置成1。

一旦某條STREX指令執(zhí)行成功后,以后再對同一段內(nèi)存嘗試使用STREX指令更新的時候,會發(fā)現(xiàn)獨占標記已經(jīng)被清空了,就不能再更新了,從而實現(xiàn)獨占訪問的機制。

編程實例

本實例實現(xiàn)如下流程。

任務(wù)Example_TaskEntry初始化自旋鎖,創(chuàng)建兩個任務(wù)Example_SpinTask1、Example_SpinTask2,分別運行于兩個核。

Example_SpinTask1、Example_SpinTask2中均執(zhí)行申請自旋鎖的操作,同時為了模擬實際操作,在持有自旋鎖后進行延遲操作,最后釋放自旋鎖。

300Tick后任務(wù)Example_TaskEntry被調(diào)度運行,刪除任務(wù)Example_SpinTask1和Example_SpinTask2。

#include "los_spinlock.h"
#include "los_task.h"

/* 自旋鎖句柄id */
SPIN_LOCK_S g_testSpinlock;
/* 任務(wù)ID */
UINT32 g_testTaskId01;
UINT32 g_testTaskId02;

VOID Example_SpinTask1(VOID)
{
    UINT32 i;
    UINTPTR intSave;

    /* 申請自旋鎖 */
    dprintf("task1 try to get spinlock\n");
    LOS_SpinLockSave(&g_testSpinlock, &intSave);
    dprintf("task1 got spinlock\n");
    for(i = 0; i < 5000; i++) {
        asm volatile("nop");
    }

    /* 釋放自旋鎖 */
    dprintf("task1 release spinlock\n");
    LOS_SpinUnlockRestore(&g_testSpinlock, intSave);

    return;
}

VOID Example_SpinTask2(VOID)
{
    UINT32 i;
    UINTPTR intSave;

    /* 申請自旋鎖 */
    dprintf("task2 try to get spinlock\n");
    LOS_SpinLockSave(&g_testSpinlock, &intSave);
    dprintf("task2 got spinlock\n");
    for(i = 0; i < 5000; i++) {
        asm volatile("nop");
    }

    /* 釋放自旋鎖 */
    dprintf("task2 release spinlock\n");
    LOS_SpinUnlockRestore(&g_testSpinlock, intSave);

    return;
}

UINT32 Example_TaskEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S stTask1;
    TSK_INIT_PARAM_S stTask2;

    /* 初始化自旋鎖 */
    LOS_SpinInit(&g_testSpinlock);

    /* 創(chuàng)建任務(wù)1 */
    memset(&stTask1, 0, sizeof(TSK_INIT_PARAM_S));
    stTask1.pfnTaskEntry  = (TSK_ENTRY_FUNC)Example_SpinTask1;
    stTask1.pcName        = "SpinTsk1";
    stTask1.uwStackSize   = LOSCFG_TASK_MIN_STACK_SIZE;
    stTask1.usTaskPrio    = 5;
#ifdef LOSCFG_KERNEL_SMP
    /* 綁定任務(wù)到CPU0運行 */
    stTask1.usCpuAffiMask = CPUID_TO_AFFI_MASK(0);
#endif
    ret = LOS_TaskCreate(&g_testTaskId01, &stTask1);
    if(ret != LOS_OK) {
        dprintf("task1 create failed .\n");
        return LOS_NOK;
    }

    /* 創(chuàng)建任務(wù)2 */
    memset(&stTask2, 0, sizeof(TSK_INIT_PARAM_S));
    stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SpinTask2;
    stTask2.pcName       = "SpinTsk2";
    stTask2.uwStackSize  = LOSCFG_TASK_MIN_STACK_SIZE;
    stTask2.usTaskPrio   = 5;
#ifdef LOSCFG_KERNEL_SMP
    /* 綁定任務(wù)到CPU1運行 */
    stTask1.usCpuAffiMask = CPUID_TO_AFFI_MASK(1);
#endif
    ret = LOS_TaskCreate(&g_testTaskId02, &stTask2);
    if(ret != LOS_OK) {
        dprintf("task2 create failed .\n");
        return LOS_NOK;
    }

    /* 任務(wù)休眠300Ticks */
    LOS_TaskDelay(300);

    /* 刪除任務(wù)1 */
    ret = LOS_TaskDelete(g_testTaskId01);
    if(ret != LOS_OK) {
        dprintf("task1 delete failed .\n");
        return LOS_NOK;
    }
    /* 刪除任務(wù)2 */
    ret = LOS_TaskDelete(g_testTaskId02);
    if(ret != LOS_OK) {
        dprintf("task2 delete failed .\n");
        return LOS_NOK;
    }

    return LOS_OK;
}
運行結(jié)果
task2 try to get spinlock
task2 got spinlock
task1 try to get spinlock
task2 release spinlock
task1 got spinlock
task1 release spinlock

總結(jié)

自旋鎖用于解決CPU核間競爭資源的問題

因為自旋鎖會讓CPU陷入睡眠狀態(tài),所以鎖的代碼不能太長,否則容易導致意外出現(xiàn),也影響性能.

必須由匯編代碼實現(xiàn),因為C語言寫不出讓CPU進入真正睡眠,核間競爭的代碼.

編輯:hfy

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

    關(guān)注

    68

    文章

    10698

    瀏覽量

    209343
  • 鴻蒙系統(tǒng)
    +關(guān)注

    關(guān)注

    183

    文章

    2627

    瀏覽量

    65783
  • 自旋鎖
    +關(guān)注

    關(guān)注

    0

    文章

    11

    瀏覽量

    1567
收藏 人收藏

    評論

    相關(guān)推薦

    深度解析自旋自旋的實現(xiàn)方案

    入場券自旋和MCS自旋都屬于排隊自旋(queued spinlock),進程按照申請
    發(fā)表于 09-19 11:39 ?4263次閱讀
    深度解析<b class='flag-5'>自旋</b><b class='flag-5'>鎖</b>及<b class='flag-5'>自旋</b><b class='flag-5'>鎖</b>的實現(xiàn)方案

    Linux驅(qū)動開發(fā)筆記-自旋和信號量

    (&lock, flags);.//關(guān)閉中斷保存中斷狀態(tài)到flags,然后獲取自旋www.arm8.net 嵌入式論壇釋放:spin_unlock_irq(&lock);或者
    發(fā)表于 08-30 18:08

    Linux內(nèi)核同步機制的自旋原理是什么?

    自旋是專為防止多處理器并發(fā)而引入的一種,它在內(nèi)核中大量應(yīng)用于中斷處理等部分(對于單處理器來說,防止中斷處理的并發(fā)可簡單采用關(guān)閉中斷的方
    發(fā)表于 03-31 08:06

    開源的鴻蒙系統(tǒng)其他手機廠商會用嗎?

    轉(zhuǎn)眼間就來到了2020年下半年,而備受大家關(guān)注的華為鴻蒙OS系統(tǒng),發(fā)布距今也有一年多的時間了,華為鴻蒙OS系統(tǒng)迎來2.0版本,被應(yīng)用到PC、
    發(fā)表于 09-24 10:42

    哪些地方用到差分線?

    什么是差分線差分線有什么用哪些地方用到差分線高速差分線設(shè)計的硬件要求
    發(fā)表于 02-25 06:26

    怎么在atmega128實現(xiàn)自旋?

    什么是自旋?有哪些缺陷?怎么在atmega128實現(xiàn)自旋?
    發(fā)表于 01-24 06:54

    Linux內(nèi)核同步機制的自旋原理

    一、自旋 自旋是專為防止多處理器并發(fā)而引入的一種,它在內(nèi)核中大量應(yīng)用于中斷處理等部分(對
    發(fā)表于 06-08 14:50 ?1293次閱讀

    信號量和自旋

    量應(yīng)用于中斷處理等部分(對于單處理器來說,防止中斷處理的并發(fā)可簡單采用關(guān)閉中斷的方式,不需要自旋)。??? 自旋最多只能被一個
    發(fā)表于 04-02 14:43 ?776次閱讀

    Linux 自旋spinlock

    ,所以同一時刻只能有一個任務(wù)獲取到。 內(nèi)核當發(fā)生訪問資源沖突的時候,通常有兩種處理方式: 一個是原地等待 一個是掛起當前進程,調(diào)度其他進程執(zhí)行(睡眠) 自旋 Spinlock 是
    的頭像 發(fā)表于 09-11 14:36 ?1988次閱讀

    自旋的發(fā)展歷史與使用方法

    自旋是Linux內(nèi)核里最常用的之一,自旋的概念很簡單,就是如果加鎖失敗在等
    的頭像 發(fā)表于 08-08 08:51 ?1590次閱讀

    自旋和互斥的區(qū)別有哪些

    自旋 自旋與互斥很相似,在訪問共享資源之前對自旋
    的頭像 發(fā)表于 07-21 11:19 ?9016次閱讀

    如何用C++11實現(xiàn)自旋

    )不同之處在于當自旋嘗試獲取時以忙等待(busy waiting)的形式不斷地循環(huán)檢查是否可用。 在多CPU的環(huán)境, 對持有
    的頭像 發(fā)表于 11-11 16:48 ?1240次閱讀
    如何用C++11實現(xiàn)<b class='flag-5'>自旋</b><b class='flag-5'>鎖</b>

    互斥自旋的區(qū)別 自旋臨界區(qū)可以被中斷嗎?

    互斥自旋的區(qū)別 自旋臨界區(qū)可以被中斷嗎? 互斥
    的頭像 發(fā)表于 11-22 17:41 ?659次閱讀

    自旋和互斥的使用場景是什么

    制,它在等待的過程,線程會不斷地檢查的狀態(tài),直到被釋放。自旋適用于以下場景: 1.1
    的頭像 發(fā)表于 07-10 10:05 ?523次閱讀

    互斥自旋的實現(xiàn)原理

    互斥自旋是操作系統(tǒng)中常用的同步機制,用于控制對共享資源的訪問,以避免多個線程或進程同時訪問同一資源,從而引發(fā)數(shù)據(jù)不一致或競爭條件等問題。 互斥
    的頭像 發(fā)表于 07-10 10:07 ?273次閱讀