事件Event:带你体验鸿蒙轻内核中一对多、多对多任务同步
摘要:本文通过分析鸿蒙轻内核事件模块的源码,深入掌握事件的使用。
本文分享自华为云社区《鸿蒙轻内核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:带你体验鸿蒙轻内核中一对多、多对多任务同步的更多相关文章
- 带你熟悉鸿蒙轻内核Kconfig使用指南
摘要:本文介绍了Kconfig的基础知识,和鸿蒙轻内核的图形化配置及进阶的使用方法. 本文分享自华为云社区<鸿蒙轻内核Kconfig使用笔记>,作者: zhushy. 1. Kconfig ...
- 从五大结构体,带你掌握鸿蒙轻内核动态内存Dynamic Memory
摘要:本文带领大家一起剖析了鸿蒙轻内核的动态内存模块的源代码,包含动态内存的结构体.动态内存池初始化.动态内存申请.释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列九 动态内存Dyna ...
- 鸿蒙轻内核定时器Swtmr:不受硬件和数量限制,满足用户需求
摘要:本文通过分析鸿蒙轻内核定时器模块的源码,掌握定时器使用上的差异. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十四 软件定时器Swtmr>,作者:zhushy . 软件定时器(S ...
- 深层剖析鸿蒙轻内核M核的动态内存如何支持多段非连续性内存
摘要:鸿蒙轻内核M核新增支持了多段非连续性内存区域,把多个非连续性内存逻辑上合一,用户不感知底层的不同内存块. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列九 动态内存Dynamic Mem ...
- 鸿蒙轻内核M核的故障管家:Fault异常处理
摘要:本文先简单介绍下Fault异常类型,向量表及其代码,异常处理C语言程序,然后详细分析下异常处理汇编函数实现代码. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十八 Fault异常处理& ...
- 鸿蒙轻内核M核源码分析:LibC实现之Musl LibC
摘要:本文学习了LiteOS-M内核Musl LibC的实现,特别是文件系统和内存分配释放部分. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十九 Musl LibC>,作者:zhus ...
- 鸿蒙轻内核源码分析:文件系统LittleFS
摘要:本文先介绍下LFS文件系统结构体的结构体和全局变量,然后分析下LFS文件操作接口. 本文分享自华为云社区<# 鸿蒙轻内核M核源码分析系列二一 02 文件系统LittleFS>,作者: ...
- 鸿蒙轻内核源码分析:文件系统FatFS
摘要:本文为大家介绍FatFS文件系统结构体的结构体和全局变量,并分析FatFS文件操作接口. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列二一 03 文件系统FatFS>,作者:zh ...
- 手把手带你体验鸿蒙 harmonyOS
wNlRGd.png 前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 image.png 一.为什么要尝鲜 harmonyos? wNlfx ...
- Linux内核中锁机制之原子操作、自旋锁
很多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实是由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态.这其中包括了我们所熟知的SMP系统,多 ...
随机推荐
- 记一次 openSUSE Tumbleweed 下安装 k8s
出现的问题 因为没有K8s基础的而踩了不少坑. kubeadm kubelet 最好指定版本安装,因为kubelet的版本需要小于等于kubeadm的版本,否则就会报错. 运行 kubeadm ini ...
- nginx学习(基本概念、配置和命令、反向代理、负载均衡、动静分离)
之前都只会照着网上的nginx配置和代码什么的直接拿过来用,但是没系统学习过,所以来系统学习一下nginx内容. 建议服务器不要关闭防火墙,按需开启端口就好,然后云服务器也要设置SSH密钥,安全性高一 ...
- 相邻像素相关性的matlab实现
目录 加密性能分析(一)相邻像素相关性的matlab实现 相邻像素相关性 (一)概念 (二)相邻像素的相关系数 (三)相邻像素分布图 加密性能分析(一)相邻像素相关性的matlab实现 相邻像素相关性 ...
- 使用visualvm远程监控JVM
参考:http://blog.sina.com.cn/s/blog_4e90b3ba0100muco.html 1.首先要修改JDK中JMX服务的配置文件,以获得相应的权限: 在jdk-1.6.0_2 ...
- Kubernetes:kube-apiserver 之鉴权
kubernetes:kube-apiserver 系列文章: Kubernetes:kube-apiserver 之 scheme(一) Kubernetes:kube-apiserver 之 sc ...
- 7z 一键压缩备份
该批处理已开源 开原地址: 点击进入 磁盘备份 工具有很多,如果你需要增量式备份的话,以下这些方法并不适合你.goodsync 可以了解一下. 以下方式仅适用于,懒人一键压缩备份. 对于我来说 定期的 ...
- 震荡指标(一)RSI指标
相对强弱指数RSI是根据一定时期内上涨点数和涨跌点数之和的比率制作出的一种技术曲线.能够反映出市场在一定时期内的景气程度.由威尔斯.威尔德(Welles Wilder)最早应用于期货买卖,后来人们发现 ...
- Netty源码学习5——服务端是如何读取数据的
系列文章目录和关于我 零丶引入 在前面<Netty源码学习4--服务端是处理新连接的&netty的reactor模式>的学习中,我们了解到服务端是如何处理新连接的,即注册Serve ...
- python01-基础概念与环境搭建
学习目标 了解硬件 & 操作系统 & 软件(应用系统)之间的关系. 了解常见的操作系统都有哪些. 了解编译器和解释器的区别和作用. 了解编程语言进行分类 了解Python解释器的种类 ...
- 吉特日化MES & 标签的设计与选择
吉特日化MES & 标签的设计与选择:如今条码和二维码在生活中随处可见,作为一种能够快速精准读取识别的手段,条码和二维码在生产车间也应用的较为广泛.如果说条码和二维码哪一种更好,我更加倾向于使 ...