作者 | 深入研究鸿蒙,鸿蒙内核发烧友

出品 | CSDN(ID:CSDNnews)

头图 | CSDN 下载自东方 IC

阅读之前建议先读本系列其他文章,以便对本文任务调度机制的理解。

为什么要学这么多的相关概念?

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

举个例子就明白了:

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

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

进程和线程的状态迁移图

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

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

调度的内容 task 已经有了,那他们是如何有序的被调度?答案:是32个进程和线程就绪队列,各32个哈,为什么是32个,鸿蒙系统源码分析其他文章里有详细说明,自己去翻。这张进程状态迁移示意图一定要看明白,线程的状态迁移大家去官方文档看,不一一列出来,太多了占地方。

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

  • 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_VDSOOsUpdateVdsoTimeval();
#endif#ifdef LOSCFG_KERNEL_TICKLESSOsTickIrqFlagSet(OsTicklessFlagGet());
#endif#if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES)HalClockIrqClear(); /* diff from every platform */
#endifOsTimesliceCheck();OsTaskScan(); /* task timeout scan *///*kyf 任务扫描,发起调度#if (LOSCFG_BASE_CORE_SWTMR == YES)OsSwtmrScan();
#endif
}

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

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

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

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

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

这里提下图中的 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();//*kyf 申请调度}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 ! 这里抛个问题请大家思考,为何 创建进程用 copy ,创建线程用 new ?

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

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

VOID OsSchedResched(VOID)
{LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//*kyf 调度过程要上锁newTask = OsGetTopTask(); //*kyf 获取最高优先级任务OsSchedSwitchProcess(runProcess, newProcess);//*kyf 切换运行的进程(VOID)OsTaskSwitchCheck(runTask, newTask);OsCurrTaskSet((VOID*)newTask);//*kyf 设置当前任务if (OsProcessIsUserMode(newProcess)) {OsCurrUserTaskSet(newTask->userArea);//*kyf 运行空间}/* do the task context switch */OsTaskSchedule(newTask, runTask); //*kyf 切换任务上下文
}

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

  1. 调度过程要自旋锁,不允许任何中断发生,没错,说的是任何事是不能去打断它,否则后果太严重了,这可是内核在切换进程和线程的操作啊。

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

  3. 切换进程,就是 task/线程 归属的那个进程为当前进程,这里要注意,老的 task 和老进程只是让出了 CPU 指令执行权,其他都还在内存。

  4. 设置新任务为当前任务

  5. 用户模式下需要设置 task 运行空间,因为每个 task 栈是不一样的

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

什么是任务上下文?看鸿蒙系统源码分析其他文章,有专门的介绍。这里要说明的是在 CPU 的层面,它只认任务上下文!这里看不到任何代码了,因为这是跟 CPU 相关的,不同的 CPU 需要去适配不同的汇编代码,所以这些汇编代码不会出现在一个通用工程中。请留意后续鸿蒙内核源码分析(汇编指令篇)。

请读懂内核最美函数 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();
#endifLosProcessCB *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)) {
#endifnewTask->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
}

原文地址:https://blog.csdn.net/kuangyufei/article/details/108705968

点分享点点赞点在看

鸿蒙内核源码分析:调度机制篇相关推荐

  1. v09.04 鸿蒙内核源码分析(调度故事) | 用故事说内核调度 | 百篇博客分析HarmonyOS源码

    子曰:"吾与回言终日,不违如愚.退而省其私,亦足以发.回也,不愚."<论语>:为政篇 百篇博客系列篇.本篇为: v09.xx 鸿蒙内核源码分析(调度故事篇) | 用故事 ...

  2. v74.01 鸿蒙内核源码分析(编码方式篇) | 机器指令是如何编码的 | 百篇博客分析OpenHarmony源码

    Python微信订餐小程序课程视频 https://blog.csdn.net/m0_56069948/article/details/122285951 Python实战量化交易理财系统 https ...

  3. 鸿蒙内核分析,鸿蒙内核源码分析(中断概念篇) | 外人眼中权势滔天的当红海公公...

    关于中断部分系列篇将用三篇详细说明整个过程. ● 中断概念篇 中断概念很多,比如中断控制器,中断源,中断向量,中断共享,中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.本篇的主角是海公 ...

  4. 鸿蒙关键技术研究,鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 百篇博客分析HarmonyOS源码 | v54.02...

    百篇博客系列篇.本篇为: 下图是一个可执行文件编译,链接的过程. 本篇将通过一个完整的小工程来阐述ELF编译,链接过程,并分析.o和bin文件中各区,符号表之间的关系.从一个崭新的视角去看中间过程,阅 ...

  5. v86.01 鸿蒙内核源码分析 (静态分配篇) | 很简单的一位小朋友 | 百篇博客分析 OpenHarmony 源码

  6. 鸿蒙内核源码分析系列 | 读懂HarmonyOS内核源代码!

    本系列从HarmonyOS架构层视角整理成文, 并用生活场景及讲故事的方式试图去解构内核,一窥究竟.帮助你读懂并快速理解鸿蒙操作系统源码. 1.鸿蒙内核源码分析(调度机制篇) 2.鸿蒙内核源码分析(进 ...

  7. v03.06 鸿蒙内核源码分析(时钟任务) | 调度的源动力从哪来 | 百篇博客分析HarmonyOS源码

    子曰:"巧言.令色.足恭,左丘明耻之,丘亦耻之.匿怨而友其人,左丘明耻之,丘亦耻之."<论语>:公冶长篇 百篇博客系列篇.本篇为: v03.xx 鸿蒙内核源码分析(时钟 ...

  8. v05.05 鸿蒙内核源码分析(任务管理) | 如何管理任务池 | 百篇博客分析HarmonyOS源码

    曾子曰:"吾日三省吾身:为人谋而不忠乎?与朋友交而不信乎?传不习乎?"<论语>:学而篇 百篇博客系列篇.本篇为: v05.xx 鸿蒙内核源码分析(任务管理篇) | 如何 ...

  9. v21.07 鸿蒙内核源码分析(线程概念) | 是谁在不断的折腾CPU | 百篇博客分析OpenHarmony源码

    子曰:"若圣与仁,则吾岂敢.抑为之不厌,诲人不倦,则可谓云尔已矣." <论语>:述而篇 百篇博客系列篇.本篇为: v21.xx 鸿蒙内核源码分析(线程概念篇) | 是谁 ...

最新文章

  1. 微服务实战之春云与刀客(三)—— 面向接口调用代码结构实例
  2. 基于tcp和udp的socket实现
  3. SP3946 MKTHNUM - K-th Number(整体二分)
  4. fail2ban防止暴力破解
  5. linux文件统计命令,linux文件统计命令和目录统计命令
  6. jQuery知识汇总
  7. C语言中,当计算字符数组长度时,用sizeof 和strlen 的原理及两者的区别
  8. Ubuntu 16.04系统下配置cocos2dx-3.10
  9. C#高效编程话题集1(每期10话题)
  10. mybatis 一对一 一对多 级联查询
  11. C++——OOP(Object-Oriented Programming) vs. GP(Generic Programming)
  12. 波兰表达式 构建 表达式树
  13. GetModuleHandle
  14. 95-910-146-源码-FlinkSQL-Flink SQL中TableFunction使用分析
  15. Android开发之Canvas rotate方法释疑
  16. static_cast vs dynamic_cast
  17. 雷锋科普:小米M2之芯高通APQ8064芯片组解析
  18. abap语言去除重复项怎么写
  19. Echarts X轴内容过长自动隐藏,鼠标移动上去显示全部
  20. Elasticsearch的使用RestHighLevelClient

热门文章

  1. 打破校史!这位参与发表学校首篇Science的博士小姐姐,近日一作再发Nature
  2. 牛X,一系列Chrome 灵魂插件!爱了爱了!
  3. Github上十大热门可视化面板!再也不用担心画图啦!
  4. 如何让人工智能更智能?你需要一个开源平台
  5. 谷歌开源框架 FUSS,让声音分离不再成为难题
  6. 马腾宇:AI 学界一颗冉冉升起的新星
  7. 「大咖云集硅谷AI大会」人工智能商业化的趋势与挑战
  8. 「模型解读」“不正经”的卷积神经网络
  9. 2019年上半年收集到的人工智能深度学习方向干货文章
  10. 深度学习中的网络表征学习的算法目标简介