鴻蒙內核M核源碼分析系列十一 信號量Semaphore

      網友投稿 725 2025-04-04

      鴻蒙輕內核M核源碼分析系列十一 信號量Semaphore


      信號量(Semaphore)是一種實現任務間通信的機制,可以實現任務間同步或共享資源的互斥訪問。一個信號量的數據結構中,通常有一個計數值,用于對有效資源數的計數,表示剩下的可被使用的共享資源數。以同步為目的的信號量和以互斥為目的的信號量在使用上存在差異。本文通過分析鴻蒙輕內核信號量模塊的源碼,掌握信號量使用上的差異。本文中所涉及的源碼,以OpenHarmony LiteOS-M內核為例,均可以在開源站點https://gitee.com/openharmony/kernel_liteos_m 獲取。

      接下來,我們看下信號量的結構體,信號量初始化,信號量常用操作的源代碼。

      1、信號量結構體定義和常用宏定義

      1.1 信號量結構體定義

      在文件kernel\include\los_sem.h定義的信號量控制塊結構體為LosSemCB,結構體源代碼如下。信號量狀態.semStat取值OS_SEM_UNUSED、OS_SEM_USED,其他成員變量的注釋見注釋部分。

      typedef struct { UINT16 semStat; /**< 信號量狀態 */ UINT16 semCount; /**< 可用的信號量數量 */ UINT16 maxSemCount; /**< 可用的信號量最大數量 */ UINT16 semID; /**< 信號量Id */ LOS_DL_LIST semList; /**< 阻塞在該信號量的任務鏈表 */ } LosSemCB;

      1.2 信號量常用宏定義

      系統支持創建多少信號量是根據開發板情況使用宏LOSCFG_BASE_IPC_SEM_LIMIT定義的,每一個信號量semId是UINT32類型的,取值為[0,LOSCFG_BASE_IPC_SEM_LIMIT),表示信號量池中各個的信號量的編號。

      ⑴處的宏表示二值信號量的最大值為1,⑵處、⑶處的宏表示信號量未使用、使用狀態值。⑷處根據信號量阻塞任務雙向鏈表中的鏈表節點指針ptr獲取信號量控制塊結構體指針。⑸處從信號量池中獲取指定信號量semId對應的信號量控制塊。

      ⑴ #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、信號量初始化

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

      下面,我們分析下信號量初始化的代碼。

      ⑴初始化雙向循環鏈表g_unusedSemList,維護未使用的信號量池。⑵為信號量池申請內存,如果申請失敗,則返回錯誤。⑶循環每一個信號量進行初始化,為每一個信號量節點指定索引semID,把.semStat設置為未使用OS_SEM_UNUSED,并執行⑷把信號量節點插入未使用信號量雙向鏈表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、信號量常用操作

      鴻蒙輕內核M核源碼分析系列十一 信號量Semaphore

      3.1 信號量創建

      我們可以使用函數LOS_SemCreate(UINT16 count, UINT32 *semHandle)來創建計數信號量,使用UINT32 LOS_BinarySemCreate(UINT16 count, UINT32 *semHandle)創建二值信號量,下面通過分析源碼看看如何創建信號量的。

      2個函數的傳入參數一樣,需要傳入信號量的數量count,和保存信號量編號的semHandle。計數信號量的最大數量為OS_SEM_COUNTING_MAX_COUNT,二值信號量的最大數量為OS_SEM_BINARY_MAX_COUNT。會進一步調用函數OsSemCreate()實現信號量的創建,下文繼續分析。

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

      我們看看創建信號量的函數OsSemCreate(),需要3個參數,創建的信號量的數量,最大數量,以及信號量編號。

      ⑴判斷g_unusedSemList是否為空,還有可以使用的信號量資源?如果沒有可以使用的信號量,調用函數OsSemInfoGetFullDataHook()做些調測相關的檢測,這個函數需要開啟調測開關,后續系列專門分析。

      ⑵處如果g_unusedSemList不為空,則獲取第一個可用的信號量節點,接著從雙向鏈表g_unusedSemList中刪除,然后調用宏GET_SEM_LIST獲取LosSemCB *semCreated

      ,初始化創建的信號量信息,包含信號量的狀態、信號量數量,信號量最大數量等信息。⑶初始化雙向鏈表&semCreated->semList,阻塞在這個信號量上的任務會掛在這個鏈表上。⑷賦值給輸出參數*semHandle,后續程序使用這個信號量編號對信號量進行其他操作。

      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 信號量刪除

      我們可以使用函數LOS_semDelete(UINT32 semHandle)來刪除信號量,下面通過分析源碼看看如何刪除信號量的。

      ⑴處判斷信號量semHandle是否超過LOSCFG_BASE_IPC_SEM_LIMIT,如果超過則返回錯誤碼。如果信號量編號沒有問題,獲取信號量控制塊LosSemCB *semDeleted。⑵處判斷要刪除的信號量的狀態,如果處于未使用狀態,則跳轉到錯誤標簽ERR_HANDLER:進行處理。⑶如果信號量的阻塞任務列表不為空,不允許刪除,跳轉到錯誤標簽進行處理。⑷處如果信號量可用刪除,則會把.semStat設置為未使用OS_SEM_UNUSED,并把信號量節點插入未使用信號量雙向鏈表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 信號量申請

      我們可以使用函數UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)來請求信號量,需要的2個參數分別是信號量semHandle和等待時間timeout,取值范圍為[0, LOS_WAIT_FOREVER],單位為Tick。下面通過分析源碼看看如何請求信號量的。

      申請信號量時首先會進行信號量編號、參數的合法性校驗。⑴處代碼表示信號量如果大于配置的最大值,則返回錯誤碼。⑵處獲取要申請的信號量控制塊semPended。⑶處調用函數對信號量控制塊進行校驗,如果信號量未創建,處于中斷處理期間,處于鎖任務調度期間,則返回錯誤碼。⑷處如果校驗不通過,跳轉到ERROR_SEM_PEND:標簽停止信號量的申請。

      ⑸如果信號量計數大于0,信號量計數減1,返回申請成功的結果。⑹如果信號量計數等于0,并且零等待時間timeout,則返回結果碼LOS_ERRNO_SEM_UNAVAILABLE。⑺如果申請的信號量被全部占用,需要等待時,把當前任務阻塞的信號量.taskSem標記為申請的信號量,然后調用函數OsSchedTaskWait(),該函數詳細代碼上文已分析,把當前任務狀態設置為阻塞狀態,加入信號量的阻塞鏈表.semList。如果不是永久等待LOS_WAIT_FOREVER,還需要更改任務狀態為OS_TASK_STATUS_PEND_TIME,并且設置waitTimes等待時間。⑻處觸發任務調度進行任務切換,暫時不執行后續代碼。

      如果等待時間超時,信號量還不可用,本任務獲取不到信號量時,繼續執行⑼,更改任務狀態,返回錯誤碼。如果信號量可用,執行⑽,本任務獲取到信號量,返回申請成功。

      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 信號量釋放

      我們可以使用函數UINT32 LOS_semPost(UINT32 semHandle)來釋放信號量,下面通過分析源碼看看如何釋放信號量的。

      釋放信號量時首先會進行信號量編號、參數的合法性校驗,這些比較簡單,自行閱讀即可。⑴處驗判斷是否信號量溢出。⑵如果信號量的任務阻塞鏈表不為空,執行⑶從阻塞鏈表中獲取第一個任務,設置.taskSem為NULL,不再阻塞信號量。執行⑷把獲取到信號量的任務調整其狀態,并加入就行隊列。⑸觸發任務調度進行任務切換。⑹如果信號量的任務阻塞鏈表為空,則把信號量的計數加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、信號量使用總結

      4.1 計數信號量、二值信號量和互斥鎖

      計數信號量和二值信號量唯一的區別就是信號量的初始數量不一致,二值信號量初始數量只能為0和1,計數信號量的初始值可以為0和大于1的整數。

      互斥鎖可以理解為一種特性的二值信號量,在實現實現對臨界資源的獨占式處理、互斥場景時,沒有本質的區別。比對下二值的結構體,互斥鎖的成員變量.muxCount表示加鎖的次數,信號量的成員變量.semCount表示信號量的計數,含義稍有不同。

      4.2 信號量的互斥和同步

      信號量可用用于互斥和同步兩種場景,以同步為目的的信號量和以互斥為目的的信號量在使用上,有如下不同:

      用于互斥的信號量

      初始信號量計數值不為0,表示可用的共享資源個數。在需要使用共享資源前,先獲取信號量,然后使用一個共享資源,使用完畢后釋放信號量。這樣在共享資源被取完,即信號量計數減至0時,其他需要獲取信號量的任務將被阻塞,從而保證了共享資源的互斥訪問。對信號量的申請和釋放,需要成對出現,在同一個任務里完成申請和釋放。

      用于同步的信號量

      多任務同時訪問同一份共享資源時,會導致沖突,這時候就需要引入任務同步機制使得各個任務按業務需求一個一個的對共享資源進行有序訪問操作。任務同步的實質就是任務按需進行排隊。

      用于同步的信號量,初始信號量計數值為0。任務1申請信號量而阻塞,直到任務2或者某中斷釋放信號量,任務1才得以進入Ready或Running態,從而達到了任務間的同步。信號量的能不能申請成功,依賴其他任務是否釋放信號量,申請和釋放在不同的任務里完成。

      小結

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

      IoT 輕量級操作系統 LiteOS

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:企業生產管理研究(企業生產經營管理調研)
      下一篇:Excel表格中看板圖的作法是什么
      相關文章
      国产亚洲人成网站观看| 日本中文一区二区三区亚洲| 亚洲色偷偷狠狠综合网| 亚洲色大成网站www永久网站| 久久精品国产亚洲αv忘忧草| 亚洲国产精品综合久久2007| 亚洲明星合成图综合区在线| 亚洲美女视频免费| 亚洲电影免费观看| 日产亚洲一区二区三区| 亚洲日韩区在线电影| 99人中文字幕亚洲区| 亚洲美女一区二区三区| 亚洲成A∨人片在线观看无码| 亚洲第一成年网站大全亚洲| 亚洲福利视频网址| 亚洲免费人成视频观看| 亚洲人成777在线播放| 2020久久精品亚洲热综合一本 | 亚洲午夜电影在线观看高清| 亚洲噜噜噜噜噜影院在线播放| 亚洲剧情在线观看| 亚洲国产成a人v在线观看| 亚洲久悠悠色悠在线播放| 亚洲日韩av无码中文| 亚洲av日韩综合一区久热| 色偷偷亚洲第一综合网| 亚洲成a人片在线播放| 久久久青草青青国产亚洲免观 | 亚洲国产成人在线视频| 亚洲一区无码中文字幕乱码| 亚洲小说图区综合在线| 丰满亚洲大尺度无码无码专线| 亚洲国产精品尤物YW在线观看| 国产美女亚洲精品久久久综合| 亚洲AV中文无码乱人伦下载| 亚洲美女人黄网成人女| 一本色道久久88—综合亚洲精品 | 亚洲精品美女久久7777777| www亚洲一级视频com| 亚洲夜夜欢A∨一区二区三区 |