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

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

3天内不再提示

鸿蒙内核源码分析之时钟节拍的实现方式

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

时钟概念

时间是非常重要的概念,我们整个学生阶段有个东西很重要,就是校园铃声. 它控制着上课,下课,吃饭,睡觉的节奏.没有它学校的管理就乱套了,老师拖课想拖多久就多久,那可不行,下课铃声一响就是在告诉老师时间到了,该停止了让学生HAPPY去了.

操作系统也一样,需要通过时间来规范其任务的执行,操作系统中最小的时间单位是时钟节拍 (OS Tick)。任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等。时钟节拍是特定的周期性中断,这个中断可以看做是系统心跳,中断之间的时间间隔取决于不同的应用,一般是 1ms–100ms,时钟节拍率越快,系统的实时响应越快,但是系统的额外开销就越大,从系统启动开始计数的时钟节拍数称为系统时间。

鸿蒙内核中,时钟节拍的长度可以根据 LOSCFG_BASE_CORE_TICK_PER_SECOND 的定义来调整,等于 1/LOSCFG_BASE_CORE_TICK_PER_SECOND 秒。

时钟节拍的实现方式

时钟节拍由配置为中断触发模式的硬件定时器产生,当中断到来时,将调用一次:void OsTickHandler(void),通知操作系统已经过去一个系统时钟;不同硬件定时器中断实现都不同,

/**
 * @ingroup los_config
 * Number of Ticks in one second
 */
#ifndef LOSCFG_BASE_CORE_TICK_PER_SECOND
#define LOSCFG_BASE_CORE_TICK_PER_SECOND 100 //默认每秒100次触发,当然这是可以改的
#endif

每秒100个tick,时间单位为10毫秒, 即每秒调用时钟中断处理程序100次.

/*
 * Description : Tick interruption handler
 *///节拍中断处理函数 ,鸿蒙默认10ms触发一次
LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
{
    //...
    OsTimesliceCheck();//进程和任务的时间片检查
    OsTaskScan(); /* task timeout scan *///任务扫描
#if (LOSCFG_BASE_CORE_SWTMR == YES)
    OsSwtmrScan();//定时器扫描,看是否有超时的定时器
#endif
}

它主要干了三件事情

第一:检查当前任务的时间片,任务执行一次分配多少时间呢?答案是2个时间片,即 20ms.

#ifndef LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT
#define LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT 2 //2个时间片,20ms
#endif
//检查进程和任务的时间片,如果没有时间片了直接调度
LITE_OS_SEC_TEXT VOID OsTimesliceCheck(VOID)
{
    LosTaskCB *runTask = NULL;
    LosProcessCB *runProcess = OsCurrProcessGet();//获取当前进程
    if (runProcess->policy != LOS_SCHED_RR) {//进程调度算法是否是抢占式
        goto SCHED_TASK;//进程不是抢占式调度直接去检查任务的时间片
    }

    if (runProcess->timeSlice != 0) {//进程还有时间片吗?
        runProcess->timeSlice--;//进程时间片减少一次
        if (runProcess->timeSlice == 0) {//没有时间片了
            LOS_Schedule();//进程时间片用完,发起调度
        }
    }

SCHED_TASK:
    runTask = OsCurrTaskGet();//获取当前任务
    if (runTask->policy != LOS_SCHED_RR) {//任务调度算法是否是抢占式
        return;//任务不是抢占式调度直接结束检查
    }

    if (runTask->timeSlice != 0) {//任务还有时间片吗?
        runTask->timeSlice--;//任务时间片也减少一次
        if (runTask->timeSlice == 0) {//没有时间片了
            LOS_Schedule();//任务时间片用完,发起调度
        }
    }
}

第二:扫描任务,主要是检查被阻塞的任务是否可以被重新调度

LITE_OS_SEC_TEXT VOID OsTaskScan(VOID)
{
    SortLinkList *sortList = NULL;
    LosTaskCB *taskCB = NULL;
    BOOL needSchedule = FALSE;
    UINT16 tempStatus;
    LOS_DL_LIST *listObject = NULL;
    SortLinkAttribute *taskSortLink = NULL;

    taskSortLink = &OsPercpuGet()->taskSortLink;//获取任务的排序链表
    taskSortLink->cursor = (taskSortLink->cursor + 1) & OS_TSK_SORTLINK_MASK;
    listObject = taskSortLink->sortLink + taskSortLink->cursor;//只处理这个游标上的链表,因为系统对超时任务都已经规链表了.
 //当任务因超时而挂起时,任务块处于超时排序链接上,(每个cpu)和ipc(互斥锁、扫描电镜等)的块同时被唤醒
    /*不管是超时还是相应的ipc,它都在等待。现在使用synchronize sortlink precedure,因此整个任务扫描需要保护,防止另一个核心同时删除sortlink。
     * When task is pended with timeout, the task block is on the timeout sortlink
     * (per cpu) and ipc(mutex,sem and etc.)'s block at the same time, it can be waken
     * up by either timeout or corresponding ipc it's waiting.
     *
     * Now synchronize sortlink preocedure is used, therefore the whole task scan needs
     * to be protected, preventing another core from doing sortlink deletion at same time.
     */
    LOS_SpinLock(&g_taskSpin);

    if (LOS_ListEmpty(listObject)) {
        LOS_SpinUnlock(&g_taskSpin);
        return;
    }
    sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);//拿本次Tick对应链表的SortLinkList的第一个节点sortLinkNode
    ROLLNUM_DEC(sortList->idxRollNum);//滚动数--

    while (ROLLNUM(sortList->idxRollNum) == 0) {//找到时间到了节点,注意这些节点都是由定时器产生的,
        LOS_ListDelete(&sortList->sortLinkNode);
        taskCB = LOS_DL_LIST_ENTRY(sortList, LosTaskCB, sortList);//拿任务,这里的任务都是超时任务
        taskCB->taskStatus &= ~OS_TASK_STATUS_PEND_TIME;
        tempStatus = taskCB->taskStatus;
        if (tempStatus & OS_TASK_STATUS_PEND) {
            taskCB->taskStatus &= ~OS_TASK_STATUS_PEND;
#if (LOSCFG_KERNEL_LITEIPC == YES)
            taskCB->ipcStatus &= ~IPC_THREAD_STATUS_PEND;
#endif
            taskCB->taskStatus |= OS_TASK_STATUS_TIMEOUT;
            LOS_ListDelete(&taskCB->pendList);
            taskCB->taskSem = NULL;
            taskCB->taskMux = NULL;
        } else {
            taskCB->taskStatus &= ~OS_TASK_STATUS_DELAY;
        }

        if (!(tempStatus & OS_TASK_STATUS_SUSPEND)) {
            OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB, OS_PROCESS_STATUS_PEND);
            needSchedule = TRUE;
        }

        if (LOS_ListEmpty(listObject)) {
            break;
        }

        sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
    }

    LOS_SpinUnlock(&g_taskSpin);

    if (needSchedule != FALSE) {//需要调度
        LOS_MpSchedule(OS_MP_CPU_ALL);//核间通讯,给所有CPU发送调度信号
        LOS_Schedule();//开始调度
    }
}

第三:定时器扫描,看是否有超时的定时器

/*
 * Description: Tick interrupt interface module of software timer
 * Return     : LOS_OK on success or error code on failure
 *///OsSwtmrScan 由系统时钟中断处理函数调用
LITE_OS_SEC_TEXT VOID OsSwtmrScan(VOID)//扫描定时器,如果碰到超时的,就放入超时队列
{
    SortLinkList *sortList = NULL;
    SWTMR_CTRL_S *swtmr = NULL;
    SwtmrHandlerItemPtr swtmrHandler = NULL;
    LOS_DL_LIST *listObject = NULL;
    SortLinkAttribute* swtmrSortLink = &OsPercpuGet()->swtmrSortLink;//拿到当前CPU的定时器链表

    swtmrSortLink->cursor = (swtmrSortLink->cursor + 1) & OS_TSK_SORTLINK_MASK;
    listObject = swtmrSortLink->sortLink + swtmrSortLink->cursor;
 //由于swtmr是在特定的sortlink中,所以需要很小心的处理它,但其他CPU Core仍然有机会处理它,比如停止计时器
    /*
     * it needs to be carefully coped with, since the swtmr is in specific sortlink
     * while other cores still has the chance to process it, like stop the timer.
     */
    LOS_SpinLock(&g_swtmrSpin);

    if (LOS_ListEmpty(listObject)) {
        LOS_SpinUnlock(&g_swtmrSpin);
        return;
    }
    sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
    ROLLNUM_DEC(sortList->idxRollNum);

    while (ROLLNUM(sortList->idxRollNum) == 0) {
        sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
        LOS_ListDelete(&sortList->sortLinkNode);
        swtmr = LOS_DL_LIST_ENTRY(sortList, SWTMR_CTRL_S, stSortList);

        swtmrHandler = (SwtmrHandlerItemPtr)LOS_MemboxAlloc(g_swtmrHandlerPool);//取出一个可用的软时钟处理项
        if (swtmrHandler != NULL) {
            swtmrHandler->handler = swtmr->pfnHandler;
            swtmrHandler->arg = swtmr->uwArg;

            if (LOS_QueueWrite(OsPercpuGet()->swtmrHandlerQueue, swtmrHandler, sizeof(CHAR *), LOS_NO_WAIT)) {
                (VOID)LOS_MemboxFree(g_swtmrHandlerPool, swtmrHandler);
            }
        }

        if (swtmr->ucMode == LOS_SWTMR_MODE_ONCE) {
            OsSwtmrDelete(swtmr);

            if (swtmr->usTimerID < (OS_SWTMR_MAX_TIMERID - LOSCFG_BASE_CORE_SWTMR_LIMIT)) {
                swtmr->usTimerID += LOSCFG_BASE_CORE_SWTMR_LIMIT;
            } else {
                swtmr->usTimerID %= LOSCFG_BASE_CORE_SWTMR_LIMIT;
            }
        } else if (swtmr->ucMode == LOS_SWTMR_MODE_NO_SELFDELETE) {
            swtmr->ucState = OS_SWTMR_STATUS_CREATED;
        } else {
            swtmr->ucOverrun++;
            OsSwtmrStart(swtmr);
        }

        if (LOS_ListEmpty(listObject)) {
            break;
        }

        sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
    }

    LOS_SpinUnlock(&g_swtmrSpin);
}

最后看调度算法的实现

//调度算法的实现
VOID OsSchedResched(VOID)
{
    LosTaskCB *runTask = NULL;
    LosTaskCB *newTask = NULL;
    LosProcessCB *runProcess = NULL;
    LosProcessCB *newProcess = NULL;
    LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//必须持有任务自旋锁,自旋锁是不是进程层面去抢锁,而是CPU各自核之间去争夺锁

    if (!OsPreemptableInSched()) {//是否置了重新调度标识位
        return;
    }
    runTask = OsCurrTaskGet();//获取当前任务
    newTask = OsGetTopTask();//获取优先级最最最高的任务
    /* always be able to get one task */
    LOS_ASSERT(newTask != NULL);//不能没有需调度的任务
    if (runTask == newTask) {//当前任务就是最高任务,那还调度个啥的,直接退出.
        return;
    }
    runTask->taskStatus &= ~OS_TASK_STATUS_RUNNING;//当前任务状态位置成不在运行状态
    newTask->taskStatus |= OS_TASK_STATUS_RUNNING;//最高任务状态位置成在运行状态
    runProcess = OS_PCB_FROM_PID(runTask->processID);//通过进程ID索引拿到进程实体
    newProcess = OS_PCB_FROM_PID(newTask->processID);//同上
    OsSchedSwitchProcess(runProcess, newProcess);//切换进程,里面主要涉及进程空间的切换,也就是MMU的上下文切换.
#if (LOSCFG_KERNEL_SMP == YES)//CPU多核的情况
    /* mask new running task's owner processor */
    runTask->currCpu = OS_TASK_INVALID_CPUID;//当前任务不占用CPU
    newTask->currCpu = ArchCurrCpuid();//让新任务占用CPU
#endif
    (VOID)OsTaskSwitchCheck(runTask, newTask);//切换task的检查
#if (LOSCFG_KERNEL_SCHED_STATISTICS == YES)
    OsSchedStatistics(runTask, newTask);
#endif
    if ((newTask->timeSlice == 0) && (newTask->policy == LOS_SCHED_RR)) {//没有时间片且是抢占式调度的方式,注意 非抢占式都不需要时间片的.
        newTask->timeSlice = LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT;//给新任务时间片 默认 20ms
    }
    OsCurrTaskSet((VOID*)newTask);//设置新的task为CPU核的当前任务
    if (OsProcessIsUserMode(newProcess)) {//用户模式下会怎么样?
        OsCurrUserTaskSet(newTask->userArea);//设置task栈空间
    }
    /* do the task context switch */
    OsTaskSchedule(newTask, runTask);//切换任务上下文,注意OsTaskSchedule是一个汇编函数 见于 los_dispatch.s
}

编辑:hfy

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

    关注

    37

    文章

    6293

    浏览量

    121908
  • 定时器
    +关注

    关注

    23

    文章

    3148

    浏览量

    112103
  • 系统时钟
    +关注

    关注

    1

    文章

    30

    浏览量

    9020
  • 鸿蒙系统
    +关注

    关注

    183

    文章

    2631

    浏览量

    65281
收藏 人收藏

    评论

    相关推荐

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

    意义上所理解的线程呢。狭义上的后续有 鸿蒙内核源码分析(启动过程篇) 来说明。不知道大家有没有这种体会,学一个东西的过程中要接触很多新概念,尤其像 Java/android 的生态,概
    发表于 10-14 14:00

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

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

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

    设计的系统,高瞻远瞩,格局远大,设计精良, 知识点巨多, 把研究过程心得写成鸿蒙源码分析系列篇,如此 源码中文注释+系列篇文章 将加速理解鸿蒙
    发表于 11-19 10:32

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

    ,如此 源码中文注释+系列篇文章 将加速理解鸿蒙内核实现过程。系列篇文章 进入 >> 鸿蒙系统源码分析
    发表于 11-19 15:06

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

    注解|-鸿蒙内核源码分析(内存映射篇) | 虚拟内存和物理内存之间是怎么映射的|-鸿蒙内核
    发表于 11-20 11:24

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

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

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

    本文分析任务调度机制源码 详见:代码库建议先阅读阅读之前建议先读本系列其他文章,进入鸿蒙系统源码分析(总目录),以便对本文任务调度机制的理解
    发表于 11-23 10:53

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

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

    HarmonyOS内核源码分析(上)电子书-上线了

    `为方便大家开发鸿蒙系统,小编为大家编辑整理了一本HarmonyOS内核源码分析系列电子书,需要参考学习的朋友快来下吧!本电子书主要介绍如何给鸿蒙
    发表于 11-25 17:13

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

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

    浅谈鸿蒙内核源码的栈

    上面的代码和鸿蒙内核用栈方式一样,都采用了递减满栈的方式, 什么是递减满栈?
    的头像 发表于 04-24 11:21 1117次阅读
    浅谈<b class='flag-5'>鸿蒙</b><b class='flag-5'>内核</b><b class='flag-5'>源码</b>的栈

    鸿蒙内核源码分析鸿蒙内核的每段汇编代码解析

    本篇说清楚CPU的工作模式 读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇. 正如一个互联网项目的后台管理系统有权限管理一样,CPU工作是否也有权限(模式)? 一个成熟的软硬件架构
    的头像 发表于 03-02 09:56 3739次阅读
    <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><b class='flag-5'>内核</b>的每段汇编代码解析

    鸿蒙内核源码分析: 虚拟内存和物理内存是怎么管理的

    有了上篇鸿蒙内核源码分析(内存概念篇)的基础,本篇讲内存管理部分,本章源码超级多,很烧脑,但笔者关键处都加了注释。废话不多说,开始吧。内存一
    发表于 11-23 11:45 19次下载
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>内核</b><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次下载