摘要:本文通過分析鴻蒙輕內核事件模塊的源碼,深入掌握事件的使用。
本文分享自華為雲社區《鴻蒙輕內核M核源碼分析系列十二 事件Event》,原文作者:zhushy 。
事件(Event)是一種任務間通信的機制,可用於任務間的同步。多任務環境下,任務之間往往需要同步操作,一個等待即是一個同步。事件可以提供一對多、多對多的同步操作。本文通過分析鴻蒙輕內核事件模塊的源碼,深入掌握事件的使用。本文中所涉及的源碼,以OpenHarmony LiteOS-M內核為例,均可以在開源站點https://gitee.com/openharmony... 獲取。
接下來,我們看下事件的結構體,事件初始化,事件常用操作的源代碼。
1、事件結構體定義和常用宏定義
1.1 事件結構體定義
在文件kernel\include\los_event.h定義的事件控制塊結構體為EVENT_CB_S,結構體源代碼如下,結構體成員的解釋見註釋部分。
typedef struct tagEvent {
UINT32 uwEventID; /**< 事件ID,每一位標識一種事件類型 */
LOS_DL_LIST stEventList; /**< 讀取事件的任務鏈表 */
} EVENT_CB_S, *PEVENT_CB_S;
1.2 事件常用宏定義
在讀事件時,可以選擇讀取模式。讀取模式由如下幾個宏定義:
- 所有事件(LOS_WAITMODE_AND):
邏輯與,基於接口傳入的事件類型掩碼eventMask,只有這些事件都已經發生才能讀取成功,否則該任務將阻塞等待或者返回錯誤碼。
- 任一事件(LOS_WAITMODE_OR):
邏輯或,基於接口傳入的事件類型掩碼eventMask,只要這些事件中有任一種事件發生就可以讀取成功,否則該任務將阻塞等待或者返回錯誤碼。
- 清除事件(LOS_WAITMODE_CLR):
這是一種附加讀取模式,需要與所有事件模式或任一事件模式結合使用(LOS_WAITMODE_AND | LOS_WAITMODE_CLR或 LOS_WAITMODE_OR | LOS_WAITMODE_CLR)。在這種模式下,當設置的所有事件模式或任一事件模式讀取成功後,會自動清除事件控制塊中對應的事件類型位。
#define LOS_WAITMODE_AND (4)
#define LOS_WAITMODE_OR (2)
#define LOS_WAITMODE_CLR (1)
3、事件常用操作
3.1 初始化事件
在使用事件前,必須使用函數UINT32 LOS_EventInit(PEVENT_CB_S eventCB)來初始化事件,需要的參數是結構體指針變量PEVENT_CB_S eventCB。分析下代碼,⑴處表示傳入的參數不能為空,否則返回錯誤碼。⑵處把事件編碼.uwEventID初始化為0,然後初始化雙向循環鏈表.stEventList,用於掛載讀取事件的任務。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventInit(PEVENT_CB_S eventCB)
{
⑴ if (eventCB == NULL) {
return LOS_ERRNO_EVENT_PTR_NULL;
}
⑵ eventCB->uwEventID = 0;
LOS_ListInit(&eventCB->stEventList);
OsHookCall(LOS_HOOK_TYPE_EVENT_INIT);
return LOS_OK;
}
3.2 校驗事件掩碼
我們可以使用函數UINT32 LOS_EventPoll(UINT32 *eventId, UINT32 eventMask, UINT32 mode)來校驗事件掩碼,需要的參數為事件結構體的事件編碼eventId、用户傳入的待校驗的事件掩碼eventMask及讀取模式mode,返回用户傳入的事件是否發生: 返回值為0時,表示用户預期的事件沒有發生,否則表示用户期望的事件發生。
我們看下源碼,⑴處先檢查傳入參數的合法性,事件編碼不能為空。然後執行⑵處的代碼進行校驗。如果是任一事件讀取模式,接下來的判斷不等於表示至少有一個事件發生了,返回值ret就表示哪些事件發生了。⑶如果是所有事情讀取模式,當邏輯與運算*eventId & eventMask還等於eventMask時,表示期望的事件全部發生了,返回值ret就表示哪些事件發生了。⑷處當ret不為0,期望的事件發生,並且是清除事件讀取模式時,需要把已經發生的事情進行清除。看來,這個函數不僅僅是查詢事件有沒有發生,還會有更新事件編碼的動作。
LITE_OS_SEC_TEXT UINT32 LOS_EventPoll(UINT32 *eventID, UINT32 eventMask, UINT32 mode)
{
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) {
ret = *eventID & eventMask;
}
} else {
⑶ if ((eventMask != 0) && (eventMask == (*eventID & eventMask))) {
ret = *eventID & eventMask;
}
}
⑷ if (ret && (mode & LOS_WAITMODE_CLR)) {
*eventID = *eventID & ~(ret);
}
LOS_IntRestore(intSave);
return ret;
}
3.3 讀/寫事件
3.3.1 讀取指定事件類型
我們可以使用函數LOS_EventRead()來讀取事件,需要4個參數。eventCB是初始化好的事件結構體,eventMask表示需要讀取的事件掩碼,mode是上文説明過的讀取模式,timeout是讀取超時,單位是Tick。函數返回0時,表示期望的事件沒有發生,讀取事件失敗,進入阻塞。返回非0時表示期望的事件發生了,成功讀取事件。下面我們分析下函數的源碼來看看如何讀取事件的。
⑴處調用函數OsEventReadParamCheck()進行基礎的校驗,比如第25位保留不能使用,事件掩碼eventMask不能為零,讀取模式組合是否合法。⑵處表示不能中斷中讀取事件。⑶處調用校驗函數OsEventPoll()檢查事件eventMask是否發生。如果事件發生ret不為0,成功讀取直接返回。ret為0,事件沒有發生時,執行⑷,如果超時時間timeout為0,調用者不能等待時,直接返回。⑸如果鎖任務調度時,不能讀取事件,返回錯誤碼。
⑹更新當前任務的阻塞的事件掩碼.eventMask和事件讀取模式.eventMode。執行⑺調用函數OsSchedTaskWait更改當前任務的狀態為阻塞狀態,掛載到事件的任務阻塞鏈表上。如果timeout不是永久等待,還會把任務設置為OS_TASK_STATUS_PEND_TIME狀態並設置等待時間。⑻處觸發任務調度,後續程序需要等到讀取到事件才會繼續執行。
⑼如果等待時間超時,事件還不可讀,本任務讀取不到指定的事件時,返回錯誤碼。如果可以讀取到指定的事件時,執行⑽,檢查事件eventMask是否發生,然後返回結果值。
LITE_OS_SEC_TEXT UINT32 LOS_EventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeOut)
{
UINT32 ret;
UINT32 intSave;
LosTaskCB *runTsk = NULL;
⑴ ret = OsEventReadParamCheck(eventCB, eventMask, mode);
if (ret != LOS_OK) {
return ret;
}
⑵ if (OS_INT_ACTIVE) {
return LOS_ERRNO_EVENT_READ_IN_INTERRUPT;
}
intSave = LOS_IntLock();
⑶ ret = LOS_EventPoll(&(eventCB->uwEventID), eventMask, mode);
OsHookCall(LOS_HOOK_TYPE_EVENT_READ, eventCB, eventMask, mode);
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);
LOS_IntRestore(intSave);
⑻ LOS_Schedule();
⑼ 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);
}
LOS_IntRestore(intSave);
return ret;
}
3.3.2 寫入指定的事件類型
我們可以使用函數UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)來寫入指定的事件類型。代碼如下所示:
下面通過分析源碼來看看如何寫入事件類型的。⑴處代碼把事件結構體的事件掩碼和要寫入的事件類型events進行邏輯或計算,來完成事件的寫入。⑵如果等待事件的任務鏈表不為空,需要處理寫入事件後是否有任務能讀取到相應的事件。⑶處for循環依次遍歷事件阻塞鏈表上的任務,⑷獲取下一個任務nextTask。⑸處
分不同的讀取模式判斷事件是否符合任務resumedTask讀取事件的要求,如果滿足讀取事件,執行⑹設置退出標記exitFlag,然後調用函數OsSchedTaskWake()把讀取事件的任務更改狀態並放入就緒隊列,繼續執行⑺,遍歷事件的阻塞任務鏈表中的每一個任務。⑻如果有任務讀取到事件,需要觸發任務調度。
LITE_OS_SEC_TEXT UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)
{
LosTaskCB *resumedTask = NULL;
LosTaskCB *nextTask = (LosTaskCB *)NULL;
UINT32 intSave;
UINT8 exitFlag = 0;
if (eventCB == NULL) {
return LOS_ERRNO_EVENT_PTR_NULL;
}
if ((eventCB->stEventList.pstNext == NULL) || (eventCB->stEventList.pstPrev == NULL)) {
return LOS_ERRNO_EVENT_NOT_INITIALIZED;
}
if (events & LOS_ERRTYPE_ERROR) {
return LOS_ERRNO_EVENT_SETBIT_INVALID;
}
intSave = LOS_IntLock();
⑴ eventCB->uwEventID |= events;
OsHookCall(LOS_HOOK_TYPE_EVENT_WRITE, eventCB);
⑵ if (!LOS_ListEmpty(&eventCB->stEventList)) {
⑶ for (resumedTask = LOS_DL_LIST_ENTRY((&eventCB->stEventList)->pstNext, LosTaskCB, pendList);
&resumedTask->pendList != (&eventCB->stEventList);) {
⑷ 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);
}
⑺ resumedTask = nextTask;
}
if (exitFlag == 1) {
LOS_IntRestore(intSave);
⑻ LOS_Schedule();
return LOS_OK;
}
}
LOS_IntRestore(intSave);
return LOS_OK;
}
3.4 清除事件
我們可以使用函數UINT32 LOS_EventClear(PEVENT_CB_S eventCB, UINT32 eventMask)來清除指定的事件類型,下面通過分析源碼看看如何清除事件類型的。
函數參數為事件結構體eventCB和要清除的事件類型eventMask。清除事件時首先會進行結構體參數是否為空的校驗,這些比較簡單。⑴處把事件結構體的事件掩碼和要清除的事件類型eventMask進行邏輯與計算,來完成事件的清理。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_EventClear(PEVENT_CB_S eventCB, UINT32 eventMask)
{
UINT32 intSave;
if (eventCB == NULL) {
return LOS_ERRNO_EVENT_PTR_NULL;
}
intSave = LOS_IntLock();
⑴ eventCB->uwEventID &= eventMask;
LOS_IntRestore(intSave);
OsHookCall(LOS_HOOK_TYPE_EVENT_CLEAR, eventCB);
return LOS_OK;
}
3.5 銷燬事件
我們可以使用函數UINT32 LOS_EventDestroy(PEVENT_CB_S eventCB)來銷燬指定的事件控制塊,下面通過分析源碼看看如何銷燬事件的。
函數參數為事件結構體,銷燬事件時首先會進行結構體參數是否為空的校驗,這些比較簡單。⑴處如果事件的任務阻塞鏈表不為空,則不能銷燬事件。⑵把事件結構體的讀取事件的任務鏈表stEventList設置為空,完成事件的銷燬。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventDestroy(PEVENT_CB_S eventCB)
{
UINT32 intSave;
if (eventCB == NULL) {
return LOS_ERRNO_EVENT_PTR_NULL;
}
intSave = LOS_IntLock();
⑴ if (!LOS_ListEmpty(&eventCB->stEventList)) {
LOS_IntRestore(intSave);
return LOS_ERRNO_EVENT_SHOULD_NOT_DESTORY;
}
⑵ eventCB->stEventList.pstNext = (LOS_DL_LIST *)NULL;
eventCB->stEventList.pstPrev = (LOS_DL_LIST *)NULL;
LOS_IntRestore(intSave);
OsHookCall(LOS_HOOK_TYPE_EVENT_DESTROY);
return LOS_OK;
}
小結
本文帶領大家一起剖析了鴻蒙輕內核的事件模塊的源代碼,包含事件的結構體、事件初始化、事件創建刪除、申請釋放等。感謝閲讀,如有任何問題、建議,都可以留言給我們: https://gitee.com/openharmony... 。為了更容易找到鴻蒙輕內核代碼倉,建議訪問 https://gitee.com/openharmony... ,關注Watch、點贊Star、並Fork到自己賬户下,謝謝。
點擊關注,第一時間瞭解華為雲新鮮技術~