前文提要
FreeRTOS学习(1)—为什么使用RTOS
FreeRTOS学习(2)-链表和节点之结构体分析
FreeRTOS学习(3)-链表和节点程序分析
FreeRTOS学习(4)-什么是任务?
FreeRTOS学习(5)-任务之静态创建函数解析
FreeRTOS学习(6)-任务之静态创建函数解析2(重点)
FreeRTOS学习(7)-任务切换理论分析(重点)
FreeRTOS学习(8)–pendsv和systick优先级分析以及任务切换场景
调度器(Scheduler)是操作系统的核心组件之一,其主要功能是管理和协调多任务的执行。它通过决定在特定时刻哪一个任务应该获得CPU的使用权,从而实现多任务的并发执行。
1、调度器的启动由 vTaskStartScheduler()函数来完成
(1)源代码
void vTaskStartScheduler( void )
{
/* 手动指定第一个运行的任务 */
pxCurrentTCB = &Task1TCB;
/* 启动调度器 */
if( xPortStartScheduler() != pdFALSE )
{
/* 调度器启动成功,则不会返回,即不会来到这里 */
}
}
(2)代码解释
第一个就是指定当前的任务pxCurrentTCB,我们通常通过这个全局变量 pxCurrentTCB(指向当前任务控制块的指针)来跟踪当前正在运行的任务。
第二个就是启动调度器函数xPortStartScheduler,这个函数是里面包括汇编语言。接下来将详细解释。
2、启动调度器xPortStartScheduler函数解析
(1)函数源码
BaseType_t xPortStartScheduler( void )
{
/* 配置PendSV 和 SysTick 的中断优先级为最低 */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* 启动第一个任务,不再返回 */
prvStartFirstTask();
/* 不应该运行到这里 */
return 0;
}
(2)代码解析
①为什么Pendsv和SysTick的优先级设置最低,上一篇博客已经写明原因。>FreeRTOS学习(8)–pendsv和systick优先级分析以及任务切换场景
②启动第一个任务prvStartFirstTask函数,单独解析。
3、在分析第一个启动任务之前,我们先介绍一下主堆栈(Main Stack)和进程堆栈(Process Stack)
(1)主堆栈(Main Stack)
是在 ARM Cortex-M 微控制器中用来保存处理器执行期间的上下文信息(如寄存器值)的内存区域。具体来说,主堆栈是由主堆栈指针(MSP)管理的堆栈。
(2)主堆栈作用
①异常和中断处理:
主堆栈主要用于处理异常(如硬件中断)和中断服务程序(ISR)。在发生中断时,处理器会自动使用主堆栈保存当前任务的上下文信息,然后跳转到中断服务程序。
当中断服务程序执行完毕后,处理器会从主堆栈中恢复上下文信息,继续执行被中断的任务。
②系统启动和初始化:
在系统启动和初始化阶段,处理器通常会使用主堆栈。这是因为在系统启动时,还没有创建其他任务,主堆栈是唯一可用的堆栈。
③特权级别的任务:
一些操作系统内核和特权级别的任务(例如,操作系统的关键任务)可能会使用主堆栈,以确保它们在受保护的堆栈中执行。
(3)进程堆栈
进程堆栈(Process Stack)通常由普通用户任务或线程使用,每个任务或线程都有自己的堆栈空间,以实现任务间的隔离和独立。
(4)ARM Cortex-M 微控制器支持两个堆栈指针:
主堆栈指针(MSP):用于管理主堆栈。
进程堆栈指针(PSP):用于管理进程堆栈。
(5)切换堆栈指针
ARM Cortex-M 处理器可以通过修改 CONTROL 寄存器来在 MSP 和 PSP 之间切换堆栈指针。具体来说,CONTROL 寄存器的第一个位(CONTROL[1])决定当前使用的是 MSP 还是 PSP:
当 CONTROL[1] 位为 0 时,处理器使用主堆栈指针(MSP)。
当 CONTROL[1] 位为 1 时,处理器使用进程堆栈指针(PSP)。
4、prvStartFirstTask函数解析
(1)源代码
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* 在Cortex-M中,0xE000ED08是SCB_VTOR这个寄存器的地址,
里面存放的是向量表的起始地址,即MSP的地址 */
ldr r0, =0xE000ED08
ldr r0, [r0]
ldr r0, [r0]
/* 设置主堆栈指针msp的值 */
msr msp, r0
/* 使能全局中断 */
cpsie i
cpsie f
dsb
isb
/* 调用SVC去启动第一个任务 */
svc 0
nop
nop
}
(2)代码解析
这段代码的主要目的是为启动第一个任务做好准备,具体步骤如下:
①从向量表读取初始主堆栈指针(MSP)并设置 MSP。
②使能全局中断,以确保中断处理可以进行。
③触发 SVC 中断,该中断处理程序将会执行第一个任务的上下文切换,从而启动第一个任务。
(3)汇编代码的逐句解析
5、 SVC 中断vPortSVCHandler函数解析
(1)源代码
__asm void vPortSVCHandler( void )
{
extern pxCurrentTCB;
PRESERVE8
ldr r3, =pxCurrentTCB