阅读 124

鸿蒙内核源码分析(时钟任务篇) | 谁是触发调度的最大动力 | 百篇博客分析HarmonyOS源码 | v3.05

时钟概念

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

登录后复制


鸿蒙源码百篇博客 往期回顾

在给 鸿蒙内核源码加中文注释 过程中,整理出以下文章.内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感.百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思.更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了.:P

写文章比写代码累多了,越深入研究,越觉得没写好,所以文章和注解会反复修正, .xx代表修改的次数, 将持续完善源码注解和文档内容,精雕细琢,言简意赅, 尽全力打磨精品内容.

  • v50.xx (编译环境篇) | 编译鸿蒙看这篇或许真的够了 < csdn | 51cto | osc >

  • v49.xx (信号消费篇) | 用户栈到内核栈的两次切换 < csdn | 51cto | osc >

  • v48.xx (信号生产篇) | 如何安装和发送信号? < csdn | 51cto | osc >

  • v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | osc >

  • v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | osc >

  • v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 ? < csdn | 51cto | osc >

  • v44.xx (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | 51cto | osc >

  • v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | osc >

  • v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | osc >

  • v41.xx (任务切换篇) | 汇编告诉任务到底在切换什么 < csdn | 51cto | osc >

  • v40.xx (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | 51cto | osc >

  • v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | osc >

  • v38.xx (寄存器篇) | arm所有寄存器一网打尽,不再神秘 < csdn | 51cto | osc >

  • v37.xx (系统调用篇) | 系统调用到底经历了什么 < csdn | 51cto | osc >

  • v36.xx (工作模式篇) | cpu是韦小宝,有哪七个老婆? < csdn | 51cto | osc >

  • v35.xx (时间管理篇) | tick是操作系统的基本时间单位 < csdn | 51cto | osc >

  • v34.xx (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | 51cto | osc >

  • v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | 51cto | osc >

  • v32.xx (cpu篇) | 整个内核就是一个死循环 < csdn | 51cto | osc >

  • v31.xx (定时器篇) | 内核最高优先级任务是谁? < csdn | 51cto | osc >

  • v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | osc >

  • v29.xx (信号量篇) | 信号量解决任务同步问题 < csdn | 51cto | osc >

  • v28.xx (进程通讯篇) | 九种进程间通讯方式速揽 < csdn | 51cto | osc >

  • v27.xx (互斥锁篇) | 比自旋锁丰满许多的互斥锁 < csdn | 51cto | osc >

  • v26.xx (自旋锁篇) | 自旋锁当立贞节牌坊! < csdn | 51cto | osc >

  • v25.xx (并发并行篇) | 怎么记住并发并行的区别? < csdn | 51cto | osc >

  • v24.xx (进程概念篇) | 进程在管理哪些资源? < csdn | 51cto | osc >

  • v23.xx (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | 51cto | osc >

  • v22.xx (汇编基础篇) | cpu在哪里打卡上班? < csdn | 51cto | osc >

  • v21.xx (线程概念篇) | 是谁在不断的折腾cpu? < csdn | 51cto | osc >

  • v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | osc >

  • v19.xx (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | 51cto | osc >

  • v18.xx (源码结构篇) | 梳理内核源文件的作用和含义 < csdn | 51cto | osc >

  • v17.xx (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | 51cto | osc >

  • v16.xx (内存规则篇) | 内存管理到底在管什么? < csdn | 51cto | osc >

  • v15.xx (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | 51cto | osc >

  • v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | 51cto | osc >

  • v13.xx (源码注释篇) | 鸿蒙必须成功,也必然成功 < csdn | 51cto | osc >

  • v12.xx (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | 51cto | osc >

  • v11.xx (内存分配篇) | 内存有哪些分配方式? < csdn | 51cto | osc >

  • v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | 51cto | osc >

  • v09.xx (调度故事篇) | 用故事说内核调度过程 < csdn | 51cto | osc >

  • v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | osc >

  • v07.xx (调度机制篇) | 任务是如何被调度执行的? < csdn | 51cto | osc >

  • v06.xx (调度队列篇) | 内核有多少个调度队列? < csdn | 51cto | osc >

  • v05.xx (任务管理篇) | 任务池是如何管理的? < csdn | 51cto | osc >

  • v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | osc >

  • v03.xx (时钟任务篇) | 谁是触发调度的最大动力 < csdn | 51cto | osc >

  • v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | osc >

  • v01.xx (双向链表篇) | 谁是内核最重要结构体 < csdn | 51cto | osc >

进入 >> osc | csdn | 51cto | 掘金 | 公众号 | 头条号 | gitee | github

各大站点搜 "鸿蒙内核源码分析" .原创不易,欢迎转载,但请注明出处.

热爱是所有的理由和答案 - turing

©著作权归作者所有:来自51CTO博客作者weharmony的原创作品,如需转载,请注明出处,否则将追究法律责任


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐