任务管理
任务切换应该算是UCOS最基本的部分,首先保存当前任务寄存器的内容到当前任务的堆栈:接着弹出即将进行的任务的堆栈内容到寄存器中然后就是按寄存器内容执行,这个过程成为上下文切换。任务堆栈在创建任务之前都应该是定义好了的,堆栈实际上是两个不同的概念堆和栈,这里的堆栈实际上是指栈,栈的存放规则是后进先出,在任务切换是栈是用来保存局部变量,传递参数,保存任务状态也就是任务切换前的寄存器值的。

对于Cortex—M3内核的堆栈
从内核权威指南里可以知道,Cortex—M3有两个堆栈指针,一个MSP主堆栈指针,还有一个PSP进程堆栈指针。在逻辑地址上他们都是R13寄存器,同一时刻只有一个可见,就如同STM32里的RXBUFF与TXBUFF寄存器一样。在handle模式(运行异常服务程序或者中断程序时的状态)下只能使用MSP,程序的默认也是使用MSP指针,且中间只要没有人为切换会一直使用MSP,常见的裸板(前后台)跑程序时就一直只用了MSP指针。

参考M3权威指南可以看到,堆栈指针的切换有两种方法
1.修改CONTROL寄存器的1位,设置的具体如图所示:

2.在异常返回时通过修改LR(R14)寄存器的位2也可以切换模式。寄存器R14在调用程序时时是用以保存调用之前的下一条指令的地址用以调用返回,子程序返回只要跳转到LR寄存器保存的地址就可以实现调用返回。执行异常服务程序之前,返回地址也会被压入堆栈,可能是MSP也可能是PSP。异常返回时会自动出栈恢复异常前的状态,这是普通终端服务程序的任务切换。进入异常服务程序后LR摇身一变成为EXC_RETURN寄存器,它的各个为的做一从指导手册可以看出,通过修改其中的位2就可以切换返回时的堆栈指针。

对于中断的自动压栈参考官方指南就知道压入了什么按什么顺序压栈的,指南中如此解释假设入栈开始时, SP的值为N,则在入栈后,堆栈内部的变化如表表示:

这是硬件自动完成的,原文中这样解释为什么不压栈R4-R11这几个寄存器组,“为啥袒护R0‐R3以及R12呢, R4‐R11就是下等公民?原来,在ARM上,有一套的C函数调用标准约定(《 C/C++ Procedure Call Standard for the ARM Architecture》,AAPCS, Ref5)。个中原因就在它上面:它使得中断服务例程能用C语言编写,编译器优先使用被入栈的寄存器来保存中间结果(当然,如果程序过大也可能要用到R4‐R11,此时编译器负责生成代码来push它们”。

再看任务创建函数时似乎明白了一点,OSTaskStkInit()函数中

*(--p_stk) = (CPU_STK)0x01000000u;                            /* xPSR                                                   */
*(--p_stk) = (CPU_STK)p_task; /* Entry Point */
*(--p_stk) = (CPU_STK)OS_TaskReturn; /* R14 (LR) */
*(--p_stk) = (CPU_STK)0x12121212u; /* R12 */
*(--p_stk) = (CPU_STK)0x03030303u; /* R3 */
*(--p_stk) = (CPU_STK)0x02020202u; /* R2 */
*(--p_stk) = (CPU_STK)p_stk_limit; /* R1 */
*(--p_stk) = (CPU_STK)p_arg; /* R0 : argument */
*(--p_stk) = (CPU_STK)0x11111111u; /* R11 */
*(--p_stk) = (CPU_STK)0x10101010u; /* R10 */
*(--p_stk) = (CPU_STK)0x09090909u; /* R9 */
*(--p_stk) = (CPU_STK)0x08080808u; /* R8 */
*(--p_stk) = (CPU_STK)0x07070707u; /* R7 */
*(--p_stk) = (CPU_STK)0x06060606u; /* R6 */
*(--p_stk) = (CPU_STK)0x05050505u; /* R5 */
*(--p_stk) = (CPU_STK)0x04040404u; /* R4 */

第二行实际是对应PC指针,所以他指向函数开头,OS_TaskReturn就对应LR函数返回的地址,这是UCOS系统为了防止用户没有将任务写称死循环为了怎家系统健壮性而有意引导程序进入死循环的保护代码详细如下:

void  OS_TaskReturn (void)
{
OS_ERR err;
OSTaskReturnHook(OSTCBCurPtr); /* Call hook to let user decide on what to do */
#if OS_CFG_TASK_DEL_EN > 0u
OSTaskDel((OS_TCB *)0, /* Delete task if it accidentally returns! */
(OS_ERR *)&err);
#else
for (;;) {
OSTimeDly((OS_TICK )OSCfg_TickRate_Hz,
(OS_OPT )OS_OPT_TIME_DLY,
(OS_ERR *)&err);
}
#endif
}

当一个任务被创建后他就置于就绪列表该有的位置等待任务调度时被执行,任务调度是系统的核心,查看任务调度函数好像很简单基本就是一些参数检查等,但最后一句才是导火索OS_TASK_SW();通过查看他的函数内容

OSCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR

实际就是置位了一个PENDSVS中断。然后在中断函数内进行了一些操作。

结合M3权威指南,这里实际上是软件置位了PendSV_Handler中断,这是M3内核的一个特殊的中断事件它可以被悬起,在有比它中断优先级更高的中断在请求时系统会先执行其他中断,因为对于实时系统中断是一种实时行要求最高的任务,因此在UCOS里PendSV_Handler通常被配置为优先级最低的中断从而保证在PendSV_Handler的任务切换不会在中断请求时进行。继续上面的内容在OSSched()最后悬起了PendSV_Handler中断,在没有其他中断执行的时候这个中断函数将会执行,函数如下:

PendSV_Handler
CPSID I ; 关闭总中断
MRS R0, PSP ; 取PSP到R0,此时硬件已近压栈了上图中断压栈的寄存器,且SP指针也指向最后
CBZ R0, PendSVHandler_nosave ; 如果是第一次任务调度直接跳转到PendSVHandler_nosave ;使用FPU才会有的压栈,暂时不管.
TST R14, #0X10
IT EQ
VSTMDBEQ R0!,{S16-S31} SUBS R0, R0, #0x20 ; 将SP指针向下移动8*4个字节,即32 即0X20,然后保存R4-R11到栈
STM R0, {R4-R11}
;将sp现在的指向位置值给当前任务的OSTCBCurPtr->OSTCBStkPtr元素,用于恢复调用时出栈
LDR R1, =OSTCBCurPtr ; OSTCBCurPtr->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out ; 到这里当前任务现场已近保存了
PendSVHandler_nosave
PUSH {R14} ; 因为后面要调用函数所以Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14} ;返回 LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0] LDR R0, =OSTCBCurPtr ; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R1, =OSTCBHighRdyPtr
LDR R2, [R1]
STR R2, [R0]
                ;到这里已经取出了下一个即将调度的任务的任务控制块
                                 ;sp从新任务块取出,然后弹出之前保存的R4-R11到对应的寄存器
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20         ;将Sp指针人为移动到上次中断自动压栈的地方 ;;使用FPU才会有的压栈,暂时不管.
TST R14, #0x10
IT EQ
VLDMIAEQ R0!, {S16-S31} MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; 保证EXC_RETURN(LR)寄存器的2位值
CPSIE I
BX LR ; 中断自动返回,其中也包含了出栈数据到寄存器的过程
END

其实深入理解,任务调度其实最关键的就是在PendSV_Handler服务函数里进行了一个“偷梁换柱”的过程,在以往的中断返回过程中,sp指针在函数过程中不会改变,因此会返回到中断前的位置继续执行,但在PendSV_Handler里认为的将Sp指针换成了下一个任务的sp指针值,然后CPU会依然保持以往的中断返回过程,但其实返回的地方就是我们要调度去的位置,到此任务调度结束。其中最为精妙的是OSTCBCurPtr数据结构的设计让汇编可以很方便的访问结构体内存放sp值的位置。

同时UCOS还有个中断级的任务调度,实际了解内核机制的人一看就懂,因为这个宏和普通任务调度是相同的,这是内核的一个“中断咬尾”机制。手册上给的解释是当处理器在响应某异常时,如果又发生其它异常,但它们优先级不够高,则被阻塞——这个我们已经知道。那么在当前的异常执行返回后,系统处理悬起的异常时,倘若还是先POP然后又把POP出来的内容PUSH回去,这不成了砸锅炼铁再铸锅,白白浪费CPU时间,正因此, CM3不会傻乎乎地POP这些寄存器,而是继续使用上一个异常已经PUSH好的成果,消灭了这种铺张浪费。这么一来,看上去好像后一个异常把前一个的尾巴咬掉了,前前后后只执行了一次入栈/出栈操作。

简单的理解就是,本该PendSV进行的入栈操作,此时是借用了其他比他优先级更高的任务的现成“功劳”,这是一个硬件机制的程序不用单独控制,因此你可以看到两个宏是一样的。同时对应的有一个晚到(的高优先级)异常机制,这个机制是之当一个低优先级的中断压栈完成时且未执行其他服务函数时,此时又有高优先级的中断请求,此时高优先级中断任务会借用地优先级任务的现成成果(已经压栈好的数据)然后直接执行自己的服务函数,从而省去寄存器压栈的过程,这两个机制都是设计出来加快中断服务响应的。接下来还有一些任务相关的代码都是非常简单的。

μC/OS-III---I笔记12---任务管理的更多相关文章

  1. Python3+Selenium3+webdriver学习笔记12(js操作应用:滚动条 日历 内嵌div)

    #!/usr/bin/env python# -*- coding:utf-8 -*-'''Selenium3+webdriver学习笔记12(js操作应用:滚动条 日历 内嵌div)'''from ...

  2. 机器学习实战 - 读书笔记(12) - 使用FP-growth算法来高效发现频繁项集

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第12章 - 使用FP-growth算法来高效发现频繁项集. 基本概念 FP-growt ...

  3. Ext.Net学习笔记12:Ext.Net GridPanel Filter用法

    Ext.Net学习笔记12:Ext.Net GridPanel Filter用法 Ext.Net GridPanel的用法在上一篇中已经介绍过,这篇笔记讲介绍Filter的用法. Filter是用来过 ...

  4. SQL反模式学习笔记12 存储图片或其他多媒体大文件

    目标:存储图片或其他多媒体大文件 反模式:图片存储在数据库外的文件系统中,数据库表中存储文件的对应的路径和名称. 缺点:     1.文件不支持Delete操作.使用SQL语句删除一条记录时,对应的文 ...

  5. uc/os iii移植到STM32F4---IAR开发环境

    也许是先入为主的原因,时钟用不惯Keil环境,大多数的教程都是拿keil写的,尝试将官方的uc/os iii 移植到IAR环境. 1.首先尝试从官网上下载的官方移植的代码,编译通过,但是执行会报堆栈溢 ...

  6. JAVA自学笔记12

    JAVA自学笔记12 1.Scanner 1)JDK5后用于获取用户的键盘输入 2)构造方法:public Scanner(InputStream source) 3)System.in 标准的输入流 ...

  7. 基于μC/OS—III的CC1120驱动程序设计

    基于μC/OS—III的CC1120驱动程序设计 时间:2014-01-21 来源:电子设计工程 作者:张绍游,张贻雄,石江宏 关键字:CC1120   嵌入式操作系统   STM32F103ZE   ...

  8. golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题

    golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题 今天测试了重新建一个项目生成新的表,然后复制到旧的项目 ...

  9. Spring MVC 学习笔记12 —— SpringMVC+Hibernate开发(1)依赖包搭建

    Spring MVC 学习笔记12 -- SpringMVC+Hibernate开发(1)依赖包搭建 用Hibernate帮助建立SpringMVC与数据库之间的联系,通过配置DAO层,Service ...

  10. 强化学习读书笔记 - 12 - 资格痕迹(Eligibility Traces)

    强化学习读书笔记 - 12 - 资格痕迹(Eligibility Traces) 学习笔记: Reinforcement Learning: An Introduction, Richard S. S ...

随机推荐

  1. 【Android初级】使用TypeFace设置TextView的文字字体(附源码)

    在Android里面设置一个TextView的文字颜色和文字大小,都很简单,也是一个常用的基本功能.但很少有设置文字字体的,今天要分享的是通过TypeFace去设置TextView的文字字体,布局里面 ...

  2. 阿姆达尔定律 Amdahl's law

    Amdahl's law - Wikipedia https://en.wikipedia.org/wiki/Amdahl%27s_law 阿姆达尔定律(英语:Amdahl's law,Amdahl' ...

  3. 获取控制台的错误信息 onerror

    js 获取控制台的错误信息 https://www.bbsmax.com/A/Vx5ML2NmJN/ <!DOCTYPE html> <html lang="en" ...

  4. Tensorflow-各种优化器总结与比较

    优化器总结 机器学习中,有很多优化方法来试图寻找模型的最优解.比如神经网络中可以采取最基本的梯度下降法. 梯度下降法(Gradient Descent) 梯度下降法是最基本的一类优化器,目前主要分为三 ...

  5. IdentityServer4之Implicit和纯前端好像很配哦

    前言 上一篇Resource Owner Password Credentials模式虽然有用户参与,但对于非信任的第三方的来说,使用这种模式是有风险的,所以相对用的不多:这里接着说说implicit ...

  6. 为什么Redis集群要使用反向代理?

    为什么要使用反向代理? 如果没有方向代理,一台Redis可能需要跟很多个客户端连接: 看着是不是很慌?看没关系,主要是连接需要消耗线程资源,没有代理的话,Redis要将很大一部分的资源用在与客户端建立 ...

  7. 通过 JFR 与日志深入探索 JVM - TLAB 原理详解

    全系列目录:通过 JFR 与日志深入探索 JVM - 总览篇 什么是 TLAB? TLAB(Thread Local Allocation Buffer)线程本地分配缓存区,这是一个线程专用的内存分配 ...

  8. 从零搭建一个IdentityServer——初识OpenIDConnect

    上一篇文章实现了IdentityServer4与Asp.net core Identity的集成,可以使用通过identity注册功能添加的用户,以Password的方式获取Access token, ...

  9. Java——继承,重载,重写三剑客

    About-继承 所有Java的类均是由java.lang.Object类继承而来的,所以Object是所有类的祖先类,而除了Object外,所有类必须有一个父类. 继承可以理解为一个对象从另一个对象 ...

  10. java架构《并发线程高级篇三》

    本章主要介绍和讲解concurrent.util里面的常用的工具类. 一.CountDownLatch使用:(用于阻塞主线程) 应用场景 :通知线程休眠和运行的工具类,是wait和notify的升级版 ...