μC/OS-III---I笔记12---任务管理
任务管理
任务切换应该算是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---任务管理的更多相关文章
- Python3+Selenium3+webdriver学习笔记12(js操作应用:滚动条 日历 内嵌div)
#!/usr/bin/env python# -*- coding:utf-8 -*-'''Selenium3+webdriver学习笔记12(js操作应用:滚动条 日历 内嵌div)'''from ...
- 机器学习实战 - 读书笔记(12) - 使用FP-growth算法来高效发现频繁项集
前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第12章 - 使用FP-growth算法来高效发现频繁项集. 基本概念 FP-growt ...
- Ext.Net学习笔记12:Ext.Net GridPanel Filter用法
Ext.Net学习笔记12:Ext.Net GridPanel Filter用法 Ext.Net GridPanel的用法在上一篇中已经介绍过,这篇笔记讲介绍Filter的用法. Filter是用来过 ...
- SQL反模式学习笔记12 存储图片或其他多媒体大文件
目标:存储图片或其他多媒体大文件 反模式:图片存储在数据库外的文件系统中,数据库表中存储文件的对应的路径和名称. 缺点: 1.文件不支持Delete操作.使用SQL语句删除一条记录时,对应的文 ...
- uc/os iii移植到STM32F4---IAR开发环境
也许是先入为主的原因,时钟用不惯Keil环境,大多数的教程都是拿keil写的,尝试将官方的uc/os iii 移植到IAR环境. 1.首先尝试从官网上下载的官方移植的代码,编译通过,但是执行会报堆栈溢 ...
- JAVA自学笔记12
JAVA自学笔记12 1.Scanner 1)JDK5后用于获取用户的键盘输入 2)构造方法:public Scanner(InputStream source) 3)System.in 标准的输入流 ...
- 基于μC/OS—III的CC1120驱动程序设计
基于μC/OS—III的CC1120驱动程序设计 时间:2014-01-21 来源:电子设计工程 作者:张绍游,张贻雄,石江宏 关键字:CC1120 嵌入式操作系统 STM32F103ZE ...
- golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题
golang学习笔记12 beego table name `xxx` repeat register, must be unique 错误问题 今天测试了重新建一个项目生成新的表,然后复制到旧的项目 ...
- Spring MVC 学习笔记12 —— SpringMVC+Hibernate开发(1)依赖包搭建
Spring MVC 学习笔记12 -- SpringMVC+Hibernate开发(1)依赖包搭建 用Hibernate帮助建立SpringMVC与数据库之间的联系,通过配置DAO层,Service ...
- 强化学习读书笔记 - 12 - 资格痕迹(Eligibility Traces)
强化学习读书笔记 - 12 - 资格痕迹(Eligibility Traces) 学习笔记: Reinforcement Learning: An Introduction, Richard S. S ...
随机推荐
- std::thread线程库详解(3)
目录 目录 前言 lock_guard scoped_lock (C++17) unique_lock shared_lock 总结 ref 前言 前两篇的博文分别介绍了标准库里面的线程和锁,这一次的 ...
- 备份和还原Windows DHCP服务器
在本教程中,您将学习如何使用DHCP控制台和PowerShell备份和还原Windows DHCP服务器. 您是否曾经经历过DHCP服务器崩溃或故障?在设备开始重新启动之前,一切都会平静. 用户将抱怨 ...
- 浅析鸿蒙中的 Gn 与 Ninja(一)
目录: Ninja简介 make 的 3 个特性 举例说明Ninja 的用法 如何向构建工具 Ninja 描述构建图 后记 鸿蒙系统的编译构建是基于 Gn 和 Ninja 完成的,那么 Gn 和 Ni ...
- Android N selectQualifiedNetwork分析
前言: 参考:Android N wifi auto connect流程分析 后续 Android 8.0/9.0 wifi 自动连接评分机制 分析 前面说了,handleScanResults会去调 ...
- MySQL主从复制配置部署
配置前准备:安装MySQL MySQL在centOS上的安装传送门: 1.集群规划 hadoop105 hadoop106 hadoop107 MySQL(master) MySQL(slave) ...
- Go 语言编译过程
走进Golang之编译器原理_大愚Talk-CSDN博客 https://blog.csdn.net/hel12he/article/details/103061921 go编译器 - 知乎 http ...
- 浅析 record 使用场景
浅析 record 使用场景 Intro 之前我们有介绍过 record 基本知识,record 会实现基于值的类型比较,最近遇到的几个问题觉得用 record 来解决会非常方便,分享一下 基于值的类 ...
- 自己动手实现java断点/单步调试(一)
又是好长时间没有写博客了,今天我们就来谈一下java程序的断点调试.写这篇主题的主要原因是身边的公司或者个人都执着于做apaas平台,简单来说apaas平台就是一个零代码或者低代码的配置平台,通过配置 ...
- (二)基于shard-jdbc中间件,实现数据分库分表
基于shard-jdbc中间件,实现数据分库分表 Sharding-JDBC简介 Sharding配置示意图 1.水平分割 1.1 水平分库 1.2 水平分表 2.Shard-jdbc中间件 2.1 ...
- 使用 html5 svg 绘制图形
有一次看一个项目的时候,看到图片的格式为svg,作为萌新的我瞬间有点小懵,这可是之前从没有见到过的格式,于是就开始上某度进行学习,发现某博主的优秀文章,进行转载方便自己学习,感谢原博主的优秀文章. · ...