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

事件(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_POSIX
typedef 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

2、事件常用操作

2.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;
}

2.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;
}

2.3 读/写事件

2.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;
}

2.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;
}

2.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;
}

2.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到自己账户下.谢谢。

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

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

聊聊LiteOS事件模块的结构体、初始化及常用操作的更多相关文章

  1. Linux C中结构体初始化

          在阅读GNU/Linux内核代码时,我们会遇到一种特殊的结构初始化方式.该方式是某些C教材(如谭二版.K&R二版)中没有介绍过的.这种方式称为指定初始化(designated in ...

  2. struct--------构造函数对结构体初始化的影响

    struct--------构造函数对结构体初始化的影响. 没有构造函数时使用如下: struct ClassBook{  int number;  int age; }; int main() { ...

  3. Linux C 结构体初始化三种形式

    最近看linux代码时发现了结构体 struct 一种新的初始化方式,各方查找对比后总结如下: 1. 顺序初始化教科书上讲C语言结构体初始化是按照顺序方式来讲的,没有涉及到乱序的方式.顺序初始化str ...

  4. Linux下C结构体初始化[总结]

    1.前言 今天在公司看一同事写的代码,代码中用到了struct,初始化一个struct用的是乱序格式,如下代码所示: typedef struct _data_t { int a; int b; }d ...

  5. Linux下C结构体初始化

    1.前言 今天在公司看一同事写的代码,代码中用到了struct,初始化一个struct用的是乱序格式,如下代码所示: typedef struct _data_t { int a; int b; }d ...

  6. C语言结构体初始化方法

    早上苏凯童鞋问我这个问题来着,写在这里. 我了解到的C中结构体初始化的方法大概有三种. 如这里我定义了一个结构体: typedef struct node { int x, y; }Node; 第一种 ...

  7. golang | Go语言入门教程——结构体初始化与继承

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是golang专题第10篇文章,我们继续来看golang当中的面向对象部分. 在上一篇文章当中我们一起学习了怎么创建一个结构体,以及怎么 ...

  8. 【2016-08-18】转载:总结C++中几种结构体初始化的方法

    作者:Ac_Von 博客地址:http://www.cnblogs.com/vongang/ 文章地址:http://www.cnblogs.com/vongang/archive/2011/07/3 ...

  9. c结构体初始化问题

    结构体中的数组的初始化问题 我的代码 C/C++ code #include <stdio.h> #include <stdlib.h> struct a { int leng ...

  10. 一个C/C++结构体初始化有趣的现象

    我们知道C语言当中结构可以使用{}进行初始化,例如有结构体定义如下: typedef struct type_t { int a; int b; int c; int d; }type_t; 我们可以 ...

随机推荐

  1. 从零用VitePress搭建博客教程(4) – 如何自定义首页布局和主题样式修改?

    接上一节:从零用VitePress搭建博客教程(3) - VitePress页脚.标题logo.最后更新时间等相关细节配置 六.首页样式修改 有时候觉得自带的样式不好看,想自定义,首先我们在docs/ ...

  2. 数据类型python

    type()语句的用法 运行结果

  3. SQL Server数据库创建远程服务器备份计划(小白详细图文教程)

    一.前言 最近项目系统做安全加固,以前是本地备份,现在需要做远程内网服务器数据库备份,后期也有可能做异地备份.下面以SQL Server2016 内网服务器数据库备份为例, 数据库服务器地址:192. ...

  4. 21. 从零用Rust编写正反向代理,tokio竟然这样对待socket!

    wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,内网穿透,后续将实现websocket代理等,会将实现 ...

  5. 一键整合,万用万灵,Python3.10项目嵌入式一键整合包的制作(Embed)

    我们知道Python是一门解释型语言,项目运行时需要依赖Python解释器,并且有时候需要安装项目中对应的三方依赖库.对于专业的Python开发者来说,可以直接通过pip命令进行安装即可.但是如果是分 ...

  6. kubernetes container device interface (CDI)

    CDI 是什么? Container Device Interface (CDI) 是一个提议的标准,它定义了如何在容器运行时环境中向容器提供设备.这个提议的目的是使得设备供应商能够更容易地将其设备集 ...

  7. Hdu4742 (CDQ分治)

    题意:给出n个三维点对(x,y,z),可随意排列,求三维非严格最长上升子序列长度和最长上升子序列数量. 输入格式:第一行为一整数T表示用例组数,每组用例第一行为一整数n表示点数,之后n行每行三个整数x ...

  8. 【让AI女友跟我表白】大白话说Python+Flask入门(四)Flask Sijax的使用

    写在前面 先吐槽两句,搞个mysql安装配置弄了4个小时,怎么都是外网无法访问,我靠,我特么也是服了. 当然,后来我投降了,明天再说,学什么不是学,娘的,换个方向,状态依然在! Sijax是什么? 代 ...

  9. 架构师的知行合一(内容由AI的全文生成,满分100分我打99分)

    大型架构是怎么来的 随着科技的不断发展,越来越多的企业和组织开始意识到数字化转型的重要性.为了更好地适应市场的变化,满足客户的需求,提高企业的竞争力,大型架构成为了企业和组织不可或缺的一部分.那么,大 ...

  10. 山海鲸Cesium:用更简单的方式升级视效

    CesiumJS作为绝大多数人都在用的开源地球可视化引擎,视觉效果并不拔尖,这让很多giser都想着有一天升级一下视效,从众多平庸的项目中脱颖而出.然而,对于一些使用Cesium的项目来说,要想达到C ...