文章目录
- Event模块涉及2个头文件,分别是kernel\include\los_event.h、kernel\base\include\los_event_pri.h,前者定义了对外的解结构体、宏定义和接口;后者声明Event模块内部使用的函数接口。
- 在文件kernel\include\los_event.h定义的事件控制块结构体为EVENT_CB_S,结构体源代码如下,结构体成员的解释见注释部分。 typedef struct tagEvent { UINT32 uwEventID; /**< 事件ID,每一位标识一种事件类型 */ LOS_DL_LIST stEventList; /**< 读取事件的任务链表 */} EVENT_CB_S, *PEVENT_CB_S;
- 在读事件时,可以选择读取模式。读取模式由如下几个宏定义: 所有事件(LOS_WAITMODE_AND): 逻辑与,基于接口传入的事件类型掩码eventMask,只有这些事件都已经发生才能读取成功,否则该任务将阻塞等待或者返回错误码。 任一事件(LOS_WAITMODE_OR): 逻辑或,基于接口传入的事件类型掩码eventMask,只要这些事件中有任一种事件发生就可以读取成功,否则该任务将阻塞等待或者返回错误码。 清除事件(LOS_WAITMODE_CLR): 这是一种附加读取模式,需要与所有事件模式或任一事件模式结合使用(LOS_WAITMODE_AND | LOS_WAITMODE_CLR或 LOS_WAITMODE_OR | LOS_WAITMODE_CLR)。在这种模式下,当设置的所有事件模式或任一事件模式读取成功后,会自动清除事件控制块中对应的事件类型位。 在kernel\include\los_event.h可以看到这些宏定义的数值。 #define LOS_WAITMODE_AND 4U #define LOS_WAITMODE_OR 2U #define LOS_WAITMODE_CLR 1U
- 在源代码文件kernel\base\ipc\los_event.c中实现事件的对外函数。
- 在使用事件前,必须使用函数UINT32 LOS_EventInit(PEVENT_CB_S eventCB)来初始化事件,需要的参数是结构体指针变量PEVENT_CB_S eventCB。分析下代码,⑴处表示传入的参数不能为空,否则返回错误码。⑵处把事件编码.uwEventID初始化为0,然后初始化双向循环链表.stEventList,用于挂载读取事件的任务。和M核的Event模块的初始化事件相比,增加了开关中断LOS_IntLock()、LOS_IntRestore(intSave)。 LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventInit(PEVENT_CB_S eventCB){ UINT32 intSave;⑴ if (eventCB == NULL) { return LOS_ERRNO_EVENT_PTR_NULL; } intSave = LOS_IntLock();⑵ eventCB->uwEventID = 0; LOS_ListInit(&eventCB->stEventList); LOS_IntRestore(intSave); OsHookCall(LOS_HOOK_TYPE_EVENT_INIT, eventCB); return LOS_OK;}
- 我们可以使用函数UINT32 LOS_EventPoll(UINT32 *eventId, UINT32 eventMask, UINT32 mode)来校验事件掩码,需要的参数为事件结构体的事件编码eventId、用户传入的待校验的事件掩码eventMask及读取模式mode,返回用户传入的事件是否发生: 返回值为0时,表示用户预期的事件没有发生,否则表示用户期望的事件发生。 我们看下源码,函数LOS_EventPoll先后调用了函数OsEventParamCheck和OsEventPoll。我们先看下函数OsEventParamCheck,如何对参数进行校验。⑴处先检查传入参数的合法性,事件编码不能为空,事件掩码不能等于0、不能为保留的第25比特位。然后执行⑵,对事件模式进行校验。 继续执行函数OsEventPoll中的代码,⑶处代码开始对事件进行校验。如果是任一事件读取模式,接下来的判断不等于表示至少有一个事件发生了,返回值ret就表示哪些事件发生了。否则,如果是所有事情读取模式,当逻辑与运算*eventId & eventMask还等于eventMask时,表示期望的事件全部发生了,返回值ret就表示哪些事件发生了。⑷处当ret不为0,期望的事件发生,并且是清除事件读取模式时,需要把已经发生的事情进行清除。看来,这个函数不仅仅是查询事件有没有发生,还会有更新事件编码的动作。 和M核的Event模块的初始化事件相比,所有的代码都在函数内,A核增加了单独的内部函数用于参数校验和事件校验。 LITE_OS_SEC_TEXT STATIC UINT32 OsEventParamCheck(const VOID *ptr, UINT32 eventMask, UINT32 mode){⑴ if (ptr == NULL) { return LOS_ERRNO_EVENT_PTR_NULL; } if (eventMask == 0) { return LOS_ERRNO_EVENT_EVENTMASK_INVALID; } if (eventMask & LOS_ERRTYPE_ERROR) { return LOS_ERRNO_EVENT_SETBIT_INVALID; }⑵ if (((mode & LOS_WAITMODE_OR) && (mode & LOS_WAITMODE_AND)) || (mode & ~(LOS_WAITMODE_OR | LOS_WAITMODE_AND | LOS_WAITMODE_CLR)) || !(mode & (LOS_WAITMODE_OR | LOS_WAITMODE_AND))) { return LOS_ERRNO_EVENT_FLAGS_INVALID; } return LOS_OK;}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;}LITE_OS_SEC_TEXT UINT32 LOS_EventPoll(UINT32 *eventID, UINT32 eventMask, UINT32 mode){ UINT32 ret; UINT32 intSave; ret = OsEventParamCheck((VOID *)eventID, eventMask, mode); if (ret != LOS_OK) { return ret; } SCHEDULER_LOCK(intSave); ret = OsEventPoll(eventID, eventMask, mode); SCHEDULER_UNLOCK(intSave); return ret;}
-
- 我们可以使用函数LOS_EventRead()来读取事件,需要4个参数。eventCB是初始化好的事件结构体,eventMask表示需要读取的事件掩码,mode是上文说明过的读取模式,timeout是读取超时,单位是Tick。函数返回0时,表示期望的事件没有发生,读取事件失败,进入阻塞。返回非0时表示期望的事件发生了,成功读取事件。下面我们分析下函数的源码来看看如何读取事件的。 编程规范要求,一个函数不能超过50行。函数LOS_EventRead()拆分了几个内部函数来分别实现子功能,LOS_EventRead调用OsEventRead,OsEventRead又先后调用OsEventReadCheck、OsEventReadImp,先进行检查,然后实现事件读取。 先看读事件前如何检查的函数OsEventReadCheck。⑴处调用函数OsEventParamCheck()进行基础的校验,比如第25位保留不能使用,事件掩码eventMask不能为零,读取模式组合是否合法。⑵处表示不能中断中读取事件,接着判断下当前任务是否为系统任务,不能在系统任务中读取事件。如果是系统任务,会返回错误码。 然后再看读事件实现函数OsEventReadImp。⑶处调用校验函数OsEventPoll()检查事件eventMask是否发生。如果事件发生ret不为0,成功读取直接返回。ret为0,事件没有发生时,执行⑷,如果超时时间timeout为0,调用者不能等待时,直接返回。⑸如果锁任务调度时,不能读取事件,返回错误码。 ⑹更新当前任务的阻塞的事件掩码.eventMask和事件读取模式.eventMode。执行⑺调用函数OsTaskWaitSetPendMask更改当前任务的状态为阻塞状态,挂载到事件的任务阻塞链表上。如果timeout不是永久等待,还会把任务设置为OS_TASK_STATUS_PEND_TIME状态并设置等待时间。⑻处触发任务调度等待事件,后续的程序代码行需要等到读取到事件才会继续执行。⑼如果等待时间超时,事件还不可读,本任务读取不到指定的事件时,返回错误码。如果可以读取到指定的事件时,执行⑽,检查事件eventMask是否发生,然后返回结果值。 LITE_OS_SEC_TEXT STATIC UINT32 OsEventReadCheck(const PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode){ UINT32 ret; LosTaskCB *runTask = NULL;⑴ ret = OsEventParamCheck(eventCB, eventMask, mode); if (ret != LOS_OK) { return ret; }⑵ if (OS_INT_ACTIVE) { return LOS_ERRNO_EVENT_READ_IN_INTERRUPT; } runTask = OsCurrTaskGet(); if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) { OsBackTrace(); return LOS_ERRNO_EVENT_READ_IN_SYSTEM_TASK; } return LOS_OK;}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(); OsHookCall(LOS_HOOK_TYPE_EVENT_READ, eventCB, eventMask, mode, timeout); if (once == FALSE) {⑶ ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode); } if (ret == 0) {⑷ if (timeout == 0) { return ret; }⑸ if (!OsPreemptableInSched()) { return LOS_ERRNO_EVENT_READ_IN_LOCK; }⑹ runTask->eventMask = eventMask; runTask->eventMode = mode; runTask->taskEvent = eventCB;⑺ OsTaskWaitSetPendMask(OS_TASK_WAIT_EVENT, eventMask, timeout);⑻ ret = runTask->ops->wait(runTask, &eventCB->stEventList, timeout);⑼ if (ret == LOS_ERRNO_TSK_TIMEOUT) { return LOS_ERRNO_EVENT_READ_TIMEOUT; }⑽ ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode); } return ret;}LITE_OS_SEC_TEXT STATIC UINT32 OsEventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout, BOOL once){ UINT32 ret; UINT32 intSave; ret = OsEventReadCheck(eventCB, eventMask, mode); if (ret != LOS_OK) { return ret; } SCHEDULER_LOCK(intSave); ret = OsEventReadImp(eventCB, eventMask, mode, timeout, once); SCHEDULER_UNLOCK(intSave); return ret;}LITE_OS_SEC_TEXT UINT32 LOS_EventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout){ return OsEventRead(eventCB, eventMask, mode, timeout, FALSE);}
- 我们可以使用函数UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)来写入指定的事件类型。LOS_EventWrite函数调用一系列内部函数OsEventWrite–>OsEventWriteUnsafe–>OsEventResume–>OsEventResume。 我们先看下函数OsEventResume,函数名字为啥是resume?感觉应该是consume。该函数用于处理写入事件后,阻塞的任务如何读取、消费事件。⑴处分不同的读取模式判断事件是否符合任务resumedTask读取事件的要求,如果满足读取事件,则设置退出标记exitFlag,然后执行⑵更改任务状态并放入就绪队列等待唤醒。函数OsEventWriteUnsafe实现事件写入,⑶处代码把事件结构体的事件掩码和要写入的事件类型events进行逻辑或计算,来完成事件的写入。⑷如果等待事件的任务链表不为空,需要处理写入事件后是否有任务能读取到相应的事件。⑸处for循环依次遍历事件阻塞链表上的任务,⑹获取下一个任务nextTask。然后调用上文分析过的函数OsEventResume用于阻塞的任务读取事件。⑺如果有任务读取到事件,设置输出参数为1,用于标记是否需要触发任务调度。在函数OsEventWrite中,⑻处当标记需要触发任务调度时,会主动触发任务调度。 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; OsTaskWakeClearPendMask(resumedTask); resumedTask->ops->wake(resumedTask); } return exitFlag;}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; OsHookCall(LOS_HOOK_TYPE_EVENT_WRITE, eventCB, events);⑶ 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; if (eventCB == NULL) { return LOS_ERRNO_EVENT_PTR_NULL; } if (events & LOS_ERRTYPE_ERROR) { return LOS_ERRNO_EVENT_SETBIT_INVALID; } SCHEDULER_LOCK(intSave); OsEventWriteUnsafe(eventCB, events, once, &exitFlag); SCHEDULER_UNLOCK(intSave); if (exitFlag == 1) {⑻ LOS_MpSchedule(OS_MP_CPU_ALL); LOS_Schedule(); } return LOS_OK;}LITE_OS_SEC_TEXT UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events){ return OsEventWrite(eventCB, events, FALSE);}
- 我们可以使用函数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; } OsHookCall(LOS_HOOK_TYPE_EVENT_CLEAR, eventCB, eventMask); SCHEDULER_LOCK(intSave);⑴ eventCB->uwEventID &= eventMask; SCHEDULER_UNLOCK(intSave); return LOS_OK;}
- 我们可以使用函数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; }⑴ SCHEDULER_LOCK(intSave); if (!LOS_ListEmpty(&eventCB->stEventList)) { SCHEDULER_UNLOCK(intSave); return LOS_ERRNO_EVENT_SHOULD_NOT_DESTROY; } eventCB->uwEventID = 0;⑵ LOS_ListDelInit(&eventCB->stEventList); SCHEDULER_UNLOCK(intSave); OsHookCall(LOS_HOOK_TYPE_EVENT_DESTROY, eventCB); return LOS_OK;}
- 本文带领大家一起剖析了鸿蒙轻内核的事件模块的源代码,包含事件的结构体、事件初始化、事件创建删除、申请释放等。 想了解更多内容,请访问: 51CTO和华为官方合作共建的鸿蒙技术社区 https://ost.51cto.com

本文继续分析OpenHarmony LiteOS-A内核的源代码,接下来会分析IPC事件Event。事件(Event)是一种任务间通信的机制,可用于任务间的同步。多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。本文通过分析鸿蒙轻内核事件模块的源码,深入掌握事件的使用。本文中所涉及的源码,以OpenHarmony LiteOS-A内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_a 获取。如果涉及开发板,则默认以hispark_taurus为例。
接下来,我们看下事件的结构体,事件初始化,事件常用操作的源代码。
Event模块涉及2个头文件,分别是kernel\include\los_event.h、kernel\base\include\los_event_pri.h,前者定义了对外的解结构体、宏定义和接口;后者声明Event模块内部使用的函数接口。
在文件kernel\include\los_event.h定义的事件控制块结构体为EVENT_CB_S,结构体源代码如下,结构体成员的解释见注释部分。
typedef struct tagEvent {
UINT32 uwEventID; /**< 事件ID,每一位标识一种事件类型 */
LOS_DL_LIST stEventList; /**< 读取事件的任务链表 */
} EVENT_CB_S, *PEVENT_CB_S;
在读事件时,可以选择读取模式。读取模式由如下几个宏定义:
所有事件(LOS_WAITMODE_AND):
逻辑与,基于接口传入的事件类型掩码eventMask,只有这些事件都已经发生才能读取成功,否则该任务将阻塞等待或者返回错误码。
任一事件(LOS_WAITMODE_OR):
逻辑或,基于接口传入的事件类型掩码eventMask,只要这些事件中有任一种事件发生就可以读取成功,否则该任务将阻塞等待或者返回错误码。
清除事件(LOS_WAITMODE_CLR):
这是一种附加读取模式,需要与所有事件模式或任一事件模式结合使用(LOS_WAITMODE_AND | LOS_WAITMODE_CLR或 LOS_WAITMODE_OR | LOS_WAITMODE_CLR)。在这种模式下,当设置的所有事件模式或任一事件模式读取成功后,会自动清除事件控制块中对应的事件类型位。
在kernel\include\los_event.h可以看到这些宏定义的数值。
#define LOS_WAITMODE_AND 4U
#define LOS_WAITMODE_OR 2U
#define LOS_WAITMODE_CLR 1U
在源代码文件kernel\base\ipc\los_event.c中实现事件的对外函数。
在使用事件前,必须使用函数UINT32 LOS_EventInit(PEVENT_CB_S eventCB)来初始化事件,需要的参数是结构体指针变量PEVENT_CB_S eventCB。分析下代码,⑴处表示传入的参数不能为空,否则返回错误码。⑵处把事件编码.uwEventID初始化为0,然后初始化双向循环链表.stEventList,用于挂载读取事件的任务。和M核的Event模块的初始化事件相比,增加了开关中断LOS_IntLock()、LOS_IntRestore(intSave)。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventInit(PEVENT_CB_S eventCB)
{
UINT32 intSave;
⑴ if (eventCB == NULL) {
return LOS_ERRNO_EVENT_PTR_NULL;
}
intSave = LOS_IntLock();
⑵ eventCB->uwEventID = 0;
LOS_ListInit(&eventCB->stEventList);
LOS_IntRestore(intSave);
OsHookCall(LOS_HOOK_TYPE_EVENT_INIT, eventCB);
return LOS_OK;
}
我们可以使用函数UINT32 LOS_EventPoll(UINT32 *eventId, UINT32 eventMask, UINT32 mode)来校验事件掩码,需要的参数为事件结构体的事件编码eventId、用户传入的待校验的事件掩码eventMask及读取模式mode,返回用户传入的事件是否发生: 返回值为0时,表示用户预期的事件没有发生,否则表示用户期望的事件发生。
我们看下源码,函数LOS_EventPoll先后调用了函数OsEventParamCheck和OsEventPoll。我们先看下函数OsEventParamCheck,如何对参数进行校验。⑴处先检查传入参数的合法性,事件编码不能为空,事件掩码不能等于0、不能为保留的第25比特位。然后执行⑵,对事件模式进行校验。
继续执行函数OsEventPoll中的代码,⑶处代码开始对事件进行校验。如果是任一事件读取模式,接下来的判断不等于表示至少有一个事件发生了,返回值ret就表示哪些事件发生了。否则,如果是所有事情读取模式,当逻辑与运算*eventId & eventMask还等于eventMask时,表示期望的事件全部发生了,返回值ret就表示哪些事件发生了。⑷处当ret不为0,期望的事件发生,并且是清除事件读取模式时,需要把已经发生的事情进行清除。看来,这个函数不仅仅是查询事件有没有发生,还会有更新事件编码的动作。
和M核的Event模块的初始化事件相比,所有的代码都在函数内,A核增加了单独的内部函数用于参数校验和事件校验。
LITE_OS_SEC_TEXT STATIC UINT32 OsEventParamCheck(const VOID *ptr, UINT32 eventMask, UINT32 mode)
{
⑴ if (ptr == NULL) {
return LOS_ERRNO_EVENT_PTR_NULL;
}
if (eventMask == 0) {
return LOS_ERRNO_EVENT_EVENTMASK_INVALID;
}
if (eventMask & LOS_ERRTYPE_ERROR) {
return LOS_ERRNO_EVENT_SETBIT_INVALID;
}
⑵ if (((mode & LOS_WAITMODE_OR) && (mode & LOS_WAITMODE_AND)) ||
(mode & ~(LOS_WAITMODE_OR | LOS_WAITMODE_AND | LOS_WAITMODE_CLR)) ||
!(mode & (LOS_WAITMODE_OR | LOS_WAITMODE_AND))) {
return LOS_ERRNO_EVENT_FLAGS_INVALID;
}
return LOS_OK;
}
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;
}
LITE_OS_SEC_TEXT UINT32 LOS_EventPoll(UINT32 *eventID, UINT32 eventMask, UINT32 mode)
{
UINT32 ret;
UINT32 intSave;
ret = OsEventParamCheck((VOID *)eventID, eventMask, mode);
if (ret != LOS_OK) {
return ret;
}
SCHEDULER_LOCK(intSave);
ret = OsEventPoll(eventID, eventMask, mode);
SCHEDULER_UNLOCK(intSave);
return ret;
}
我们可以使用函数LOS_EventRead()来读取事件,需要4个参数。eventCB是初始化好的事件结构体,eventMask表示需要读取的事件掩码,mode是上文说明过的读取模式,timeout是读取超时,单位是Tick。函数返回0时,表示期望的事件没有发生,读取事件失败,进入阻塞。返回非0时表示期望的事件发生了,成功读取事件。下面我们分析下函数的源码来看看如何读取事件的。
编程规范要求,一个函数不能超过50行。函数LOS_EventRead()拆分了几个内部函数来分别实现子功能,LOS_EventRead调用OsEventRead,OsEventRead又先后调用OsEventReadCheck、OsEventReadImp,先进行检查,然后实现事件读取。
先看读事件前如何检查的函数OsEventReadCheck。⑴处调用函数OsEventParamCheck()进行基础的校验,比如第25位保留不能使用,事件掩码eventMask不能为零,读取模式组合是否合法。⑵处表示不能中断中读取事件,接着判断下当前任务是否为系统任务,不能在系统任务中读取事件。如果是系统任务,会返回错误码。
然后再看读事件实现函数OsEventReadImp。⑶处调用校验函数OsEventPoll()检查事件eventMask是否发生。如果事件发生ret不为0,成功读取直接返回。ret为0,事件没有发生时,执行⑷,如果超时时间timeout为0,调用者不能等待时,直接返回。⑸如果锁任务调度时,不能读取事件,返回错误码。
⑹更新当前任务的阻塞的事件掩码.eventMask和事件读取模式.eventMode。执行⑺调用函数OsTaskWaitSetPendMask更改当前任务的状态为阻塞状态,挂载到事件的任务阻塞链表上。如果timeout不是永久等待,还会把任务设置为OS_TASK_STATUS_PEND_TIME状态并设置等待时间。⑻处触发任务调度等待事件,后续的程序代码行需要等到读取到事件才会继续执行。⑼如果等待时间超时,事件还不可读,本任务读取不到指定的事件时,返回错误码。如果可以读取到指定的事件时,执行⑽,检查事件eventMask是否发生,然后返回结果值。
LITE_OS_SEC_TEXT STATIC UINT32 OsEventReadCheck(const PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode)
{
UINT32 ret;
LosTaskCB *runTask = NULL;
⑴ ret = OsEventParamCheck(eventCB, eventMask, mode);
if (ret != LOS_OK) {
return ret;
}
⑵ if (OS_INT_ACTIVE) {
return LOS_ERRNO_EVENT_READ_IN_INTERRUPT;
}
runTask = OsCurrTaskGet();
if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {
OsBackTrace();
return LOS_ERRNO_EVENT_READ_IN_SYSTEM_TASK;
}
return LOS_OK;
}
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();
OsHookCall(LOS_HOOK_TYPE_EVENT_READ, eventCB, eventMask, mode, timeout);
if (once == FALSE) {
⑶ ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode);
}
if (ret == 0) {
⑷ if (timeout == 0) {
return ret;
}
⑸ if (!OsPreemptableInSched()) {
return LOS_ERRNO_EVENT_READ_IN_LOCK;
}
⑹ runTask->eventMask = eventMask;
runTask->eventMode = mode;
runTask->taskEvent = eventCB;
⑺ OsTaskWaitSetPendMask(OS_TASK_WAIT_EVENT, eventMask, timeout);
⑻ ret = runTask->ops->wait(runTask, &eventCB->stEventList, timeout);
⑼ if (ret == LOS_ERRNO_TSK_TIMEOUT) {
return LOS_ERRNO_EVENT_READ_TIMEOUT;
}
⑽ ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode);
}
return ret;
}
LITE_OS_SEC_TEXT STATIC UINT32 OsEventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout,
BOOL once)
{
UINT32 ret;
UINT32 intSave;
ret = OsEventReadCheck(eventCB, eventMask, mode);
if (ret != LOS_OK) {
return ret;
}
SCHEDULER_LOCK(intSave);
ret = OsEventReadImp(eventCB, eventMask, mode, timeout, once);
SCHEDULER_UNLOCK(intSave);
return ret;
}
LITE_OS_SEC_TEXT UINT32 LOS_EventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout)
{
return OsEventRead(eventCB, eventMask, mode, timeout, FALSE);
}
我们可以使用函数UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)来写入指定的事件类型。LOS_EventWrite函数调用一系列内部函数OsEventWrite–>OsEventWriteUnsafe–>OsEventResume–>OsEventResume。
我们先看下函数OsEventResume,函数名字为啥是resume?感觉应该是consume。该函数用于处理写入事件后,阻塞的任务如何读取、消费事件。⑴处分不同的读取模式判断事件是否符合任务resumedTask读取事件的要求,如果满足读取事件,则设置退出标记exitFlag,然后执行⑵更改任务状态并放入就绪队列等待唤醒。函数OsEventWriteUnsafe实现事件写入,⑶处代码把事件结构体的事件掩码和要写入的事件类型events进行逻辑或计算,来完成事件的写入。⑷如果等待事件的任务链表不为空,需要处理写入事件后是否有任务能读取到相应的事件。⑸处for循环依次遍历事件阻塞链表上的任务,⑹获取下一个任务nextTask。然后调用上文分析过的函数OsEventResume用于阻塞的任务读取事件。⑺如果有任务读取到事件,设置输出参数为1,用于标记是否需要触发任务调度。在函数OsEventWrite中,⑻处当标记需要触发任务调度时,会主动触发任务调度。
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;
OsTaskWakeClearPendMask(resumedTask);
resumedTask->ops->wake(resumedTask);
}
return exitFlag;
}
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;
OsHookCall(LOS_HOOK_TYPE_EVENT_WRITE, eventCB, events);
⑶ 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;
if (eventCB == NULL) {
return LOS_ERRNO_EVENT_PTR_NULL;
}
if (events & LOS_ERRTYPE_ERROR) {
return LOS_ERRNO_EVENT_SETBIT_INVALID;
}
SCHEDULER_LOCK(intSave);
OsEventWriteUnsafe(eventCB, events, once, &exitFlag);
SCHEDULER_UNLOCK(intSave);
if (exitFlag == 1) {
⑻ LOS_MpSchedule(OS_MP_CPU_ALL);
LOS_Schedule();
}
return LOS_OK;
}
LITE_OS_SEC_TEXT UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)
{
return OsEventWrite(eventCB, events, FALSE);
}
我们可以使用函数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;
}
OsHookCall(LOS_HOOK_TYPE_EVENT_CLEAR, eventCB, eventMask);
SCHEDULER_LOCK(intSave);
⑴ eventCB->uwEventID &= eventMask;
SCHEDULER_UNLOCK(intSave);
return LOS_OK;
}
我们可以使用函数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;
}
⑴ SCHEDULER_LOCK(intSave);
if (!LOS_ListEmpty(&eventCB->stEventList)) {
SCHEDULER_UNLOCK(intSave);
return LOS_ERRNO_EVENT_SHOULD_NOT_DESTROY;
}
eventCB->uwEventID = 0;
⑵ LOS_ListDelInit(&eventCB->stEventList);
SCHEDULER_UNLOCK(intSave);
OsHookCall(LOS_HOOK_TYPE_EVENT_DESTROY, eventCB);
return LOS_OK;
}
本文带领大家一起剖析了鸿蒙轻内核的事件模块的源代码,包含事件的结构体、事件初始化、事件创建删除、申请释放等。
