前言
本文是关于 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, pspisbldr 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, r0dsbisbbl 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, r0isbbx r14 /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/nop
}
4. vTaskSwitchContext()
其实就是对 pxCurrentTCB 进行了手动更新,目前还很粗糙。
//任务切换函数
void vTaskSwitchContext( void )
{ /* 两个任务轮流切换 */if( pxCurrentTCB == &Task1TCB ){pxCurrentTCB = &Task2TCB;}else{pxCurrentTCB = &Task1TCB;}
}
后记
如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!