鸿蒙轻内核M核源码分析系列六 任务及任务调度(3)任务调度模块

调度,Schedule也称为Dispatch,是操作系统的一个重要模块,它负责选择系统要处理的下一个任务。调度模块需要协调处于就绪状态的任务对资源的竞争,按优先级策略从就绪队列中获取高优先级的任务,给予资源使用权。本文我们来一起学习下调度模块的源代码,文中所涉及的源代码,所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点 https://gitee.com/openharmony/kernel_liteos_m 获取。

下面,我们剖析下任务调度模块的源代码,若涉及开发板部分,以开发板工程targets\cortex-m7_nucleo_f767zi_gcc\为例进行源码分析。

1、调度模块的重要函数

文件kernel\src\los_sched.c中定义了调度模块的几个重要的函数,我们来分析下源码。

1.1 调度初始化函数

调度初始化函数UINT32 OsSchedInit(VOID)在任务初始化函数UINT32 OsTaskInit(VOID)中调用。⑴处会初始化任务就绪队列,⑵处初始化任务排序链表,⑶处初始化调度响应时间全局变量为最大值OS_SCHED_MAX_RESPONSE_TIME

UINT32 OsSchedInit(VOID)
{
    UINT16 pri;
⑴  for (pri = 0; pri < OS_PRIORITY_QUEUE_NUM; pri++) {
        LOS_ListInit(&g_priQueueList[pri]);
    }
    g_queueBitmap = 0;

⑵  g_taskSortLinkList = OsGetSortLinkAttribute(OS_SORT_LINK_TASK);
    if (g_taskSortLinkList == NULL) {
        return LOS_NOK;
    }

    OsSortLinkInit(g_taskSortLinkList);
⑶  g_schedResponseTime = OS_SCHED_MAX_RESPONSE_TIME;

    return LOS_OK;
}

1.2 任务调度函数

任务调度函数VOID LOS_Schedule(VOID)是出镜率较高的一个函数。当系统完成初始化开始调度,并且没有锁任务调度时,会调用函数HalTaskSchedule()进行任务调度。该函数定义在kernel\arch\arm\cortex-m7\gcc\los_dispatch.S,由汇编语言实现,后文会详细分析。

VOID LOS_Schedule(VOID)
{
    if (g_taskScheduled && LOS_CHECK_SCHEDULE) {
        HalTaskSchedule();
    }
}

1.3 开启调度函数

函数VOID OsSchedStart(VOID)kernel\src\los_init.c:UINT32 LOS_Start(VOID)-->kernel\arch\arm\cortex-m7\gcc\los_context.c:UINT32 HalStartSchedule(OS_TICK_HANDLER handler)函数依次调用,在系统初始化时开启任务调度。我们看下该函数的源码,⑴处调用函数获取就绪队列中优先级最高的任务,⑵把该任务状态设置为运行状态,接着把当前运行任务和新任务都设置为就绪队列中优先级最高的那个任务。⑶处设置任务调度启动状态全局变量为1,标记任务调度已经开启。⑷处设置新任务的开始运行时间,然后把新任务从就绪队列中出队。⑸处设置全局变量。⑹处调用函数设置该任务的运行过期时间。

VOID OsSchedStart(VOID)
{
    (VOID)LOS_IntLock();
⑴  LosTaskCB *newTask = OsGetTopTask();

⑵  newTask->taskStatus |= OS_TASK_STATUS_RUNNING;
    g_losTask.newTask = newTask;
    g_losTask.runTask = g_losTask.newTask;

⑶  g_taskScheduled = 1;
⑷  newTask->startTime = OsGetCurrSchedTimeCycle();
    OsSchedTaskDeQueue(newTask);

⑸  g_schedResponseTime = OS_SCHED_MAX_RESPONSE_TIME;
    g_schedResponseID = OS_INVALID;
⑹  OsSchedSetNextExpireTime(newTask->startTime, newTask->taskID, newTask->startTime + newTask->timeSlice);

    PRINTK("Entering scheduler\n");
}

1.4 任务调度切换函数

任务切换函数用于实现任务切换,被文件kernel\arch\arm\cortex-m7\gcc\los_dispatch.S中的汇编函数HalPendSV调用。我们分析下该函数的源代码。

⑴处获取当前运行的任务,然后调用函数减去其运行的时间片,开始运行时间设置为当前时间。⑵如果任务处于阻塞等待状态或延迟状态,则把其加入任务排序链表。⑶如果任务不是处于阻塞挂起状态、不是处于阻塞状态,则把其加入就绪队列。⑷处获取就绪队列中优先级最高的任务,⑸处如果当前运行任务和就绪队列汇总优先级最高的任务不是同一个任务,把当前任务状态设置为非运行状态,新任务设置为运行状态,并设置新任务的开始时间为当前任务的开始时间,然后执行⑹标记是否需要任务切换。⑺处把新任务从就绪队列中出队,⑻处计算新任务的运行结束时间,然后执行⑼设置任务到期时间。

BOOL OsSchedTaskSwitch(VOID)
{
    UINT64 endTime;
    BOOL isTaskSwitch = FALSE;
 ⑴ LosTaskCB *runTask = g_losTask.runTask;
    OsTimeSliceUpdate(runTask, OsGetCurrSchedTimeCycle());

⑵  if (runTask->taskStatus & (OS_TASK_STATUS_PEND_TIME | OS_TASK_STATUS_DELAY)) {
        OsAdd2SortLink(&runTask->sortList, runTask->startTime, runTask->waitTimes, OS_SORT_LINK_TASK);
    } else if (!(runTask->taskStatus & (OS_TASK_STATUS_PEND | OS_TASK_STATUS_SUSPEND | OS_TASK_STATUS_UNUSED))) {
⑶      OsSchedTaskEnQueue(runTask);
    }

⑷  LosTaskCB *newTask = OsGetTopTask();
    g_losTask.newTask = newTask;

    if (runTask != newTask) {
#if (LOSCFG_BASE_CORE_TSK_MONITOR == 1)
        OsTaskSwitchCheck();
#endif
⑸      runTask->taskStatus &= ~OS_TASK_STATUS_RUNNING;
        newTask->taskStatus |= OS_TASK_STATUS_RUNNING;
        newTask->startTime = runTask->startTime;
⑹      isTaskSwitch = TRUE;

        OsHookCall(LOS_HOOK_TYPE_TASK_SWITCHEDIN);
    }

⑺  OsSchedTaskDeQueue(newTask);

⑻  if (newTask->taskID != g_idleTaskID) {
        endTime = newTask->startTime + newTask->timeSlice;
    } else {
        endTime = OS_SCHED_MAX_RESPONSE_TIME;
    }
⑼   OsSchedSetNextExpireTime(newTask->startTime, newTask->taskID, endTime);

    return isTaskSwitch;
}

2、调度模块汇编函数

文件kernel\arch\arm\cortex-m7\gcc\los_dispatch.S定义了调度模块的汇编函数,我们分析下这些调度接口的源代码。汇编文件中定义了如下几个宏,见注释。

.equ OS_NVIC_INT_CTRL,           0xE000ED04     ; Interrupt Control State Register,ICSR 中断控制状态寄存器
.equ OS_NVIC_SYSPRI2,            0xE000ED20     ; System Handler Priority Register 系统优先级寄存器
.equ OS_NVIC_PENDSV_PRI,         0xF0F00000     ; PendSV异常优先级
.equ OS_NVIC_PENDSVSET,          0x10000000     ; ICSR寄存器的PENDSVSET位置1时,会触发PendSV异常
.equ OS_TASK_STATUS_RUNNING,     0x0010         ; los_task.h中的同名宏定义,数值也一样,表示任务运行状态,

2.1 HalStartToRun汇编函数

开始运行函数HalStartToRun被文件kernel\arch\arm\cortex-m7\gcc\los_context.c中的开始调度函数HalStartSchedule在系统启动阶段调用。我们接下来分析下该函数的汇编代码。

⑴处设置PendSV异常优先级为OS_NVIC_PENDSV_PRIPendSV异常一般设置为最低。⑵处往控制寄存器CONTROL写入二进制的10,表示使用PSP栈,特权级的线程模式。⑶处把全局变量地址加载到寄存器r1,类似于C语言的r1=&g_losTask。⑷处[r1 , #4]&g_losTask地址加4个字节来获取g_losTask->newTask,此时寄存器r0数值为newTask的TaskCB的内存地址。

⑸处把[r0]的值即新任务的栈指针g_losTask->newTask->stackPointer加载到寄存器R12,现在R12指向任务栈的栈指针,任务栈现在保存的是上下文,对应定义在kernel\arch\arm\cortex-m7\gcc\los_arch_context.h中的结构体TaskContext。如果支持浮点寄存器,则执行⑹,把R12加100个字节,其中包含S16S31共16个4字节,R4R11uwPriMask共9个4字节的长度,执行指令后,R12指向任务栈中上下文的UINT32 uwR0位置。

⑺处代码把任务栈上下文中的UINT32 uwR0-uwR3, UINT32 uwR12; UINT32 uwLR; UINT32 uwPC; UINT32 uwxPSR;共8个成员变量数值分别加载到寄存器R0-R7,其中R5对应UINT32 uwLRR6对应UINT32 uwPC,此时寄存器R12指向任务栈上下文的UINT32 uwxPSR。然后执行下一个指令,指针继续加72字节(=18个4字节长度),即对应S0S15UINT32 FPSCR; UINT32 NO_NAME等上下文的18个成员。此时,寄存器R12指向任务栈的栈底,紧接着执行⑻把寄存器R12写入寄存器psp

如果不支持浮点寄存器,则执行⑼,从栈指针加36字节,然后寄存器R12指向任务栈中上下文的UINT32 uwR0位置。接着把上下文中的寄存器信息加载到寄存器R0-R7,紧接着把寄存器R12写入寄存器psp

最后,执行⑽处指令,把寄存器R5写入lr寄存器,开中断,然后跳转到R6对应的上下文的PC对应的函数VOID OsTaskEntry(UINT32 taskID),去执行任务的入口函数。

    .type HalStartToRun, %function
    .global HalStartToRun
HalStartToRun:
    .fnstart
    .cantunwind

⑴  ldr     r4, =OS_NVIC_SYSPRI2
    ldr     r5, =OS_NVIC_PENDSV_PRI
    str     r5, [r4]

⑵  mov     r0, #2
    msr     CONTROL, r0

⑶  ldr     r1, =g_losTask
⑷  ldr     r0, [r1, #4]
⑸  ldr     r12, [r0]
#if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
     (defined(__FPU_USED) && (__FPU_USED == 1U)))
⑹  add     r12, r12, #100

⑺  ldmfd   r12!, {r0-r7}
    add     r12, r12, #72
⑻  msr     psp, r12
    vpush   {S0}
    vpop    {S0}
#else
⑼  add     r12, r12, #36

    ldmfd   r12!, {r0-r7}
    msr     psp, r12
#endif
⑽   mov     lr, r5
    //MSR     xPSR, R7

    cpsie   I
    bx      r6

    .fnend

2.2 OsTaskSchedule汇编函数

汇编函数HalTaskSchedule实现新老任务的切换调度。从上文可以知道,被任务调度函数VOID LOS_Schedule(VOID)调用。我们看看这个汇编函数的源代码,首先往中断控制状态寄存器OS_NVIC_INT_CTRL中的OS_NVIC_PENDSVSET位置1,触发PendSV异常。执行完毕HalTaskSchedule函数,返回上层调用函数。PendSV异常的回调函数是HalPendSV汇编函数,下文会分析此函数。汇编函数HalTaskSchedule如下:

    .type HalTaskSchedule, %function
    .global HalTaskSchedule
HalTaskSchedule:
    .fnstart
    .cantunwind

    ldr     r0, =OS_NVIC_INT_CTRL
    ldr     r1, =OS_NVIC_PENDSVSET
    str     r1, [r0]
    dsb
    isb
    bx      lr
   .fnend

3.4 HalPendSV汇编函数

接下来,我们分析下HalPendSV汇编函数的源代码。⑴处把寄存器PRIMASK数值写入寄存器r12,备份中断的开关状态,然后执行指令cpsid I屏蔽全局中断。⑵处把寄存器r12lr入栈,然后调用上文分析过的任务切换函数OsSchedTaskSwitch。函数执行完毕,执行⑶处指令出栈,恢复寄存器r12lr数值。⑷处比较寄存器r0即任务切换函数OsSchedTaskSwitch的返回值与0,然后执行⑸使用r0寄存器保存lr寄存器的值,如果⑷处的比较不相等,则执行⑹跳转到标签TaskContextSwitch进行任务上下文切换。⑺处恢复中断状态,然后返回。

我们来看下需要任务上下文切换的情况,接着看标签TaskContextSwitch。⑻处从r0寄存器恢复lr寄存器的值。⑼处使用r0寄存器指示栈指针,然后把寄存器r4-r12的数值压入当前任务栈。如果支持浮点寄存器,还需要执行⑽,把寄存器d8-d15的数值压入当前任务栈,r0为任务栈指针。

⑾处指令把全局变量g_losTask地址加载到寄存器r5,⑿获取当前运行任务的栈指针,然后更新当前运行任务的栈指针。⒀处指令获取新任务newTask的地址,接着的指令把新任务地址赋值给当前运行任务,即runTask = newTask。⒁处指令把r1寄存器表示新任务的栈指针。如果支持浮点,⒂指令把新任务栈中的数据加载到寄存器d8-d15寄存器,继续执行后续指令继续加载数据到r4-r12寄存器,然后执行⒃处指令更新psp任务栈指针。⒄处指令恢复中断状态,然后执行跳转指令,后续继续执行C代码VOID OsTaskEntry(UINT32 taskId)进入任务执行入口函数。

    .type HalPendSV, %function
    .global HalPendSV
HalPendSV:
    .fnstart
    .cantunwind

⑴  mrs     r12, PRIMASK
    cpsid   I

HalTaskSwitch:
⑵  push    {r12, lr}
    blx     OsSchedTaskSwitch
⑶  pop     {r12, lr}
⑷  cmp     r0, #0
⑸  mov     r0, lr
⑹  bne     TaskContextSwitch
⑺  msr     PRIMASK, r12
    bx      lr

TaskContextSwitch:
⑻  mov     lr, r0
⑼  mrs     r0, psp

    stmfd   r0!, {r4-r12}

#if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
     (defined(__FPU_USED) && (__FPU_USED == 1U)))
⑽   vstmdb   r0!, {d8-d15}
#endif
⑾  ldr     r5, =g_losTask
⑿  ldr     r6, [r5]
    str     r0, [r6]

⒀  ldr     r0, [r5, #4]
    str     r0, [r5]

⒁  ldr     r1, [r0]

#if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
     (defined(__FPU_USED) && (__FPU_USED == 1U)))
⒂  vldmia   r1!, {d8-d15}
#endif
    ldmfd   r1!, {r4-r12}
⒃  msr     psp,  r1

⒄  msr     PRIMASK, r12

    bx      lr
    .fnend

小结

本文带领大家一起剖析了鸿蒙轻内核调度模块的源代码,包含调用接口及底层的汇编函数实现。

如果大家想更加深入的学习 OpenHarmony 开发的内容,不妨可以参考以下相关学习文档进行学习,助你快速提升自己:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/682883.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java多线程原理

1.线程创建与启动&#xff1a;通过继承Thread类或实现Runnable接口创建线程&#xff0c;并调用start()方法启动线程。 1.线程状态&#xff1a;线程在其生命周期中有多种状态&#xff0c;包括新建、运行、阻塞、死亡等。了解这些状态以及如何在它们之间转换对于管理线程至关重要…

完美解决 mysql 报错ERROR 1524 (HY000): Plugin ‘mysql_native_password‘ is not loaded

文章目录 错误描述错误原因解决步骤 跟着我下面的步骤走&#xff0c;解决你的问题&#xff0c;如果解决不了 私信我来给你解决 错误描述 执行ALTER USER root% IDENTIFIED WITH mysql_native_password BY 123456;报错ERROR 1524 (HY000): Plugin mysql_native_password is not …

RPA实战演练UiBot6.0校园学生教评机器人

前言 校园学生教评机器人&#xff0c;也称为全自动校园教评RPA&#xff08;Robotic Process Automation&#xff0c;机器人流程自动化&#xff09;机器人&#xff0c;是一种利用软件机器人技术来模拟和执行学生教评流程中的各项任务和操作的智能化系统。以下是关于校园学生教评…

Mamba v2诞生:1 儒(Transformers)释(SSD)道(Mamba)本是一家?!

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

因子区间[牛客周赛44]

思路分析: 我们可以发现125是因子个数的极限了,所以我们可以用二维数组来维护第几个数有几个因子,然后用前缀和算出来每个区间合法个数,通过一个排列和从num里面选2个 ,c num 2 来计算即可 #include<iostream> #include<cstring> #include<string> #include…

数据库中锁的机制和MVCC协议以及隔离级别

文章目录 数据库中的锁锁与索引的关系释放锁的时机乐观锁与悲观锁行锁与表锁共享锁与排它锁意向锁记录锁、间隙锁和临键锁记录锁间隙锁临键锁 锁优化方案 MVCC协议MySQL的隔离级别脏读和幻读快照读和当前读 版本链Read ViewRead View 与已提交读Read View 与可重复读m_up_limit…

8. C#多线程基础概念

文章目录 一. 目标二. 技能介绍① 进程和线程② 为什么需要多线程③ C#实现多线程的方式④ 线程的操作(创建_终止_挂起_恢复) 一. 目标 进程和线程基本概念为什么需要多线程?C#实现多线程的方式?线程Thread的创建,终止,挂起和恢复? 二. 技能介绍 ① 进程和线程 什么是进程…

F5G城市光网,助力“一网通城”筑基数字中国

《淮南子》中说&#xff0c;“临河而羡鱼&#xff0c;不如归家织网”。 这句话在后世比喻为做任何事情都需要提前做好准备&#xff0c;有了合适的工具&#xff0c;牢固的基础&#xff0c;各种难题也会迎刃而解。 如今&#xff0c;数字中国发展建设如火如荼&#xff0c;各项任务…

C语言 | Leetcode C语言题解之第119题杨辉三角II

题目&#xff1a; 题解&#xff1a; int* getRow(int rowIndex, int* returnSize) {*returnSize rowIndex 1;int* row malloc(sizeof(int) * (*returnSize));row[0] 1;for (int i 1; i < rowIndex; i) {row[i] 1LL * row[i - 1] * (rowIndex - i 1) / i;}return row…

排序-快速排序

前言 本期主角 是这个小老头 图灵奖得主&#xff0c; 美国国家科学院外籍院士&#xff0c; 美国国家工程院外籍院士&#xff0c; 英国皇家工程院院士&#xff0c; 英国皇家学会院士 鼓掌&#x1f44f;&#x1f44f;&#x1f44f; 感觉这个小老头很叼噢(确实很叼) 从标…

MQTT.FX的使用

背景 在如今物联网的时代下&#xff0c;诞生了许多的物联网产品&#xff0c;这些产品通过BLE、WIFI、4G等各种各样的通信方式讲数据传输到各种各样的平台。 除了各个公司私有的云平台外&#xff0c;更多的初学者会接触到腾讯云、阿里云之类的平台。设备接入方式也有着多种多样…

react基础学习 JSX

JSX的测试网站 Babel Babel 可以测试代码的效果 JSX实现map列表 注意 key不一样&#xff08;使用遍历的时候&#xff09; 简单条件渲染 复杂条件渲染 绑定事件 function App() {const colorse (e)>{console.log("测试点击",e);}const colorse1 (name)>{…

数仓建模—指标体系指标拆解和选取

数仓建模—指标拆解和选取 第一节指标体系初识介绍了什么是指标体系 第二节指标体系分类分级和评价管理介绍了指标体系管理相关的,也就是指标体系的分级分类 这一节我们看一下指标体系的拆解和指标选取,这里我们先说指标选取,其实在整个企业的数字化建设过程中我们其实最…

vuInhub靶场实战系列-DC-6实战

免责声明 本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关。 目录 免责声明前言一、环境配置二、信息收集2.1 主机发现2.1.1 nmap扫描存活主机2.1.2 arp-scan扫描存活主机 2.2 端口扫描2.3 指纹识别2.3.1 尝试指纹识别2.3.…

2024050302-重学 Java 设计模式《实战享元模式》

重学 Java 设计模式&#xff1a;实战享元模式「基于Redis秒杀&#xff0c;提供活动与库存信息查询场景」 一、前言 程序员&#x1f468;‍&#x1f4bb;‍的上下文是什么&#xff1f; 很多时候一大部分编程开发的人员都只是关注于功能的实现&#xff0c;只要自己把这部分需求…

现代控制中可控性的Gramian判据

知乎三角猫frank对于这块内容写的非常好&#xff0c;但这个输入的构造还是很难过于没头没尾 数学好的人&#xff0c;可能看一眼根据形式就能推出gramian的构造&#xff0c;但对我这种比较钻牛角尖的人&#xff0c;我就想有一个逻辑链条——gramian是怎么被构造出来的&#xff1…

eNSP学习——配置RIPv2认证

目录 主要命令 原理概述 实验目的 实验内容 实验拓扑 实验编址 实验步骤 1、基本配置 2、搭建RIP网络 3、模拟网络攻击 4、配置RIPv2简单验证 5、配置RIPv2 MD5密文验证 需要eNSP各种配置命令的点击链接自取&#xff1a;华为&#xff45;NSP各种设备配置命令大全PD…

区块链游戏(链游)安全防御:抵御攻击的策略与实践

一、引言 区块链游戏&#xff0c;或称为链游&#xff0c;近年来随着区块链技术的普及而迅速崛起。然而&#xff0c;如同其他任何在线平台一样&#xff0c;链游也面临着各种安全威胁。本文将探讨链游可能遭遇的攻击类型以及如何通过有效的策略和技术手段进行防御。 二、链游可…

如何手动批准内核扩展 Tuxera NTFS for mac内核扩展需要批准 内核扩展怎么打开

在了解如何手动批准内核扩展之前&#xff0c;我们应该先了解什么叫做内核扩展。内核扩展又被称为KEXT&#xff0c;通过它可以实现macOS系统与软件组件之间的交互&#xff0c;例如磁盘管理、任务管理和内存管理等等。 kext 是内核扩展&#xff08;Kernel Extension&#xff09;…

[ue5]建模场景学习笔记(2)——用vectornoise降低重复率

1.问题分析&#xff1a; 利用改uv的方式降低重复率并不理想&#xff0c;在一定程度上的确能够达到降低重复率的效果&#xff0c;但远看仍然有较清晰的重复效果&#xff0c;尝试优化一下。 2.操作实现&#xff1a; 1.首先先看一下修改后的效果&#xff1a; 这是未修改前&#…