鴻蒙輕內(nèi)核M核源碼分析系列十一 信號(hào)量Semaphore
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
信號(hào)量(Semaphore)是一種實(shí)現(xiàn)任務(wù)間通信的機(jī)制,可以實(shí)現(xiàn)任務(wù)間同步或共享資源的互斥訪問。一個(gè)信號(hào)量的數(shù)據(jù)結(jié)構(gòu)中,通常有一個(gè)計(jì)數(shù)值,用于對(duì)有效資源數(shù)的計(jì)數(shù),表示剩下的可被使用的共享資源數(shù)。以同步為目的的信號(hào)量和以互斥為目的的信號(hào)量在使用上存在差異。本文通過分析鴻蒙輕內(nèi)核信號(hào)量模塊的源碼,掌握信號(hào)量使用上的差異。
接下來,我們看下信號(hào)量的結(jié)構(gòu)體,信號(hào)量初始化,信號(hào)量常用操作的源代碼。
1、信號(hào)量結(jié)構(gòu)體定義和常用宏定義
1.1 信號(hào)量結(jié)構(gòu)體定義
在文件kernel\include\los_sem.h定義的信號(hào)量控制塊結(jié)構(gòu)體為L(zhǎng)osSemCB,結(jié)構(gòu)體源代碼如下。信號(hào)量狀態(tài).semStat取值OS_SEM_UNUSED、OS_SEM_USED,其他成員變量的注釋見注釋部分。
- typedef struct {
- UINT16 semStat; /**< 信號(hào)量狀態(tài) */
- UINT16 semCount; /**< 可用的信號(hào)量數(shù)量 */
- UINT16 maxSemCount; /**< 可用的信號(hào)量最大數(shù)量 */
- UINT16 semID; /**< 信號(hào)量Id */
- LOS_DL_LIST semList; /**< 阻塞在該信號(hào)量的任務(wù)鏈表 */
- } LosSemCB;
1.2 信號(hào)量常用宏定義
系統(tǒng)支持創(chuàng)建多少信號(hào)量是根據(jù)開發(fā)板情況使用宏LOSCFG_BASE_IPC_SEM_LIMIT定義的,每一個(gè)信號(hào)量semId是UINT32類型的,取值為[0,LOSCFG_BASE_IPC_SEM_LIMIT),表示信號(hào)量池中各個(gè)的信號(hào)量的編號(hào)。
⑴處的宏表示二值信號(hào)量的最大值為1,⑵處、⑶處的宏表示信號(hào)量未使用、使用狀態(tài)值。⑷處根據(jù)信號(hào)量阻塞任務(wù)雙向鏈表中的鏈表節(jié)點(diǎn)指針ptr獲取信號(hào)量控制塊結(jié)構(gòu)體指針。⑸處從信號(hào)量池中獲取指定信號(hào)量semId對(duì)應(yīng)的信號(hào)量控制塊。
- ⑴ #define OS_SEM_BINARY_MAX_COUNT 1
- ⑵ #define OS_SEM_UNUSED 0
- ⑶ #define OS_SEM_USED 1
- ⑷ #define GET_SEM_LIST(ptr) LOS_DL_LIST_ENTRY(ptr, LosSemCB, semList)
- ⑸ #define GET_SEM(semid) (((LosSemCB *)g_allSem) + (semid))
2、信號(hào)量初始化
信號(hào)量在內(nèi)核中默認(rèn)開啟,用戶可以通過宏LOSCFG_BASE_IPC_SEM進(jìn)行關(guān)閉。開啟信號(hào)量的情況下,在系統(tǒng)啟動(dòng)時(shí),在kernel\src\los_init.c中調(diào)用OsSemInit()進(jìn)行信號(hào)量模塊初始化。
下面,我們分析下信號(hào)量初始化的代碼。
⑴初始化雙向循環(huán)鏈表g_unusedSemList,維護(hù)未使用的信號(hào)量池。⑵為信號(hào)量池申請(qǐng)內(nèi)存,如果申請(qǐng)失敗,則返回錯(cuò)誤。⑶循環(huán)每一個(gè)信號(hào)量進(jìn)行初始化,為每一個(gè)信號(hào)量節(jié)點(diǎn)指定索引semID,把.semStat設(shè)置為未使用OS_SEM_UNUSED,并執(zhí)行⑷把信號(hào)量節(jié)點(diǎn)插入未使用信號(hào)量雙向鏈表g_unusedSemList。
- LITE_OS_SEC_TEXT_INIT UINT32 OsSemInit(VOID)
- {
- LosSemCB *semNode = NULL;
- UINT16 index;
- ⑴ LOS_ListInit(&g_unusedSemList);
- if (LOSCFG_BASE_IPC_SEM_LIMIT == 0) {
- return LOS_ERRNO_SEM_MAXNUM_ZERO;
- }
- ⑵ g_allSem = (LosSemCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_SEM_LIMIT * sizeof(LosSemCB)));
- if (g_allSem == NULL) {
- return LOS_ERRNO_SEM_NO_MEMORY;
- }
- ⑶ for (index = 0; index < LOSCFG_BASE_IPC_SEM_LIMIT; index++) {
- semNode = ((LosSemCB *)g_allSem) + index;
- semNode->semID = index;
- semNode->semStat = OS_SEM_UNUSED;
- ⑷ LOS_ListTailInsert(&g_unusedSemList, &semNode->semList);
- }
- return LOS_OK;
- }
3、信號(hào)量常用操作
3.1 信號(hào)量創(chuàng)建
我們可以使用函數(shù)LOS_SemCreate(UINT16 count, UINT32 *semHandle)來創(chuàng)建計(jì)數(shù)信號(hào)量,使用UINT32 LOS_BinarySemCreate(UINT16 count, UINT32 *semHandle)創(chuàng)建二值信號(hào)量,下面通過分析源碼看看如何創(chuàng)建信號(hào)量的。
2個(gè)函數(shù)的傳入?yún)?shù)一樣,需要傳入信號(hào)量的數(shù)量count,和保存信號(hào)量編號(hào)的semHandle。計(jì)數(shù)信號(hào)量的最大數(shù)量為OS_SEM_COUNTING_MAX_COUNT,二值信號(hào)量的最大數(shù)量為OS_SEM_BINARY_MAX_COUNT。會(huì)進(jìn)一步調(diào)用函數(shù)OsSemCreate()實(shí)現(xiàn)信號(hào)量的創(chuàng)建,下文繼續(xù)分析。
- LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemCreate(UINT16 count, UINT32 *semHandle)
- {
- return OsSemCreate(count, OS_SEM_COUNTING_MAX_COUNT, semHandle);
- }
- LITE_OS_SEC_TEXT_INIT UINT32 LOS_BinarySemCreate(UINT16 count, UINT32 *semHandle)
- {
- return OsSemCreate(count, OS_SEM_BINARY_MAX_COUNT, semHandle);
- }
我們看看創(chuàng)建信號(hào)量的函數(shù)OsSemCreate(),需要3個(gè)參數(shù),創(chuàng)建的信號(hào)量的數(shù)量,最大數(shù)量,以及信號(hào)量編號(hào)。
⑴判斷g_unusedSemList是否為空,還有可以使用的信號(hào)量資源?如果沒有可以使用的信號(hào)量,調(diào)用函數(shù)OsSemInfoGetFullDataHook()做些調(diào)測(cè)相關(guān)的檢測(cè),這個(gè)函數(shù)需要開啟調(diào)測(cè)開關(guān),后續(xù)系列專門分析。
⑵處如果g_unusedSemList不為空,則獲取第一個(gè)可用的信號(hào)量節(jié)點(diǎn),接著從雙向鏈表g_unusedSemList中刪除,然后調(diào)用宏GET_SEM_LIST獲取LosSemCB *semCreated
,初始化創(chuàng)建的信號(hào)量信息,包含信號(hào)量的狀態(tài)、信號(hào)量數(shù)量,信號(hào)量最大數(shù)量等信息。⑶初始化雙向鏈表&semCreated->semList,阻塞在這個(gè)信號(hào)量上的任務(wù)會(huì)掛在這個(gè)鏈表上。⑷賦值給輸出參數(shù)*semHandle,后續(xù)程序使用這個(gè)信號(hào)量編號(hào)對(duì)信號(hào)量進(jìn)行其他操作。
- LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle)
- {
- UINT32 intSave;
- LosSemCB *semCreated = NULL;
- LOS_DL_LIST *unusedSem = NULL;
- UINT32 errNo;
- UINT32 errLine;
- if (semHandle == NULL) {
- return LOS_ERRNO_SEM_PTR_NULL;
- }
- if (count > maxCount) {
- OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_OVERFLOW);
- }
- intSave = LOS_IntLock();
- ⑴ if (LOS_ListEmpty(&g_unusedSemList)) {
- LOS_IntRestore(intSave);
- OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_ALL_BUSY);
- }
- ⑵ unusedSem = LOS_DL_LIST_FIRST(&(g_unusedSemList));
- LOS_ListDelete(unusedSem);
- semCreated = (GET_SEM_LIST(unusedSem));
- semCreated->semCount = count;
- semCreated->semStat = OS_SEM_USED;
- semCreated->maxSemCount = maxCount;
- ⑶ LOS_ListInit(&semCreated->semList);
- ⑷ *semHandle = (UINT32)semCreated->semID;
- LOS_IntRestore(intSave);
- OsHookCall(LOS_HOOK_TYPE_SEM_CREATE, semCreated);
- return LOS_OK;
- ERR_HANDLER:
- OS_RETURN_ERROR_P2(errLine, errNo);
- }
3.2 信號(hào)量刪除
我們可以使用函數(shù)LOS_semDelete(UINT32 semHandle)來刪除信號(hào)量,下面通過分析源碼看看如何刪除信號(hào)量的。
⑴處判斷信號(hào)量semHandle是否超過LOSCFG_BASE_IPC_SEM_LIMIT,如果超過則返回錯(cuò)誤碼。如果信號(hào)量編號(hào)沒有問題,獲取信號(hào)量控制塊LosSemCB *semDeleted。⑵處判斷要?jiǎng)h除的信號(hào)量的狀態(tài),如果處于未使用狀態(tài),則跳轉(zhuǎn)到錯(cuò)誤標(biāo)簽ERR_HANDLER:進(jìn)行處理。⑶如果信號(hào)量的阻塞任務(wù)列表不為空,不允許刪除,跳轉(zhuǎn)到錯(cuò)誤標(biāo)簽進(jìn)行處理。⑷處如果信號(hào)量可用刪除,則會(huì)把.semStat設(shè)置為未使用OS_SEM_UNUSED,并把信號(hào)量節(jié)點(diǎn)插入未使用信號(hào)量雙向鏈表g_unusedSemList。
- LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemDelete(UINT32 semHandle)
- {
- UINT32 intSave;
- LosSemCB *semDeleted = NULL;
- UINT32 errNo;
- UINT32 errLine;
- ⑴ if (semHandle >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {
- OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);
- }
- semDeleted = GET_SEM(semHandle);
- intSave = LOS_IntLock();
- ⑵ if (semDeleted->semStat == OS_SEM_UNUSED) {
- LOS_IntRestore(intSave);
- OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);
- }
- ⑶ if (!LOS_ListEmpty(&semDeleted->semList)) {
- LOS_IntRestore(intSave);
- OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_PENDED);
- }
- ⑷ LOS_ListAdd(&g_unusedSemList, &semDeleted->semList);
- semDeleted->semStat = OS_SEM_UNUSED;
- LOS_IntRestore(intSave);
- OsHookCall(LOS_HOOK_TYPE_SEM_DELETE, semDeleted);
- return LOS_OK;
- ERR_HANDLER:
- OS_RETURN_ERROR_P2(errLine, errNo);
- }
3.3 信號(hào)量申請(qǐng)
我們可以使用函數(shù)UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)來請(qǐng)求信號(hào)量,需要的2個(gè)參數(shù)分別是信號(hào)量semHandle和等待時(shí)間timeout,取值范圍為[0, LOS_WAIT_FOREVER],單位為Tick。下面通過分析源碼看看如何請(qǐng)求信號(hào)量的。
申請(qǐng)信號(hào)量時(shí)首先會(huì)進(jìn)行信號(hào)量編號(hào)、參數(shù)的合法性校驗(yàn)。⑴處代碼表示信號(hào)量如果大于配置的最大值,則返回錯(cuò)誤碼。⑵處獲取要申請(qǐng)的信號(hào)量控制塊semPended。⑶處調(diào)用函數(shù)對(duì)信號(hào)量控制塊進(jìn)行校驗(yàn),如果信號(hào)量未創(chuàng)建,處于中斷處理期間,處于鎖任務(wù)調(diào)度期間,則返回錯(cuò)誤碼。⑷處如果校驗(yàn)不通過,跳轉(zhuǎn)到ERROR_SEM_PEND:標(biāo)簽停止信號(hào)量的申請(qǐng)。
⑸如果信號(hào)量計(jì)數(shù)大于0,信號(hào)量計(jì)數(shù)減1,返回申請(qǐng)成功的結(jié)果。⑹如果信號(hào)量計(jì)數(shù)等于0,并且零等待時(shí)間timeout,則返回結(jié)果碼LOS_ERRNO_SEM_UNAVAILABLE。⑺如果申請(qǐng)的信號(hào)量被全部占用,需要等待時(shí),把當(dāng)前任務(wù)阻塞的信號(hào)量.taskSem標(biāo)記為申請(qǐng)的信號(hào)量,然后調(diào)用函數(shù)OsSchedTaskWait(),該函數(shù)詳細(xì)代碼上文已分析,把當(dāng)前任務(wù)狀態(tài)設(shè)置為阻塞狀態(tài),加入信號(hào)量的阻塞鏈表.semList。如果不是永久等待LOS_WAIT_FOREVER,還需要更改任務(wù)狀態(tài)為OS_TASK_STATUS_PEND_TIME,并且設(shè)置waitTimes等待時(shí)間。⑻處觸發(fā)任務(wù)調(diào)度進(jìn)行任務(wù)切換,暫時(shí)不執(zhí)行后續(xù)代碼。
如果等待時(shí)間超時(shí),信號(hào)量還不可用,本任務(wù)獲取不到信號(hào)量時(shí),繼續(xù)執(zhí)行⑼,更改任務(wù)狀態(tài),返回錯(cuò)誤碼。如果信號(hào)量可用,執(zhí)行⑽,本任務(wù)獲取到信號(hào)量,返回申請(qǐng)成功。
- LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)
- {
- UINT32 intSave;
- LosSemCB *semPended = NULL;
- UINT32 retErr;
- LosTaskCB *runningTask = NULL;
- ⑴ if (semHandle >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {
- OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);
- }
- ⑵ semPended = GET_SEM(semHandle);
- intSave = LOS_IntLock();
- ⑶ retErr = OsSemValidCheck(semPended);
- if (retErr) {
- ⑷ goto ERROR_SEM_PEND;
- }
- ⑸ if (semPended->semCount > 0) {
- semPended->semCount--;
- LOS_IntRestore(intSave);
- OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runningTask);
- return LOS_OK;
- }
- ⑹ if (!timeout) {
- retErr = LOS_ERRNO_SEM_UNAVAILABLE;
- goto ERROR_SEM_PEND;
- }
- ⑺ runningTask = (LosTaskCB *)g_losTask.runTask;
- runningTask->taskSem = (VOID *)semPended;
- OsSchedTaskWait(&semPended->semList, timeout);
- LOS_IntRestore(intSave);
- OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runningTask);
- ⑻ LOS_Schedule();
- intSave = LOS_IntLock();
- ⑼ if (runningTask->taskStatus & OS_TASK_STATUS_TIMEOUT) {
- runningTask->taskStatus &= (~OS_TASK_STATUS_TIMEOUT);
- retErr = LOS_ERRNO_SEM_TIMEOUT;
- goto ERROR_SEM_PEND;
- }
- LOS_IntRestore(intSave);
- ⑽ return LOS_OK;
- ERROR_SEM_PEND:
- LOS_IntRestore(intSave);
- OS_RETURN_ERROR(retErr);
- }
3.4 信號(hào)量釋放
我們可以使用函數(shù)UINT32 LOS_semPost(UINT32 semHandle)來釋放信號(hào)量,下面通過分析源碼看看如何釋放信號(hào)量的。
釋放信號(hào)量時(shí)首先會(huì)進(jìn)行信號(hào)量編號(hào)、參數(shù)的合法性校驗(yàn),這些比較簡(jiǎn)單,自行閱讀即可。⑴處驗(yàn)判斷是否信號(hào)量溢出。⑵如果信號(hào)量的任務(wù)阻塞鏈表不為空,執(zhí)行⑶從阻塞鏈表中獲取第一個(gè)任務(wù),設(shè)置.taskSem為NULL,不再阻塞信號(hào)量。執(zhí)行⑷把獲取到信號(hào)量的任務(wù)調(diào)整其狀態(tài),并加入就行隊(duì)列。⑸觸發(fā)任務(wù)調(diào)度進(jìn)行任務(wù)切換。⑹如果信號(hào)量的任務(wù)阻塞鏈表為空,則把信號(hào)量的計(jì)數(shù)加1。
- LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle)
- {
- UINT32 intSave;
- LosSemCB *semPosted = GET_SEM(semHandle);
- LosTaskCB *resumedTask = NULL;
- if (semHandle >= LOSCFG_BASE_IPC_SEM_LIMIT) {
- return LOS_ERRNO_SEM_INVALID;
- }
- intSave = LOS_IntLock();
- if (semPosted->semStat == OS_SEM_UNUSED) {
- LOS_IntRestore(intSave);
- OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);
- }
- ⑴ if (semPosted->maxSemCount == semPosted->semCount) {
- LOS_IntRestore(intSave);
- OS_RETURN_ERROR(LOS_ERRNO_SEM_OVERFLOW);
- }
- ⑵ if (!LOS_ListEmpty(&semPosted->semList)) {
- ⑶ resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(semPosted->semList)));
- resumedTask->taskSem = NULL;
- ⑷ OsSchedTaskWake(resumedTask);
- LOS_IntRestore(intSave);
- OsHookCall(LOS_HOOK_TYPE_SEM_POST, semPosted, resumedTask);
- ⑸ LOS_Schedule();
- } else {
- ⑹ semPosted->semCount++;
- LOS_IntRestore(intSave);
- OsHookCall(LOS_HOOK_TYPE_SEM_POST, semPosted, resumedTask);
- }
- return LOS_OK;
- }
4、信號(hào)量使用總結(jié)
4.1 計(jì)數(shù)信號(hào)量、二值信號(hào)量和互斥鎖
計(jì)數(shù)信號(hào)量和二值信號(hào)量唯一的區(qū)別就是信號(hào)量的初始數(shù)量不一致,二值信號(hào)量初始數(shù)量只能為0和1,計(jì)數(shù)信號(hào)量的初始值可以為0和大于1的整數(shù)。
互斥鎖可以理解為一種特性的二值信號(hào)量,在實(shí)現(xiàn)實(shí)現(xiàn)對(duì)臨界資源的獨(dú)占式處理、互斥場(chǎng)景時(shí),沒有本質(zhì)的區(qū)別。比對(duì)下二值的結(jié)構(gòu)體,互斥鎖的成員變量.muxCount表示加鎖的次數(shù),信號(hào)量的成員變量.semCount表示信號(hào)量的計(jì)數(shù),含義稍有不同。
4.2 信號(hào)量的互斥和同步
信號(hào)量可用用于互斥和同步兩種場(chǎng)景,以同步為目的的信號(hào)量和以互斥為目的的信號(hào)量在使用上,有如下不同:
- 用于互斥的信號(hào)量
初始信號(hào)量計(jì)數(shù)值不為0,表示可用的共享資源個(gè)數(shù)。在需要使用共享資源前,先獲取信號(hào)量,然后使用一個(gè)共享資源,使用完畢后釋放信號(hào)量。這樣在共享資源被取完,即信號(hào)量計(jì)數(shù)減至0時(shí),其他需要獲取信號(hào)量的任務(wù)將被阻塞,從而保證了共享資源的互斥訪問。對(duì)信號(hào)量的申請(qǐng)和釋放,需要成對(duì)出現(xiàn),在同一個(gè)任務(wù)里完成申請(qǐng)和釋放。
- 用于同步的信號(hào)量
多任務(wù)同時(shí)訪問同一份共享資源時(shí),會(huì)導(dǎo)致沖突,這時(shí)候就需要引入任務(wù)同步機(jī)制使得各個(gè)任務(wù)按業(yè)務(wù)需求一個(gè)一個(gè)的對(duì)共享資源進(jìn)行有序訪問操作。任務(wù)同步的實(shí)質(zhì)就是任務(wù)按需進(jìn)行排隊(duì)。
用于同步的信號(hào)量,初始信號(hào)量計(jì)數(shù)值為0。任務(wù)1申請(qǐng)信號(hào)量而阻塞,直到任務(wù)2或者某中斷釋放信號(hào)量,任務(wù)1才得以進(jìn)入Ready或Running態(tài),從而達(dá)到了任務(wù)間的同步。信號(hào)量的能不能申請(qǐng)成功,依賴其他任務(wù)是否釋放信號(hào)量,申請(qǐng)和釋放在不同的任務(wù)里完成。
小結(jié)
本文帶領(lǐng)大家一起剖析了鴻蒙輕內(nèi)核的信號(hào)量模塊的源代碼,包含信號(hào)量的結(jié)構(gòu)體、信號(hào)量池初始化、信號(hào)量創(chuàng)建刪除、申請(qǐng)釋放等。感謝閱讀!
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)