写点什么

聊聊 LiteOS 事件模块的结构体、初始化及常用操作

发布于: 2021 年 04 月 13 日

摘要: 本文通过分析 LiteOS 事件模块的源码,深入掌握事件的使用。


本文分享自华为云社区《LiteOS内核源码分析系列九 事件Event》,原文作者:zhushy 。


事件(Event)是一种任务间通信的机制,可用于任务间的同步。多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。本文通过分析 LiteOS 事件模块的源码,深入掌握事件的使用。


LiteOS 事件模块的源代码,均可以在 LiteOS 开源站点https://gitee.com/LiteOS/LiteOS 获取。事件源代码、开发文档,示例程序代码如下:


  • LiteOS 内核事件源代码


包括事件的私有头文件 kernel\base\include\los_event_pri.h、头文件 kernel\include\los_event.h、

C 源代码文件 kernel\base\los_event.c。


  • 开发指南文档–事件


在线文档

https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E4%BA%8B%E4%BB%B6


接下来,我们看下事件的结构体,事件初始化,事件常用操作的源代码。

1、事件结构体定义和常用宏定义

1.1 事件结构体定义


在文件 kernel\base\include\los_event.h 定义的事件控制块结构体为 EVENT_CB_S,结构体源代码如下,结构体成员的解释见注释部分。


typedef struct tagEvent {    UINT32 uwEventID;        /**< 事件ID,每一位标识一种事件类型 */    LOS_DL_LIST stEventList; /**< 读取事件的任务链表 */} EVENT_CB_S, *PEVENT_CB_S;
复制代码


​开启兼容 POSIX 的宏时 LOSCFG_COMPAT_POSIX 时,在文件 kernel\base\include\los_event_pri.h 还定义了结构体 EventCond,如下:


#ifdef LOSCFG_COMPAT_POSIXtypedef struct {    volatile INT32 *realValue;    INT32 value;    UINT32 clearEvent;} EventCond;
复制代码


1.2 事件常用宏定义


系统是否支持事件可以通过宏 LOSCFG_BASE_IPC_EVENT 来配置,默认开启支持。在读事件时,可以选择读取模式。读取模式由如下几个宏定义:


  • 所有事件(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                    4U
#define LOS_WAITMODE_OR 2U
#define LOS_WAITMODE_CLR 1U
复制代码


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){    UINT32 intSave;
LOS_TRACE(EVENT_CREATE, (UINTPTR)eventCB);
⑴ if (eventCB == NULL) { return LOS_ERRNO_EVENT_PTR_NULL; }
intSave = LOS_IntLock();⑵ eventCB->uwEventID = 0; LOS_ListInit(&eventCB->stEventList); LOS_IntRestore(intSave); return LOS_OK;}
复制代码


3.2 校验事件掩码


我们可以使用函数 UINT32 LOS_EventPoll(UINT32 *eventId, UINT32 eventMask,UINT32 mode)来校验事件掩码,需要的参数为事件结构体的事件编码 eventId、用户传入的待校验的事件掩码 eventMask 及读取模式 mode,返回用户传入的事件是否发生。返回值为 0 时表示用户预期的事件没有发生,否则表示用户期望的事件发生。我们看下源码,⑴处先检查传入参数的合法性,然后执行⑵处的函数 OsEventPoll()进行校验。


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;}
复制代码


​我们继续看下函数 OsEventPoll()。如果是任一事件读取模式,接下来的判断不等于表示至少有一个事件发生了,返回值 ret 就表示哪些事件发生了。⑵如果是所有事情读取模式,当逻辑与运算*eventId & eventMask 还等于 eventMask 时,表示期望的事件全部发生了,返回值 ret 就表示哪些事件发生了。⑶当 ret 不为 0,期望的事件发生,并且是清除事件读取模式时,需要把已经发生的事情进行清除。看来,这个函数不仅仅是查询事件有没有发生,还会有更新事件编码的动作。


LITE_OS_SEC_TEXT STATIC UINT32 OsEventPoll(UINT32 *eventId, UINT32 eventMask, UINT32 mode){    UINT32 ret = 0;
LOS_ASSERT(ArchIntLocked()); 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;}
复制代码


3.3 读/写事件

3.3.1 读取指定事件类型


我们可以使用函数 LOS_EventRead()来读取事件,需要 4 个参数。eventCB 是初始化好的事件结构体,eventMask 表示需要读取的事件掩码,mode 是上文说明过的读取模式,timeout 是读取超时,单位是 Tick。该函数又进一步调用函数 OsEventRead()实现事件的读取,如下:


LITE_OS_SEC_TEXT UINT32 LOS_EventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout){    return OsEventRead(eventCB, eventMask, mode, timeout, FALSE);}
复制代码


​下面通过分析函数 OsEventRead()的源码看看如何读取事件的。函数参数多了个 BOOL once,表示是否只读取一次,对于函数 LOS_EventRead(),once 取值 FALSE。


⑴处调用函数 OsEventReadCheck()进行基础的校验,比如第 25 位保留不能使用,事件掩码 eventMask 不能为零,读取模式组合是否合法,不能中断中读取事件。如果是系统任务读取事件,会打印警告信息。然后⑵继续调用函数 OsEventReadImp()来读取事件。


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; }
LOS_TRACE(EVENT_READ, (UINTPTR)eventCB, eventCB->uwEventID, eventMask, mode, timeout);
SCHEDULER_LOCK(intSave);⑵ ret = OsEventReadImp(eventCB, eventMask, mode, timeout, once, &intSave); SCHEDULER_UNLOCK(intSave); return ret;}
复制代码


​我们继续分析下函数 OsEventReadImp()。⑴处当 once 为假时,调用校验函数 OsEventPoll()检查事件 eventMask 是否发生。如果事件发生 ret 不为 0,直接返回。ret 为 0,事件没有发生时,执行⑵,如果超时时间 timeout 为 0,调用者不能等待时,直接返回。⑶如果锁任务调度时,不能读取事件,返回错误码。


⑷更新当前任务的阻塞的事件掩码.eventMask 和事件读取模式.eventMode。执行⑸,更改当前任务的状态不再是就绪状态,设置为阻塞状态,挂在事件的任务阻塞链表上。如果 timeout 不是永久等待,还会把任务挂在定时器排序链表里。⑹处触发任务调度,后续程序需要等到读取到事件才会继续执行。


⑺如果等待时间超时,事件还不可读,本任务读取不到指定的事件时,返回错误码。如果可以读取到指定的事件时,执行⑻,检查事件 eventMask 是否发生,然后返回结果值。


LITE_OS_SEC_TEXT STATIC UINT32 OsEventReadImp(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode,                                              UINT32 timeout, BOOL once, UINT32 *intSave){    UINT32 ret = 0;    LosTaskCB *runTask = OsCurrTaskGet();
⑴ 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;
⑸ OsTaskWait(&eventCB->stEventList, OS_TASK_STATUS_PEND, timeout);
⑹ OsSchedResched();
SCHEDULER_UNLOCK(*intSave); SCHEDULER_LOCK(*intSave);
⑺ if (runTask->taskStatus & OS_TASK_STATUS_TIMEOUT) { runTask->taskStatus &= ~OS_TASK_STATUS_TIMEOUT; return LOS_ERRNO_EVENT_READ_TIMEOUT; }
⑻ ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode); } return ret;}
复制代码


3.3.2 写入指定的事件类型


我们可以使用函数 UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)来写入指定的事件类型。该函数又进一步调用函数 OsEventWrite()实现事件的写入,如下所示:


LITE_OS_SEC_TEXT UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events){    return OsEventWrite(eventCB, events, FALSE);}
复制代码


​下面通过分析 OsEventWrite()的源码看看如何写入事件类型的。函数参数多了个 BOOL once,表示是否只读取一次,对于函数 LOS_EventWrite(),once 取值 FALSE。⑴处代码把事件结构体的事件掩码和要写入的事件类型 events 进行逻辑或计算,来完成事件的写入。⑵如果等待事件的任务链表不为空,需要处理写入事件后是否有任务能读取到相应的事件。⑶处 for 循环依次遍历事件阻塞链表上的任务,⑷获取下一个任务 nextTask。⑸处分不同的读取模式判断事件是否符合任务 resumedTask 读取事件的要求,如果满足读取事件,执行⑹设置退出标记 exitFlag,然后调用函数 OsTaskWake()把读取事件的任务更改状态并放入就绪队列。⑺处如果参数 once 为真,则只处理事件的阻塞任务链表中的第一个任务。如果为假,继续执行⑻,遍历事件的阻塞任务链表中的每一个任务。⑼如果有任务读取到事件,需要触发任务调度。


LITE_OS_SEC_TEXT STATIC UINT32 OsEventWrite(PEVENT_CB_S eventCB, UINT32 events, BOOL once){    LosTaskCB *resumedTask = NULL;    LosTaskCB *nextTask = NULL;    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; }
LOS_TRACE(EVENT_WRITE, (UINTPTR)eventCB, eventCB->uwEventID, events);
SCHEDULER_LOCK(intSave);
⑴ 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 (((resumedTask->eventMode & LOS_WAITMODE_OR) && ((resumedTask->eventMask & events) != 0)) || ((resumedTask->eventMode & LOS_WAITMODE_AND) && ((resumedTask->eventMask & eventCB->uwEventID) == resumedTask->eventMask))) {⑹ exitFlag = 1; OsTaskWake(resumedTask, OS_TASK_STATUS_PEND); }⑺ if (once == TRUE) { break; }⑻ resumedTask = nextTask; } }
SCHEDULER_UNLOCK(intSave);
if (exitFlag == 1) {⑼ LOS_MpSchedule(OS_MP_CPU_ALL); LOS_Schedule(); } return LOS_OK;}
复制代码


3.4 清除事件


我们可以使用函数 UINT32 LOS_EventClear(PEVENT_CB_S eventCB, UINT32 events)来清除指定的事件类型,下面通过分析源码看看如何清除事件类型的。


函数参数为事件结构体 eventCB 和要清除的事件类型 events。清除事件时首先会进行结构体参数是否为空的校验,这些比较简单。⑴处把事件结构体的事件掩码和要清除的事件类型 events 进行逻辑与计算,来完成事件的清理。


LITE_OS_SEC_TEXT_MINOR UINT32 LOS_EventClear(PEVENT_CB_S eventCB, UINT32 events){    UINT32 intSave;
if (eventCB == NULL) { return LOS_ERRNO_EVENT_PTR_NULL; }
LOS_TRACE(EVENT_CLEAR, (UINTPTR)eventCB, eventCB->uwEventID, events);
SCHEDULER_LOCK(intSave);⑴ eventCB->uwEventID &= events; SCHEDULER_UNLOCK(intSave);
return LOS_OK;}
复制代码


3.5 销毁事件


我们可以使用函数 UINT32 LOS_EventDestroy(PEVENT_CB_S eventCB)来销毁指定的事件控制块,下面通过分析源码看看如何销毁事件的。


函数参数为事件结构体,销毁事件时首先会进行结构体参数是否为空的校验,这些比较简单。⑴处如果事件的任务阻塞链表不为空,则不能销毁事件。⑵把事件结构体的事件掩码设置为 0,完成事件的销毁。


LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventDestroy(PEVENT_CB_S eventCB){    UINT32 intSave;    UINT32 ret = LOS_OK;
if (eventCB == NULL) { return LOS_ERRNO_EVENT_PTR_NULL; }
SCHEDULER_LOCK(intSave);⑴ if (!LOS_ListEmpty(&eventCB->stEventList)) { ret = LOS_ERRNO_EVENT_SHOULD_NOT_DESTORY; goto OUT; }
⑵ eventCB->uwEventID = 0;OUT: SCHEDULER_UNLOCK(intSave);
LOS_TRACE(EVENT_DELETE, (UINTPTR)eventCB, ret); return ret;}
复制代码


小结


本文带领大家一起剖析了 LiteOS 事件模块的源代码,包含事件的结构体、事件初始化、事件创建删除、申请释放等。感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/LiteOS/LiteOS/issues 。为了更容易找到 LiteOS 代码仓,建议访问 https://gitee.com/LiteOS/LiteOS ,关注 Watch、点赞 Star、并 Fork 到自己账户下.谢谢。


点击关注,第一时间了解华为云新鲜技术~

发布于: 2021 年 04 月 13 日阅读数: 29
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
聊聊LiteOS事件模块的结构体、初始化及常用操作