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

本文分享自华为云社区《鸿蒙轻内核M核源码分析系列十二 事件Event》,原文作者:zhushy 。

事件(Event)是一种任务间通信的机制,可用于任务间的同步。多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。本文通过分析鸿蒙轻内核事件模块的源码,深入掌握事件的使用。本文中所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。

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

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

1.1 事件结构体定义

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

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

1.2 事件常用宏定义

在读事件时,可以选择读取模式。读取模式由如下几个宏定义:

  • 所有事件(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                   (4)#define LOS_WAITMODE_OR                    (2)#define LOS_WAITMODE_CLR                   (1)

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)
{
⑴  if (eventCB == NULL) {return LOS_ERRNO_EVENT_PTR_NULL;}
⑵  eventCB->uwEventID = 0;LOS_ListInit(&eventCB->stEventList);OsHookCall(LOS_HOOK_TYPE_EVENT_INIT);return LOS_OK;
}

3.2 校验事件掩码

我们可以使用函数UINT32 LOS_EventPoll(UINT32 *eventId, UINT32 eventMask, UINT32 mode)来校验事件掩码,需要的参数为事件结构体的事件编码eventId、用户传入的待校验的事件掩码eventMask及读取模式mode,返回用户传入的事件是否发生: 返回值为0时,表示用户预期的事件没有发生,否则表示用户期望的事件发生。

我们看下源码,⑴处先检查传入参数的合法性,事件编码不能为空。然后执行⑵处的代码进行校验。如果是任一事件读取模式,接下来的判断不等于表示至少有一个事件发生了,返回值ret就表示哪些事件发生了。⑶如果是所有事情读取模式,当逻辑与运算*eventId & eventMask还等于eventMask时,表示期望的事件全部发生了,返回值ret就表示哪些事件发生了。⑷处当ret不为0,期望的事件发生,并且是清除事件读取模式时,需要把已经发生的事情进行清除。看来,这个函数不仅仅是查询事件有没有发生,还会有更新事件编码的动作。

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) {ret = *eventID & eventMask;}} else {
⑶      if ((eventMask != 0) && (eventMask == (*eventID & eventMask))) {ret = *eventID & eventMask;}}
⑷  if (ret && (mode & LOS_WAITMODE_CLR)) {*eventID = *eventID & ~(ret);}LOS_IntRestore(intSave);return ret;
}

3.3 读/写事件

3.3.1 读取指定事件类型

我们可以使用函数LOS_EventRead()来读取事件,需要4个参数。eventCB是初始化好的事件结构体,eventMask表示需要读取的事件掩码,mode是上文说明过的读取模式,timeout是读取超时,单位是Tick。函数返回0时,表示期望的事件没有发生,读取事件失败,进入阻塞。返回非0时表示期望的事件发生了,成功读取事件。下面我们分析下函数的源码来看看如何读取事件的。

⑴处调用函数OsEventReadParamCheck()进行基础的校验,比如第25位保留不能使用,事件掩码eventMask不能为零,读取模式组合是否合法。⑵处表示不能中断中读取事件。⑶处调用校验函数OsEventPoll()检查事件eventMask是否发生。如果事件发生ret不为0,成功读取直接返回。ret为0,事件没有发生时,执行⑷,如果超时时间timeout为0,调用者不能等待时,直接返回。⑸如果锁任务调度时,不能读取事件,返回错误码。

⑹更新当前任务的阻塞的事件掩码.eventMask和事件读取模式.eventMode。执行⑺调用函数OsSchedTaskWait更改当前任务的状态为阻塞状态,挂载到事件的任务阻塞链表上。如果timeout不是永久等待,还会把任务设置为OS_TASK_STATUS_PEND_TIME状态并设置等待时间。⑻处触发任务调度,后续程序需要等到读取到事件才会继续执行。

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

LITE_OS_SEC_TEXT UINT32 LOS_EventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeOut)
{UINT32 ret;UINT32 intSave;LosTaskCB *runTsk = NULL;⑴  ret = OsEventReadParamCheck(eventCB, eventMask, mode);if (ret != LOS_OK) {return ret;}⑵  if (OS_INT_ACTIVE) {return LOS_ERRNO_EVENT_READ_IN_INTERRUPT;}intSave = LOS_IntLock();
⑶  ret = LOS_EventPoll(&(eventCB->uwEventID), eventMask, mode);OsHookCall(LOS_HOOK_TYPE_EVENT_READ, eventCB, eventMask, mode);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);LOS_IntRestore(intSave);
⑻      LOS_Schedule();⑼      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);}LOS_IntRestore(intSave);return ret;
}

3.3.2 写入指定的事件类型

我们可以使用函数UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)来写入指定的事件类型。代码如下所示:

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

LITE_OS_SEC_TEXT UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)
{LosTaskCB *resumedTask = NULL;LosTaskCB *nextTask = (LosTaskCB *)NULL;UINT32 intSave;UINT8 exitFlag = 0;if (eventCB == NULL) {return LOS_ERRNO_EVENT_PTR_NULL;}if ((eventCB->stEventList.pstNext == NULL) || (eventCB->stEventList.pstPrev == NULL)) {return LOS_ERRNO_EVENT_NOT_INITIALIZED;}if (events & LOS_ERRTYPE_ERROR) {return LOS_ERRNO_EVENT_SETBIT_INVALID;}intSave = LOS_IntLock();
⑴  eventCB->uwEventID |= events;OsHookCall(LOS_HOOK_TYPE_EVENT_WRITE, eventCB);
⑵  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;OsSchedTaskWake(resumedTask);}
⑺          resumedTask = nextTask;}if (exitFlag == 1) {LOS_IntRestore(intSave);
⑻          LOS_Schedule();return LOS_OK;}}LOS_IntRestore(intSave);return LOS_OK;
}

3.4 清除事件

我们可以使用函数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;}intSave = LOS_IntLock();
⑴  eventCB->uwEventID &= eventMask;LOS_IntRestore(intSave);OsHookCall(LOS_HOOK_TYPE_EVENT_CLEAR, eventCB);return LOS_OK;
}

3.5 销毁事件

我们可以使用函数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;}intSave = LOS_IntLock();⑴  if (!LOS_ListEmpty(&eventCB->stEventList)) {LOS_IntRestore(intSave);return LOS_ERRNO_EVENT_SHOULD_NOT_DESTORY;}
⑵  eventCB->stEventList.pstNext = (LOS_DL_LIST *)NULL;eventCB->stEventList.pstPrev = (LOS_DL_LIST *)NULL;LOS_IntRestore(intSave);OsHookCall(LOS_HOOK_TYPE_EVENT_DESTROY);return LOS_OK;
}

小结

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

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

事件Event:带你体验鸿蒙轻内核中一对多、多对多任务同步相关推荐

  1. 带你熟悉鸿蒙轻内核Kconfig使用指南

    摘要:本文介绍了Kconfig的基础知识,和鸿蒙轻内核的图形化配置及进阶的使用方法. 本文分享自华为云社区<鸿蒙轻内核Kconfig使用笔记>,作者: zhushy. 1. Kconfig ...

  2. 从五大结构体,带你掌握鸿蒙轻内核动态内存Dynamic Memory

    摘要:本文带领大家一起剖析了鸿蒙轻内核的动态内存模块的源代码,包含动态内存的结构体.动态内存池初始化.动态内存申请.释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列九 动态内存Dyna ...

  3. 带你剖析鸿蒙轻内核任务栈的源代码

    摘要:本文带领大家一起学习了鸿蒙轻内核的任务栈.任务上下文的基础概念,剖析了任务栈初始化的代码. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列七 任务及任务调度(1)任务栈>,原文作者 ...

  4. 互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”

    摘要:本文带领大家一起剖析鸿蒙轻内核的互斥锁模块的源代码,包含互斥锁的结构体.互斥锁池初始化.互斥锁创建删除.申请释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十 互斥锁Mutex& ...

  5. 双向循环链表:鸿蒙轻内核中数据的“驿站”

    本文分享自华为云社区<鸿蒙轻内核M核源码分析系列二 数据结构-双向循环链表>,原文作者:zhushy . 在学习OpenHarmony鸿蒙轻内核源代码的时候,常常会遇到一些数据结构的使用. ...

  6. 鸿蒙轻内核源码分析:异常钩子模块系统中断异常,如何转储异常信息

    摘要:本篇介绍下鸿蒙轻内核中异常钩子模块发生系统中断异常时如何转储异常信息. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十七(3) 异常信息ExcInfo>,作者: zhushy. ...

  7. MPU:鸿蒙轻内核的任务栈的溢出检察官

    摘要:MPU(Memory Protection Unit,内存保护单元)把内存映射为一系列内存区域,定义这些内存区域的维洲,大小,访问权限和内存熟悉信息. 本文分享自华为云社区<鸿蒙轻内核M核 ...

  8. 鸿蒙轻内核M核源码分析:数据结构之任务就绪队列

    摘要:本文会给读者介绍鸿蒙轻内核M核源码中重要的数据结构,任务基于优先级的就绪队列Priority Queue. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列三 数据结构-任务就绪队列> ...

  9. 鸿蒙轻内核的得力助手:带你掌握4种内存调试方法

    摘要:内存调测方法旨在辅助定位动态内存相关问题,提供了内存池信息统计.内存泄漏检测和踩内存检测三种调测手段. 本文分享自华为云社区<鸿蒙轻内核-内存调测-内存信息统计>,作者:zhushy ...

最新文章

  1. 在linux环境上装java_linux已经安装了java,怎么配置环境
  2. pip 安装依赖库版本低问题
  3. Leetcode 18. 四数之和 (每日一题 20211011)
  4. js遍历多层嵌套对象存在的JSO数据
  5. Java通过ftp上传文件
  6. CSS3 -webkit-transition(属性渐变)
  7. ASP.NET Core loves JavaScript
  8. 跨域请求Ajax(POST)处理方法
  9. 残缺、时间一起的爱情
  10. 锐捷Linux认证失败,如何解决win10系统锐捷认证失败?锐捷认证失败的解决方法步骤...
  11. SSH连接树莓派+设置开机自启动程序
  12. PR 2019 快速入门(8)
  13. 标志寄存器——标志位
  14. Python学习:安装pycharm教程(Python、pycharm安装包,永久免费使用)
  15. 【社区图书馆】30+危机,最值得读烂的5本书|必读
  16. roguelike2d 摄像机参数设置
  17. iconfont图标引入方法
  18. U盘启动盘安装系统,使用Diskpart命令对磁盘进行分区
  19. 授权注册-软件保护工具(1):聊聊流程
  20. 沙鲁克汗机器人_沙鲁克汗2011年最新电影Ra One 印度又一科幻大作

热门文章

  1. Bootstrap3 插件的选项
  2. JSON 语法之 JSON 文件
  3. android 生命周期管理,Android Activity生命周期和堆栈管理的详解_Android_脚本之家...
  4. mysql phpmyadmin 安装_phpmyadmin怎么安装
  5. SLAM Cartographer(16)约束构建器
  6. 有mysql的虚拟主机吗,虚拟主机能用mysql么
  7. java中怎么让原有的集合反转_Java如何反转集合中的元素?
  8. 天气预报为什么要用超级计算机,为什么天气预报不够准确?
  9. php mysql 高效,php+mysql 大容量数据高效分页效果(弃用limit)
  10. 云班课计算机第二阶段测试卷,云班课测试题答案