200字
RTOS任务调度
2024-03-02
2026-03-23

RTOS任务调度

**注: 本部分涉及太多理论内容 需要加强 **

①提前阅读Cortex-M3权威指南中文.pdf和《Cortex M3与M4权威指南》

②结合文档教程FreeRTOS开发指南_V1.5.pdf第八章进行学习

一、开启任务调度器(熟悉)

开启任务调度器函数vTaskStartScheduler()

vTaskStartScheduler()

作用:用于启动任务调度器,任务调度器启动后, FreeRTOS 便会开始进行任务调度

该函数内部实现,如下:

  1. 创建空闲任务
  2. 如果使能软件定时器,则创建定时器任务
  3. 关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
  4. 初始化全局变量,并将任务调度器的运行标志设置为已运行
  5. 初始化任务运行时间统计功能的时基定时器
  6. 调用函数 xPortStartScheduler()

配置硬件架构及启动第一个任务函数xPortStartScheduler()

作用:该函数用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务

该函数内部实现,如下:

  1. 检测用户在 FreeRTOSConfig. h 文件中对中断的相关配置是否有误

  2. 配置 PendSV 和 SysTick 的中断优先级为最低优先级

  3. 调用函数 vPortSetupTimerInterrupt()配置 SysTick

  4. 初始化临界区嵌套计数器为 0

  5. 调用函数 prvEnableVFP()使能 FPU

  6. 调用函数 prvStartFirstTask()启动第一个任务

启动第一个任务

prvStartFirstTask () /* 开启第一个任务 */

vPortSVCHandler () /* SVC中断服务函数 */

想象一下应该如何启动第一个任务

假设我们要启动的第一个任务是任务A,那么就需要将任务A的寄存器值恢复到CPU寄存器

任务A的寄存器值,在一开始创建任务时就保存在任务堆栈里边!

注意:

  1. 中断产生时,硬件自动将xPSR,PC(R15),LR(R14),R12,R3-R0出/入栈;而R4~R11需要手动出/入栈

  2. 进入中断后硬件会强制使用MSP指针 ,此时LR(R14)的值将会被自动被更新为特殊的EXC_RETURN

关于这些寄存器功能,可查看《 Cortex M3权威指南(中文) 》第27页

prvStartFirstTask () 介绍

用于初始化启动第一个任务前的环境,主要是重新设置MSP 指针,并使能全局中断

1、什么是MSP指针?

程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时,MCU 会自动更新 SP 指针,ARM Cortex-M 内核提供了两个栈空间

  • 主堆栈指针(MSP) :它是给OS内核、异常服务程序以及所有需要特权访问的应用程序代码来使用的。
  • 进程堆栈指针(PSP) :用于常规的应用程序代码(不处于异常服务程序中时使用)。

在FreeRTOS中,中断使用MSP(主堆栈),中断以外使用PSP(进程堆栈)

2、为什么汇编代码要PRESERVE8(八字节对齐)

答:因为栈在任何时候都是需要4字节对齐的,而在调用入口得8字节对齐,在C编程的时候,编译器会自动帮我们完成对齐操作,而汇编则需要手动对齐。

3、prvStartFirstTask()为什么要操作 0xE000ED08?

因为需从 0xE000ED08 获取向量表的偏移,为啥要获得向量表呢?因为向量表的第一个是 MSP 指针!

取 MSP 的初始值的思路是先根据向量表的位置寄存器 VTOR (0xE000ED08) 来获取向量表存储的地址;在根据向量表存储的地址,来访问第一个元素,也就是初始的 MSP

CM3 允许向量表重定位——从其它地址处开始定位各异常向量 这个就是向量表偏移量寄存器,向量表的起始地址保存的就是主栈指针MSP 的初始值

vPortSVCHandler ()介绍

当使能了全局中断,并且手动触发 SVC 中断后,就会进入到 SVC 的中断服务函数中

注意:SVC中断只在启动第一次任务时会调用一次,以后均不调用

  1. 通过 pxCurrentTCB 获取优先级最高的就绪态任务的任务栈地址,优先级最高的就绪态任务是系统将要运行的任务 。

  2. 通过任务的栈顶指针,将任务栈中的内容出栈到 CPU 寄存器中,任务栈中的内容在调用任务创建函数的时候,已初始化,然后设置 PSP 指针 。

  3. 通过往 BASEPRI 寄存器中写 0,允许中断。

  4. R14 是链接寄存器 LR,在 ISR 中(此刻我们在 SVC 的 ISR 中),它记录了异常返回值 EXC_RETURN

而EXC_RETURN 只有 6 个合法的值(M4、M7),如下表所示:

描述 使用浮点单元 未使用浮点单元 M3
中断返回后进入Hamdler模式,并使用MSP 0xFFFFFFE1 0xFFFFFFF1
中断返回后进入线程模式,并使用MSP 0xFFFFFFE9 0xFFFFFFF9
中断返回后进入线程模式,并使用PSP 0xFFFFFFED 0xFFFFFFFD

出栈/压栈汇编指令详解

  1. 出栈(恢复现场) ​,方向:从下往上(低地址往高地址):假设r0地址为0x04汇编指令示例:
    ldmia r0!, {r4-r6} /* 任务栈r0地址由低到高,将r0存储地址里面的内容手动加载到 CPU寄存器r4、r5、r6 */
    r0地址(0x04)内容加载到r4,此时地址r0 = r0+4 = 0x08
    r0地址(0x08)内容加载到r5,此时地址r0 = r0+4 = 0x0C
    r0地址(0x0C)内容加载到r6,此时地址r0 = r0+4 = 0x10
  2. 压栈(保存现场) ​,方向:从上往下(高地址往低地址):假设r0地址为0x10汇编指令示例:
    stmdb r0!, {r4-r6} } /* r0的存储地址由高到低递减,将r4、r5、r6里的内容存储到r0的任务栈里面。 */
    地址:r0 = r0-4 = 0x0C,将r6的内容(寄存器值)存放到r0所指向地址(0x0C)
    地址:r0 = r0-4 = 0x08,将r5的内容(寄存器值)存放到r0所指向地址(0x08)
    地址:r0 = r0-4 = 0x04,将r4的内容(寄存器值)存放到r0所指向地址(0x04)

三、任务切换(掌握)

任务切换的本质

任务切换的本质就是CPU寄存器内容的切换。

假设当由任务A切换到任务B时,主要分为两步:

第一步:需暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈,这个过程叫做保存现场;

第二步:将任务B的各个寄存器值(被存于任务堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场;

对任务A保存现场,对任务B恢复现场,这个整体的过程称之为:上下文切换

注意:任务切换的过程在PendSV中断服务函数里边完成

image

PendSV中断是如何触发的?

  1. 滴答定时器中断调用

  2. 执行FreeRTOS提供的相关API函数:portYIELD()

本质:通过向中断控制和状态寄存器 ICSR 的bit28 写入 1 挂起 PendSV 来启动 PendSV 中断

image

查找最高优先级任务

vTaskSwitchContext( ) /* 查找最高优先级任务 */

taskSELECT_HIGHEST_PRIORITY_TASK( ) /* 通过这个函数完成 */

 #define taskSELECT_HIGHEST_PRIORITY_TASK()                                                       \
    {                                                                                            \
        UBaseType_t uxTopPriority;                                                                     \
        portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );              \
        configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );   \
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );     \
    }

前导置零指令

所谓的前导置零指令,可以简单理解为计算一个32位数,头部0的个数

通过前导置零指令获得最高优先级

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

image

获取最高优先级任务的任务控制块

通过该函数获取当前最高优先级任务的任务控制块

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                            		 \
    {                                                      							 \
        List_t * const pxConstList = ( pxList );              		 \
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;       			 \
        if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) 	\
        {                   							 \
            ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;   		 \
        }                                         							 \	
        ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                	 \ 
    }

image09-FreeRTOS

任务调度(课堂总结).pdf

评论