1 概念性内容
开机到调度需要经历的步骤有:
- 系统初始化
- 任务创建
- 启动调度器
- 上下文切换
- 时间分片
- 任务执行
1.1 任务本质
FreeRTOS 的 任务(Task)本质上就是一个运行在任务自己的栈区中无限循环的函数 + 一段上下文(context)数据 。
一个任务有四种状态:就绪态、挂起态、运行态、阻塞态
- 就绪态:任务已准备好运行,处于就绪列表中,按优先级排序
- 运行态:当前cpu正在执行该任务。
- 阻塞态:因某些原因不能继续执行,被放入阻塞列表(延时会放入延时列表),阻塞消除会重新回到就绪列表。
- 挂起态:手动调用api挂起,必须再调用resume才能恢复执行。
- 终止态:不在调度器控制内。
1.2 什么是上下文
上下文其实就是线程的状态
线程状态包括:
类别 | 保存内容 |
---|---|
CPU寄存器 | R0~R12, LR, PC, xPSR |
高级寄存器 | R4~R11(手动保存) |
栈指针 | PSP(Process Stack Pointer) |
状态寄存器 | xPSR |
任务控制块 | 保存任务的栈顶指针(SP)和其他调度信息 |
对寄存器不熟悉的同学可以参考:
ARM架构 中的寄存器内容
不同信息的保存实现:
类型 | 内容 | 描述 |
---|---|---|
自动保存寄存器 | R0 ~ R3, R12, LR, PC, xPSR | 由硬件在异常(中断)进入时自动压栈 |
手动保存寄存器 | R4~R11 | 由 FreeRTOS 的上下文切换汇编代码手动压栈 |
特殊寄存器 | SP、 LR、 PSP/MSP | 栈指针、返回地址,决定任务能否正确返回 |
FreeRTOS 会将这些上下文信息 保存在任务的栈 中,并把 栈顶指针 (即当前任务运行到哪)记录在任务的 TCB 中。
当再次切回这个任务时,就从它保存的栈中把这些寄存器恢复回来,任务就能从中断前的状态"无感恢复"。
2 代码
2.1 关键代码
(1)SVC_Handler
用途: SVC 异常通常用于实现系统调用,允许用户在特权级别(Supervisor)执行一些特殊的操作,例如请求操作系统服务。在启动调度时候会执行。
一般的,SVC_Handler主要用于启动第一个任务以及初始化系统的运行环境。在 FreeRTOS 启动过程中,prvStartFirstTask() 函数会被调用,该函数最终通过 SVC 请求来跳转至初始任务的执行路径。
c
__asm void vPortSVCHandler( void )
{
PRESERVE8 // 保留R4-R11和LR寄存器,确保不会被改
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB // pxCurrentTCB地址加载到寄存器r3。pxCurrentTCB:指向当前任务控制块的指针。
ldr r1, [r3] // pxCurrentTCB 的值加载到r1
ldr r0, [r1] // r1 指向的地址(TCB中的栈顶地址)加载到r0
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14}// 加载多个寄存器,保存当前任务上下文
msr psp, r0 // 更新程序堆栈指针(PSP)的值为r0,加载下一个任务的上下文
isb // 指令同步屏障,确保更新PSP
mov r0, #0 // 将0移至寄存器r0,用于接下来重置基本优先级寄存器。
msr basepri, r0 // 中断优先级控制寄存器设置为零,允许所有中断。
bx r14 // 使用r14(LR)的值返回到SVC调用之前的地址
}
(2)PendSVHandler
- 为什么采用了pendSVHandler函数切换任务而不是在systick定时中断切换任务:
如果优先级低,那么切换任务时会被别的高优先级任务打断,容易出问题。
如果优先级高,那么高优先级任务执行切换任务的函数,耗时较长,实时性差。 - 解决的办法:
仅使用systick异常通知需要切换任务,
使用优先级最低的PendSV_Handler中断函数来真正切换任务
c
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
/* *INDENT-OFF* */
PRESERVE8
mrs r0, psp
isb
ldr r3, =pxCurrentTCB /* Get the location of the current TCB. */
ldr r2, [ r3 ]
stmdb r0 !, { r4 - r11 } /* Save the remaining registers. */
str r0, [ r2 ] /* Save the new top of stack into the first member of the TCB. */
stmdb sp !, { r3, r14 }
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
dsb
isb
bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
ldmia sp !, { r3, r14 }
ldr r1, [ r3 ]
ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. */
ldmia r0 !, { r4 - r11 } /* Pop the registers and the critical nesting count. */
msr psp, r0
isb
bx r14
nop
/* *INDENT-ON* */
}
其中vTaskSwitchContext
的函数实现如下
c
void vTaskSwitchContext( void )
{
//如果调度器挂起那就不能进行任务切换
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();
taskCHECK_FOR_STACK_OVERFLOW(); /* Check for stack overflow, if configured. */
taskSELECT_HIGHEST_PRIORITY_TASK();
/* 获取最高优先级的任务
实现是一个宏,首先执行从任务列表中获取最高优先级任务,
然后在最高优先级的任务就绪列表中获取下一个任务的控制块TCP。 */
traceTASK_SWITCHED_IN();
}
}