鴻蒙內核源碼分析(事件控制篇) | 任務間多對多的同步方案
官方概述
先看官方對事件的描述.
事件(Event)是一種任務間通信的機制,可用于任務間的同步。
多任務環境下,任務之間往往需要同步操作,一個等待即是一個同步。事件可以提供一對多、多對多的同步操作。
● 一對多同步模型:一個任務等待多個事件的觸發??梢允侨我庖粋€事件發生時喚醒任務處理事件,也可以是幾個事件都發生后才喚醒任務處理事件。
● 多對多同步模型:多個任務等待多個事件的觸發。
鴻蒙提供的事件具有如下特點:
● 任務通過創建事件控制塊來觸發事件或等待事件。
● 事件間相互獨立,內部實現為一個32位無符號整型,每一位標識一種事件類型。第25位不可用,因此最多可支持31種事件類型。
● 事件僅用于任務間的同步,不提供數據傳輸功能。
● 多次向事件控制塊寫入同一事件類型,在被清零前等效于只寫入一次。
● 多個任務可以對同一事件進行讀寫操作。
● 支持事件讀寫超時機制。
再看事件圖
注意圖中提到了三個概念 事件控制塊 事件 任務 接下來結合代碼來理解事件模塊的實現.
事件控制塊長什么樣?
- typedef struct tagEvent {
- UINT32 uwEventID; /**< Event mask in the event control block,//標識發生的事件類型位,事件ID,每一位標識一種事件類型
- indicating the event that has been logically processed. */
- LOS_DL_LIST stEventList; /**< Event control block linked list *///讀取事件任務鏈表
- } EVENT_CB_S, *PEVENT_CB_S;
事件控制塊<>事件<>任務 三者關系
一定要搞明白這三者的關系,否則搞不懂事件模塊是如何運作的.
● 任務是事件的生產者,通過 LOS_EventWrite,向外部廣播發生了XX事件,并喚醒此前已在事件控制塊中登記過的要等待XX事件發生的XX任務.
● 事件控制塊EVENT_CB_S 是記錄者,只干兩件事件:
1.uwEventID按位記錄哪些事件發生了,它只是記錄,怎么消費它不管的.
2.stEventList記錄哪些任務在等待事件,但任務究竟在等待哪些事件它也是不記錄的
● 任務也是消費者,通過 LOS_EventRead消費,只有任務自己清楚要以什么樣的方式,消費什么樣的事件. 先回顧下任務結構體 LosTaskCB 對事件部分的描述如下:
- typedef struct {
- //...去掉不相關的部分
- VOID *taskEvent; //和任務發生關系的事件控制塊
- UINT32 eventMask; //對哪些事件進行屏蔽
- UINT32 eventMode; //事件三種模式(LOS_WAITMODE_AND,LOS_WAITMODE_OR,LOS_WAITMODE_CLR)
- } LosTaskCB;
taskEvent 指向的就是 EVENT_CB_S
eventMask 屏蔽掉 事件控制塊 中的哪些事件
eventMode 已什么樣的方式去消費事件,三種讀取模式
- #define LOS_WAITMODE_AND 4U
- #define LOS_WAITMODE_OR 2U
- #define LOS_WAITMODE_CLR 1U
◊ 所有事件(LOS_WAITMODE_AND):邏輯與,基于接口傳入的事件類型掩碼eventMask,只有這些事件都已經發生才能讀取成功,否則該任務將阻塞等待或者返回錯誤碼。
◊ 任一事件(LOS_WAITMODE_OR):邏輯或,基于接口傳入的事件類型掩碼eventMask,只要這些事件中有任一種事件發生就可以讀取成功,否則該任務將阻塞等待或者返回錯誤碼。
◊ 清除事件(LOS_WAITMODE_CLR):這是一種附加讀取模式,需要與所有事件模式或任一事件模式結合使用(LOS_WAITMODE_AND | LOS_WAITMODE_CLR或 LOS_WAITMODE_OR | LOS_WAITMODE_CLR)。在這種模式下,當設置的所有事件模式或任一事件模式讀取成功后,會自動清除事件控制塊中對應的事件類型位。
● 一個事件控制塊EVENT_CB_S中的事件可以來自多個任務,多個任務也可以同時消費事件控制塊中的事件,并且這些任務之間可以沒有任何關系!
函數列表
事件可應用于多種任務同步場景,在某些同步場景下可替代信號量。
其中讀懂 OsEventWrite 和 OsEventRead 就明白了事件模塊.
事件初始化 -> LOS_EventInit
- //初始化一個事件控制塊
- LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventInit(PEVENT_CB_S eventCB)
- {
- UINT32 intSave;
- intSave = LOS_IntLock();//鎖中斷
- eventCB->uwEventID = 0; //其中每一位表示一種事件類型(0表示該事件類型未發生、1表示該事件類型已經發生)
- LOS_ListInit(&eventCB->stEventList);//事件鏈表初始化
- LOS_IntRestore(intSave);//恢復中斷
- return LOS_OK;
- }
代碼解讀:
● 事件是共享資源,所以操作期間不能產生中斷.
● 初始化兩個記錄者 uwEventID stEventList
事件生產過程 -> OsEventWrite
- LITE_OS_SEC_TEXT VOID OsEventWriteUnsafe(PEVENT_CB_S eventCB, UINT32 events, BOOL once, UINT8 *exitFlag)
- {
- LosTaskCB *resumedTask = NULL;
- LosTaskCB *nextTask = NULL;
- BOOL schedFlag = FALSE;
- eventCB->uwEventID |= events;//對應位貼上標簽
- 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 (OsEventResume(resumedTask, eventCB, events)) {//是否恢復任務
- schedFlag = TRUE;//任務已加至就緒隊列,申請發生一次調度
- }
- if (once == TRUE) {//是否只處理一次任務
- break;//退出循環
- }
- resumedTask = nextTask;//檢查鏈表中下一個任務
- }
- }
- if ((exitFlag != NULL) && (schedFlag == TRUE)) {//是否讓外面調度
- *exitFlag = 1;
- }
- }
- //寫入事件
- LITE_OS_SEC_TEXT STATIC UINT32 OsEventWrite(PEVENT_CB_S eventCB, UINT32 events, BOOL once)
- {
- UINT32 intSave;
- UINT8 exitFlag = 0;
- SCHEDULER_LOCK(intSave); //禁止調度
- OsEventWriteUnsafe(eventCB, events, once, &exitFlag);//寫入事件
- SCHEDULER_UNLOCK(intSave); //允許調度
- if (exitFlag == 1) { //需要發生調度
- LOS_MpSchedule(OS_MP_CPU_ALL);//通知所有CPU調度
- LOS_Schedule();//執行調度
- }
- return LOS_OK;
- }
代碼解讀:
1.給對應位貼上事件標簽,eventCB->uwEventID |= events; 注意uwEventID是按位管理的.每個位代表一個事件是否寫入,例如 uwEventID = 00010010 代表產生了 1,4 事件
2.循環從stEventList鏈表中取出等待這個事件的任務判斷是否喚醒任務. OsEventResume
- //事件恢復,判斷是否喚醒任務
- LITE_OS_SEC_TEXT STATIC UINT8 OsEventResume(LosTaskCB *resumedTask, const PEVENT_CB_S eventCB, UINT32 events)
- {
- UINT8 exitFlag = 0;//是否喚醒
- if (((resumedTask->eventMode & LOS_WAITMODE_OR) && ((resumedTask->eventMask & events) != 0)) ||
- ((resumedTask->eventMode & LOS_WAITMODE_AND) &&
- ((resumedTask->eventMask & eventCB->uwEventID) == resumedTask->eventMask))) {//邏輯與 和 邏輯或 的處理
- exitFlag = 1;
- resumedTask->taskEvent = NULL;
- OsTaskWake(resumedTask);//喚醒任務,加入就緒隊列
- }
- return exitFlag;
- }
3.喚醒任務OsTaskWake只是將任務重新加入就緒隊列,需要立即申請一次調度 LOS_Schedule .
事件消費過程 -> OsEventRead
- LITE_OS_SEC_TEXT STATIC UINT32 OsEventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout,
- BOOL once)
- {
- UINT32 ret;
- UINT32 intSave;
- SCHEDULER_LOCK(intSave);
- ret = OsEventReadImp(eventCB, eventMask, mode, timeout, once);//讀事件實現函數
- SCHEDULER_UNLOCK(intSave);
- return ret;
- }
- //讀取指定事件類型的實現函數,超時時間為相對時間:單位為Tick
- LITE_OS_SEC_TEXT STATIC UINT32 OsEventReadImp(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode,
- UINT32 timeout, BOOL once)
- {
- UINT32 ret = 0;
- LosTaskCB *runTask = OsCurrTaskGet();
- runTask->eventMask = eventMask;
- runTask->eventMode = mode;
- runTask->taskEvent = eventCB;//事件控制塊
- ret = OsTaskWait(&eventCB->stEventList, timeout, TRUE);//任務進入等待狀態,掛入阻塞鏈表
- if (ret == LOS_ERRNO_TSK_TIMEOUT) {//如果返回超時
- runTask->taskEvent = NULL;
- return LOS_ERRNO_EVENT_READ_TIMEOUT;
- }
- ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode);//檢測事件是否符合預期
- return ret;
- }
代碼解讀:
● 事件控制塊是給任務使用的, 任務給出讀取一個事件的條件eventMask 告訴系統屏蔽掉這些事件,對屏蔽的事件不感冒.
1. eventMask 告訴系統屏蔽掉這些事件,對屏蔽的事件不感冒.
2. eventMode 已什么樣的方式去消費事件,是必須都滿足給的條件,還是只滿足一個就響應.
3. 條件給完后,自己進入等待狀態 OsTaskWait,等待多久 timeout決定,任務自己說了算.
4. OsEventPoll檢測事件是否符合預期,啥意思?看下它的代碼就知道了.
- //根據用戶傳入的事件值、事件掩碼及校驗模式,返回用戶傳入的事件是否符合預期
- LITE_OS_SEC_TEXT UINT32 OsEventPoll(UINT32 *eventID, UINT32 eventMask, UINT32 mode)
- {
- UINT32 ret = 0;//事件是否發生了
- LOS_ASSERT(OsIntLocked());//斷言不允許中斷了
- LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//任務自旋鎖
- 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;
- }
- return ret;
- }
編程實例
本實例實現如下流程。
示例中,任務Example_TaskEntry創建一個任務Example_Event,Example_Event讀事件阻塞,Example_TaskEntry向該任務寫事件。可以通過示例日志中打印的先后順序理解事件操作時伴隨的任務切換。
● 在任務Example_TaskEntry創建任務Example_Event,其中任務Example_Event優先級高于Example_TaskEntry。
● 在任務Example_Event中讀事件0x00000001,阻塞,發生任務切換,執行任務Example_TaskEntry。
● 在任務Example_TaskEntry向任務Example_Event寫事件0x00000001,發生任務切換,執行任務Example_Event。
● Example_Event得以執行,直到任務結束。
● Example_TaskEntry得以執行,直到任務結束。
- #include "los_event.h"
- #include "los_task.h"
- #include "securec.h"
- /* 任務ID */
- UINT32 g_testTaskId;
- /* 事件控制結構體 */
- EVENT_CB_S g_exampleEvent;
- /* 等待的事件類型 */
- #define EVENT_WAIT 0x00000001
- /* 用例任務入口函數 */
- VOID Example_Event(VOID)
- {
- UINT32 ret;
- UINT32 event;
- /* 超時等待方式讀事件,超時時間為100 ticks, 若100 ticks后未讀取到指定事件,讀事件超時,任務直接喚醒 */
- printf("Example_Event wait event 0x%x \n", EVENT_WAIT);
- event = LOS_EventRead(&g_exampleEvent, EVENT_WAIT, LOS_WAITMODE_AND, 100);
- if (event == EVENT_WAIT) {
- printf("Example_Event,read event :0x%x\n", event);
- } else {
- printf("Example_Event,read event timeout\n");
- }
- }
- UINT32 Example_TaskEntry(VOID)
- {
- UINT32 ret;
- TSK_INIT_PARAM_S task1;
- /* 事件初始化 */
- ret = LOS_EventInit(&g_exampleEvent);
- if (ret != LOS_OK) {
- printf("init event failed .\n");
- return -1;
- }
- /* 創建任務 */
- (VOID)memset_s(&task1, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
- task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Event;
- task1.pcName = "EventTsk1";
- task1.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE;
- task1.usTaskPrio = 5;
- ret = LOS_TaskCreate(&g_testTaskId, &task1);
- if (ret != LOS_OK) {
- printf("task create failed .\n");
- return LOS_NOK;
- }
- /* 寫g_testTaskId 等待事件 */
- printf("Example_TaskEntry write event .\n");
- ret = LOS_EventWrite(&g_exampleEvent, EVENT_WAIT);
- if (ret != LOS_OK) {
- printf("event write failed .\n");
- return LOS_NOK;
- }
- /* 清標志位 */
- printf("EventMask:%d\n", g_exampleEvent.uwEventID);
- LOS_EventClear(&g_exampleEvent, ~g_exampleEvent.uwEventID);
- printf("EventMask:%d\n", g_exampleEvent.uwEventID);
- /* 刪除任務 */
- ret = LOS_TaskDelete(g_testTaskId);
- if (ret != LOS_OK) {
- printf("task delete failed .\n");
- return LOS_NOK;
- }
- return LOS_OK;
- }
運行結果
- Example_Event wait event 0x1
- Example_TaskEntry write event .
- Example_Event,read event :0x1
- EventMask:1
- EventMask:0
參與貢獻
● 訪問注解倉庫地址
● Fork 本倉庫 >> 新建 Feat_xxx 分支 >> 提交代碼注解 >> 新建 Pull Request
● 新建 Issue