
Liteos-A任务调度之任务上下文切换 原创
任务调度要调度的就是任务所拥有的CPU资源,其中最主要的就是寄存器,包括通用寄存器和状态寄存器,操作系统刚开始启动时任务调度的代码主要是加载第一个任务,然后随着操作系统的运行需求开始运行不同的任务,即就开始了任务的切换,任务切换是任务调度的核心,它的主要工作就是保存当前任务的现场,加载新任务的现场并运行,而这部分的工作只能由汇编完成,los_dispatch.S就是承担Liteos-A任务切换的汇编源文件,它使用的是32位的ARM汇编。
一、加载任务现场
任务切换时应先保存任务现场,然后再加载任务现场,但是在liteos-a启动开始,还未进行任务切换时,系统需要有一个任务运行,这时就会调用加载任务现场接口——OsTaskContextLoad,这样在任务切换调用保存任务现场时逻辑就会连上,所以第一个先说明OsTaskContextLoad接口,另外,操作系统加载的第一个任务的现场就是这篇博客中初始化的现场:Liteos-A任务调度之OsTaskStackInit函数
加载任务现场是一个出栈操作,所以加载之前让我们先看看栈里的内容:
这就是初始化时栈中的情况,在加载新任务现场时就会从初始化好的结构体中加载到寄存器中。
可以看到,加载新任务现场实际上就是liteos-a任务上下文结构体TaskContext的成员的出栈。
有几个需要注意的问题
1.CLREX指令
这个可以参考:CLREX指令
<font color=blue>该指令的作用就是在独占访问结束时,清除cpu中本地处理器针对某块内存区域的独占访问标志(核中的某个状态寄存器),以防在未清除时的其他操作,对系统产生影响。对于是否同时清除全局的独占访问标志,需要在设计cpu时的架构师决定。
这个描述我也不太深刻的理解,如果有大佬知到,欢迎指教。
2.关于浮点寄存器的出栈
浮点寄存器的出栈是一个宏:
由代码可知,如果!defined(LOSCFG_ARCH_FPU_DISABLE),则出栈D0-D15,如果defined(LOSCFG_ARCH_FPU_VFP_D32),那么D16-D31也出栈,所以上面的堆栈图中的栈顶有可能是D0-D15,也可能是D0-D31。
3.关于CPU的模式
ARM有七种工作模式,在用户和非用户模式下加载新任务现场的代码是不一样的,在用户模式下需要打开中断,因为用户模式的优先级较低,要允许被中断打断,如果被中断打断,则还需要保存当前的SP和LR,也就是任务上下文结构体TaskContext中的USP和ULR。
关于ARM架构CPU的七种工作模式可见:ARM的七种工作模式介绍
二、任务上下文切换
完整的任务上下文切换代码如下,其中OsTaskContextLoad接口不再做注释:
需要说明的一点是,保存任务现场的时候保存了两次LR的值,为了方便理解,我分析一下下图:
一条指令的生命周期(此指在ARM流水线CPU中)有三个阶段,取指、译码、和执行,在同一时钟周期中,CPU不同的器件会处理不同的阶段,上图中,CPU的各器件处在指令pc-8的执行阶段、指令pc-4的译码阶段、指令pc的取指阶段,所以当任务被打断时,当前阶段的译码阶段,也就是PC-4这条指令还未被执行,这条指令会被保存在LR中,等任务被重新恢复的时候会重新取指并译码执行,这就是第一个LR的入栈。而第二个LR的入栈就是任务的LR寄存器入栈,无须多说。
后面的代码就是继续保存当前任务的寄存器,无需多说。
水平有限,如有任何想法,欢迎交流!
