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种方式,分别为:

  1. 直接设置CONTROL寄存器,这种方式比较简单,但是不够优雅,或者说程序架构不够清晰。
  2. 通过中断+修改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可能会有如下问题:

  1. EXC_RETURN是CPU专门为中断设计的,使中断服务程序可以使用C来编写,关于EXC_RETURN的详细介绍,可以参考前面的文章。所以使用EXC_RETURN机制,就意味着必须要配合着中断使用,而能被程序配置触发的中断,似乎只有Systick 、PendSV和SVC中断。而Systick中断,我们一般只是提供时钟节拍,不会用于任务调度,上下文切换,所以很显然,我们就剩下2种选择:PendSV和SVC,不过这也足够了。
  2. 我们使用中断+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种方式,分别为:

  1. 直接设置CONTROL寄存器,这种方式比较简单,但是不够优雅,或者说程序架构不够清晰。
  2. 通过中断+修改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流程详细分析相关推荐

  1. uboot启动流程详细分析(基于i.m6ull)

    uboot介绍 uboot就是一段引导程序,在加载系统内核之前,完成硬件初始化,内存映射,为后续内核的引导提供一个良好的环境.uboot是bootloader的一种,全称为universal boot ...

  2. U-BOOT启动流程详细分析[转]

    http://www.cnblogs.com/heaad/archive/2010/07/17/1779829.html U-Boot启动内核的过程可以分为两个阶段,两个阶段的功能如下: (1)第一阶 ...

  3. ARMv8架构u-boot启动流程详细分析(一)

    文章目录 1 概述 2 armv8 u-boot的启动 3 u-boot源码整体结构和一些编译配置方式 3.1 编译配置方式 3.2 u-boot源码结构 4 u-boot armv8链接脚本 4.1 ...

  4. 【内核】linux内核启动流程详细分析【转】

    转自:http://www.cnblogs.com/lcw/p/3337937.html Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件 ...

  5. 【内核】linux内核启动流程详细分析

    Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码, 主要作用 ...

  6. ARMv8架构u-boot启动流程详细分析(二)

    文章目录 1 u-boot在汇编启动阶段对系统的一些初始化 1.1 启动前为后续流程做的一些平台相关操作 1.2 开启地址无关后的重定位地址操作 1.3 进入_main之前系统寄存器初始化和从核的引导 ...

  7. lk启动流程详细分析

    转载请注明来源:cuixiaolei的技术博客 这篇文章是lk启动流程分析(以高通为例),将会详细介绍下面的内容: 1).正常开机引导流程 2).recovery引导流程 3).fastboot引导流 ...

  8. 海思uboot启动流程详细分析(二)

    1. 第二个start.S 从start_armboot开始,在startup.c中有包含#include <config.h> 在config.h中: /* Automatically ...

  9. RTOS系列文章(6):Cortex-M3/4之SP,MSP,PSP,Thread模式、Handler模式、内核态、用户态

    FreeRTOS系列(1):基础知识--中断嵌套 FreeRTOS系列文章(2):PendSV功能,为什么需要PendSV FreeRTOS系列文章(3): 为什么将SysTick和PendSV的优先 ...

最新文章

  1. js两个等号和三个等号_js中两个等号(==)和三个等号(===)的区别
  2. numpy批量iou
  3. Java程序员从笨鸟到菜鸟之(七十二)细谈Spring(四)利用注解实现spring基本配置详解
  4. hierarchy change - ERP side debugging
  5. android中文离线api_比林肯法球Linken sphere浏览器更多更新指纹的国产防关联软件-VMLogin中文版浏览器...
  6. php删除文件代码指定,PHP删除指定文件夹所有文件代码
  7. 纪念BLives 1.0版本发布
  8. 【kuangbin专题】Manacher
  9. 通过PDB文件实现非嵌入式的c++反射
  10. 2016级算法第一次练习赛-D.AlvinZH的儿时回忆——跳房子
  11. 计算机无法登陆提示rpc服务器不可用,电脑提示RPC服务器不可用的解决方法
  12. Apple Pay 详解
  13. 从西洋跳棋开始机器学习
  14. mui.ajax执行的次数,MUI 中使用 ajax下拉刷新时,数据怎么才能做到累加呢,谢谢...
  15. 攻略:大陆人成立香港公司以后如何运营?
  16. js判断数字,如果出现全角数字,将其转换为半角
  17. 用ajax做级联操作,学习笔记之MVC级联及Ajax操作
  18. BUPT计导第三次机考12.8数组+二分答案详解
  19. OFDM和OFDMA区别笔记
  20. 计算机专业方面期刊介绍--

热门文章

  1. 斐讯k3怎么设置虚拟服务器,斐讯 K3 无线路由器无线中继设置教程
  2. ztext - 简单几行代码创建酷炫 3D 特效文字的开源 JS 库
  3. Python从入门到实践第9章课后作业
  4. QueryPerformanceCounter
  5. 问答学习系统 - 针式PKM V8.20新增功能
  6. [转]我的IT学习生活(搜藏)
  7. 美标Class压力等级与MPa等级换算关系 by阿斯米合金
  8. Python3网络爬虫
  9. 1155网址大全:打造简单实用的网址导航
  10. Android 动画基础知识学习(下)