前言
本文是关于 FreeRTOS 中实现两个任务轮流切换并执行的代码详解。目前不支持优先级,仅实现两个任务轮流切换。
一、任务的自传
任务从生到死的过程究竟是怎么样的呢?(其实也没死),这个问题一直困扰着我,单纯看野火的讲解有点云里雾里,所有自己捋了一遍嘿嘿:P。
下面这个图描述了任务的创建,调度器的使用以及手动切换任务的过程(这时还没用到优先级进行任务切换)。
二、任务切换相关函数详解
1. taskYIELD()
- 其实是一个宏定义,代码如下:
#define taskYIELD() portYIELD()
- 用于在任务执行完成后手动调用进行任务切换:
/* 任务 */
void Task1_Entry( void *p_arg )
{
for( ;; )
{
//下面是任务代码
/* 任务切换,这里是手动切换 */
taskYIELD();
}
}
2. portYIELD()
- 通过设置标志位触发 PendSV 中断
- 代码如下:
#define portYIELD() \
{ \
/* 触发PendSV,产生上下文切换 */ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
- 其中标志位定义如下:
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
- 该标志位定义的根据是
PM0056Programming manual
STM32F10xxx Cortex-M3 programming manual
3. xPortPendSVHandler()
- 该函数即 PendSVC Handler
- 函数分为五个部分
① 上个任务运行环境的保存(将CPU寄存器的值保存到任务栈中)
② 调用任务指针切换函数前对寄存器的保护
③ 调用任务指针切换函数,使任务指针指向新的任务
④ 调用任务指针切换函数后对寄存器的恢复
⑤ 下个任务运行环境的加载(将任务栈中的值加载到CPU寄存器) - 函数代码如下:
__asm void xPortPendSVHandler( void )
{
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
//1. 上个任务运行环境的保存(将CPU寄存器的值保存到任务栈中)
/* 当进入PendSVC Handler时,上一个任务运行的环境即:
xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
这些CPU寄存器的值会自动保存到任务的栈中,剩下的r4~r11需要手动保存 */
/* 获取任务栈指针到r0 */
mrs r0, psp
isb
ldr r3, =pxCurrentTCB /* 加载pxCurrentTCB的地址到r3 */
ldr r2, [r3] /* 加载pxCurrentTCB到r2 */
stmdb r0!, {r4-r11} /* 将CPU寄存器r4~r11的值存储到r0指向的地址 */
str r0, [r2] /* 将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员,即栈顶指针 */
//2. 调用任务指针切换函数前对寄存器的保护
stmdb sp!, {r3, r14} /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,
调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护;
R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护 */
//3. 调用任务指针切换函数,使任务指针指向新的任务
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY /* 进入临界段 */
msr basepri, r0
dsb
isb
bl vTaskSwitchContext /* 调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */
mov r0, #0 /* 退出临界段 */
msr basepri, r0
//4. 调用任务指针切换函数后对寄存器的恢复
ldmia sp!, {r3, r14} /* 恢复r3和r14 */
//5. 下个任务运行环境的加载(将任务栈中的值加载到CPU寄存器)
ldr r1, [r3]
ldr r0, [r1] /* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/
ldmia r0!, {r4-r11} /* 出栈 */
msr psp, r0
isb
bx r14 /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、
使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,
然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,
当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/
nop
}
4. vTaskSwitchContext()
其实就是对 pxCurrentTCB 进行了手动更新,目前还很粗糙。
//任务切换函数
void vTaskSwitchContext( void )
{
/* 两个任务轮流切换 */
if( pxCurrentTCB == &Task1TCB )
{
pxCurrentTCB = &Task2TCB;
}
else
{
pxCurrentTCB = &Task1TCB;
}
}
后记
如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!