一文帶你剖析LiteOS互斥鎖Mutex原始碼

華為雲開發者社群發表於2021-04-12
摘要:多工環境下會存在多個任務訪問同一公共資源的場景,而有些公共資源是非共享的臨界資源,只能被獨佔使用。LiteOS使用互斥鎖來避免這種衝突,互斥鎖是一種特殊的二值性訊號量,用於實現對臨界資源的獨佔式處理。

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

本文我們來一起學習下LiteOS互斥鎖模組的原始碼,文中所涉及的原始碼,均可以在LiteOS開源站點https://gitee.com/LiteOS/LiteOS 獲取。互斥鎖原始碼、開發文件,示例程式程式碼如下:

  • LiteOS核心互斥鎖原始碼

包括互斥鎖的私有標頭檔案kernel\base\include\los_mux_pri.h、標頭檔案kernel\include\los_mux.h、C原始碼檔案kernel\base\los_mux.c。

  • 開發指南文件–互斥鎖

線上文件https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E4%BA%92%E6%96%A5%E9%94%81

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

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

1.1 互斥鎖結構體定義

在檔案kernel\base\include\los_mux_pri.h定義的互斥鎖控制塊結構體有2個,MuxBaseCB和LosMuxCB,前者和後者的前三個成員一樣,可以和pthread_mutex_t共享核心互斥鎖機制。結構體原始碼如下,結構體成員的解釋見註釋部分。

typedef struct {
    LOS_DL_LIST muxList; /**< 互斥鎖雙向連結串列 */
    LosTaskCB *owner; /**< 當前持有鎖的任務 */
    UINT16 muxCount; /**< 鎖被持有的次數*/
} MuxBaseCB;

typedef struct {
    LOS_DL_LIST muxList; /**< 互斥鎖雙向連結串列 */
    LosTaskCB *owner; /**< 當前持有鎖的任務 */
    UINT16 muxCount; /**< 鎖被持有的次數*/
    UINT8 muxStat; /**< 互斥鎖狀態: OS_MUX_UNUSED, OS_MUX_USED */
    UINT32 muxId; /**< 互斥鎖Id */
} LosMuxCB;

1.2 互斥鎖常用巨集定義

系統支援建立多少互斥鎖是根據開發板情況使用巨集LOSCFG_BASE_IPC_MUX_LIMIT定義的,互斥鎖Id是UINT32型別的,由2部分組成:count和muxId,分別處於高16位和低16位。建立互斥鎖,使用後刪除時,互斥鎖回收到互斥鎖池時,互斥鎖Id的高16位即count值會加1,這樣可以用來表示該互斥鎖被建立刪除的次數。muxId取值為[0,LOSCFG_BASE_IPC_MUX_LIMIT),表示互斥鎖池中各個的互斥鎖的編號。

⑴處的巨集用來分割count和muxId的位數,⑵處互斥鎖被刪除時更新互斥鎖Id,可以看出高16位為count和低16位為muxId。⑶處獲取互斥鎖Id的低16位。⑷根據互斥鎖Id獲取對應的互斥鎖被建立刪除的次數count。⑸處從互斥鎖池中獲取指定互斥鎖Id對應的互斥鎖控制塊。

#define MUX_SPLIT_BIT 16#define SET_MUX_ID(count, muxId)    (((count) << MUX_SPLIT_BIT) | (muxId))#define GET_MUX_INDEX(muxId)        ((muxId) & ((1U << MUX_SPLIT_BIT) - 1))#define GET_MUX_COUNT(muxId)        ((muxId) >> MUX_SPLIT_BIT)#define GET_MUX(muxId)              (((LosMuxCB *)g_allMux) + GET_MUX_INDEX(muxId))

2、互斥鎖初始化

互斥鎖在核心中預設開啟,使用者可以通過巨集LOSCFG_BASE_IPC_MUX進行關閉。開啟互斥鎖的情況下,在系統啟動時,在kernel\init\los_init.c中呼叫OsMuxInit()進行互斥鎖模組初始化。

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

⑴初始化雙向迴圈連結串列g_unusedMuxList,維護未使用的互斥鎖。⑵為互斥鎖申請記憶體,如果申請失敗,則返回錯誤LOS_ERRNO_MUX_NO_MEMORY
⑶迴圈每一個互斥鎖進行初始化,為每一個互斥鎖節點指定索引muxId,owner為空,muxStat為未使用OS_MUX_UNUSED,並把互斥鎖節點插入未使用互斥鎖雙向連結串列g_unusedMuxList。
⑷如果開啟了互斥鎖調測開關,則呼叫函式UINT32 OsMuxDbgInit(VOID)進行初始化。

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

⑴  LOS_ListInit(&g_unusedMuxList);
⑵  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 = g_allMux + index;
        muxNode->muxId = index;
        muxNode->owner = NULL;
        muxNode->muxStat = OS_MUX_UNUSED;
        LOS_ListTailInsert(&g_unusedMuxList, &muxNode->muxList);
    }

⑷  if (OsMuxDbgInitHook() != LOS_OK) {
        return LOS_ERRNO_MUX_NO_MEMORY;
    }
    return LOS_OK;
}

3、互斥鎖常用操作

3.1 互斥鎖建立

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

⑴判斷g_unusedMuxList是否為空,還有可以使用的互斥鎖資源?如果沒有可以使用的互斥鎖,呼叫函式OsMutexCheckHook()判斷是否有互斥鎖溢位等錯誤,這個函式需要開啟調測開關。⑵處如果g_unusedMuxList不為空,則獲取第一個可用的互斥鎖節點,接著從雙向連結串列g_unusedMuxList中刪除,然後呼叫LOS_DL_LIST_ENTRY(unusedMux, LosMuxCB, muxList)獲取LosMuxCB *muxCreated,初始化建立的互斥鎖資訊,包含持有鎖的次數、狀態、持有者等資訊。⑶初始化雙向連結串列&muxCreated->muxList,阻塞在這個互斥上的任務會掛在這個連結串列上。⑷賦值給輸出引數*muxHandle,後續程式使用這個互斥鎖Id對互斥鎖進行其他操作。⑸開啟調測時,會呼叫函式OsMuxDbgUpdateHook()更新互斥鎖的使用情況。

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

    SCHEDULER_LOCK(intSave);
⑴  if (LOS_ListEmpty(&g_unusedMuxList)) {
        SCHEDULER_UNLOCK(intSave);
        OsMutexCheckHook();
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_ALL_BUSY);
    }

⑵  unusedMux = LOS_DL_LIST_FIRST(&g_unusedMuxList);
    LOS_ListDelete(unusedMux);
    muxCreated = LOS_DL_LIST_ENTRY(unusedMux, LosMuxCB, muxList);
    muxCreated->muxCount = 0;
    muxCreated->muxStat = OS_MUX_USED;
    muxCreated->owner = NULL;
⑶  LOS_ListInit(&muxCreated->muxList);
    *muxHandle = muxCreated->muxId;

⑸  OsMuxDbgUpdateHook(muxCreated->muxId, OsCurrTaskGet()->taskEntry);

    SCHEDULER_UNLOCK(intSave);

    LOS_TRACE(MUX_CREATE, muxCreated->muxId);
    return LOS_OK;

ERR_HANDLER:
    OS_RETURN_ERROR_P2(errLine, errNo);
}

3.2 互斥鎖刪除

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

⑴處判斷互斥鎖handleId是否超過LOSCFG_BASE_IPC_MUX_LIMIT,如果超過則返回錯誤碼。⑵獲取互斥鎖控制塊LosMuxCB *muxDeleted。⑶如果要刪除的互斥鎖Id有問題,或者要刪除的互斥鎖處於未使用狀態,跳轉到錯誤標籤進行處理。⑷如果互斥鎖的持有者數量不為空,不允許刪除,跳轉到錯誤標籤進行處理。⑸把刪除的互斥鎖回收到未使用互斥鎖雙向連結串列g_unusedMuxList,然後更新為未使用狀態,更新互斥鎖Id。⑹開啟調測時,會呼叫函式OsMuxDbgUpdateHook()更新互斥鎖的使用情況。

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

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

⑵  muxDeleted = GET_MUX(muxHandle);

    LOS_TRACE(MUX_DELETE, muxHandle, muxDeleted->muxStat, muxDeleted->muxCount,
        ((muxDeleted->owner == NULL) ? 0xFFFFFFFF : muxDeleted->owner->taskId));

    SCHEDULER_LOCK(intSave);
⑶  if ((muxDeleted->muxId != muxHandle) || (muxDeleted->muxStat == OS_MUX_UNUSED)) {
        SCHEDULER_UNLOCK(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_MUX_INVALID);
    }

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

⑸  LOS_ListTailInsert(&g_unusedMuxList, &muxDeleted->muxList);
    muxDeleted->muxStat = OS_MUX_UNUSED;
    muxDeleted->muxId = SET_MUX_ID(GET_MUX_COUNT(muxDeleted->muxId) + 1, GET_MUX_INDEX(muxDeleted->muxId));

⑹  OsMuxDbgUpdateHook(muxDeleted->muxId, NULL);

    SCHEDULER_UNLOCK(intSave);

    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,申請失敗返回。⑸如果當前鎖任務排程,不允許申請互斥鎖,列印回溯棧並返回錯誤碼。

能執行到⑹處,表示互斥鎖已被其他任務持有。在當前申請互斥鎖的任務優先順序高於持有互斥鎖的任務優先順序時,修改持有互斥鎖的優先順序為當前任務的優先順序,持有鎖的任務優先順序備份到成員變數muxPended->owner->priBitMap。通過這樣的修改,可以避免優先順序翻轉。⑺處的函式OsMuxPendOp()下文繼續分析。

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

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

    muxPended = GET_MUX(muxHandle);

    LOS_TRACE(MUX_PEND, muxHandle, muxPended->muxCount,
        ((muxPended->owner == NULL) ? 0xFFFFFFFF : muxPended->owner->taskId), timeout);

    SCHEDULER_LOCK(intSave);

    ret = OsMuxParaCheck(muxPended, muxHandle);
    if (ret != LOS_OK) {
        goto OUT_UNLOCK;
    }

    runTask = OsCurrTaskGet();
⑴  if (runTask->taskFlags & OS_TASK_FLAG_SYSTEM) {
        PRINT_DEBUG("Warning: DO NOT recommend to use %s in system tasks.\n", __FUNCTION__);
    }

⑵  if (muxPended->muxCount == 0) {
        OsMuxDlockNodeInsertHook(runTask->taskId, muxPended);
        muxPended->muxCount++;
        muxPended->owner = runTask;
        goto OUT_UNLOCK;
    }

⑶  if (muxPended->owner == runTask) {
        muxPended->muxCount++;
        goto OUT_UNLOCK;
    }

⑷  if (!timeout) {
        ret = LOS_ERRNO_MUX_UNAVAILABLE;
        goto OUT_UNLOCK;
    }

⑸  if (!OsPreemptableInSched()) {
        ret = LOS_ERRNO_MUX_PEND_IN_LOCK;
        PRINT_ERR("!!!LOS_ERRNO_MUX_PEND_IN_LOCK!!!\n");
        OsBackTrace();
        goto OUT_UNLOCK;
    }

⑹  OsMuxBitmapSet(runTask, (MuxBaseCB *)muxPended);
⑺  ret = OsMuxPendOp(runTask, (MuxBaseCB *)muxPended, timeout, &intSave);

OUT_UNLOCK:
    SCHEDULER_UNLOCK(intSave);
    return ret;
}

接下來繼續分析函式OsMuxPendOp(),⑴處設定申請互斥鎖的任務的結構體成員變數runTask->taskMux為申請的互斥鎖。⑵處獲取互斥鎖的雙向連結串列,阻塞在請求這個互斥鎖的任務都掛在這個連結串列上,後文詳細分析這個函式。⑶處把申請互斥鎖的任務改為非就緒狀態、阻塞狀態,插入到互斥鎖的阻塞任務列表裡。如果是非永久等待互斥鎖,還需要把任務加入超時排序連結串列裡。⑷觸發任務排程,後續程式暫時不再執行,需要等到可以獲取互斥鎖或者時間超時。

如果時間超時或者申請到互斥鎖,系統重新排程到執行此任務,程式從⑸處繼續執行。如果是時間超時,⑹處更新任務狀態並返回碼,申請互斥鎖失敗。⑺如果成功申請到互斥鎖,並且超時時間不等於LOS_WAIT_FOREVER,需要判斷是否恢復任務優先順序。

LITE_OS_SEC_TEXT UINT32 OsMuxPendOp(LosTaskCB *runTask, MuxBaseCB *muxPended, UINT32 timeout,
                                    UINT32 *intSave)
{
    LOS_DL_LIST *node = NULL;
    UINT32 ret = LOS_OK;
    LosTaskCB *owner = muxPended->owner;

⑴  runTask->taskMux = (VOID *)muxPended;
⑵  node = OsMuxPendFindPos(runTask, muxPended);
⑶  OsTaskWait(node, OS_TASK_STATUS_PEND, timeout);
⑷  OsSchedResched();
    SCHEDULER_UNLOCK(*intSave);
⑸  SCHEDULER_LOCK(*intSave);

⑹  if (runTask->taskStatus & OS_TASK_STATUS_TIMEOUT) {
        runTask->taskStatus &= ~OS_TASK_STATUS_TIMEOUT;
        ret = LOS_ERRNO_MUX_TIMEOUT;
    }

⑺  if (timeout != LOS_WAIT_FOREVER) {
        OsMuxBitmapRestore(runTask, owner);
    }

    return ret;
}

接下來,分析下內部函式OsMuxPendFindPos()。LiteOS互斥鎖支援2種等待模式,可以通過巨集來配置:

  • LOSCFG_MUTEX_WAITMODE_PRIO

互斥鎖基於任務優先順序的等待模式,阻塞在互斥鎖的任務裡,誰的優先順序高,在互斥鎖釋放時,誰先獲取到互斥鎖。

  • LOSCFG_MUTEX_WAITMODE_FIFO

互斥鎖基於FIFO的等待模式,阻塞在互斥鎖的任務裡,誰先進入阻塞佇列,在互斥鎖釋放時,誰先獲取到互斥鎖。

在開啟巨集LOSCFG_MUTEX_WAITMODE_FIFO,互斥鎖基於FIFO的等待模式時,函式OsMuxPendFindPos()的原始碼比較簡單,直接獲取互斥鎖的阻塞連結串列,在後續的OsTaskWait()函式裡,會把任務掛在在獲取的阻塞連結串列的尾部。程式碼如下:

LITE_OS_SEC_TEXT STATIC LOS_DL_LIST *OsMuxPendFindPos(const LosTaskCB *runTask, MuxBaseCB *muxPended)
{
    LOS_DL_LIST *node = NULL;
    node = &muxPended->muxList;
    return node;
}

我們再來看看開啟巨集LOSCFG_MUTEX_WAITMODE_PRIO,互斥鎖基於任務優先順序的等待模式時的函式的程式碼。⑴如果互斥鎖的阻塞連結串列為空,直接返回連結串列即可。⑵阻塞連結串列不為空時,從連結串列中獲取第一個和最後一個連結串列節點,分別為pendedTask1和pendedTask2。⑶如果阻塞連結串列第一個任務的優先順序低於當前任務的優先順序,連結串列中所有的任務的優先順序都會低,返回互斥鎖的阻塞連結串列的第一個節點介面。⑷如果阻塞連結串列的最後一個任務的優先順序大於當前任務的優先順序,返回互斥鎖阻塞連結串列的頭結點即可。⑸對於其他情況,需要呼叫函式OsMuxPendFindPosSub()進行處理。

LITE_OS_SEC_TEXT STATIC LOS_DL_LIST *OsMuxPendFindPos(const LosTaskCB *runTask, MuxBaseCB *muxPended)
{
    LOS_DL_LIST *node = NULL;
    LosTaskCB *pendedTask1 = NULL;
    LosTaskCB *pendedTask2 = NULL;

⑴  if (LOS_ListEmpty(&muxPended->muxList)) {
        node = &muxPended->muxList;
    } else {
⑵      pendedTask1 = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&muxPended->muxList));
        pendedTask2 = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_LAST(&muxPended->muxList));
⑶      if ((pendedTask1 != NULL) && (pendedTask1->priority > runTask->priority)) {
            node = muxPended->muxList.pstNext;
⑷      } else if ((pendedTask2 != NULL) && (pendedTask2->priority <= runTask->priority)) {
            node = &muxPended->muxList;
        } else {
⑸          node = OsMuxPendFindPosSub(runTask, muxPended);
        }
    }
    return node;
}

繼續分析下函式OsMuxPendFindPosSub()。⑴迴圈遍歷互斥鎖阻塞連結串列,⑵如果連結串列上任務優先順序大於當前任務的優先順序,則繼續遍歷。⑶如果連結串列上任務優先順序小於當前任務的優先順序,不需要繼續遍歷了,返回連結串列的當前節點。⑷如果優先順序相等,返回連結串列的下一個節點。

LITE_OS_SEC_TEXT STATIC LOS_DL_LIST *OsMuxPendFindPosSub(const LosTaskCB *runTask, const MuxBaseCB *muxPended)
{
    LosTaskCB *pendedTask = NULL;
    LOS_DL_LIST *node = NULL;

⑴  LOS_DL_LIST_FOR_EACH_ENTRY(pendedTask, &(muxPended->muxList), LosTaskCB, pendList) {
⑵      if (pendedTask->priority < runTask->priority) {
            continue;
⑶      } else if (pendedTask->priority > runTask->priority) {
            node = &pendedTask->pendList;
            break;
        } else {
⑷          node = pendedTask->pendList.pstNext;
            break;
        }
    }

    return node;
}

3.4 互斥鎖釋放

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

釋放互斥鎖時首先會進行互斥鎖Id、引數的合法性校驗,這些比較簡單,自行閱讀即可。⑴處如果要釋放的互斥鎖沒有被持有、或者不是被當前任務持有,返回錯誤碼。⑵互斥鎖的持有數量減1,如果不為0,當前任務巢狀持有該互斥鎖,不需要排程,返回釋放互斥鎖成功。如果釋放一次後,當前任務不再持有互斥鎖,則呼叫⑶處函式OsMuxPostOp(),判斷是否有任務阻塞在該互斥鎖,是否需要觸發任務排程等,下文分析該函式。執行完畢⑶後,執行⑷,如果需要排程則觸發任務排程。

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

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

    LOS_TRACE(MUX_POST, muxHandle, muxPosted->muxCount,
        ((muxPosted->owner == NULL) ? 0xFFFFFFFF : muxPosted->owner->taskId));

    SCHEDULER_LOCK(intSave);

    ret = OsMuxParaCheck(muxPosted, muxHandle);
    if (ret != LOS_OK) {
        SCHEDULER_UNLOCK(intSave);
        return ret;
    }

    runTask = OsCurrTaskGet();
⑴  if ((muxPosted->muxCount == 0) || (muxPosted->owner != runTask)) {
        SCHEDULER_UNLOCK(intSave);
        OS_RETURN_ERROR(LOS_ERRNO_MUX_INVALID);
    }

⑵  if (--muxPosted->muxCount != 0) {
        SCHEDULER_UNLOCK(intSave);
        return LOS_OK;
    }

⑶  ret = OsMuxPostOp(runTask, (MuxBaseCB *)muxPosted);
    SCHEDULER_UNLOCK(intSave);
⑷  if (ret == MUX_SCHEDULE) {
        LOS_MpSchedule(OS_MP_CPU_ALL);
        LOS_Schedule();
    }

    return LOS_OK;
}

我們繼續分析函式OsMuxPostOp()。⑴處如果等到該互斥鎖的任務列表為空,則標記沒有任務持有該互斥鎖,並返回不需要排程。⑵獲取等待互斥鎖的第一個任務resumedTask。如果開啟巨集LOSCFG_MUTEX_WAITMODE_PRIO,如果等待互斥鎖的任務resumedTask的優先順序比當前優先順序低,需要恢復當前任務的優先順序。如果當前任務優先順序runTask->priBitMap不為0,會呼叫⑷處的OsMuxPostOpSub函式,稍後分析該函式。

⑸處把該互斥鎖的持有數量設定為1,持有人設定為等待互斥鎖的第一個任務resumedTask,resumedTask持有了互斥鎖不再阻塞在該互斥鎖resumedTask->taskMux = NULL。然後2個語句,屬於調測特性的。⑹處呼叫OsTaskWake()函式,把resumedTask從互斥鎖的阻塞連結串列中刪除,從定時器排序連結串列中刪除。更新任務狀態,加入就緒佇列,返回任務需要排程。

LITE_OS_SEC_TEXT UINT32 OsMuxPostOp(LosTaskCB *runTask, MuxBaseCB *muxPosted)
{
    LosTaskCB *resumedTask = NULL;

⑴  if (LOS_ListEmpty(&muxPosted->muxList)) {
        muxPosted->owner = NULL;
        OsMuxDlockNodeDeleteHook(runTask->taskId, muxPosted);
        return MUX_NO_SCHEDULE;
    }

⑵  resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(muxPosted->muxList)));
#ifdef LOSCFG_MUTEX_WAITMODE_PRIO
⑶  if (resumedTask->priority > runTask->priority) {
        if (LOS_HighBitGet(runTask->priBitMap) != resumedTask->priority) {
            LOS_BitmapClr(&runTask->priBitMap, resumedTask->priority);
        }
    } else if (runTask->priBitMap != 0) {
⑷      OsMuxPostOpSub(runTask, muxPosted);
    }
#else
    if (runTask->priBitMap != 0) {
⑷      OsMuxPostOpSub(runTask, muxPosted);
    }
#endif

⑸  muxPosted->muxCount = 1;
    muxPosted->owner = resumedTask;
    resumedTask->taskMux = NULL;
    OsMuxDlockNodeDeleteHook(runTask->taskId, muxPosted);
    OsMuxDlockNodeInsertHook(resumedTask->taskId, muxPosted);

⑹  OsTaskWake(resumedTask, OS_TASK_STATUS_PEND);

    return MUX_SCHEDULE;
}

最後,我們分析函式OsMuxPostOpSub()。

⑴如果互斥鎖上還有其他任務阻塞著,獲取當前執行任務記錄的優先順序.priBitMap。⑵處迴圈遍歷掛在互斥鎖阻塞連結串列上的每一個任務,如果阻塞任務的優先順序不等於bitMapPri,則執行⑶清理優先順序位。⑷處恢復當前持有互斥鎖的任務的優先順序。

LITE_OS_SEC_TEXT STATIC VOID OsMuxPostOpSub(LosTaskCB *runTask, MuxBaseCB *muxPosted)
{
    LosTaskCB *pendedTask = NULL;
    UINT16 bitMapPri;

⑴  if (!LOS_ListEmpty(&muxPosted->muxList)) {
        bitMapPri = LOS_HighBitGet(runTask->priBitMap);
⑵      LOS_DL_LIST_FOR_EACH_ENTRY(pendedTask, (&muxPosted->muxList), LosTaskCB, pendList) {
            if (bitMapPri != pendedTask->priority) {
⑶              LOS_BitmapClr(&runTask->priBitMap, pendedTask->priority);
            }
        }
    }
⑷  bitMapPri = LOS_LowBitGet(runTask->priBitMap);
    LOS_BitmapClr(&runTask->priBitMap, bitMapPri);
    OsTaskPriModify(muxPosted->owner, bitMapPri);
}

小結

本文帶領大家一起剖析了LiteOS互斥鎖模組的原始碼,包含互斥鎖的結構體、互斥鎖池初始化、互斥鎖建立刪除、申請釋放等。

 本文分享自華為雲社群《LiteOS核心原始碼分析系列七 互斥鎖Mutex》,原文作者:zhushy。

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

相關文章