参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili
一、开启任务调度器
1、vTaskStartScheduler函数
(1)作用:启动任务调度器,任务调度器启动后,FreeRTOS便会开始进行任务调度。
(2)函数的执行流程:
①创建空闲任务。
②如果使能软件定时器,则创建定时器任务。
③关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断。
④初始化全局变量,并将任务调度器的运行标志设置为已运行。
⑤初始化任务运行时间统计功能的时基定时器。
⑥调用函数xPortStartScheduler。
(3)源码剖析:
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
traceENTER_vTaskStartScheduler();
#if ( configUSE_CORE_AFFINITY == 1 ) && ( configNUMBER_OF_CORES > 1 )
{
configASSERT( ( sizeof( UBaseType_t ) * taskBITS_PER_BYTE ) >= configNUMBER_OF_CORES );
}
#endif /* #if ( configUSE_CORE_AFFINITY == 1 ) && ( configNUMBER_OF_CORES > 1 ) */
xReturn = prvCreateIdleTasks(); //创建空闲任务
#if ( configUSE_TIMERS == 1 ) //如果使能软件定时器,则创建定时器任务
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask(); //创建软件定时器任务
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
if( xReturn == pdPASS ) //判断任务是否创建成功
{
#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
{
freertos_tasks_c_additions_init();
}
#endif
portDISABLE_INTERRUPTS(); //关中断,防止调度器开启之前或过程中受中断干扰,会在运行第一个任务时打开中断
#if ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 )
{
configSET_TLS_BLOCK( pxCurrentTCB->xTLSBlock );
}
#endif
xNextTaskUnblockTime = portMAX_DELAY; //下一个任务的阻塞时间初始化为最大值,因为当前调度器还未开启
xSchedulerRunning = pdTRUE; //开启任务调度器(置调度器的运行状态位为1)
xTickCount = (TickType_t) configINITIAL_TICK_COUNT; //节拍计数初始化为0
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); //任务运行时间统计(需要用户实现,可选)
traceTASK_SWITCHED_IN(); //供调试使用(需要用户实现,可选)
( void ) xPortStartScheduler(); //完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务
}
else
{
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
( void ) xIdleTaskHandles;
( void ) uxTopUsedPriority;
traceRETURN_vTaskStartScheduler();
}
2、xPortStartScheduler函数
(1)作用:完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务。
(2)函数的执行流程:
①检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误。
②配置PendSV和SysTick的中断优先级为最低优先级。
③调用函数vPortSetupTimerInterrupt配置SysTick。
④初始化临界区嵌套计数器为0。
⑤调用函数prvEnableVFP使能FPU。(ARM Cortex-M3内核MCU无FPU)
⑥调用函数prvStartFirstTask启动第一个任务。
(3)源码剖析:
BaseType_t xPortStartScheduler( void )
{
#if ( configASSERT_DEFINED == 1 ) //检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误
{
volatile uint8_t ucOriginalPriority;
volatile uint32_t ulImplementedPrioBits = 0;
volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;
ucOriginalPriority = *pucFirstUserPriorityRegister;
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
configASSERT( ( configMAX_SYSCALL_INTERRUPT_PRIORITY & ( ~ucMaxPriorityValue ) ) == 0U );
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
ulImplementedPrioBits++;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
if( ulImplementedPrioBits == 8 )
{
configASSERT( ( configMAX_SYSCALL_INTERRUPT_PRIORITY & 0x1U ) == 0U );
ulMaxPRIGROUPValue = 0;
}
else
{
ulMaxPRIGROUPValue =portMAX_PRIGROUP_BITS -ulImplementedPrioBits;
}
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
*pucFirstUserPriorityRegister = ucOriginalPriority;
}
#endif /* configASSERT_DEFINED */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI; //配置PendSV的中断优先级为最低优先级
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI; //配置SysTick的中断优先级为最低优先级
vPortSetupTimerInterrupt(); //初始化滴答定时器
uxCriticalNesting = 0; //临界区嵌套数初始化为0
prvStartFirstTask(); //启动第一个任务
return 0;
}
二、启动第一个任务
1、prvStartFirstTask函数
(1)要想启动第一个任务,就需要将该任务的值恢复到CPU的寄存器中,而任务的值在创建任务时已经保存到任务堆栈中。(中断产生时,硬件自动将xPSR、PC(R15)、LR(R14)、R12、R3-R0保存和恢复,而R4-R11需要手动保存和恢复)
(2)prvStartFirstTask函数的作用:初始化启动第一个任务前的环境,主要是重新设置MSP指针,并使能全局中断。
(3)函数的执行流程:
①首先使用PRESERVE8进行8字节对齐,这是因为在任何时候都是需要4字节对齐的,而在调用入口得8字节对齐,在进行C编程的时候,编译器会自动完成的对齐的操作,而对于汇编,就需要开发者手动进行对齐。
②获取MSP的初始值。(进入中断后会强制使用MSP指针)
程序在运行过程中需要一定的栈空间来保存局部变量等一些信息,当有信息保存到栈中时,MCU会自动更新SP指针,使SP指针指向最后一个入栈的元素,那么程序就可以根据SP指针来从栈中存取信息。
ARMCortex-M提供了两个栈空间,这两个栈空间的堆栈指针分别是MSP(主堆栈指针)和PSP(进程堆指针)。在FreeRTOS中,MSP是给系统栈空间使用的,而PSP是给任务栈使用的,也就是说,FreeRTOS任务的空间是通过PSP指向的,而在进入中断服务函数时,则是使用MSP指针。当使用不同的堆栈指针时,SP会等于当前使用的堆栈指针。
③在获取了栈顶指针后,将MSP指针重新赋值为栈底指针(相当于逻辑删除程序之前保存在栈中的数据)。
④在vTaskStartScheduler函数中关闭了受FreeRTOS控制的中断,这里需要重新将它们打开。
⑤使用SVC命令,传入系统调用号0,触发SVC中断,以启动第一个任务。
(4)源码剖析:
__asm void prvStartFirstTask( void )
{
PRESERVE8 //8字节对齐
ldr r0, =0xE000ED08 //0xE000ED08是向量表偏移量寄存器VTOR的地址
ldr r0, [ r0 ] //获取VTOR的值
ldr r0, [ r0 ] //获取MSP的初始值
/* 初始化MSP */
msr msp, r0
/* 使能全局中断 */
cpsie i
cpsie f
dsb
isb
/* 使用SVC命令,传入系统调用号0,触发SVC中断,以启动第一个任务 */
svc 0
nop
nop
}
2、vPortSVCHandler函数
(1)作用:当使能了全局中断,并且手动触发SVC中断后,就会进入到SVC的中断服务函数中,SVC中断只在启动第一次任务时会调用一次,以后均不调用。
(2)函数的执行流程:
①通过pxCurrentTCB获取优先级最高的就绪态任务的任务栈地址,优先级最高的就绪态任务是系统将要运行的任务。
②通过任务的栈顶指针将任务栈中的内容出栈到CPU寄存器中,任务栈中的内容在调用任务创建函数的时候已初始化,然后设置PSP指针。
③往BASEPRI寄存器中写0,允许中断。
④在SVC中断服务函数中,r14中的值为EXC_RETURN,这是一个特殊的值,有如下6个合法的选项,Cortex-M3中未使用浮点单元,且SVC中断结束后将使用PSP,并进入线程模式,故该值应为0xFFFFFFFD。
描述 | 使用浮点单元 | 未使用浮点单元 |
中断返回后进入Hamdler模式,并使用MSP | 0xFFFFFFE1 | 0xFFFFFFF1 |
中断返回后进入线程模式,并使用 MSP | 0xFFFFFFE9 | 0xFFFFFFF9 |
中断返回后进入线程模式,并使用 PSP | 0xFFFFFFED | 0xFFFFFFFD |
(3)源码剖析:
__asm void vPortSVCHandler( void )
{
PRESERVE8 //8字节对齐
/* 获取任务栈地址 */
ldr r3, =pxCurrentTCB //获取优先级最高的任务的任务控制块地址
ldr r1, [ r3 ] //获取其首成员的地址
ldr r0, [ r1 ] //获取首成员的值(该任务栈空间的栈顶)
/* 模拟出栈,并设置PSP */
ldmia r0 !, { r4 - r11 } //任务栈弹出到CPU寄存器
msr psp, r0 //使PSP指向R0寄存器
isb
/* 使能所有中断 */
mov r0, # 0
msr basepri, r0
/* 使用PSP指针,并跳转到任务函数 */
orr r14, # 0xd //将R14中的值设置为0xFFFFFFFD
bx r14 //从PSP指向的栈中出栈R0-xPSR寄存器
}
三、任务切换
1、任务切换概述
(1)任务切换的本质其实就是CPU寄存器的切换(又叫上下文切换),这个过程在PendSV中断服务函数里完成。
(2)假设由任务A切换到任务B,这个过程主要分为两步:
①需暂停任务A的执行,并将此时任务A的寄存器值保存到任务堆栈,这个过程叫做保存现场。
②将任务B的各个寄存器值(被存于任务堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场。
2、PendSV中断
(1)PendSV中断的触发时机:
①滴答定时器中断调用。
②执行FreeRTOS提供的相关API函数,主要是portYIELD。
(2)启动PendSV中断的底层逻辑:通过向中断控制和状态寄存器ICSR的bit28写入1,挂起PendSV,以启动PendSV中断。
(3)滴答定时器中断调用可以触发PendSV中断,如下是xPortSysTickHandler函数(滴答定时器中断服务函数)的源码剖析:
①xPortSysTickHandler函数本体:
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
traceISR_ENTER();
{
if( xTaskIncrementTick() != pdFALSE ) //如果需要任务切换(上下文切换)
{
traceISR_EXIT_TO_SCHEDULER();
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; //向中断控制和状态寄存器ICSR的bit28写入1,触发PendSV中断
}
else
{
traceISR_EXIT();
}
}
vPortClearBASEPRIFromISR();
}
②xTaskIncrementTick函数:
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
#if ( configUSE_PREEMPTION == 1 ) && ( configNUMBER_OF_CORES > 1 )
BaseType_t xYieldRequiredForCore[ configNUMBER_OF_CORES ] = { pdFALSE };
#endif /* #if ( configUSE_PREEMPTION == 1 ) && ( configNUMBER_OF_CORES > 1 ) */
traceENTER_xTaskIncrementTick();
traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) 0U )
{
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U )
taskSWITCH_DELAYED_LISTS();
else
mtCOVERAGE_TEST_MARKER();
if( xConstTickCount >= xNextTaskUnblockTime ) //判断是否到达下一个任务的阻塞超时时间
{
for( ; ; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
pxTCB = listGET_OWNER_OF_HEAD_ENTRY(pxDelayedTaskList);
xItemValue =listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue )
{
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 ( configNUMBER_OF_CORES == 1 )
{
//判断唤醒的阻塞任务优先级是否高于当前运行任务的优先级,是则需要任务切换
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
xSwitchRequired = pdTRUE;
else
mtCOVERAGE_TEST_MARKER();
}
#else /* #if( configNUMBER_OF_CORES == 1 ) */
{
prvYieldForTask( pxTCB );
}
#endif /* #if( configNUMBER_OF_CORES == 1 ) */
}
#endif /* #if ( configUSE_PREEMPTION == 1 ) */
}
}
}
//如果使能了抢占式调度和时间片调度,编译如下代码块
#if ( (configUSE_PREEMPTION == 1) && (configUSE_TIME_SLICING == 1) )
{
#if ( configNUMBER_OF_CORES == 1 )
{
//如果同等优先级的就绪列表中的任务数量大于1,则需要进行任务切换
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > 1U )
xSwitchRequired = pdTRUE;
else
mtCOVERAGE_TEST_MARKER();
}
#else /* #if ( configNUMBER_OF_CORES == 1 ) */
{
BaseType_t xCoreID;
for( xCoreID = 0; xCoreID < ( ( BaseType_t ) configNUMBER_OF_CORES ); xCoreID++ )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCBs[ xCoreID ]->uxPriority ] ) ) > 1U )
xYieldRequiredForCore[ xCoreID ] = pdTRUE;
else
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */
}
#endif /* #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
#if ( configUSE_TICK_HOOK == 1 )
{
if( xPendedTicks == ( TickType_t ) 0 )
vApplicationTickHook();
else
mtCOVERAGE_TEST_MARKER();
}
#endif /* configUSE_TICK_HOOK */
#if ( configUSE_PREEMPTION == 1 )
{
#if ( configNUMBER_OF_CORES == 1 )
{
if( xYieldPendings[ 0 ] != pdFALSE )
xSwitchRequired = pdTRUE;
else
mtCOVERAGE_TEST_MARKER();
}
#else /* #if ( configNUMBER_OF_CORES == 1 ) */
{
BaseType_t xCoreID, xCurrentCoreID;
xCurrentCoreID = ( BaseType_t ) portGET_CORE_ID();
for( xCoreID = 0; xCoreID < ( BaseType_t ) configNUMBER_OF_CORES; xCoreID++ )
{
#if ( configUSE_TASK_PREEMPTION_DISABLE == 1 )
if( pxCurrentTCBs[xCoreID]->xPreemptionDisable ==pdFALSE )
#endif
{
if( ( xYieldRequiredForCore[ xCoreID ] != pdFALSE ) || ( xYieldPendings[ xCoreID ] != pdFALSE ) )
{
if( xCoreID == xCurrentCoreID )
xSwitchRequired = pdTRUE;
else
prvYieldCore( xCoreID );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */
}
#endif /* #if ( configUSE_PREEMPTION == 1 ) */
}
else
{
xPendedTicks += 1U;
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
traceRETURN_xTaskIncrementTick( xSwitchRequired );
return xSwitchRequired;
}
(4)portYIELD函数源码剖析:
#define portYIELD() \
{ \
/*向中断控制和状态寄存器ICSR的bit28写入1,触发PendSV中断*/ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
\
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
(5)PendSV的中断服务函数:
①PendSV的中断服务函数本体:
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
/* *INDENT-OFF* */
PRESERVE8 //8字节对齐
mrs r0, psp //将psp赋给r0(硬件自动压栈后,指针指向EXC_RETURN/R14)
isb
ldr r3, =pxCurrentTCB //获取当前运行任务的控制块首成员(栈顶指针)的地址并赋给r3
ldr r2, [ r3 ] //根据当前运行任务的栈顶指针地址获取栈顶指针并赋给r2
stmdb r0 !, { r4 - r11 } //手动保存R4到R11中的值
str r0, [ r2 ] //将新的堆栈顶部保存到TCB的第一个成员中
stmdb sp !, { r3, r14 } //对R14及R0-R3的内容进行压栈操作
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY //FreeRTOS可管理的最高中断优先级写入R0
msr basepri, r0 //将FreeRTOS可管理的最高中断优先级写入中断屏蔽寄存器(关中断)
dsb
isb
bl vTaskSwitchContext //寻找下一个运行的任务的任务控制块(后面都是操作下一个任务了)
mov r0, #0
msr basepri, r0 //开中断
ldmia sp !, { r3, r14 } //对R14及R0-R3的内容进行出栈操作
ldr r1, [ r3 ] //获取当前运行任务的控制块首成员(栈顶指针)的地址并赋给r1
ldr r0, [ r1 ] //根据当前运行任务的栈顶指针地址获取栈顶指针并赋给r0
ldmia r0 !, { r4 - r11 } //将R4-R11的内容进行出栈操作
msr psp, r0 //将psp赋给r0(硬件自动压栈后,指针指向EXC_RETURN/R14)
isb
bx r14 //返回线程模式,后续即可真正地执行下一个任务了
nop
/* *INDENT-ON* */
}
②vTaskSwitchContext函数:
void vTaskSwitchContext( void )
{
traceENTER_vTaskSwitchContext();
if( uxSchedulerSuspended != ( UBaseType_t ) 0U )
{
xYieldPendings[ 0 ] = pdTRUE;
}
else
{
xYieldPendings[ 0 ] = pdFALSE;
traceTASK_SWITCHED_OUT();
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime[ 0 ] );
#else
ulTotalRunTime[ 0 ] = portGET_RUN_TIME_COUNTER_VALUE();
#endif
if( ulTotalRunTime[ 0 ] > ulTaskSwitchedInTime[ 0 ] )
{
pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime[ 0 ] - ulTaskSwitchedInTime[ 0 ] );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
ulTaskSwitchedInTime[ 0 ] = ulTotalRunTime[ 0 ];
}
#endif /* configGENERATE_RUN_TIME_STATS */
taskCHECK_FOR_STACK_OVERFLOW();
#if ( configUSE_POSIX_ERRNO == 1 )
{
pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
}
#endif
taskSELECT_HIGHEST_PRIORITY_TASK(); //运行任务需要是优先级最高的任务(阻塞/挂起除外)
traceTASK_SWITCHED_IN();
portTASK_SWITCH_HOOK( pxCurrentTCB );
#if ( configUSE_POSIX_ERRNO == 1 )
{
FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
}
#endif
#if ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 )
{
configSET_TLS_BLOCK( pxCurrentTCB->xTLSBlock );
}
#endif
}
traceRETURN_vTaskSwitchContext();
}
③taskSELECT_HIGHEST_PRIORITY_TASK函数:
/*获取已有就绪任务的最高优先级,将其就绪列表中的一个任务调出来运行*/
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
do { \
UBaseType_t uxTopPriority; \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} while( 0 )