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

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

3天内不再提示

鸿蒙内核源码:谁来触发调度工作?

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

为什么学个东西要学那么多的概念?

鸿蒙的内核中 Task和 线程在广义上可以理解为是一个东西,但狭义上肯定会有区别,区别在于管理体系的不同,Task是调度层面的概念,线程是进程层面概念。比如 main()函数中首个函数 OsSetMainTask();就是设置启动任务,但此时啥都还没开始呢,Kprocess进程都没创建,怎么会有大家一般意义上所理解的线程呢。狭义上的后续有鸿蒙内核源码分析(启动过程篇)来说明。不知道大家有没有这种体会,学一个东西的过程中要接触很多新概念,尤其像 Java/android 的生态,概念贼多,很多同学都被绕在概念中出不来,痛苦不堪。那问题是为什么需要这么多的概念呢?

举个例子就明白了:

假如您去深圳参加一个面试老板问你哪里人?你会说是江西人,湖南人...而不会说是张家村二组的张全蛋,这样还谁敢要你。但如果你参加同乡会别人问你同样问题,你不会说是来自东北那旮沓的,却反而要说张家村二组的张全蛋。明白了吗?张全蛋还是那个张全蛋,但因为场景变了,您的说法就得必须跟着变,否则没法愉快的聊天。程序设计就是源于生活,归于生活,大家对程序的理解就是要用生活中的场景去打比方,更好的理解概念。

那在内核的调度层面,咱们只说task,task是内核调度的单元,调度就是围着它转。

进程和线程的状态迁移图

先看看task从哪些渠道产生:

pIYBAGCDhyqAWWxqAAC5d6zzcxM391.png

渠道很多,可能是shell的一个命令,也可能由内核创建,更多的是大家编写应用程序new出来的一个线程。

调度的内容task已经有了,那他们是如何被有序调度的呢?答案:是32个进程和线程就绪队列,各32个哈,为什么是32个,鸿蒙系统源码分析(总目录) 文章里有详细说明,自行去翻。这张进程状态迁移示意图一定要看明白.

注意:进程和线程的队列内的内容只针对就绪状态,其他状态内核并没有用队列去描述它,(线程的阻塞状态用的是pendlist链表),因为就绪就意味着工作都准备好了就等着被调度到CPU来执行了。所以理解就绪队列很关键,有三种情况会加入就绪队列。

pIYBAGCDh0GAJo5tAAAmNpJIbNI565.png

Init→Ready:

进程创建或fork时,拿到该进程控制块后进入Init状态,处于进程初始化阶段,当进程初始化完成将进程插入调度队列,此时进程进入就绪状态。

Pend→Ready / Pend→Running:

阻塞进程内的任意线程恢复就绪态时,进程被加入到就绪队列,同步转为就绪态,若此时发生进程切换,则进程状态由就绪态转为运行态。

Running→Ready:

进程由运行态转为就绪态的情况有以下两种:

有更高优先级的进程创建或者恢复后,会发生进程调度,此刻就绪列表中最高优先级进程变为运行态,那么原先运行的进程由运行态变为就绪态。

若进程的调度策略为SCHED_RR,且存在同一优先级的另一个进程处于就绪态,则该进程的时间片消耗光之后,该进程由运行态转为就绪态,另一个同优先级的进程由就绪态转为运行态。

谁来触发调度工作?

就绪队列让task各就各位,在其生命周期内不停的进行状态流转,调度是让task交给CPU处理,那又是什么让调度去工作的呢?它是如何被触发的?

笔者能想到的触发方式是以下四个:

Tick(时钟管理),类似于JAVA的定时任务,时间到了就触发。系统定时器是内核时间机制中最重要的一部分,它提供了一种周期性触发中断机制,即系统定时器以HZ(时钟节拍率)为频率自行触发时钟中断。当时钟中断发生时,内核就通过时钟中断处理程序OsTickHandler对其进行处理。鸿蒙内核默认是10ms触发一次,执行以下中断函数:

/*
 * Description : Tick interruption handler
 */
LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
{
    UINT32 intSave;

    TICK_LOCK(intSave);
    g_tickCount[ArchCurrCpuid()]++;
    TICK_UNLOCK(intSave);

#ifdef LOSCFG_KERNEL_VDSO
    OsUpdateVdsoTimeval();
#endif

#ifdef LOSCFG_KERNEL_TICKLESS
    OsTickIrqFlagSet(OsTicklessFlagGet());
#endif

#if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES)
    HalClockIrqClear(); /* diff from every platform */
#endif

    OsTimesliceCheck();//时间片检查

    OsTaskScan(); /* task timeout scan *///任务扫描,发起调度

#if (LOSCFG_BASE_CORE_SWTMR == YES)
    OsSwtmrScan();//软时钟扫描检查
#endif
}

里面对任务进行了扫描,时间片到了或就绪队列有高或同级task,会执行调度。

第二个是各种软硬中断,如何USB插拔,键盘,鼠标这些外设引起的中断,需要去执行中断处理函数。

第三个是程序主动中断,比如运行过程中需要申请其他资源,而主动让出控制权,重新调度。

最后一个是创建一个新进程或新任务后主动发起的抢占式调度,新进程会默认创建一个main task, task的首条指令(入口函数)就是我们上层程序的main函数,它被放在代码段的第一的位置。

哪些地方会申请调度?看一张图。

o4YBAGCDh06ABuMFAADGKhz-zEg614.png

这里提下图中的OsCopyProcess(),这是fork进程的主体函数,可以看出fork之后立即申请了一次调度。

LITE_OS_SEC_TEXT INT32 LOS_Fork(UINT32 flags, const CHAR *name, const TSK_ENTRY_FUNC entry, UINT32 stackSize)
{
    UINT32 cloneFlag = CLONE_PARENT | CLONE_THREAD | CLONE_VFORK | CLONE_FILES;

    if (flags & (~cloneFlag)) {
        PRINT_WARN("Clone dont support some flags!\n");
    }

    flags |= CLONE_FILES;
    return OsCopyProcess(cloneFlag & flags, name, (UINTPTR)entry, stackSize);
}

STATIC INT32 OsCopyProcess(UINT32 flags, const CHAR *name, UINTPTR sp, UINT32 size)
{
    UINT32 intSave, ret, processID;
    LosProcessCB *run = OsCurrProcessGet();

    LosProcessCB *child = OsGetFreePCB();
    if (child == NULL) {
        return -LOS_EAGAIN;
    }
    processID = child->processID;

    ret = OsForkInitPCB(flags, child, name, sp, size);
    if (ret != LOS_OK) {
        goto ERROR_INIT;
    }

    ret = OsCopyProcessResources(flags, child, run);
    if (ret != LOS_OK) {
        goto ERROR_TASK;
    }

    ret = OsChildSetProcessGroupAndSched(child, run);
    if (ret != LOS_OK) {
        goto ERROR_TASK;
    }

    LOS_MpSchedule(OS_MP_CPU_ALL);
    if (OS_SCHEDULER_ACTIVE) {
        LOS_Schedule();// 申请调度
    }

    return processID;

ERROR_TASK:
    SCHEDULER_LOCK(intSave);
    (VOID)OsTaskDeleteUnsafe(OS_TCB_FROM_TID(child->threadGroupID), OS_PRO_EXIT_OK, intSave);
ERROR_INIT:
    OsDeInitPCB(child);
    return -ret;
}

原来创建一个进程这么简单,真的就是在COPY!

源码告诉你调度过程是怎样的

以上是需要提前了解的信息,接下来直接上源码看调度过程吧,文件就三个函数,主要就是这个了:

VOID OsSchedResched(VOID)
{
    LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//调度过程要上锁
    newTask = OsGetTopTask(); //获取最高优先级任务
    OsSchedSwitchProcess(runProcess, newProcess);//切换进程
    (VOID)OsTaskSwitchCheck(runTask, newTask);//任务检查
    OsCurrTaskSet((VOID*)newTask);//*设置当前任务
    if (OsProcessIsUserMode(newProcess)) {//判断是否为用户态,使用用户空间
        OsCurrUserTaskSet(newTask->userArea);//设置任务空间
    }
    /* do the task context switch */
    OsTaskSchedule(newTask, runTask); //切换CPU任务上下文,汇编代码实现
}

函数有点长,笔者留了最重要的几行,看这几行就够了,流程如下:

调度过程要自旋锁,多核情况下只能被一个CPU core执行. 不允许任何中断发生, 没错,说的是任何事是不能去打断它,否则后果太严重了,这可是内核在切换进程和线程的操作啊。

在就绪队列里找个最高优先级的task

切换进程,就是task归属的那个进程设为运行进程,这里要注意,老的task和老进程只是让出了CPU指令执行权,其他都还在内存,资源也都没有释放.

设置新任务为当前任务

用户模式下需要设置task运行空间,因为每个task栈是不一样的.空间部分具体在系列篇内存中查看

是最重要的,切换任务上下文,参数是新老两个任务,一个要保存现场,一个要恢复现场。

什么是任务上下文?鸿蒙内核源码分析(总目录)任务切换篇已有详细的描述,请自行翻看.

读懂OsGetTopTask()

读懂OsGetTopTask(),就明白了就绪队列是怎么回事了。这里提下goto语句,几乎所有内核代码都会大量的使用goto语句,鸿蒙内核有617个goto远大于264个break,还有人说要废掉goto,你知道内核开发者青睐goto的真正原因吗?

LITE_OS_SEC_TEXT_MINOR LosTaskCB *OsGetTopTask(VOID)
{
    UINT32 priority, processPriority;
    UINT32 bitmap;
    UINT32 processBitmap;
    LosTaskCB *newTask = NULL;
#if (LOSCFG_KERNEL_SMP == YES)
    UINT32 cpuid = ArchCurrCpuid();
#endif
    LosProcessCB *processCB = NULL;
    processBitmap = g_priQueueBitmap;
    while (processBitmap) {
        processPriority = CLZ(processBitmap);
        LOS_DL_LIST_FOR_EACH_ENTRY(processCB, &g_priQueueList[processPriority], LosProcessCB, pendList) {
            bitmap = processCB->threadScheduleMap;
            while (bitmap) {
                priority = CLZ(bitmap);
                LOS_DL_LIST_FOR_EACH_ENTRY(newTask, &processCB->threadPriQueueList[priority], LosTaskCB, pendList) {
#if (LOSCFG_KERNEL_SMP == YES)
                    if (newTask->cpuAffiMask & (1U << cpuid)) {
#endif
                        newTask->taskStatus &= ~OS_TASK_STATUS_READY;
                        OsPriQueueDequeue(processCB->threadPriQueueList,
                                          &processCB->threadScheduleMap,
                                          &newTask->pendList);
                        OsDequeEmptySchedMap(processCB);
                        goto OUT;
#if (LOSCFG_KERNEL_SMP == YES)
                    }
#endif
                }
                bitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - priority - 1));
            }
        }
        processBitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - processPriority - 1));
    }

OUT:
    return newTask;
}

#ifdef __cplusplus
#if __cplusplus
}

编辑:hfy

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

    关注

    68

    文章

    10442

    浏览量

    206564
  • 线程
    +关注

    关注

    0

    文章

    489

    浏览量

    19495
  • 进程
    +关注

    关注

    0

    文章

    193

    浏览量

    13876
收藏 人收藏

    评论

    相关推荐

    鸿蒙内核源码Task/线程技术分析

    、使用内存空间等系统资源,并独立于其它线程运行。 鸿蒙内核每个进程内的线程独立运行、独立调度,当前进程内线程的调度不受其它进程内线程的影响。 鸿蒙
    的头像 发表于 10-18 10:42 1950次阅读
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>内核</b><b class='flag-5'>源码</b>Task/线程技术分析

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

    详见:../kernel/base/sched/sched_sq/los_sched.c目录建议先阅读为什么学一个东西要学那么多的概念?进程和线程的状态迁移图谁来触发调度工作
    发表于 10-14 14:00

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

    内核源码分析(时钟管理篇) | 触发调度最大的源动力|-鸿蒙内核
    发表于 11-20 11:24

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

    工作原理!操作系统就是管理场馆和确保工作人员有序工作的系统解决方案商,外面公司只要提供个节目单,就能按节目单把这台戏演好给广大观众观看。有了这个故事垫底,鸿蒙
    发表于 11-23 10:15

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

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

    鸿蒙内核源码分析(调度队列篇):进程和Task的就绪队列对调度的作用

    的数据结构实现采用的都是双向循环链表,LOS_DL_LIST实在是太重要了,是理解鸿蒙内核的关键,说是最重要的代码一点也不为过,源码出现在 sched_sq模块,说明是用于任务的调度
    发表于 11-23 11:09

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

    独立运行、独立调度,当前进程内线程的调度不受其它进程内线程的影响。鸿蒙内核中的线程采用抢占式调度机制,同时支持时间片轮转
    发表于 11-23 14:01

    鸿蒙内核源码分析(时钟管理篇):时钟是触发调度最大的源动力

    !除了钟响大爷去工作之外,还有什么情况启用大爷不要偷懒,起来走两步呢?除了tick会触发调度,还有一些情况会触发调度?全部文章进入>>
    发表于 11-24 10:01

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

    等待CPU、使用内存空间等系统资源,并独立于其它线程运行。鸿蒙内核每个进程内的线程独立运行、独立调度,当前进程内线程的调度不受其它进程内线程的影响。
    发表于 11-24 10:24

    HarmonyOS内核源码分析(中)——电子书上线啦!

    内核运行机制;3.精确了解鸿蒙内核内存的管理方式、调度方式;4.轻松快速阅读鸿蒙内核
    发表于 12-18 12:00

    鸿蒙内核源码分析(调度故事篇)

    不在这里说明,后续也有专门讲这块的故事.故事想说什么呢?故事到底想说什么呢?这就是操作系统的调度机制,熟悉了这个故事就熟悉了鸿蒙系统内核任务调度
    发表于 05-25 11:50

    鸿蒙内核源码分析:task是内核调度的单元

    从系统的角度看,线程是竞争系统资源的最小运行单元。线程可以使用或等待CPU、使用内存空间等系统资源,并独立于其它线程运行。 鸿蒙内核每个进程内的线程独立运行、独立调度,当前进程内线程的调度
    发表于 11-23 15:51 22次下载
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>内核</b><b class='flag-5'>源码</b>分析:task是<b class='flag-5'>内核</b><b class='flag-5'>调度</b>的单元

    鸿蒙内核源码分析:时钟是触发调度最大的源动力

    时钟管理模块很简单,但却有内核最重要的代码段 OsTickHandler(),这是干嘛的,可以理解为 JAVA的定时任务,但这是系统内核的定时器。因鸿蒙目前开放的是 轻量级的内核 li
    发表于 11-24 17:50 31次下载
    <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>最大的源动力

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

    为何鸿蒙内核源码分析系列开篇就说 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>最重要结构体

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

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