从源码解析FreeRTOS是如何进行任务切换
一.SysTick_Handler()
在FreeRTOSConfig.h定义了系统节拍时钟频率 ,下面表示每s有1000次心跳,即1ms一次(用户自己可以根据自己的需求修改)
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
根据上面的定义系统每隔1ms就会调用SysTick_Handler()
#define portNVIC_INT_CTRL_REG ( *( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
// SysTick_Handler()在port.c中
void SysTick_Handler( void ) /* PRIVILEGED_FUNCTION */
{
uint32_t ulPreviousMask;
//开启屏蔽其他中断
ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
{
/* xTaskIncrementTick()函数返回值:如果就绪链表中有比当前优先级高的或相同优先级的任务则返回pdTRUE,否者返回pdFALSE */
if( xTaskIncrementTick() != pdFALSE )
{
/* 通过向中断控制和状态寄存器ICSR的bit28写入1挂起PendSv来启动PendSV中断 */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
//退出屏蔽其他中断
portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}
二.xTaskIncrementTick() 函数
xTaskIncrementTick()是进行任务切换的重要函数,根据条件把阻塞的任务加入就绪链表中。
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
traceTASK_INCREMENT_TICK( xTickCount );//空函数;
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )//任务调度是否挂起
{
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;//系统时间加1
xTickCount = xConstTickCount;
//判断系统时间是否溢出,溢出则交换链表
if( xConstTickCount == ( TickType_t ) 0U )
{
taskSWITCH_DELAYED_LISTS();//交换列表,交换延时列表指针pxDelayedTaskList和溢出列表指针pxDelayedTaskList所指向的列表,并更新了xNextTaskUnblockTime的值;
}
else
{
mtCOVERAGE_TEST_MARKER();//否则什么也不做,空函数
}
if( xConstTickCount >= xNextTaskUnblockTime )//如果系统节拍 xConstTickCount的值大于等于xNextTaskUnblockTime 的值,表示有任务需要解除阻塞,因为xNextTaskUnblockTime 里保存的是下一个解除阻塞的时间点值;
{
for( ; ; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) //判断延时列表是否为空,pdTRUE时表示延时列表为空,pdFALSE表示延时列表不为空;
{
//延时列表为空,设置xNextTaskUnblockTime为最大值
xNextTaskUnblockTime = portMAX_DELAY;
break;//说明没有需要接触阻塞任务,退出循环
}
else
{
// 延时列表不为空,获取延时列表的第一个列表项的值,并获取具体值
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
// 根据判断这个值,这个值里面保存的是下一个解除阻塞态的任务对应的解除时间点,判断延时时间是否到了,到了之后就移除延时列表;
if( xConstTickCount < xItemValue )
{
//延时时间还没到,需要更新xNextTaskUnblockTime 的值,用xItemValue来更新xNextTaskUnblockTime;
xNextTaskUnblockTime = xItemValue;
break;
}
else
{
mtCOVERAGE_TEST_MARKER(); //空函数
}
//延时时间到了,将任务从延时列表中移除
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
//任务是否还在等待其他事件,如信号量、列队等,如果是的话就将任务从相应的事件列表中移除,相当于等待事件超时退出;
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER(); //空函数
}
//任务延时时间到了,并以将任务从延时列表移除,所以需要把任务添加进就绪列表中;
prvAddTaskToReadyList( pxTCB );
//使能抢占式调度
#if ( configUSE_PREEMPTION == 1 )
{
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER(); //空函数
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
//如果使能了时间片调度的话还要处理同优先级下任务的调度
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER(); //空函数
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
//如果使能了时间片钩子函数的话就执行时间片钩子函数vApplicationTickHook(),函数的具体内容由用户自己编写;
#if ( configUSE_TICK_HOOK == 1 )
{
/* Guard against the tick hook being called when the pended tick
* count is being unwound (when the scheduler is being unlocked). */
if( xPendedTicks == ( TickType_t ) 0 )
{
vApplicationTickHook();
}
else
{
mtCOVERAGE_TEST_MARKER(); //空函数
}
}
#endif /* configUSE_TICK_HOOK */
//有时候调用其它的API函数会使用变量 xYieldPending来标记是否是要需要进行上下文切换;
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER(); //空函数
}
}
#endif /* configUSE_PREEMPTION */
}
else //任务调度器被挂起
{
++xPendedTicks;; //它是全局变量,用uxPendedTicks来记录任务调度器挂起过程中的时钟节拍数;这样在用xTaskResumeAll()时就会调用uxPendedTicks次 xTaskIncrementTick(),xTickCount就会恢复,并且那些应该取消阻塞的任务也会取消阻塞;
/* The tick hook gets called at regular intervals, even if the
* scheduler is locked. */
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
return xSwitchRequired; //返回 xSwitchRequired的值,它里面保存了是否任务切换的信息,若为pdTRUE表示需要任务切换,为pdFALSE表示不需要切换任务;
}
三. PendSV_Handler函数
FreeRTOS在PendSV_Handler函数进行任务切换,此函数使用的都是汇编语言
void PendSV_Handler( void ) /* __attribute__ (( naked )) PRIVILEGED_FUNCTION */
{
__asm volatile //内联汇编 括号里面为汇编语言
(
" .syntax unified \n"
" \n"
" mrs r0, psp \n"/* 读取当前进程栈指针到r0中 */
#if ( ( configENABLE_FPU == 1 ) || ( configENABLE_MVE == 1 ) ) /* 如果使能FPU / MVU s16 - s31 寄存器值入栈保存 */
" tst lr, #0x10 \n"
" it eq \n"
" vstmdbeq r0!, {s16-s31} \n"
#endif /* configENABLE_FPU || configENABLE_MVE */
" mrs r2, psplim \n"/* r2 = PSPLIM. */
" mov r3, lr \n"/* r3 = LR/EXC_RETURN. */
" stmdb r0!, {r2-r11} \n"/* Store on the stack - PSPLIM, LR and registers that are not automatically saved. */
" \n"
" ldr r2, pxCurrentTCBConst \n"/* Read the location of pxCurrentTCB i.e. &( pxCurrentTCB ). */
" ldr r1, [r2] \n"/* Read pxCurrentTCB. */
" str r0, [r1] \n"/* Save the new top of stack in TCB. */
" \n"
" mov r0, %0 \n"/* r0 = configMAX_SYSCALL_INTERRUPT_PRIORITY */
" msr basepri, r0 \n"/* Disable interrupts upto configMAX_SYSCALL_INTERRUPT_PRIORITY. */
" dsb \n"
" isb \n"
" bl vTaskSwitchContext \n"
" mov r0, #0 \n"/* r0 = 0. */
" msr basepri, r0 \n"/* Enable interrupts. */
" \n"
" ldr r2, pxCurrentTCBConst \n"/* Read the location of pxCurrentTCB i.e. &( pxCurrentTCB ). */
" ldr r1, [r2] \n"/* Read pxCurrentTCB. */
" ldr r0, [r1] \n"/* The first item in pxCurrentTCB is the task top of stack. r0 now points to the top of stack. */
" ldmia r0!, {r2-r11} \n"/* Read from stack - r2 = PSPLIM, r3 = LR and r4-r11 restored. */
#if ( ( configENABLE_FPU == 1 ) || ( configENABLE_MVE == 1 ) )
" tst r3, #0x10 \n"/* Test Bit[4] in LR. Bit[4] of EXC_RETURN is 0 if the Extended Stack Frame is in use. */
" it eq \n"
" vldmiaeq r0!, {s16-s31} \n"/* Restore the additional FP context registers which are not restored automatically. */
" msr psplim, r2 \n"/* Restore the PSPLIM register value for the task. */
" msr psp, r0 \n"/* Remember the new top of stack for the task. */
" bx r3 \n"
" \n"
" .align 4 \n"
"pxCurrentTCBConst: .word pxCurrentTCB \n"
::"i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY )
);
}