FreeRTOS学习 --- 任务切换
任务切换的本质:就是CPU寄存器的切换。
假设当由任务A切换到任务B时,主要分为两步:
第一步:需暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈,这个过程叫做保存现场;
第二步:将任务B的各个寄存器值(被存于任务堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场;
对任务A保存现场,对任务B恢复现场,这个整体的过程称之为:上下文切换
注意:任务切换的过程在PendSV中断服务函数里边完成
PendSV中断是如何触发的?
1、滴答定时器中断触发PendSV中断
2、执行FreeRTOS提供的相关API函数:portYIELD()触发PendSV中断
本质:通过向中断控制和状态寄存器 ICSR 的bit28 写入 1 挂起 PendSV 来启动 PendSV 中断
上表摘取于《Cortex M3权威指南(中文)》第131页。
查找最高优先级任务
vTaskSwitchContext( ) /* 查找最高优先级任务 */
taskSELECT_HIGHEST_PRIORITY_TASK( ) /* 通过这个函数完成 */
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
}
获取最高优先级任务的任务控制块
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{
List_t * const pxConstList = ( pxList ); \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
} \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
通过该函数获取当前最高优先级任务的任务控制块,同时同等优先级的任务会在一个时间片后进行切换,这就是时间片调度。
任务切换具体步骤:
1.mrs r0, psp
把psp存到r0,当前的psp是任务A的栈指针,这时的psp指向的是任务栈A中的R0
2. ldr r3, =pxCurrentTCB
ldr r2, [ r3 ]
获取当前运行任务的栈顶地址即R2保存的栈顶地址,注意R3等于pxCurrentTCB的地址
3.stmdb r0!, {r4-r11, r14}
压栈,从上往下压,将r0的值,当压栈的起始地址,开始压栈(保存现场)
4.str r0, [ r2 ]
将r0的值(前面的底部地址),写到r2地址所指向的内存中(即栈顶地址指向的内存, pxTopOfStack中)
5.stmdb sp!, {r0, r3}
把R0和R3的值压入MSP
6.bl vTaskSwitchContext
通过该函数,获取下一个执行任务的任务控制块,赋值给pxCurrentTCB
7.ldmia sp!, {r0, r3}
从sp(MSP)恢复r3,即把r3恢复成&pxCurrentTCB。后续就可以利用r3得到新的任务控制块了。
8.ldmia r0!, {r4-r11, r14}
出栈,以寻址地址开始,从下往上进行出栈,将保存在这些地址的值恢复到寄存器里边去
9.msr psp, r0
bx r14
①将r0更新给psp线程堆栈
②返回线程模式,执行新任务