精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

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

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

3天內不再提示

鴻蒙系統內核中哪些地方會用到自旋鎖?

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

內核中哪些地方會用到自旋鎖?看圖:

pIYBAGCFCHaAKalfAAEbq5iNP4k545.png

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

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

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

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

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

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

自旋鎖長什么樣?

typedef struct Spinlock {//自旋鎖結構體

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

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

自旋鎖使用流程

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

創建自旋鎖:使用LOS_SpinInit初始化自旋鎖,或者使用SPIN_LOCK_INIT初始化靜態內存的自旋鎖。

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

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

幾個關鍵函數

自旋鎖模塊由內聯函數實現,見于los_spinlock.h代碼不多,主要是三個函數.

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

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

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函數

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

ArchSpinTrylock 匯編代碼

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

    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                     @數據存儲隔離,以保證緩沖中的數據已經落實到RAM中
        bx      lr              @跳回調用ArchSpinLock函數

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

ArchSpinUnlock 匯編代碼

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

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

匯編指令之 WFI / WFE / SEV

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

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

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

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

開始之初資源空閑

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

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

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

CPU核2 獲得資源

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

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

SEV和WFE的實現很像設計模式的觀察者模式.

匯編指令之 LDREX / STREX

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

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

如果執行LDREX指令的時候發現已經被標記為獨占訪問了,并不會對指令的執行產生影響。

而STREX在更新內存數值時,會檢查該段內存是否已經被標記為獨占訪問,并以此來決定是否更新內存中的值:

STREX Rx, Ry, [Rz]如果執行這條指令的時候發現已經被標記為獨占訪問了,則將寄存器Ry中的值更新到寄存器Rz指向的內存,并將寄存器Rx設置成0。指令執行成功后,會將獨占訪問標記位清除。

而如果執行這條指令的時候發現沒有設置獨占標記,則不會更新內存,且將寄存器Rx的值設置成1。

一旦某條STREX指令執行成功后,以后再對同一段內存嘗試使用STREX指令更新的時候,會發現獨占標記已經被清空了,就不能再更新了,從而實現獨占訪問的機制。

編程實例

本實例實現如下流程。

任務Example_TaskEntry初始化自旋鎖,創建兩個任務Example_SpinTask1、Example_SpinTask2,分別運行于兩個核。

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

300Tick后任務Example_TaskEntry被調度運行,刪除任務Example_SpinTask1和Example_SpinTask2。

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

/* 自旋鎖句柄id */
SPIN_LOCK_S g_testSpinlock;
/* 任務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);

    /* 創建任務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
    /* 綁定任務到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;
    }

    /* 創建任務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
    /* 綁定任務到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;
    }

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

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

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

總結

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

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

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

編輯:hfy

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

    關注

    68

    文章

    10826

    瀏覽量

    211160
  • 鴻蒙系統
    +關注

    關注

    183

    文章

    2634

    瀏覽量

    66220
  • 自旋鎖
    +關注

    關注

    0

    文章

    11

    瀏覽量

    1579
收藏 人收藏

    評論

    相關推薦

    深度解析自旋自旋的實現方案

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

    Linux驅動開發筆記-自旋和信號量

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

    Linux內核同步機制的自旋原理是什么?

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

    開源的鴻蒙系統其他手機廠商會用嗎?

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

    哪些地方用到差分線?

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

    怎么在atmega128實現自旋?

    什么是自旋?有哪些缺陷?怎么在atmega128實現自旋
    發表于 01-24 06:54

    Linux內核同步機制的自旋原理

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

    信號量和自旋

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

    Linux 自旋spinlock

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

    自旋的發展歷史與使用方法

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

    自旋和互斥的區別有哪些

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

    如何用C++11實現自旋

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

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

    互斥自旋的區別 自旋臨界區可以被中斷嗎? 互斥
    的頭像 發表于 11-22 17:41 ?780次閱讀

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

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

    互斥自旋的實現原理

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