互斥鎖Mutex:鴻蒙輕內核中處理臨界資源獨占的“法官”


摘要:本文帶領大家一起剖析鴻蒙輕內核的互斥鎖模塊的源代碼,包含互斥鎖的結構體、互斥鎖池初始化、互斥鎖創建刪除、申請釋放等。

本文分享自華為雲社區《鴻蒙輕內核M核源碼分析系列十 互斥鎖Mutex》,原文作者:zhushy 。

多任務環境下會存在多個任務訪問同一公共資源的場景,而有些公共資源是非共享的臨界資源,只能被獨占使用。鴻蒙輕內核使用互斥鎖來避免這種沖突,互斥鎖是一種特殊的二值性信號量,用於實現對臨界資源的獨占式處理。另外,互斥鎖可以解決信號量存在的優先級翻轉問題。用互斥鎖處理臨界資源的同步訪問時,如果有任務訪問該資源,則互斥鎖為加鎖狀態。此時其他任務如果想訪問這個臨界資源則會被阻塞,直到互斥鎖被持有該鎖的任務釋放后,其他任務才能重新訪問該公共資源,此時互斥鎖再次上鎖,如此確保同一時刻只有一個任務正在訪問這個臨界資源,保證了臨界資源操作的完整性。

本文我們來一起學習下鴻蒙輕內核互斥鎖模塊的源代碼,本文中所涉及的源碼,以OpenHarmony LiteOS-M內核為例,均可以在開源站點https://gitee.com/openharmony/kernel_liteos_m 獲取。

接下來,我們看下互斥鎖的結構體,互斥鎖初始化,互斥鎖常用操作的源代碼。

1、互斥鎖結構體定義和常用宏定義

1.1 互斥鎖結構體定義

在文件kernel\include\los_mux.h定義的互斥鎖控制塊結構體LosMuxCB,源代碼如下,結構體成員的解釋見注釋部分。

typedef struct {
    UINT8 muxStat;       /**< 互斥鎖狀態:OS_MUX_UNUSED, OS_MUX_USED */
    UINT16 muxCount;     /**< 鎖被持有的次數 */
    UINT32 muxID;        /**< 互斥鎖Id */
    LOS_DL_LIST muxList; /**< 互斥鎖雙向鏈表 */
    LosTaskCB *owner;    /**< 當前持有鎖的任務 */
    UINT16 priority;     /**< 當前持有鎖的任務的優先級,為避免優先級翻轉,可能會更改任務的優先級,此時有備份的作用 */
} LosMuxCB;

1.2 互斥鎖常用宏定義

系統支持創建多少互斥鎖是根據開發板情況使用宏LOSCFG_BASE_IPC_MUX_LIMIT定義的,互斥鎖muxId是UINT32類型的,muxId取值為[0,LOSCFG_BASE_IPC_MUX_LIMIT),表示互斥鎖池中各個的互斥鎖的編號。

⑴處、⑵處的宏表示互斥鎖的未使用、使用狀態值。⑶處從互斥鎖池中獲取指定互斥鎖muxid對應的互斥鎖控制塊。⑷處根據互斥鎖雙向鏈表中的鏈表節點指針ptr獲取互斥鎖控制塊結構體指針。

#define OS_MUX_UNUSED 0#define OS_MUX_USED   1#define GET_MUX(muxid) (((LosMuxCB *)g_allMux) + (muxid))#define GET_MUX_LIST(ptr) LOS_DL_LIST_ENTRY(ptr, LosMuxCB, muxList)

2、互斥鎖初始化

互斥鎖在內核中默認開啟,用戶可以通過宏LOSCFG_BASE_IPC_MUX進行關閉。開啟互斥鎖的情況下,在系統啟動時,在kernel\src\los_init.c中調用OsMuxInit()進行互斥鎖模塊初始化。

下面,我們分析下互斥鎖初始化的代碼。

⑴初始化雙向循環鏈表g_unusedMuxList,維護未使用的互斥鎖。⑵處如果沒有設置宏LOSCFG_BASE_IPC_MUX,則返回錯誤碼。⑶為互斥鎖申請內存,如果申請失敗,則返回錯誤LOS_ERRNO_MUX_NO_MEMORY
⑷循環每一個互斥鎖進行初始化,為每一個互斥鎖節點指定索引muxID,muxStat為未使用OS_MUX_UNUSED,並把互斥鎖節點插入未使用互斥鎖雙向鏈表g_unusedMuxList。
⑷如果開啟了互斥鎖調測開關,則調用函數UINT32 OsMuxDbgInit(VOID)進行初始化。

LITE_OS_SEC_TEXT_INIT UINT32 OsMuxInit(VOID)
{
    LosMuxCB *muxNode = NULL;
    UINT32 index;

⑴  LOS_ListInit(&g_unusedMuxList);

⑵  if (LOSCFG_BASE_IPC_MUX_LIMIT == 0) {
        return LOS_ERRNO_MUX_MAXNUM_ZERO;
    }

⑶  g_allMux = (LosMuxCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_MUX_LIMIT * sizeof(LosMuxCB)));
    if (g_allMux == NULL) {
        return LOS_ERRNO_MUX_NO_MEMORY;
    }

⑷  for (index = 0; index < LOSCFG_BASE_IPC_MUX_LIMIT; index++) {
        muxNode = ((LosMuxCB *)g_allMux) + index;
        muxNode->muxID = index;
        muxNode->muxStat = OS_MUX_UNUSED;
        LOS_ListTailInsert(&g_unusedMuxList, &muxNode->muxList);
    }
    return LOS_OK;
}

3、互斥鎖常用操作

3.1 互斥鎖創建

我們可以使用函數UINT32 LOS_MuxCreate(UINT32 *muxHandle)來創建互斥鎖,下面通過分析源碼看看如何創建互斥鎖的。

⑴判斷未使用互斥鎖鏈表g_unusedMuxList是否為空,如果沒有可以使用的互斥鎖,跳轉到錯誤碼。⑵處如果g_unusedMuxList不為空,則獲取第一個可用的互斥鎖節點,接着從雙向鏈表g_unusedMuxList中刪除,然后調用GET_MUX_LIST宏函數獲取LosMuxCB *muxCreated,接着初始化創建的互斥鎖信息,包含持有鎖的次數、狀態、優先級等信息。⑶初始化雙向鏈表&muxCreated->muxList,阻塞在這個互斥鎖上的任務會掛在這個鏈表上。⑷賦值給輸出參數*muxHandle,后續程序使用這個互斥鎖Id對互斥鎖進行其他操作。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_MuxCreate(UINT32 *muxHandle)
{
    UINT32 intSave;
    LosMuxCB *muxCreated = NULL;
    LOS_DL_LIST *unusedMux = NULL;
    UINT32 errNo;
    UINT32 errLine;

    if (muxHandle == NULL) {
        return LOS_ERRNO_MUX_PTR_NULL;
    }

    intSave = LOS_IntLock();
⑴  if (LOS_ListEmpty(&g_unusedMuxList)) {
        LOS_IntRestore(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_ALL_BUSY);
    }

⑵  unusedMux = LOS_DL_LIST_FIRST(&(g_unusedMuxList));
    LOS_ListDelete(unusedMux);
    muxCreated = (GET_MUX_LIST(unusedMux));
    muxCreated->muxCount = 0;
    muxCreated->muxStat = OS_MUX_USED;
    muxCreated->priority = 0;
    muxCreated->owner = (LosTaskCB *)NULL;
⑶  LOS_ListInit(&muxCreated->muxList);
⑷  *muxHandle = (UINT32)muxCreated->muxID;
    LOS_IntRestore(intSave);
    OsHookCall(LOS_HOOK_TYPE_MUX_CREATE, muxCreated);
    return LOS_OK;
ERR_HANDLER:
    OS_RETURN_ERROR_P2(errLine, errNo);
}

3.2 互斥鎖刪除

我們可以使用函數LOS_MuxDelete(UINT32 muxHandle)來刪除互斥鎖,下面通過分析源碼看看如何刪除互斥鎖的。

⑴處判斷互斥鎖muxHandle是否超過LOSCFG_BASE_IPC_MUX_LIMIT,如果超過則返回錯誤碼。⑵獲取互斥鎖控制塊LosMuxCB *muxDeleted。⑶如果要刪除的互斥鎖處於未使用狀態,跳轉到錯誤標簽進行處理。⑷如果互斥鎖的持有者數量不為空,不允許刪除,跳轉到錯誤標簽進行處理。⑸把刪除的互斥鎖回收到未使用互斥鎖雙向鏈表g_unusedMuxList,然后更新為未使用狀態。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_MuxDelete(UINT32 muxHandle)
{
    UINT32 intSave;
    LosMuxCB *muxDeleted = NULL;
    UINT32 errNo;
    UINT32 errLine;

⑴  if (muxHandle >= (UINT32)LOSCFG_BASE_IPC_MUX_LIMIT) {
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_INVALID);
    }

⑵  muxDeleted = GET_MUX(muxHandle);
    intSave = LOS_IntLock();
⑶  if (muxDeleted->muxStat == OS_MUX_UNUSED) {
        LOS_IntRestore(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_INVALID);
    }

⑷  if ((!LOS_ListEmpty(&muxDeleted->muxList)) || muxDeleted->muxCount) {
        LOS_IntRestore(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_PENDED);
    }

⑸  LOS_ListAdd(&g_unusedMuxList, &muxDeleted->muxList);
    muxDeleted->muxStat = OS_MUX_UNUSED;

    LOS_IntRestore(intSave);

    OsHookCall(LOS_HOOK_TYPE_MUX_DELETE, muxDeleted);
    return LOS_OK;
ERR_HANDLER:
    OS_RETURN_ERROR_P2(errLine, errNo);
}

3.3 互斥鎖申請

我們可以使用函數UINT32 LOS_MuxPend(UINT32 muxHandle, UINT32 timeout)來請求互斥鎖,需要的2個參數分別是互斥鎖Id和等待時間timeout,單位Tick,取值范圍為[0, LOS_WAIT_FOREVER]。

下面通過分析源碼看看如何請求互斥鎖的。

申請互斥鎖時首先會進行互斥鎖Id、參數的合法性校驗,這些比較簡單。⑴處代碼獲取當前運行的任務,⑵如果互斥鎖沒有被持有,更新互斥鎖的持有次數、持有者信息和優先級,完成互斥鎖的申請。⑶處如果互斥鎖的持有次數不為0,並且被當前任務持有,可以持有次數加1,再次嵌套持有,完成互斥鎖的申請。如果代碼執行到⑷,說明申請的互斥鎖被其他任務持有着,此時如果等待時間為0,則申請失敗返回。⑸處更新當前任務阻塞在申請的互斥鎖上。

⑹處代碼表示在當前申請互斥鎖的任務優先級高於持有互斥鎖的任務優先級時,修改持有互斥鎖的優先級為當前任務的優先級。通過這樣的修改,可以避免優先級翻轉。⑺處調用函數OsSchedTaskWait()更新當前任務的狀態,設置等待時間,然后調用函數LOS_Schedule觸發任務調度。后續程序暫時不再執行,需要等到可以獲取互斥鎖或者時間超時。

如果時間超時或者申請到互斥鎖,系統重新調度到執行此任務,程序從⑻處繼續執行。如果是時間超時,⑼處更新任務狀態並返回碼,申請互斥鎖失敗。如果成功申請到互斥鎖,執行⑽,返回成功。

LITE_OS_SEC_TEXT UINT32 LOS_MuxPend(UINT32 muxHandle, UINT32 timeout)
{
    UINT32 intSave;
    LosMuxCB *muxPended = NULL;
    UINT32 retErr;
    LosTaskCB *runningTask = NULL;

    if (muxHandle >= (UINT32)LOSCFG_BASE_IPC_MUX_LIMIT) {
        OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
    }

    muxPended = GET_MUX(muxHandle);
    intSave = LOS_IntLock();
    retErr = OsMuxValidCheck(muxPended);
    if (retErr) {
        goto ERROR_MUX_PEND;
    }

⑴  runningTask = (LosTaskCB *)g_losTask.runTask;
⑵  if (muxPended->muxCount == 0) {
        muxPended->muxCount++;
        muxPended->owner = runningTask;
        muxPended->priority = runningTask->priority;
        LOS_IntRestore(intSave);
        goto HOOK;
    }

⑶  if (muxPended->owner == runningTask) {
        muxPended->muxCount++;
        LOS_IntRestore(intSave);
        goto HOOK;
    }

⑷  if (!timeout) {
        retErr = LOS_ERRNO_MUX_UNAVAILABLE;
        goto ERROR_MUX_PEND;
    }

⑸  runningTask->taskMux = (VOID *)muxPended;

⑹  if (muxPended->owner->priority > runningTask->priority) {
        (VOID)OsSchedModifyTaskSchedParam(muxPended->owner, runningTask->priority);
    }

⑺  OsSchedTaskWait(&muxPended->muxList, timeout);

    LOS_IntRestore(intSave);
    OsHookCall(LOS_HOOK_TYPE_MUX_PEND, muxPended);
    LOS_Schedule();

⑻  intSave = LOS_IntLock();
    if (runningTask->taskStatus & OS_TASK_STATUS_TIMEOUT) {
⑼      runningTask->taskStatus &= (~OS_TASK_STATUS_TIMEOUT);
        retErr = LOS_ERRNO_MUX_TIMEOUT;
        goto ERROR_MUX_PEND;
    }

    LOS_IntRestore(intSave);
⑽  return LOS_OK;

HOOK:
    OsHookCall(LOS_HOOK_TYPE_MUX_PEND, muxPended);
    return LOS_OK;

ERROR_MUX_PEND:
    LOS_IntRestore(intSave);
    OS_RETURN_ERROR(retErr);
}

3.4 互斥鎖釋放

我們可以使用函數UINT32 LOS_MuxPost(UINT32 muxHandle)來釋放互斥鎖,下面通過分析源碼看看如何釋放互斥鎖的。

釋放互斥鎖時首先會進行互斥鎖Id、參數的合法性校驗,這些比較簡單,自行閱讀即可。⑴處如果要釋放的互斥鎖沒有被持有、或者不是被當前任務持有,返回錯誤碼。⑵互斥鎖的持有數量減1,如果不為0,當前任務嵌套持有該互斥鎖,不需要調度,返回釋放互斥鎖成功。如果釋放一次后,當前任務不再持有互斥鎖,則執行⑶,如果持有互斥鎖任務的優先級不等於互斥鎖的備份優先級低,需要恢復當前任務的優先級。

⑷如果互斥鎖上還有其他任務阻塞着,獲取阻塞的任務resumedTask,該任務成功獲取到互斥鎖,然后執行⑸更新互斥鎖的持有信息。執行⑹更新任務resumedTask的狀態,然后調用函數LOS_Schedule觸發調度。

LITE_OS_SEC_TEXT UINT32 LOS_MuxPost(UINT32 muxHandle)
{
    UINT32 intSave;
    LosMuxCB *muxPosted = GET_MUX(muxHandle);
    LosTaskCB *resumedTask = NULL;
    LosTaskCB *runningTask = NULL;

    intSave = LOS_IntLock();

    if ((muxHandle >= (UINT32)LOSCFG_BASE_IPC_MUX_LIMIT) ||
        (muxPosted->muxStat == OS_MUX_UNUSED)) {
        LOS_IntRestore(intSave);
        OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
    }

    runningTask = (LosTaskCB *)g_losTask.runTask;
⑴  if ((muxPosted->muxCount == 0) || (muxPosted->owner != runningTask)) {
        LOS_IntRestore(intSave);
        OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
    }

⑵  if (--(muxPosted->muxCount) != 0) {
        LOS_IntRestore(intSave);
        OsHookCall(LOS_HOOK_TYPE_MUX_POST, muxPosted);
        return LOS_OK;
    }

⑶  if ((muxPosted->owner->priority) != muxPosted->priority) {
        (VOID)OsSchedModifyTaskSchedParam(muxPosted->owner, muxPosted->priority);
    }

⑷  if (!LOS_ListEmpty(&muxPosted->muxList)) {
        resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(muxPosted->muxList)));

⑸      muxPosted->muxCount = 1;
        muxPosted->owner = resumedTask;
        muxPosted->priority = resumedTask->priority;
        resumedTask->taskMux = NULL;

⑹      OsSchedTaskWake(resumedTask);

        LOS_IntRestore(intSave);
        OsHookCall(LOS_HOOK_TYPE_MUX_POST, muxPosted);
        LOS_Schedule();
    } else {
        LOS_IntRestore(intSave);
    }

    return LOS_OK;
}

小結

本文帶領大家一起剖析了鴻蒙輕內核的互斥鎖模塊的源代碼,包含互斥鎖的結構體、互斥鎖池初始化、互斥鎖創建刪除、申請釋放等。感謝閱讀,如有任何問題、建議,都可以留言給我們: https://gitee.com/openharmony/kernel_liteos_m/issues 。為了更容易找到鴻蒙輕內核代碼倉,建議訪問 https://gitee.com/openharmony/kernel_liteos_m ,關注Watch、點贊Star、並Fork到自己賬戶下,謝謝。

 

點擊關注,第一時間了解華為雲新鮮技術~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM