任务切换的源码实现
过程差不多了解了,那看看FreeRTOS中怎么实现吧!!
FreeRTOS有两种方法触发任务切换:
一种就是systick触发PendSV异常,这是最经常使用的。
另一种是主动进行切换任务,执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换。
1
先说说第一种吧,就在systick中断中调用xPortSysTickHandler();
下面是源码:
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
{
/* Increment the RTOS tick.*/
if( xTaskIncrementTick() !=pdFALSE )
{
/* A context switch isrequired. Context switching is performedin
the PendSVinterrupt. Pend the PendSV interrupt. */
portNVIC_INT_CTRL_REG =portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
它的执行过程是这样子的,屏蔽所有中断,因为SysTick以最低的中断优先级运行,所以当这个中断执行时所有中断必须被屏蔽。
vPortRaiseBASEPRI();就是屏蔽所有中断的。而且并不需要保存本次中断的值,因为systick的中断优先级是已知的,执行完直接恢复所有中断即可。
在xTaskIncrementTick()中会对tick的计数值进行自加,然后检查有没有处于就绪态的最优先级任务,如果有,则返回非零值,然后表示需要进行任务切换,而并非马上进行任务切换,此处要注意,它只是向中断状态寄存器bit28位写入1,只是将PendSV挂起,假如没有比PendSV更高优先级的中断,它才会进入PendSV中断服务函数进行任务切换。
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
然后解除屏蔽所有中断。
vPortClearBASEPRIFromISR();
2
另一种方法是主动进行任务切换,不管是使用taskYIELD()还是portYIELD_FROM_ISR(),最终都会执行下面的代码:
#define portYIELD()
{
/* Set a PendSV to request acontext switch. */
portNVIC_INT_CTRL_REG =portNVIC_PENDSVSET_BIT;
__d***( portSY_FULL_READ_WRITE);
__i***( portSY_FULL_READ_WRITE);
}
这portYIELD()其实是一个宏定义来的。同样是向中断状态寄存器bit28位写入1,将PendSV挂起,然后等待任务的切换。
具体的任务切换源码
一直在说怎么进行任务切换的,好像还没看到任务切换的源码啊,哎,下面来看看任务切换的真面目!!
__asm void xPortPendSVHandler(void)
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp
i***
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 stackinto the first member of the TCB. */
stmdb sp!, {r3, r14}
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
d***
i***
bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
ldmia sp!, {r3, r14}
ldr r1, [r3]
ldr r0, [r1] /* The first item inpxCurrentTCB is the task top of stack. */
ldmia r0!, {r4-r11} /* Pop the registers and the criticalnesting count. */
msr psp, r0
i***
bx r14
nop
}
不是我不想看,是我看到汇编就头大啊,这几天我也在看源码,实在是头大。
找到核心的函数看看就好啦,不管那么多,有兴趣的可以研究一下中断代码,有不懂的也很欢迎你们来问我,一起研究研究,也是不错的选择。
下面是看重点的地方了:
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
这两句代码是关闭中断的。关中断就得干活了,嘿嘿嘿~
bl vTaskSwitchContext
BL是跳转指令嘛,这个我还是有点懂的。
调用函数vTaskSwitchContext(),寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换,然后就是打开中断,退出去了。
寻找下一个要运行任务
是不是感觉没什么大不了的样子,如果你是这样子觉得的,可能还没学到家,赶紧去看看FreeRTOS的源码,在config.h配置文件中是不是有一个叫做硬件查找下一个运行的任务呢?
configUSE_PORT_OPTIMISED_TASK_SELECTION,这个在FreeRTOS中叫做特殊方法,其实也是硬件查找啦,但是并不是每种单片机都支持的,如果是不支持的话,只能选择软件查找的方法了,就是所谓的通用方法。通用方法我就不多说了,因为我用的是STM32,他是支持硬件方法的,这样子效率更高,所以我也没必要去研究他的软件方法,假如有兴趣的小伙伴可以研读一下源码,有不懂的可以向我提问,源码如下: #define taskSELECT_HIGHEST_PRIORITY_TASK()
{
UBaseType_t uxTopPriority =uxTopReadyPriority;
/* Find the highest priorityqueue that contains ready tasks. */
while( listLIST_IS_EMPTY(&( pxReadyTasksLists[ uxTopPriority ] ) ) )
{
configASSERT(uxTopPriority );
--uxTopPriority;
}
/*listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of
the same priority get anequal share of the processor time. */
listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
uxTopReadyPriority =uxTopPriority;
} /*taskSELECT_HIGHEST_PRIORITY_TASK */
而硬件的方法源码则在下面:
#define taskSELECT_HIGHEST_PRIORITY_TASK()
{
UBaseType_tuxTopPriority;
/* Find the highest prioritylist 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() */
其方法是利用硬件提供的计算前导零指令CLZ,具体宏定义为:
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
静态变量uxTopReadyPriority包含了处于就绪态任务的最高优先级的信息,因为FreeRTOS运行的永远是处于最高优先级的运行态,而下个处于最高优先级的就绪态则必定会在下次任务切换的时候运行,uxTopReadyPriority使用每一位来表示任务是否处于就绪态,比如变量uxTopReadyPriority的bit0为1,则表示存在优先级为0的任务处于就绪态,bit6为1则表示存在优先级为6的任务处于就绪态。
并且,由于bit0的优先级高于bit6,那么下个任务就是bit0的任务运行了(数组越低优先级越高)。由于32位整形数最多只有32位,因此使用这种特殊方法限定最大可用优先级数目为32,即优先级0~31。得到了下个处于最高优先级就绪态任务了,就调用listGET_OWNER_OF_NEXT_ENTRY来获取下一个任务的列表项,然后将该列表项的任务控制块TCB赋值给pxCurrentTCB,那么我们就得到下一个要运行的任务了。
至此,任务切换已经完成。
END