关于openharmony:OpenHarmony内核对象事件之源码详解

108次阅读

共计 4681 个字符,预计需要花费 12 分钟才能阅读完成。

近年来,国内开源实现跨越式倒退,并成为企业晋升创新能力、生产力、合作和透明度的要害。作为 OpenAtom OpenHarmony(以下简称“OpenHarmony”)开源我的项目共建单位之一,深开鸿以成为智能物联网操作系统领军者为战略目标,基于 OpenHarmony 聚焦智能物联网操作系统(KaihongOS)的技术研发与继续翻新。
身为深开鸿 OS 内核开发师,咱们长年深耕于 OpenHarmony 的内核开发,心愿通过分享一些工作上的教训,帮忙大家把握开源常识。
OpenHarmony LiteOS-M 内核是面向 IoT 畛域构建的轻量级物联网操作系统内核,具备小体积、低功耗、高性能的特点,其代码构造简略,实现了过程、线程、内存等管理机制,提供了常见工作间 IPC、软定时器等公共模块,大幅度降低了嵌入式设施开发的难度。目前 OpenHarmony 的事件提供一种工作间的 IPC,即一个或多个工作能够通过写一个或多个不同的事件来触发内核调度,让另一个期待读取事件的工作进入运行状态,从而实现工作间的同步。
对于嵌入式开发工作人员和技术爱好者来说,深刻理解常见工作间 IPC,有助于学习和研发内核。本文将从数据结构和算法解析 OpenHarmony 的事件机制,带大家深刻理解内核工作间 IPC 原理。
要害数据结构
在解读事件的源码之前,首先理解下事件的要害的数据结构 PEVENT_CB_S:

typedef struct tagEvent {
    UINT32 uwEventID;       
    LOS_DL_LIST stEventList; /**< Event control block linked list */  
} EVENT_CB_S, *PEVENT_CB_S;

uwEventID:即标记工作的事件类型,每个 bit 能够标识一个事件,最多反对 31 个事件(第 25bit 保留)。
stEventList:即事件管制块的双向循环链表,了解这个字段是了解事件的要害。在双向循环链表中惟一不变的节点就是头节点,而这里的 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, eventCB);
    return LOS_OK;
}

PEVENT_CB_S 相当于 EVENT_CB_S *,因而 eventCB 是指针。
阐明事件管制块由工作本人创立,内核事件模块只负责保护。工作定义本人的事件管制块变量,通过 LOS_EventInit 初始化,此时没有事件产生,事件链表为空。
用图来表白就是:

事件写操作
工作能够通过 LOS_EventWrite 来写触发一个或多个事件:

LITE_OS_SEC_TEXT UINT32 LOS_EventWrite(PEVENT_CB_S eventCB, UINT32 events)
{
    ...
    eventCB->uwEventID |= events;                    ---1
    if (!LOS_ListEmpty(&eventCB->stEventList)) {     ---2
        for (resumedTask = LOS_DL_LIST_ENTRY((&eventCB->stEventList)->pstNext, LosTaskCB, pendList);
             &resumedTask->pendList != (&eventCB->stEventList);) { -------3
            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);       ---4
            }
            resumedTask = nextTask;
        }

        if (exitFlag == 1) {LOS_IntRestore(intSave);
            LOS_Schedule();                        ---5
            return LOS_OK;
        }
    }
    ...
}  

1 处,保留事件应用的或运算操作,因而一个或多个工作能够写一个或多个事件,写一次或屡次,而且每次为不同的事件,屡次写同一个事件相当于只写了一次;
2 处,有事件产生了就该查看是否有工作在期待事件,事件链表不为空阐明有工作在期待事件;
3 处,遍历事件链表,唤醒符合条件的工作。
LOS_DL_LIST_ENTRY((&eventCB->stEventList)->pstNext,LosTaskCB,pendList) 后面提到,头节点是空节点,第一次遍历从头节点的下一个节点开始,后续再顺次找出 nextTask,直到回到头节点;
4 处,针对事件读取模式,找到满足条件的工作并唤醒该工作;
5 处,一旦匹配到期待事件的工作,则执行任务调度,被唤醒的工作失去执行。
写事件实际操作如下图:

事件读操作
LiteOS 为用户提供了两个事件的函数:
● LOS_EventPoll():依据工作传入的事件值、掩码及校验模式,返回满足条件的事件,工作能够被动查看事件是否产生而不用被挂起;
● LOS_EventRead():读取事件,能够了解为阻塞式读,如果事件没有产生,能够指定等待时间,挂起当前任务。
上面是 LOS_EventPoll() 的实现:

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

1 处,如果读取模式是 LOS_WAITMODE_OR,只有有一个事件产生则读取胜利,返回产生的那个事件;
2 处,如果读取模式 LOS_WAITMODE_AND,全副查看事件产生才算读取胜利,并返回全副产生事件;
3 处,事件读取胜利后事件管制块中的事件标记怎么解决?这里通过 LOS_WAITMODE_CLR 来决定是否革除事件标记。
能够看出以上实现了两种事件的读取形式:一种是多个事件只有一个产生就算产生,另一种是全副事件产生才算产生。
上面是 LOS_EventRead():

LITE_OS_SEC_TEXT UINT32 LOS_EventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeOut)
{
    ...
    ret = LOS_EventPoll(&(eventCB->uwEventID), eventMask, mode);           ---1
    OsHookCall(LOS_HOOK_TYPE_EVENT_READ, eventCB, eventMask, mode, timeOut);
    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);                  ---2
        LOS_IntRestore(intSave);
        LOS_Schedule();                                                   ---3

        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);       ---4
    } 
    ...
}  

1 处,被动查问想要的事件是否曾经产生;
2 处,如果事件没有产生,就把当前任务挂起到期待事件链表中;
3 处,如果事件没有产生,以后读事件的工作被挂起,让出 CPU;
4 处,事件产生时期待事件的工作被调度再次取得 CPU 复原执行,读取事件。
事件读写整个过程串起来如下图所示:

事件销毁操作
做事善始善终,事件生产实现剩下的事件是革除事件和期待事件的工作链表。

LITE_OS_SEC_TEXT_MINOR UINT32 LOS_EventClear(PEVENT_CB_S eventCB, UINT32 eventMask)
{
    ...
    eventCB->uwEventID &= eventMask;
    ...
}


LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventDestroy(PEVENT_CB_S eventCB)
{
    ...
    eventCB->stEventList.pstNext = (LOS_DL_LIST *)NULL;
    eventCB->stEventList.pstPrev = (LOS_DL_LIST *)NULL;
    ...
}

在 LOS_EventClear 中通过使 eventMask=0 来清空事件,在 LOS_EventDestroy 中清空事件链表指针。
小结
看了下面的形容,置信大家对 OpenHarmony LiteOS-M 内核事件的运作机制有了更加粗浅的了解,开发者能够更好地应用事件的 API 来进行工作间的同步操作,也能够进一步尝试批改内核事件告诉机制,做出一个更适宜本人工作的 IPC 机制。
OpenHarmony 生态建设离不开每位开发者的参加,心愿有更多的开发者分享本人开源我的项目的教训和成绩,独特为 OpenHarmony 生态建设奉献一份力量。

正文完
 0