近年來,國內開源實現跨越式發展,并成為企業提升創新能力、生產力、協作和透明度的關鍵。作為 OpenAtom OpenHarmony(以下簡稱“OpenHarmony”)開源項目共建單位之一,深開鴻以成為智能物聯網操作系統領軍者為戰略目標,基于 OpenHarmony 聚焦智能物聯網操作系統(KaihongOS)的技術研發與持續創新。
身為深開鴻 OS 內核開發師,我們常年深耕于 OpenHarmony 的內核開發,希望通過分享一些工作上的經驗,幫助大家掌握開源知識。
OpenHarmony LiteOS-M 內核是面向 IoT 領域構建的輕量級物聯網操作系統內核,具有小體積、低功耗、高性能的特點,其代碼結構簡單,實現了進程、線程、內存等管理機制,提供了常見任務間 IPC、軟定時器等公共模塊,大幅度降低了嵌入式設備開發的難度。目前 OpenHarmony 的事件提供一種任務間的 IPC,即一個或多個任務可以通過寫一個或多個不同的事件來觸發內核調度,讓另一個等待讀取事件的任務進入運行狀態,從而實現任務間的同步。
對于嵌入式開發工作人員和技術愛好者來說,深入了解常見任務間 IPC,有助于學習和研發內核。本文將從數據結構和算法解析 OpenHarmony 的事件機制,帶大家深入了解內核任務間 IPC 原理。
關鍵數據結構
在解讀事件的源碼之前,首先了解下事件的關鍵的數據結構 PEVENT_CB_S:
typedefstructtagEvent{ UINT32 uwEventID; LOS_DL_LIST stEventList; /**< Event control block linked list */ } EVENT_CB_S, *PEVENT_CB_S;
uwEventID:即標記任務的事件類型,每個bit可以標識一個事件,最多支持 31 個事件(第 25bit 保留)。
stEventList:即事件控制塊的雙向循環鏈表,理解這個字段是理解事件的關鍵。在雙向循環鏈表中唯一不變的節點就是頭節點,而這里的 stEventList 就是頭節點。當有任務等待事件但事件還沒發生時,任務會被掛載到等待鏈表中;當事件發生時,系統喚醒等待事件的任務,此時任務就會被剔出鏈表。
事件初始化
下面是事件初始化源碼:
LITE_OS_SEC_TEXT_INITUINT32LOS_EventInit(PEVENT_CB_SeventCB){ if (eventCB == NULL) { return LOS_ERRNO_EVENT_PTR_NULL; } eventCB->uwEventID = 0; LOS_ListInit(&eventCB->stEventList); OsHookCall(LOS_HOOK_TYPE_EVENT_INIT, eventCB); return LOS_OK;}
PEVENT_CB_S 相當于 EVENT_CB_S *, 因此 eventCB 是指針。
說明事件控制塊由任務自己創建,內核事件模塊只負責維護。任務定義自己的事件控制塊變量,通過 LOS_EventInit 初始化,此時沒有事件發生,事件鏈表為空。
用圖來表達就是:
事件寫操作
任務可以通過 LOS_EventWrite 來寫觸發一個或多個事件:
LITE_OS_SEC_TEXTUINT32LOS_EventWrite(PEVENT_CB_SeventCB,UINT32events){ ... eventCB->uwEventID |= events; ---1 if (!LOS_ListEmpty(&eventCB->stEventList)) { ---2 for (resumedTask = LOS_DL_LIST_ENTRY((&eventCB->stEventList)->pstNext, LosTaskCB, pendList); &resumedTask->pendList != (&eventCB->stEventList);) { -------3 nextTask = LOS_DL_LIST_ENTRY(resumedTask->pendList.pstNext, LosTaskCB, pendList); if (((resumedTask->eventMode & LOS_WAITMODE_OR) && (resumedTask->eventMask & events) != 0) || ((resumedTask->eventMode & LOS_WAITMODE_AND) && ((resumedTask->eventMask & eventCB->uwEventID) == resumedTask->eventMask))) { exitFlag = 1; OsSchedTaskWake(resumedTask); ---4 } resumedTask = nextTask; } if (exitFlag == 1) { LOS_IntRestore(intSave); LOS_Schedule(); ---5 return LOS_OK; } } ...}
1處,保存事件使用的或運算操作,因此一個或多個任務可以寫一個或多個事件,寫一次或多次,而且每次為不同的事件,多次寫同一個事件相當于只寫了一次;
2處,有事件發生了就該檢查是否有任務在等待事件,事件鏈表不為空說明有任務在等待事件;
3處,遍歷事件鏈表,喚醒符合條件的任務。LOS_DL_LIST_ENTRY((&eventCB->stEventList)->pstNext,LosTaskCB,pendList) 前面提到,頭節點是空節點,第一次遍歷從頭節點的下一個節點開始,后續再依次找出 nextTask,直到回到頭節點;
4處,針對事件讀取模式,找到滿足條件的任務并喚醒該任務;
5處,一旦匹配到等待事件的任務,則執行任務調度,被喚醒的任務得到執行。
寫事件實際操作如下圖:
事件讀操作
LiteOS 為用戶提供了兩個事件的函數:
● LOS_EventPoll():根據任務傳入的事件值、掩碼及校驗模式,返回滿足條件的事件,任務可以主動檢查事件是否發生而不必被掛起;
● LOS_EventRead():讀取事件,可以理解為阻塞式讀,如果事件沒有發生,可以指定等待時間,掛起當前任務。
下面是 LOS_EventPoll() 的實現:
LITE_OS_SEC_TEXTUINT32LOS_EventPoll(UINT32*eventID,UINT32eventMask,UINT32mode){ UINT32 ret = 0; UINT32 intSave; if (eventID == NULL) { return LOS_ERRNO_EVENT_PTR_NULL; } intSave = LOS_IntLock(); if (mode & LOS_WAITMODE_OR) { if ((*eventID & eventMask) != 0) { ---1 ret = *eventID & eventMask; } } else { if ((eventMask != 0) && (eventMask == (*eventID & eventMask))) { ---2 ret = *eventID & eventMask; } } if (ret && (mode & LOS_WAITMODE_CLR)) { ---3 *eventID = *eventID & ~(ret); } LOS_IntRestore(intSave); return ret;}
1處,如果讀取模式是LOS_WAITMODE_OR,只要有一個事件發生則讀取成功,返回發生的那個事件;
2處,如果讀取模式LOS_WAITMODE_AND,全部檢查事件發生才算讀取成功,并返回全部發生事件;
3處,事件讀取成功后事件控制塊中的事件標記怎么處理?這里通過LOS_WAITMODE_CLR來決定是否清除事件標記。
可以看出以上實現了兩種事件的讀取方式:一種是多個事件只要一個發生就算發生,另一種是全部事件發生才算發生。
下面是 LOS_EventRead():
LITE_OS_SEC_TEXTUINT32LOS_EventRead(PEVENT_CB_SeventCB,UINT32eventMask,UINT32mode,UINT32timeOut){ ... ret = LOS_EventPoll(&(eventCB->uwEventID), eventMask, mode); ---1 OsHookCall(LOS_HOOK_TYPE_EVENT_READ, eventCB, eventMask, mode, timeOut); if (ret == 0) { if (timeOut == 0) { LOS_IntRestore(intSave); return ret; } if (g_losTaskLock) { LOS_IntRestore(intSave); return LOS_ERRNO_EVENT_READ_IN_LOCK; } runTsk = g_losTask.runTask; runTsk->eventMask = eventMask; runTsk->eventMode = mode; OsSchedTaskWait(&eventCB->stEventList, timeOut); ---2 LOS_IntRestore(intSave); LOS_Schedule(); ---3 intSave = LOS_IntLock(); if (runTsk->taskStatus & OS_TASK_STATUS_TIMEOUT) { runTsk->taskStatus &= ~OS_TASK_STATUS_TIMEOUT; LOS_IntRestore(intSave); return LOS_ERRNO_EVENT_READ_TIMEOUT; } ret = LOS_EventPoll(&eventCB->uwEventID, eventMask, mode); ---4 } ...}
1處,主動查詢想要的事件是否已經發生;
2處,如果事件沒有發生,就把當前任務掛起到等待事件鏈表中;
3處,如果事件沒有發生,當前讀事件的任務被掛起,讓出 CPU;
4處,事件發生時等待事件的任務被調度再次獲得 CPU 恢復執行,讀取事件。
事件讀寫整個過程串起來如下圖所示:
事件銷毀操作
做事有始有終,事件消費完成剩下的事情是清除事件和等待事件的任務鏈表。
LITE_OS_SEC_TEXT_MINORUINT32LOS_EventClear(PEVENT_CB_SeventCB,UINT32eventMask){ ... eventCB->uwEventID &= eventMask; ...} LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventDestroy(PEVENT_CB_S eventCB){ ... eventCB->stEventList.pstNext = (LOS_DL_LIST *)NULL; eventCB->stEventList.pstPrev = (LOS_DL_LIST *)NULL; ...}
在LOS_EventClear 中通過使 eventMask=0 來清空事件,在 LOS_EventDestroy 中清空事件鏈表指針。
小結
看了上面的描述,相信大家對 OpenHarmony LiteOS-M 內核事件的運作機制有了更加深刻的理解,開發者可以更好地使用事件的 API 來進行任務間的同步操作,也可以進一步嘗試修改內核事件通知機制,做出一個更適合自己任務的IPC機制。
OpenHarmony 生態建設離不開每位開發者的參與,希望有更多的開發者分享自己開源項目的經驗和成果,共同為 OpenHarmony 生態建設貢獻一份力量。
原文標題:OpenHarmony——內核對象事件之源碼詳解
文章出處:【微信公眾號:深開鴻】歡迎添加關注!文章轉載請注明出處。
-
cpu
+關注
關注
68文章
10859瀏覽量
211699 -
操作系統
+關注
關注
37文章
6818瀏覽量
123320 -
IPC
+關注
關注
3文章
347瀏覽量
51910 -
數據結構
+關注
關注
3文章
573瀏覽量
40127 -
OpenHarmony
+關注
關注
25文章
3718瀏覽量
16297
原文標題:OpenHarmony——內核對象事件之源碼詳解
文章出處:【微信號:gh_15d2f062a168,微信公眾號:深開鴻】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論