鸿蒙内核源码分析(事件控制篇) | 任务间多对多的同步方案

官方概述

先看官方对事件的描述.

事件(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;

简单是简单,就两个变量,如下:
uwEventID:用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0表示该事件类型未发生、1表示该事件类型已经发生),一共31种事件类型,第25位系统保留。

stEventList,这又是一个双向链表, 双向链表是内核最重要的结构体.
LOS_DL_LIST像狗皮膏药一样牢牢的寄生在宿主结构体上stEventList上挂的是所有等待这个事件的任务.

事件控制块<>事件<>任务 三者关系

一定要搞明白这三者的关系,否则搞不懂事件模块是如何运作的.

  • 任务是事件的生产者,通过 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中的事件可以来自多个任务,多个任务也可以同时消费事件控制块中的事件,并且这些任务之间可以没有任何关系!

函数列表

事件可应用于多种任务同步场景,在某些同步场景下可替代信号量。

其中读懂 OsEventWriteOsEventRead 就明白了事件模块.

事件初始化 -> 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;
}

代码解读:

  • 事件控制块是给任务使用的, 任务给出读取一个事件的条件
    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

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/832318.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HFP event

0 Preface/Foreword NOTE&#xff1a;在来电提示音在响时候&#xff0c;每3s会进入一次。 也就是说&#xff1a;协议栈&#xff0c;没3s会调用一次app_hfp_event_callback。 回调函数在hfp初始化时候通过btif_hf_register_callback注册。 提示音调节&#xff1a; iPhone6s iO…

区块链开发用的是哪种编程语言?

区块链技术作为近年来备受瞩目的新兴技术之一&#xff0c;其核心的特性之一就是去中心化、安全性高、透明度高和可扩展性强。而区块链的开发语言则是实现这一技术的关键因素之一。那么&#xff0c;区块链开发语言是哪一种编程语言呢&#xff1f; 一、区块链开发语言的特点和选…

OZON卖家必看!2024年OZON运营必备工具大全

OZON运营过程中会用到许多工具网站&#xff0c;都是OZON跨境人运营必备的。为了帮助新卖家在运营OZON时更高效&#xff0c;下面汇总了一份我们在日常运营中频繁使用的工具网站列表。这样大家可以一次性找到所需的所有网址&#xff0c;无需在多个网站间来回切换&#xff0c;节省…

C++:哈希表和unordered系列容器的封装

一、unordered系列关联式容器的介绍 在C98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0c;在查询时效率可达到log2N&#xff0c;即最差情况下需要比较红黑树的高度次&#xff0c;当树中的节点非常多时&#xff0c;查询效率也不理想。最好的查询是&…

K. 子串翻转回文串

给一个串 s  s1s2... sn&#xff0c;你可以选定其一个非空子串&#xff0c;然后将该子串翻转。具体来说&#xff0c;若选定的子串区间为 [l, r]&#xff08;1 ≤ l ≤ r ≤ n&#xff09;&#xff0c;则翻转后该串变为 s1s2... sl - 1srsr - 1... slsr  1... sn…

海外仓管理软件:如果你这么用,它对你有害无益

虽然说海外仓管理系统是个功能强大的工具&#xff0c;但它并不能解决海外仓所有的问题。 那么什么时候适合使用海外仓系统&#xff0c;什么样的使用方式是错误的&#xff1f;今天就系统的和大家聊聊这个问题。 不要把海外仓管理系统当成“救急”工具 很多时候人们在遇到问题的时…

Day_1

1. 环境搭建 技术选型 后端项目结构 sky-take-out maven父工程&#xff0c;统一管理依赖版本&#xff0c;聚合其他子模块 sky-common 子模块&#xff0c;存放公共类&#xff0c;例如&#xff1a;工具类、常量类、异常类等 sky-pojo 子模块&#xff0c;存放实体类、VO、DTO…

node.js对数据库的操作 之 query(查询)与pool(连接池)

一、Query&#xff08;查询&#xff09; &#xff08;1&#xff09;意义 query是指向数据库发送的一个命令或请求&#xff0c;以检索、更新、插入或删除数据。它是一个具体的SQL语句或NoSQL命令&#xff0c;用于从数据库中获取或修改数据。 &#xff08;2&#xff09;用途 …

4个可将 iPhone iPad iPod 修复至正常状态的 iOS 系统恢复软件

许多iOS用户对操作系统问题感到恐慌&#xff0c;例如iPhone卡在恢复模式、白屏死机、黑屏死机、iOS系统损坏、iTunes连接屏幕、iPhone数据丢失等。这些状态通常很无聊&#xff0c;因为您无法使用 iPhone 执行任何操作。 4个可将 iPhone iPad iPod 修复至正常状态的 iOS 系统恢复…

一键自动化博客发布工具,用过的人都说好(segmentfault篇)

segmentfault是我在这些平台中看过界面最为简洁的博客平台了。 今天就以segmentfault为例&#xff0c;讲讲在blog-auto-publishing-tools中的实现原理。 前提条件 前提条件当然是先下载 blog-auto-publishing-tools这个博客自动发布工具,地址如下&#xff1a;https://github…

win中python中OpenCV使用cv2.imshow()报错的解决办法

1. 问题 cv2.error: OpenCV(4.9.0) D:\a\opencv-python\opencv-python\opencv\modules\highgui\src\window.cpp:1272: error: (-2:Unspecified error) The function is not implemented. Rebuild the library with Windows, GTK 2.x or Cocoa support. If you are on Ubuntu o…

leetcode45.跳跃游戏||

问题描述&#xff1a; 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j < n 返…

KNOWLEDGE DISTILLATION BASED ON TRANSFORMED TEACHER MATCHING

摘要 温度标度作为logit匹配和概率分布匹配的bridge技术&#xff0c;在知识蒸馏&#xff08;KD&#xff09;中起着至关重要的作用。传统上&#xff0c;在蒸馏中&#xff0c;温度标度适用于教师的logits和学生的logits。在本文中&#xff0c;受到最近一些研究成果的启发&#x…

解锁程序员高效编程之谜:软件工具、编辑器与插件的秘密武器大公开!

程序员如何提高编程效率&#xff1f; 程序员提高编程效率是一个多方面的过程&#xff0c;涉及技能提升、工具使用、时间管理以及工作习惯等多个方面。以下是一些建议&#xff0c;帮助程序员提高编程效率&#xff1a; 1. 选择适合的工具 使用高效的代码编辑器或集成开发环境&…

高防护皮带机巡检机器人:适应恶劣环境的智能助手

在众多工业领域中&#xff0c;皮带机作为一种重要的物料输送设备&#xff0c;广泛应用于发电厂、煤栈等场所。然而&#xff0c;长期以来&#xff0c;皮带机的巡检工作一直依赖人工&#xff0c;存在着劳动强度大、检测效率低、安全性差等问题。为了解决这些痛点&#xff0c;皮带…

Redis 渐进式遍历 -- scan

前言 keys 可以一次性把 Redis 中的所有 key 都获取到&#xff0c;但这个操作比较危险&#xff0c;一次性获取所有的key 很容易会导致 Redis 阻塞。 而通过渐进式遍历&#xff08;不是一个命令就将所有的 key 值拿到&#xff0c;而是每执行一次命令只获取其中的一小部分&#x…

我独自升级崛起在哪下载 我独自升级电脑PC端下载教程分享

将于5月8日在全球舞台闪亮登场的动作角色扮演游戏《我独自升级崛起》&#xff0c;灵感源自同名热门动画与网络漫画&#xff0c;承诺为充满激情的游戏玩家群体带来一场集深度探索与广阔体验于一身的奇幻旅程。该游戏以独特的网络武侠世界观为基底&#xff0c;展现了一位普通人踏…

CPU炼丹——YOLOv5s

1.Anaconda安装与配置 1.1安装与配置 Anaconda3的安装看下面的教程&#xff1a; 最新Anaconda3的安装配置及使用教程&#xff08;详细过程&#xff09;http://t.csdnimg.cn/yygXD&#xff0c;接上面文章下载后&#xff0c;配置环境变量的时候记得在原来你装的Python更下面添…

【项目】高并发内存池实现(化简版tcmalloc)

前言 因为偶然的机会&#xff0c;我通过同学那里知道这个google有一个开源项目tcmalloc&#xff0c;他讲的头头是道&#xff0c;而我也对其非常感兴趣。 这个tcmalloc呢&#xff0c;全称Thread-Caching Malloc&#xff0c;通过名字就能看出跟线程相关&#xff0c;也确实如此&am…

Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作

Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作 前言 通过前三篇Sprin…