OpenHarmony-內(nèi)核對象事件之源碼詳解
??51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)??
前言
OpenHarmony的事件提供一種任務(wù)間的同步機(jī)制,簡單來說就是一個或多個任務(wù)可以通過寫一個或多個不同的事件來觸發(fā)內(nèi)核調(diào)度讓另一個等待讀取事件的任務(wù)進(jìn)入運行狀態(tài),從而實現(xiàn)任務(wù)間的同步。具體是怎么實現(xiàn)的呢?今天我就帶大伙深入到內(nèi)核當(dāng)中,扒一扒事件的源代碼。
關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
在解讀事件的源碼之前還是要先了解下事件的關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)PEVENT_CB_S,數(shù)據(jù)結(jié)構(gòu)永遠(yuǎn)是內(nèi)核學(xué)習(xí)繞不開的坎:
typedef struct tagEvent {
UINT32 uwEventID;
LOS_DL_LIST stEventList; /**< Event control block linked list */
} EVENT_CB_S, *PEVENT_CB_S;
- uwEventID:標(biāo)記任務(wù)的事件類型,每個bit可以標(biāo)識一個事件最多支持31個事件(第25bit保留)。
- stEventList:事件控制塊的雙向循環(huán)鏈表,理解這個字段是理解事件的關(guān)鍵。在雙向循環(huán)鏈表中唯一不變的節(jié)點就是頭節(jié)點,而這里的stEventList就是頭節(jié)點。當(dāng)有任務(wù)等待事件但事件還沒發(fā)生時任務(wù)會被掛載到等待鏈表中,當(dāng)事件發(fā)生時系統(tǒng)喚醒等待事件的任務(wù),此時任務(wù)就會被剔出鏈表。
事件初始化
下面是事件初始化源碼:
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, eventCB);
return LOS_OK;
}
PEVENT_CB_S 相當(dāng)于 EVENT_CB_S *, 因此eventCB是指針,是指針,是指針,重要的話說三遍哈。
側(cè)面也說明事件控制塊由任務(wù)自己創(chuàng)建,內(nèi)核事件模塊只負(fù)責(zé)維護(hù)。到這里大伙就知道怎么創(chuàng)建事件控制塊了吧,任務(wù)定義自己的事件控制塊變量,然后通過LOS_EventInit來初始化,此時沒有事件發(fā)生,當(dāng)然事件鏈表空空如也。
用圖來表達(dá)就是:
事件寫操作
任務(wù)可以通過LOS_EventWrite來寫觸發(fā)一個或多個事件:
LITE_OS_SEC_TEXT UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)
{
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處,保存事件使用的或運算操作,因此一個或多個任務(wù)可以寫一個或多個事件一次或多次,當(dāng)然是不同的事件,多次寫同一個事件相當(dāng)于只寫了一次。
2處,有事件發(fā)生了就該檢查是否有任務(wù)在等待事件,事件鏈表不為空說明有任務(wù)在等待事件。
3處,遍歷事件鏈表,喚醒符合條件的任務(wù)。LOS_DL_LIST_ENTRY((&eventCB->stEventList)->pstNext, LosTaskCB, pendList) 前面說過頭節(jié)點是空節(jié)點,第一次遍歷從頭節(jié)點的下一個節(jié)點開始,后續(xù)順藤摸瓜 依次找出nextTask,直到回到頭節(jié)點。
4處,針對事件讀取模式,找到滿足條件的任務(wù)并喚醒該任務(wù)。
5處,一旦匹配到等待事件的任務(wù),則執(zhí)行任務(wù)調(diào)度,被喚醒的任務(wù)得到執(zhí)行。
寫事件實際操作如下圖:
事件讀操作
LiteOS為用戶提供了兩個事件讀函數(shù):
- LOS_EventPoll():根據(jù)任務(wù)傳入的事件值、掩碼及校驗?zāi)J?,返回滿足條件的事件,任務(wù)可以主動檢查事件是否發(fā)生而不必被掛起。
- LOS_EventRead():讀取事件,可以理解為阻塞式讀,如果事件沒有發(fā)生,可以指定等待時間,掛起當(dāng)前任務(wù);
下面是LOS_EventPoll()的實現(xiàn):
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) { ---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,只要有一個事件發(fā)生則讀取成功,返回發(fā)生的那個事件。
2處,如果讀取模式LOS_WAITMODE_AND,全部檢查事件發(fā)生才算讀取成功,并返回全部發(fā)生事件。
3處,事件讀取成功后事件控制塊中的事件標(biāo)記怎么處理?這里通過LOS_WAITMODE_CLR來決定是否清除事件標(biāo)記。
可以看出以上實現(xiàn)了兩種事件讀取方式:多個事件只要一個發(fā)生就算發(fā)生和全部事件發(fā)生才算發(fā)生。
下面是LOS_EventRead():
LITE_OS_SEC_TEXT UINT32 LOS_EventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeOut)
{
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處,主動查詢想要的事件是否已經(jīng)發(fā)生。
2處,如果事件沒有發(fā)生,就把當(dāng)前任務(wù)掛起到等待事件鏈表中。
3處,如果事件沒有發(fā)生,當(dāng)前讀事件的任務(wù)被掛起,讓出CPU。
4處,事件發(fā)生時等待事件的任務(wù)被調(diào)度再次獲得CPU恢復(fù)執(zhí)行,讀取事件。
事件讀寫整個過程串起來如下圖所示:
事件銷毀操作
做事有始有終,事件消費完成剩下的事情當(dāng)然是清除事件和等待事件的任務(wù)鏈表。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_EventClear(PEVENT_CB_S eventCB, UINT32 eventMask)
{
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中清空事件鏈表指針。
小結(jié)
事件模塊本身并不復(fù)雜,相信看了上面的描述大伙對事件的運作機(jī)制已經(jīng)有了更深刻的理解,下面我們來個總結(jié):
- 事件控制塊由任務(wù)創(chuàng)建,事件模塊本身只維護(hù)事件控制塊的內(nèi)容。
- 寫事件會觸發(fā)讀事件任務(wù)被喚醒,任務(wù)調(diào)度就這么發(fā)生了。
- 任務(wù)可以主動查詢事件,也可以被動等待事件發(fā)生時來喚醒自己。
- 事件結(jié)束后根據(jù)應(yīng)用場景可以有選擇的清除事件ID或(和)事件鏈表。
??51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)??