8.1 FreeRTOS 调度器开启过程分析
8.1.1 任务调度器开启函数分析
main()函数中先创建一个开始任务 start_task,后面紧接着调用函数 vTaskStartScheduler(),在文件 tasks.c
中有定义。
void vTaskStartScheduler( void ) //开启任务调度器
{
BaseType_t xReturn;
/* Add the idle task at the lowest priority. */
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
//...
}
#else
{
/* The Idle task is being created using dynamically allocated RAM. */
//创建空闲任务,优先级为 tskIDLE_PRIORITY
xReturn = xTaskCreate( prvIdleTask,
"IDLE", configMINIMAL_STACK_SIZE,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
&xIdleTaskHandle );
/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast
to all supported compilers. */
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
#if ( configUSE_TIMERS == 1 )
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask(); //创建定时器服务任务
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
if( xReturn == pdPASS )
{
/* Interrupts are turned off here, to ensure a tick does not occur
before or during the call to xPortStartScheduler(). The stacks of
the created tasks contain a status word with interrupts switched on
so interrupts will automatically get re-enabled when the first task
starts to run. */
portDISABLE_INTERRUPTS(); //关闭中断
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent
structure specific to the task that will run first. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE; //调度器开始运行
xTickCount = ( TickType_t ) 0U;
/* If configGENERATE_RUN_TIME_STATS is defined then the following
macro must be defined to configure the timer/counter used to generate
the run time counter time base. */
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); // 为1使能时间统计功能
/* Setting up the timer tick is hardware specific and thus in the
portable interface. */
if( xPortStartScheduler() != pdFALSE ) //初始化跟调度器启动有关的硬件
{
/* Should not reach here as if the scheduler is running the
function will not return. */
}
else
{
/* Should only reach here if a task calls xTaskEndScheduler(). */
}
}
else
{
/* This line will only be reached if the kernel could not be started,
because there was not enough FreeRTOS heap to create the idle task
or the timer task. */
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
meaning xIdleTaskHandle is not used anywhere else. */
( void ) xIdleTaskHandle;
}
8.1.2 内核相关硬件初始化函数分析
FreeRTOS 系统时钟是由滴答定时器来提供,任务切换会用到 PendSV 中断,由函数 xPortStartScheduler()初始化。
在ARM_CM4F的port.c文件中。
/*
* See header file for description.
*/
BaseType_t xPortStartScheduler( void )
{
/* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0.
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
/* This port can be used on all revisions of the Cortex-M7 core other than
the r0p1 parts. r0p1 parts should use the port from the
/source/portable/GCC/ARM_CM7/r0p1 directory. */
configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
#if( configASSERT_DEFINED == 1 )
{
//...
}
#endif /* conifgASSERT_DEFINED */
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; //设置 PendSV 为最低优先级
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; //设置滴答定时器为最低优先级
/* Start the timer that generates the tick ISR. Interrupts are disabled
here already. */
vPortSetupTimerInterrupt(); //设置滴答定时器的定时周期,使能滴答定时器的中断
/* Initialise the critical nesting count ready for the first task. */
uxCriticalNesting = 0; //初始化临界区嵌套计数器
/* Ensure the VFP is enabled - it should be anyway. */
prvEnableVFP(); //使能 VFP
/* Lazy save always. */
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS; //设置FPCCR bit31,bit30为1,异常入口和退出时会自动保存和恢复
/* Start the first task. */
prvStartFirstTask(); //启动第一个任务
/* Should not get here! */
return 0;
}
8.1.3 使能 FPU 函数分析
xPortStartScheduler()中会调用 prvEnableVFP()来使能 FPU,代码是汇编形式。
在ARM_CM4F的port.c文件中。
__asm void prvEnableVFP( void )
{
PRESERVE8
/* The FPU enable bits are in the CPACR. */
ldr.w r0, =0xE000ED88 //CPACR(bit20 ~ bit23)使能或禁止FPU
ldr r1, [r0] //读取CPACR 寄存器的值,保存在R1
/* Enable CP10 and CP11 coprocessors, then save back. */
orr r1, r1, #( 0xf << 20 ) //位或运算,bit20~bit23设为1
str r1, [r0] //R1的值写入CPACR开启FPU
bx r14 //bx 为间接跳转指令,跳转到链接寄存(LR),LR用函数或子程序调用时返回地址的保存
nop
}
8.1.4 启动第一个任务
prvStartFirstTask()启动第一个任务,代码是汇编。
在ARM_CM4F的port.c文件中。
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* Use the NVIC offset register to locate the stack. */
ldr r0, =0xE000ED08 //向量表偏移寄存器(VTOR)为0XE000ED08 保存在R0
ldr r0, [r0] //读取VTOR的值保存R0
ldr r0, [r0] //读取0X08000000的值保存R0,R0 存 MSP 的初始值
/* Set the msp back to the start of the stack. */
msr msp, r0 //复位 MSP
/* Globally enable interrupts. */
cpsie i //使能中断(清除 PRIMASK)
cpsie f //使能中断(清除 FAULTMASK)
dsb //数据同步屏障
isb //指令同步屏障
/* Call SVC to start the first task. */
svc 0 //触发请求管理调用(SVC) 中断,SVC 异常由 SVC 指令触发,启动第一个任务
nop
nop
}
8.1.5 SVC 中断服务函数
prvStartFirstTask()调用 SVC 指令触发 SVC 中断,第一个任务的启动是在 SVC 中断服务函数中完成的, SVC 中断函数为 SVC_Handler()。
FreeRTOS Config.h 中通过#define 的方式重新定义为了 xPortPendSVHandler()。
#define xPortPendSVHandler PendSV_Handler
在ARM_CM4F的port.c文件中。代码是汇编。
__asm void vPortSVCHandler( void )
{
PRESERVE8
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB //获取pxCurrentTCB指针的值地址,指向 TCB_t 的指针,永远指向正在运行的任务
ldr r1, [r3] //获取当前任务的任务控制块的值地址
ldr r0, [r1] //任务控制块的第一个字段是任务堆栈的栈顶指针 pxTopOfStack 所指向的位置
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14} //LDMIA 指令是多加载/存储指令
msr psp, r0 //设置进程栈指针(PSP)为任务的堆栈
isb //指令同步屏障
mov r0, #0 //R0 为 0
msr basepri, r0 // BASEPRI 为 R0,打开中断
bx r14 //堆栈使用进程栈 PSP,任务调度器开始运行
}
8.1.6 空闲任务
空闲任务是空闲的时候运行的任务,空闲任务是 FreeRTOS 系统自动创建,空闲任务的任务优先级是最低的,为 0。
空闲任务
- 判断系统是否有任务删除
- 运行用户设置的空闲任务钩子函数
- 判断是否开启低功耗 tickless 模式