调度器开启过程分析
任务调度器开启函数
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )//静态内存
{
StaticTask_t * pxIdleTaskTCBBuffer = NULL;
StackType_t * pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
configIDLE_TASK_NAME,
ulIdleTaskStackSize,
( void * ) NULL,
portPRIVILEGE_BIT,
pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer );
if( xIdleTaskHandle != NULL )
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
}
#else
{
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
portPRIVILEGE_BIT,
&xIdleTaskHandle );
}//创建空闲任务
#endif
#if ( configUSE_TIMERS == 1 )//使用软件定时器使能
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask();//创建定时器服务任务
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
if( xReturn == pdPASS )
{
#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
{
freertos_tasks_c_additions_init();
}
#endif
portDISABLE_INTERRUPTS();//关闭中断
#if ( ( configUSE_NEWLIB_REENTRANT == 1 ) || ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 ) )
{
configSET_TLS_BLOCK( pxCurrentTCB->xTLSBlock );
}
#endif
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;//调度器开始运行
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();//配置定时器
traceTASK_SWITCHED_IN();
xPortStartScheduler();//初始化跟调度器有关的硬件
}
else
{
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
( void ) xIdleTaskHandle;
( void ) uxTopUsedPriority;
}
内核相关硬件初始化函数
BaseType_t xPortStartScheduler( void )
{
#if ( configASSERT_DEFINED == 1 )
{
volatile uint32_t ulOriginalPriority;
volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;
ulOriginalPriority = *pucFirstUserPriorityRegister;
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) );
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
#ifdef __NVIC_PRIO_BITS
{
configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
}
#endif
#ifdef configPRIO_BITS
{
configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
}
#endif
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
*pucFirstUserPriorityRegister = ulOriginalPriority;
}
#endif
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;//设置PendSV的中断优先级,为最低优先级
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;//设置滴答定时器的中断优先级,为最低优先级
vPortSetupTimerInterrupt();//设置滴答定时器的定时周期,并且使能滴答定时器的中断
uxCriticalNesting = 0;//初始化临界区嵌套计数器
prvStartFirstTask();//开启第一个任务
return 0;
}
启动第一个任务
__asm void prvStartFirstTask( void )
{
PRESERVE8
ldr r0, =0xE000ED08//将0xE000ED08保存在寄存器r0中。是向量表偏移寄存器的地址。
ldr r0, [ r0 ]//取R0所保存的地址处的值赋给R0(在此之前R0保存的是地址),也就是从向量表偏移寄存器存储的初始地址的值0x08000000
ldr r0, [ r0 ]//取R0 地址的值,偏移向量表的起始地址保存的是主栈指针MSP初始值
msr msp, r0//复位MSP,将获得的MSP初始值赋值给msp
cpsie i//使能中断
cpsie f//使能中断
dsb//数据同步屏障
isb//指令同步屏障
svc 0//触发SVC中断,使用它来启动第一个任务
nop
nop
}
SVC中断服务函数
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp//PSP内容存入R0
isb//指令同步屏障
ldr r3, =pxCurrentTCB //R3=pxCurrentTCB的地址,这个指针永远指向正在运行的任务
ldr r2, [ r3 ]//R3所保存地址的值赋值给R2,获取了当前任务的任务控制块的存储地址
stmdb r0 !, { r4 - r11 } //保存剩余的寄存器,异常处理程序执行前,硬件自动将xPSR、PC、LR、R12、R0-R3入栈
str r0, [ r2 ] //将新的栈顶保存到任务TCB的第一个成员中
stmdb sp !, { r3, r14 }//将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护; R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY//进入临界区
msr basepri, r0//关闭中断
dsb//数据同步屏障
isb//指令同步屏障
bl vTaskSwitchContext//调用函数,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换
mov r0, #0//退出临界区
msr basepri, r0//开启中断
ldmia sp !, { r3, r14 }//恢复R3和R14
ldr r1, [ r3 ]
ldr r0, [ r1 ] //当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0
ldmia r0 !, { r4 - r11 } //出栈,R4-R11
msr psp, r0
isb
bx r14//异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行
nop
}
空闲任务
空闲任务就是空闲的适合运行的任务。任务调度器启动后必须有一个任务运行。空闲任务的任务优先级是最小的,为0.任务函数为prvIdleTask()。
任务创建过程分析
任务创建函数
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
任务初始化函数
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
TCB_t * pxNewTCB,
const MemoryRegion_t * const xRegions )
任务堆栈初始化函数
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
TaskFunction_t pxCode,
void * pvParameters )
{
/* Simulate the stack frame as it would be created by a context switch
* interrupt. */
pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
添加任务到就绪列表
任务删除过程分析
void vTaskDelete( TaskHandle_t xTaskToDelete )
任务挂起过程分析
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
任务恢复过程分析
void vTaskResume( TaskHandle_t xTaskToResume )