0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

鸿蒙内核源码分析多任务环境下的事件控制块

鸿蒙系统HarmonyOS 来源:my.oschina 作者: 鸿蒙内核源码分析 2021-04-26 14:29 次阅读

事件(Event)是一种任务间通信的机制,可用于任务间的同步。

多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。

一对多同步模型:一个任务等待多个事件的触发。可以是任意一个事件发生时唤醒任务处理事件,也可以是几个事件都发生后才唤醒任务处理事件。

多对多同步模型:多个任务等待多个事件的触发。

鸿蒙提供的事件具有如下特点:

任务通过创建事件控制块来触发事件或等待事件。

事件间相互独立,内部实现为一个32位无符号整型,每一位标识一种事件类型。第25位不可用,因此最多可支持31种事件类型。

事件仅用于任务间的同步,不提供数据传输功能。

多次向事件控制块写入同一事件类型,在被清零前等效于只写入一次。

多个任务可以对同一事件进行读写操作。

支持事件读写超时机制。

再看事件图

o4YBAGCGXN-ACrXZAADyjTTaYwI659.png

注意图中提到了三个概念事件控制块事件任务接下来结合代码来理解事件模块的实现.

事件控制块长什么样?

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

函数列表

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

pIYBAGCGXPyAad9qAABkhDgadFw029.png

其中读懂OsEventWrite和OsEventRead就明白了事件模块.

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

代码解读:

事件是共享资源,所以操作期间不能产生中断.

初始化两个记录者uwEventIDstEventList

事件生产过程 -> OsEventWrite

pIYBAGCGXRiAAFVDAACoKF6qBw0266.png

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

代码解读:

给对应位贴上事件标签,eventCB->uwEventID |= events;注意uwEventID是按位管理的.每个位代表一个事件是否写入,例如uwEventID = 00010010代表产生了 1,4 事件

循环从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

pIYBAGCGXS6ABykIAAB0t76c4h0647.png

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

代码解读:

事件控制块是给任务使用的, 任务给出读取一个事件的条件

eventMask告诉系统屏蔽掉这些事件,对屏蔽的事件不感冒.

eventMode已什么样的方式去消费事件,是必须都满足给的条件,还是只满足一个就响应.

条件给完后,自己进入等待状态OsTaskWait,等待多久timeout决定,任务自己说了算.

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
编辑:hfy

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 鸿蒙系统
    +关注

    关注

    183

    文章

    2632

    浏览量

    65365
收藏 人收藏

    评论

    相关推荐

    鸿蒙内核源码的中断环境下的任务切换

    中断环境下的任务切换 在鸿蒙内核线程就是任务,系列篇中说的任务和线程当一个东西去理解. 一般二
    的头像 发表于 04-30 16:41 2056次阅读
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>内核</b><b class='flag-5'>源码</b>的中断<b class='flag-5'>环境</b>下的<b class='flag-5'>任务</b>切换

    【HarmonyOS】鸿蒙内核源码分析(调度机制篇)

    CPU的层面,它只认任务上下文!这里看不到任何代码了,因为这是跟CPU相关的,不同的CPU需要去适配不同的汇编代码,所以这些汇编代码不会出现在一个通用工程中。请留意后续 鸿蒙内核源码
    发表于 10-14 14:00

    鸿蒙内核源码分析:用通俗易懂的语言告诉你鸿蒙内核发生了什么?

    查看。内存在内核的比重极大内存模块占了鸿蒙内核约15%代码量, 近50个文件,非常复杂。鸿蒙源码分析
    发表于 11-19 10:14

    鸿蒙内核源码分析源码注释篇):给HarmonyOS源码逐行加上中文注释

    都懂的概念去诠释或者映射一个他们从没听过的概念.说别人能听得懂的话这很重要!!! 一个没学过计算机知识的卖菜大妈就不可能知道内核的基本运作了吗? NO!,笔者在系列篇中试图用 鸿蒙源码分析
    发表于 11-19 10:32

    鸿蒙源码分析系列(总目录) | 给HarmonyOS源码逐行加上中文注释

    |-鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的|-鸿蒙
    发表于 11-20 11:24

    鸿蒙内核源码分析(必读篇):用故事说内核

    本文基于开源鸿蒙内核分析,官方源码【kernel_liteos_a】官方文档【docs】参考文档【Huawei LiteOS】本文作者:鸿蒙
    发表于 11-23 10:15

    鸿蒙内核源码分析(调度机制篇):Task是如何被调度执行的

    本文分析任务调度机制源码 详见:代码库建议先阅读阅读之前建议先读本系列其他文章,进入鸿蒙系统源码分析
    发表于 11-23 10:53

    鸿蒙内核源码分析(Task管理篇):task是内核调度的单元

    迁移示意图注意官方文档说的是线程,没有提到task(任务),但内核源码中却有大量 task代码,很少有线程(thread)代码 ,这是怎么回事?其实在鸿蒙
    发表于 11-23 14:01

    鸿蒙内核源码分析(必读篇)

    本文基于开源鸿蒙内核分析,官方源码【kernel_liteos_a】官方文档【docs】参考文档【Huawei LiteOS】本文作者:鸿蒙
    发表于 11-25 09:28

    鸿蒙内核源码分析(百篇博客分析.挖透鸿蒙内核)

    致敬内核开发者感谢开放原子开源基金会,致敬鸿蒙内核开发者。可以毫不夸张的说鸿蒙内核源码可作为大学
    发表于 07-04 17:16

    基于DSP的实时多任务调度内核设计

    基于DSP的实时多任务调度内核设计
    发表于 10-19 15:30 5次下载
    基于DSP的实时<b class='flag-5'>多任务</b>调度<b class='flag-5'>内核</b>设计

    鸿蒙内核源码分析内核最重要结构体

    为何鸿蒙内核源码分析系列开篇就说 LOS_DL_LIST ? 因为它在鸿蒙 LOS 内核中无处
    发表于 11-24 17:54 35次下载
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>内核</b><b class='flag-5'>源码</b><b class='flag-5'>分析</b> :<b class='flag-5'>内核</b>最重要结构体

    华为鸿蒙系统内核源码分析上册

    鸿蒙內核源码注释中文版【 Gitee仓】给 Harmoηy○S源码逐行加上中文注解,详细阐述设计细节,助你快速精读 Harmonyos内核源码
    发表于 04-09 14:40 16次下载

    一种实时嵌入式多任务内核分析与改进

    一种实时嵌入式多任务内核分析与改进(嵌入式开发系统)-一种实时嵌入式多任务内核分析与改进
    发表于 07-30 13:49 10次下载
    一种实时嵌入式<b class='flag-5'>多任务</b>微<b class='flag-5'>内核</b>的<b class='flag-5'>分析</b>与改进

    单片机多任务事件驱动C源码

    单片机多任务事件驱动C源码
    发表于 11-29 10:06 26次下载
    单片机<b class='flag-5'>多任务</b>事件驱动C<b class='flag-5'>源码</b>