RTOS系列(12):使用SVC或PendSV启动OS流程详细分析
RTOS系列(1):基础知识——中断嵌套
RTOS系列文章(2):PendSV功能,为什么需要PendSV
RTOS系列文章(3): 为什么将SysTick和PendSV的优先级设置为最低
RTOS系列文章(4): MDK软件仿真 + Debug-(printf)-Viewer使用方法
RTOS系列文章(5):C语言程序运行原理分析:汇编、栈、栈帧、进栈、出栈、保存现场、恢复现场、返回
RTOS系列文章(6):Cortex-M3/4之SP,MSP,PSP,Thread模式、Handler模式、内核态、用户态
RTOS系列文章(7):CM3/4之LR寄存器、EXC_RETURN深入分析
RTOS系列文章(8):深入分析中断处理过程
RTOS系列文章(9):再次分析栈帧、函数调用与中断调用的区别
RTOS系列文章(10):简单OS示例分析
RTOS系列文章(11):RTOS启动方式——直接设置CONTROL寄存器、SVC启动、PendSV启动
前言
在上一篇文章中,我们分析了启动OS的2种方式,分别为:
- 直接设置CONTROL寄存器,这种方式比较简单,但是不够优雅,或者说程序架构不够清晰。
- 通过中断+修改EXC_RETURN的方式,间接设置CONTROL寄存器,来实现OS的启动。
其中方式2是目前RTOS常用的方式,我们以FreeRTOS和uC/OS为例,FreeRTOS是使用SVC来启动第一个任务,即实现OS的启动的。而uC/OS则是通过PendSV来实现启动首个任务,达到启动OS的目的。这两种方式没有好坏之分,只不过在实现理念上,由于FreeRTOS比较年轻,所以基于SVC启动OS的方式,更加安全,比较符合大型OS的设计理念,即OS与应用解耦,应用通过SVC来调用OS的服务。
为什么要使用SVC、PendSV来启动OS
这里可能会有人说,上面的提到两种方式中,直接设置CONTROL寄存器太简单粗暴,那能不能直接设置EXC_RETURN的值,然后BL EXC_RETURN是不是更简单呢?我也想过这个问题,虽然没有真正测试过,但是如果直接 BL EXC_RETURN可能会有如下问题:
- EXC_RETURN是CPU专门为中断设计的,使中断服务程序可以使用C来编写,关于EXC_RETURN的详细介绍,可以参考前面的文章。所以使用EXC_RETURN机制,就意味着必须要配合着中断使用,而能被程序配置触发的中断,似乎只有Systick 、PendSV和SVC中断。而Systick中断,我们一般只是提供时钟节拍,不会用于任务调度,上下文切换,所以很显然,我们就剩下2种选择:PendSV和SVC,不过这也足够了。
- 我们使用中断+EXC_RETURN意味着,我们可以使用CPU专门为中断设计的自动压栈、出栈机制。通过自动出栈机制,实现任务上下文切换后,自动切换到下一个任务,这使得我们的OS启动与任务的调度的代码可以复用,更加简洁。
使用SVC启动OS分析
FreeRTOS就是使用SVC中断来启动OS的,废话不多说,直接上FreeRTOS v9.0的代码:
我们以STM32F103ZET6为例:
prvStartFirstTask
__asm void prvStartFirstTask( void )
{PRESERVE8/* Use the NVIC offset register to locate the stack. */ldr r0, =0xE000ED08 // 0xE000ED08 地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址// 执行该命令后,R0寄存器内容为0xE000ED08 ldr r0, [r0] // 将R0中的内容作为地址立即数传给R0,即将0xE000ED08指向的内容作为立即数,// 传递给R0, 以STM32F103启动文件中, 最初地址放置的__initial_sp, 值为0x08000000// 执行该命令后,R0寄存器内容为0x08000000,这正是STM32F103的Flash首地址ldr r0, [r0] // 将R0中的内容作为地址传递给R0,即将0x08000000指向的内容作为立即数,传递给// R0, 0x08000000中指向的是MSP的初始值,即初始化栈顶// 执行该命令后,R0寄存器值内容为0x20005B30/* Set the msp back to the start of the stack. */msr msp, r0 // 将 __initial_sp的初始值写入 MSP 中 /* Globally enable interrupts. */cpsie i // 开中断cpsie f // 开异常dsbisb/* Call SVC to start the first task. */svc 0 // 调用SVC中断 nopnop
}
/*-----------------------------------------------------------*/
在Cortex-M3 处理器,上电后默认使用MSP,为内核态,所以代码执行到这里的时候MSP已经更新了(因为只要执行C函数,都会使用堆栈),而一旦开启OS,程序将永远不会返回到这里,是一条不归路,所以之前使用的堆栈空间,我们就可以回收了,回收的方式也非常简单,重置MSP即可,将MSP再次指向上电初始位置。 (从这一点也能看出,FreeRTOS在内存使用上,还是比较节俭的)。
vPortSVCHandler
__asm void vPortSVCHandler( void )
{PRESERVE8ldr r3, =pxCurrentTCB // 1. 获取将要运行的任务TCP指针立即数, ldr r1, [r3] // 2. 将R3指向的内容,作为立即数赋值给R1ldr r0, [r1] // 3. 将R1指向的内容,作为立即数赋值给R0, 经过1,2,3, 后,R0中其实就存放了第一个将要// 启动运行的任务的 堆栈栈顶(The first item in pxCurrentTCB is the task top of stack) ldmia r0!, {r4-r11} // Pop the registers that are not automatically saved on exception entry and // the critical nesting count. 手动出栈,恢复一半现场 msr psp, r0 // 将更新后的任务堆栈地址psp_task 赋值给PSP,用于退出中断时,CPU自动恢复剩余的一半现场 isbmov r0, #0 msr basepri, r0 // 所有的中断都没有被屏蔽,开所有中断orr r14, #0xd // !!!这里非常重要,R14即LR,此时LR=0xFFFFFFF9,因为程序运行到这里是从线程模式进入中断,// 而且到目前为止,一直在使用MSP,所以LR被CPU自动设置为0xFFFFFFF9,// orr为[或]操作,相当于LR设置为 0xFFFFFFFD,这显然属于EXC_RETURN的取值范围 bx r14 // 退出中断,由于此时EXC_RETURN为0xFFFFFFFD, 这就相当于告诉CPU 3件事儿:// (1) 中断退出; (2) 退出后,CPU运行模式为用户态; (3)退出后,使用PSP。
}
/*-----------------------------------------------------------*/
执行完vPortSVCHandler,CPU就会感知到是中断退出,此时CPU会从当前任务的堆栈psp开始,使用PSP,自动出栈,恢复另一半现场,也就是xPSR, PC, R14, R12, R3, R2, R1, R0, 其中PC中存放了第一个任务的运行地址,CPU就开始执行第一个任务,至此,完成了启动第一个任务运行,即OS的启动。
使用SVC启动OS首个任务小结
上述流程虽然与FreeRTOS的有少许的差别,但是基本的流程是一致的。
使用PendSV启动OS分析
uC/OS-II是使用PendSV中断来启动OS的,参考uC/OS-II v2.9.3代码:
OSStart
void OSStart (void)
{if (OSRunning == OS_FALSE) {OS_SchedNew(); /* Find highest priority's task priority number */OSPrioCur = OSPrioHighRdy;OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run */OSTCBCur = OSTCBHighRdy;OSStartHighRdy(); /* Execute target specific code to start task */}
}
先启动一次任务查找,找到最高优先级的就绪任务,然后调用OSStartHighRdy运行该任务。
OSStartHighRdy
OSStartHighRdyLDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priorityLDR R1, =NVIC_PENDSV_PRISTRB R1, [R0]MOVS R0, #0 ; Set the PSP to 0 for initial context switch callMSR PSP, R0LDR R0, =OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBaseLDR R1, [R0]MSR MSP, R1 LDR R0, =OSRunning ; OSRunning = TRUEMOVS R1, #1STRB R1, [R0]LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)LDR R1, =NVIC_PENDSVSETSTR R1, [R0]CPSIE I ; Enable interrupts at processor level
这里还包括了一些初始化,比如PSP,设置OSRunning标识,最后触发PendSV中断。
OS_CPU_PendSVHandler
OS_CPU_PendSVHandlerCPSID I ; Prevent interruption during context switchMRS R0, PSP ; PSP is process stack pointerCBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first timeSUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stackSTM R0, {R4-R11}LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;LDR R1, [R1]STR R0, [R1] ; R0 is SP of process being switched out; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosavePUSH {R14} ; Save LR exc_return valueLDR R0, =OSTaskSwHook ; OSTaskSwHook();BLX R0POP {R14}LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;LDR R1, =OSPrioHighRdyLDRB R2, [R1]STRB R2, [R0]LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;LDR R1, =OSTCBHighRdyLDR R2, [R1]STR R2, [R0]LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;LDM R0, {R4-R11} ; Restore r4-11 from new process stackADDS R0, R0, #0x20MSR PSP, R0 ; Load PSP with new process SPORR LR, LR, #0x04 ; Ensure exception return uses process stackCPSIE IBX LR ; Exception return will restore remaining contextEND
上述中,通过PSP来区分是否为首次运行,如果是首次运行,则直接跳转到OS_CPU_PendSVHandler_nosave,即初次运行,就不需要保存当前任务的一半现场,而是直接启动当前最高优先级就绪任务。
特别注意, 在最后的两个重要指令:
MSR PSP, R0 ; Load PSP with new process SPORR LR, LR, #0x04 ; Ensure exception return uses process stackCPSIE IBX LR ; Exception return will restore remaining context
分别为更新当前任务堆栈地址到PSP,然后对LR进行或运算,此时LR的值为0xFFFFFFF9, 经过与0x04的或运算后,LR值变成了0xFFFFFFFD,然后调用BX LR,这里跟上面分析FreeRTOS的启动流程最后设置EXC_RETURN的机制,本质上是一样的,至此完成了OS的启动。开始运行第一个任务,后面都是一样的,通过Systick Handler来周期更新节拍,判断是否调度,然后通过PendSV进行任务调度,任务上下文切换。
小结
启动RTOS的核心是配置CONTROL寄存器,来创造OS依赖的运行环境:用户态 + 使用PSP,而配置CONTROL寄存器一般有2种方式,分别为:
- 直接设置CONTROL寄存器,这种方式比较简单,但是不够优雅,或者说程序架构不够清晰。
- 通过中断+修改EXC_RETURN的方式,间接设置CONTROL寄存器,来实现OS的启动。
其中方式2是目前RTOS常用的方式,我们以FreeRTOS和uC/OS为例,FreeRTOS是使用SVC来启动第一个任务,即实现OS的启动的。而uC/OS则是通过PendSV来实现启动首个任务,达到启动OS的目的。这两种方式没有好坏之分,只不过在实现理念上,由于FreeRTOS比较年轻,所以基于SVC启动OS的方式,更加安全,比较符合大型OS的设计理念,即OS与应用解耦,应用通过SVC来调用OS的服务。
所以,既可以通过PendSV实现OS启动,也可以通过SVC来实现OS启动。但是核心都是使用可编程中断 + 修改EXC_RETURN的方式来设置CONTROL寄存器,这也是主流的启动方式,因为有中断机制的帮助,可以让OS的启动更加合理高效。
RTOS系列(12):使用SVC或PendSV启动OS流程详细分析相关推荐
- uboot启动流程详细分析(基于i.m6ull)
uboot介绍 uboot就是一段引导程序,在加载系统内核之前,完成硬件初始化,内存映射,为后续内核的引导提供一个良好的环境.uboot是bootloader的一种,全称为universal boot ...
- U-BOOT启动流程详细分析[转]
http://www.cnblogs.com/heaad/archive/2010/07/17/1779829.html U-Boot启动内核的过程可以分为两个阶段,两个阶段的功能如下: (1)第一阶 ...
- ARMv8架构u-boot启动流程详细分析(一)
文章目录 1 概述 2 armv8 u-boot的启动 3 u-boot源码整体结构和一些编译配置方式 3.1 编译配置方式 3.2 u-boot源码结构 4 u-boot armv8链接脚本 4.1 ...
- 【内核】linux内核启动流程详细分析【转】
转自:http://www.cnblogs.com/lcw/p/3337937.html Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件 ...
- 【内核】linux内核启动流程详细分析
Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码, 主要作用 ...
- ARMv8架构u-boot启动流程详细分析(二)
文章目录 1 u-boot在汇编启动阶段对系统的一些初始化 1.1 启动前为后续流程做的一些平台相关操作 1.2 开启地址无关后的重定位地址操作 1.3 进入_main之前系统寄存器初始化和从核的引导 ...
- lk启动流程详细分析
转载请注明来源:cuixiaolei的技术博客 这篇文章是lk启动流程分析(以高通为例),将会详细介绍下面的内容: 1).正常开机引导流程 2).recovery引导流程 3).fastboot引导流 ...
- 海思uboot启动流程详细分析(二)
1. 第二个start.S 从start_armboot开始,在startup.c中有包含#include <config.h> 在config.h中: /* Automatically ...
- RTOS系列文章(6):Cortex-M3/4之SP,MSP,PSP,Thread模式、Handler模式、内核态、用户态
FreeRTOS系列(1):基础知识--中断嵌套 FreeRTOS系列文章(2):PendSV功能,为什么需要PendSV FreeRTOS系列文章(3): 为什么将SysTick和PendSV的优先 ...
最新文章
- js两个等号和三个等号_js中两个等号(==)和三个等号(===)的区别
- numpy批量iou
- Java程序员从笨鸟到菜鸟之(七十二)细谈Spring(四)利用注解实现spring基本配置详解
- hierarchy change - ERP side debugging
- android中文离线api_比林肯法球Linken sphere浏览器更多更新指纹的国产防关联软件-VMLogin中文版浏览器...
- php删除文件代码指定,PHP删除指定文件夹所有文件代码
- 纪念BLives 1.0版本发布
- 【kuangbin专题】Manacher
- 通过PDB文件实现非嵌入式的c++反射
- 2016级算法第一次练习赛-D.AlvinZH的儿时回忆——跳房子
- 计算机无法登陆提示rpc服务器不可用,电脑提示RPC服务器不可用的解决方法
- Apple Pay 详解
- 从西洋跳棋开始机器学习
- mui.ajax执行的次数,MUI 中使用 ajax下拉刷新时,数据怎么才能做到累加呢,谢谢...
- 攻略:大陆人成立香港公司以后如何运营?
- js判断数字,如果出现全角数字,将其转换为半角
- 用ajax做级联操作,学习笔记之MVC级联及Ajax操作
- BUPT计导第三次机考12.8数组+二分答案详解
- OFDM和OFDMA区别笔记
- 计算机专业方面期刊介绍--