FreeRTOS#4——任务调度
·
任务调度
开启任务调度器
vTaskStartScheduler()
作用:用于启动任务调度器
实现过程:
- 创建空闲任务
- 若使能软件定时器,则创建定时器任务
- 关闭中断,运行第一个任务时打开
- 初始化全局变量,并将任务调度器标志位 设置为 已运行
- 初始化任务运行时间统计功能的时基定时器
- 调用xPortStartScheduler()
xPortStartScheduler()
作用:用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务
实现过程:
- 检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误
- 配置PendSV和SysTick 的中断优先级为最低优先级
- 调用函数vPortSetupTimerInterrupt()配置SysTick
- 初始化临界区嵌套计数器为0
- 调用函数prvEnableVFP() 使能 FPU
- 调用函数prvStartFirstTask() 启动第一个任务
启动第一个任务
prvStartFirstTask()
- 开启第一个任务,主要重新设置MSP指针,使能全局中断,触发SVC中断
- MSP主堆栈指针:由内核、异常服务例程以及所有需要特权访问的应用程序代码来使用
- PSP进程堆栈指针:用于常规应用程序代码(不处于异常服务函数中)
vPortSVCHandler()
- SVC中断服务函数,通过pxCurrentTCB 获取优先级最高的就绪态任务的任务地址,就是将要执行的任务
- 通过任务的 栈顶指针,将任务栈中的内容出栈到CPU寄存器中,任务栈中的内容在创建任务时已被初始化
- 设置PSP指针
- 通过在basepri 寄存器中写0,开启中断
- 返回R14 中的值
// 将第一个任务 的内核寄存器入栈 开始执行
__asm void vPortSVCHandler( void )
{
PRESERVE8
/* 获取当前任务的TCB 地址 */
ldr r3, =pxCurrentTCB
ldr r1, [r3]
ldr r0, [r1]
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14} // cortex M3 无FPU,无需操作 R14,M4 M7有FPU 需要手动操作R14
msr psp, r0
isb
mov r0, #0
msr basepri, r0
bx r14
}
注意:
- 中断产生时,硬件自动将xPSR、PC、LR、R12、R3-R0保存和恢复,R4-R11需要手动保存恢复
- 进入中断后硬件会强制使用MSP指针,此时LR(R14)的值会被自动更新为特殊的EXC_RETURN
任务切换
高优先级任务触发时 会立即抢占当前任务,不等执行完当前时间片
只要调用了任务谢欢函数,就会进行切换,跟时间片无关,时间片只是定时会执行切换函数
本质:CPU内核寄存器的切换
在PendSV中执行上下文切换,切换时R4-R11、R14需要手动保存恢复 其余自动保存恢复
PendSV中断触发方式:
- 滴答定时器中断 调用
- 执行FreeRTOS中提供API:portYIELD()
// 任务切换 保存现场 恢复现场
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp
isb
//保存现场
/* 获取TCB地址 */
ldr r3, =pxCurrentTCB // 将 pxCurrentTCB 的地址加载到 r3
ldr r2, [r3] // 获取 pxCurrentTCB 的值(即当前TCB的地址)
/* 是否使用浮点计算单元,使用则手动保存R14 */
tst r14, #0x10 // 测试 EXC_RETURN 值(在 r14/LR 中)的第4位
it eq // 如果结果为0(使用FPU),则执行下一条指令
vstmdbeq r0!, {s16-s31} // 将FPU寄存器 s16-s31 压入任务栈
/* 手动保存R4-R11寄存器,其余自动保存*/
stmdb r0!, {r4-r11, r14} // 将寄存器 r4-r11 和 r14 压入任务栈
/* 保存寄存器起始地址 到TCB 的第一个元素*/
str r0, [r2] // 将更新后的栈指针保存到 TCB 的第一个成员(pxTopOfStack)
stmdb sp!, {r0, r3} // 临时保存 r0 和 r3 到主栈(MSP)
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0 // 设置 BASEPRI,屏蔽低于 5 优先级的中断
dsb // 数据同步屏障
isb // 指令同步屏障
bl vTaskSwitchContext // 调用函数,选择下一个要运行的任务
mov r0, #0
msr basepri, r0 // 取消中断屏蔽
ldmia sp!, {r0, r3} // 恢复 r0 和 r3
//恢复现场
/* 加载当前任务的 TCB 的第一个元素 */
ldr r1, [r3] // 获取新的 TCB 地址
ldr r0, [r1] // 获取新任务的栈顶指针(pxTopOfStack)
/*压入栈 */
ldmia r0!, {r4-r11, r14} // 弹出寄存器 r4-r11 和 r14(EXC_RETURN)
/* Is the task using the FPU context? If so, pop the high vfp registers
too. */
tst r14, #0x10 // 检查是否使用了FPU
it eq
vldmiaeq r0!, {s16-s31} // 如果用了,弹出FPU寄存器 s16-s31
msr psp, r0 // 将新任务的栈指针设置到 PSP
isb // 指令同步屏障
#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
#if WORKAROUND_PMU_CM001 == 1
push { r14 }
pop { pc }
nop
#endif
#endif
bx r14
}
// 获取当前最高优先级任务的 TCB,相同优先级 按时间片调度
#define taskSELECT_HIGHEST_PRIORITY_TASK()
{
UBaseType_t uxTopPriority;
/* Find the highest priority list that contains ready tasks. */
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0);
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
{
List_t * const pxConstList = ( pxList );
/* Increment the index to the next item and return the item, ensuring */
/* we don't return the marker used at the end of the list. */
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )
{
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
}
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;
}
空闲任务及其钩子函数
钩子函数作用:
- 配置进入低功耗模式
- 测量空闲任务执行时间占比
- 执行低优先级 但要不停处理的任务
钩子函数实现限制:
- 不能阻塞或挂起,否则会导致没有任务能进入运行态,如果空闲任务阻塞,而所有其他任务也在等待某些事件,系统将没有任何可运行的任务
- 需要尽快返回,在有任务被删除后 空闲任务负责回收内核资源
更多推荐

所有评论(0)