4

鸿蒙轻内核A核源码分析系列八—事件Event

 2 years ago
source link: https://os.51cto.com/article/706417.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

26a028008392bde9b4c4341ec17cb769cb4faf.png

​想了解更多内容,请访问:​

​51CTO和华为官方合作共建的鸿蒙技术社区​

​https://ost.51cto.com​

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

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

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

Event模块涉及2个头文件,分别是kernel\include\los_event.h、kernel\base\include\los_event_pri.h,前者定义了对外的解结构体、宏定义和接口;后者声明Event模块内部使用的函数接口。

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

(2) 事件常用宏定义

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

所有事件(LOS_WAITMODE_AND):

逻辑与,基于接口传入的事件类型掩码eventMask,只有这些事件都已经发生才能读取成功,否则该任务将阻塞等待或者返回错误码。

任一事件(LOS_WAITMODE_OR):

逻辑或,基于接口传入的事件类型掩码eventMask,只要这些事件中有任一种事件发生就可以读取成功,否则该任务将阻塞等待或者返回错误码。

清除事件(LOS_WAITMODE_CLR):

这是一种附加读取模式,需要与所有事件模式或任一事件模式结合使用(LOS_WAITMODE_AND | LOS_WAITMODE_CLR或 LOS_WAITMODE_OR | LOS_WAITMODE_CLR)。在这种模式下,当设置的所有事件模式或任一事件模式读取成功后,会自动清除事件控制块中对应的事件类型位。

在kernel\include\los_event.h可以看到这些宏定义的数值。

 #define LOS_WAITMODE_AND                   4U

   #define LOS_WAITMODE_OR                    2U

   #define LOS_WAITMODE_CLR                   1U

3、事件常用操作

在源代码文件kernel\base\ipc\los_event.c中实现事件的对外函数。

(1) 初始化事件

在使用事件前,必须使用函数UINT32 LOS_EventInit(PEVENT_CB_S eventCB)来初始化事件,需要的参数是结构体指针变量PEVENT_CB_S eventCB。分析下代码,⑴处表示传入的参数不能为空,否则返回错误码。⑵处把事件编码.uwEventID初始化为0,然后初始化双向循环链表.stEventList,用于挂载读取事件的任务。和M核的Event模块的初始化事件相比,增加了开关中断LOS_IntLock()、LOS_IntRestore(intSave)。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventInit(PEVENT_CB_S eventCB)
{
    UINT32 intSave;
⑴  if (eventCB == NULL) {
        return LOS_ERRNO_EVENT_PTR_NULL;
    }
    intSave = LOS_IntLock();
⑵  eventCB->uwEventID = 0;
    LOS_ListInit(&eventCB->stEventList);
    LOS_IntRestore(intSave);
    OsHookCall(LOS_HOOK_TYPE_EVENT_INIT, eventCB);
    return LOS_OK;
}

(2) 校验事件掩码

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

我们看下源码,函数LOS_EventPoll先后调用了函数OsEventParamCheck和OsEventPoll。我们先看下函数OsEventParamCheck,如何对参数进行校验。⑴处先检查传入参数的合法性,事件编码不能为空,事件掩码不能等于0、不能为保留的第25比特位。然后执行⑵,对事件模式进行校验。

继续执行函数OsEventPoll中的代码,⑶处代码开始对事件进行校验。如果是任一事件读取模式,接下来的判断不等于表示至少有一个事件发生了,返回值ret就表示哪些事件发生了。否则,如果是所有事情读取模式,当逻辑与运算*eventId & eventMask还等于eventMask时,表示期望的事件全部发生了,返回值ret就表示哪些事件发生了。⑷处当ret不为0,期望的事件发生,并且是清除事件读取模式时,需要把已经发生的事情进行清除。看来,这个函数不仅仅是查询事件有没有发生,还会有更新事件编码的动作。

和M核的Event模块的初始化事件相比,所有的代码都在函数内,A核增加了单独的内部函数用于参数校验和事件校验。

LITE_OS_SEC_TEXT STATIC UINT32 OsEventParamCheck(const VOID *ptr, UINT32 eventMask, UINT32 mode)
{
⑴  if (ptr == NULL) {
        return LOS_ERRNO_EVENT_PTR_NULL;
    }
    if (eventMask == 0) {
        return LOS_ERRNO_EVENT_EVENTMASK_INVALID;
    }
    if (eventMask & LOS_ERRTYPE_ERROR) {
        return LOS_ERRNO_EVENT_SETBIT_INVALID;
    }

⑵  if (((mode & LOS_WAITMODE_OR) && (mode & LOS_WAITMODE_AND)) ||
        (mode & ~(LOS_WAITMODE_OR | LOS_WAITMODE_AND | LOS_WAITMODE_CLR)) ||
        !(mode & (LOS_WAITMODE_OR | LOS_WAITMODE_AND))) {
        return LOS_ERRNO_EVENT_FLAGS_INVALID;
    }
    return LOS_OK;
}
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;
}
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;
}

(3) 读/写事件

读取指定事件类型

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

编程规范要求,一个函数不能超过50行。函数LOS_EventRead()拆分了几个内部函数来分别实现子功能,LOS_EventRead调用OsEventRead,OsEventRead又先后调用OsEventReadCheck、OsEventReadImp,先进行检查,然后实现事件读取。

先看读事件前如何检查的函数OsEventReadCheck。⑴处调用函数OsEventParamCheck()进行基础的校验,比如第25位保留不能使用,事件掩码eventMask不能为零,读取模式组合是否合法。⑵处表示不能中断中读取事件,接着判断下当前任务是否为系统任务,不能在系统任务中读取事件。如果是系统任务,会返回错误码。

然后再看读事件实现函数OsEventReadImp。⑶处调用校验函数OsEventPoll()检查事件eventMask是否发生。如果事件发生ret不为0,成功读取直接返回。ret为0,事件没有发生时,执行⑷,如果超时时间timeout为0,调用者不能等待时,直接返回。⑸如果锁任务调度时,不能读取事件,返回错误码。

⑹更新当前任务的阻塞的事件掩码.eventMask和事件读取模式.eventMode。执行⑺调用函数OsTaskWaitSetPendMask更改当前任务的状态为阻塞状态,挂载到事件的任务阻塞链表上。如果timeout不是永久等待,还会把任务设置为OS_TASK_STATUS_PEND_TIME状态并设置等待时间。⑻处触发任务调度等待事件,后续的程序代码行需要等到读取到事件才会继续执行。⑼如果等待时间超时,事件还不可读,本任务读取不到指定的事件时,返回错误码。如果可以读取到指定的事件时,执行⑽,检查事件eventMask是否发生,然后返回结果值。

LITE_OS_SEC_TEXT STATIC UINT32 OsEventReadCheck(const PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode)
{
    UINT32 ret;
    LosTaskCB *runTask = NULL;
⑴  ret = OsEventParamCheck(eventCB, eventMask, mode);
    if (ret != LOS_OK) {
        return ret;
    }
⑵  if (OS_INT_ACTIVE) {
        return LOS_ERRNO_EVENT_READ_IN_INTERRUPT;
    }
    runTask = OsCurrTaskGet();
    if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {
        OsBackTrace();
        return LOS_ERRNO_EVENT_READ_IN_SYSTEM_TASK;
    }
    return LOS_OK;
}
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();
    OsHookCall(LOS_HOOK_TYPE_EVENT_READ, eventCB, eventMask, mode, timeout);
    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;
        runTask->taskEvent = eventCB;
⑺      OsTaskWaitSetPendMask(OS_TASK_WAIT_EVENT, eventMask, timeout);
⑻      ret = runTask->ops->wait(runTask, &eventCB->stEventList, timeout);
⑼      if (ret == LOS_ERRNO_TSK_TIMEOUT) {
            return LOS_ERRNO_EVENT_READ_TIMEOUT;
        }
⑽      ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode);
    }
    return ret;
}
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;
    }
    SCHEDULER_LOCK(intSave);
    ret = OsEventReadImp(eventCB, eventMask, mode, timeout, once);
    SCHEDULER_UNLOCK(intSave);
    return ret;
}
LITE_OS_SEC_TEXT UINT32 LOS_EventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout)
{
    return OsEventRead(eventCB, eventMask, mode, timeout, FALSE);
}

写入指定的事件类型

我们可以使用函数UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)来写入指定的事件类型。LOS_EventWrite函数调用一系列内部函数OsEventWrite–>OsEventWriteUnsafe–>OsEventResume–>OsEventResume。

我们先看下函数OsEventResume,函数名字为啥是resume?感觉应该是consume。该函数用于处理写入事件后,阻塞的任务如何读取、消费事件。⑴处分不同的读取模式判断事件是否符合任务resumedTask读取事件的要求,如果满足读取事件,则设置退出标记exitFlag,然后执行⑵更改任务状态并放入就绪队列等待唤醒。函数OsEventWriteUnsafe实现事件写入,⑶处代码把事件结构体的事件掩码和要写入的事件类型events进行逻辑或计算,来完成事件的写入。⑷如果等待事件的任务链表不为空,需要处理写入事件后是否有任务能读取到相应的事件。⑸处for循环依次遍历事件阻塞链表上的任务,⑹获取下一个任务nextTask。然后调用上文分析过的函数OsEventResume用于阻塞的任务读取事件。⑺如果有任务读取到事件,设置输出参数为1,用于标记是否需要触发任务调度。在函数OsEventWrite中,⑻处当标记需要触发任务调度时,会主动触发任务调度。

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;
        OsTaskWakeClearPendMask(resumedTask);
        resumedTask->ops->wake(resumedTask);
    }
    return exitFlag;
}
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;
    OsHookCall(LOS_HOOK_TYPE_EVENT_WRITE, eventCB, events);
⑶  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;
    if (eventCB == NULL) {
        return LOS_ERRNO_EVENT_PTR_NULL;
    }
    if (events & LOS_ERRTYPE_ERROR) {
        return LOS_ERRNO_EVENT_SETBIT_INVALID;
    }
    SCHEDULER_LOCK(intSave);
    OsEventWriteUnsafe(eventCB, events, once, &exitFlag);
    SCHEDULER_UNLOCK(intSave);

    if (exitFlag == 1) {
⑻      LOS_MpSchedule(OS_MP_CPU_ALL);
        LOS_Schedule();
    }
    return LOS_OK;
}
LITE_OS_SEC_TEXT UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)
{
    return OsEventWrite(eventCB, events, FALSE);
}

(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;
    }
    OsHookCall(LOS_HOOK_TYPE_EVENT_CLEAR, eventCB, eventMask);
    SCHEDULER_LOCK(intSave);
⑴  eventCB->uwEventID &= eventMask;
    SCHEDULER_UNLOCK(intSave);
    return LOS_OK;
}

(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;
    }
⑴  SCHEDULER_LOCK(intSave);
    if (!LOS_ListEmpty(&eventCB->stEventList)) {
        SCHEDULER_UNLOCK(intSave);
        return LOS_ERRNO_EVENT_SHOULD_NOT_DESTROY;
    }
    eventCB->uwEventID = 0;
⑵  LOS_ListDelInit(&eventCB->stEventList);
    SCHEDULER_UNLOCK(intSave);
    OsHookCall(LOS_HOOK_TYPE_EVENT_DESTROY, eventCB);
    return LOS_OK;
}

本文带领大家一起剖析了鸿蒙轻内核的事件模块的源代码,包含事件的结构体、事件初始化、事件创建删除、申请释放等。

​想了解更多内容,请访问:​

​51CTO和华为官方合作共建的鸿蒙技术社区​

​https://ost.51cto.com​

71eea7105a1cf9982d2996c42d853b97bd50ef.jpg


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK