一、启动调度器的流程
具体做了这几件事:
1. 创建空闲任务(Idle Task)这是调度器做的第一件事。空闲任务优先级最低(0),当没有任何就绪任务时 CPU 就跑它。
代码根据编译配置走两条路:
- 静态分配(用户提供内存,调 xTaskCreateStatic)
- 动态分配(内核自己分配,调 xTaskCreate)。
创建失败则 xReturn = pdFAIL。
2. 创建定时器任务(Timer Task)如果 configUSE_TIMERS == 1 且空闲任务创建成功,就调用 xTimerCreateTimerTask() 创建软件定时器服务任务。这个任务负责处理所有软件定时器的回调。
3. 启动前的最终准备(全部成功才走到这里)依次执行:
关闭全局中断(防止启动过程中被 tick 打断)→ 设置 TLS 块(可选)→ 初始化调度器状态变量(xSchedulerRunning = pdTRUE、tick 计数归零)→ 配置运行时统计定时器 → 调用 xPortStartScheduler()。
4. 启动硬件调度器xPortStartScheduler() 是端口层函数,负责配置 SysTick、(所以,初始化的时候,不要使用利用SysTick进行delay_us()的函数,否则卡死。)设置 PendSV 中断、恢复第一个任务的上下文并跳过去执行。这个函数正常情况下永远不会返回——一旦调用,CPU 就开始跑任务了。
5. 失败路径如果内存不够创建空闲任务或定时器任务,触发 configASSERT 断言,系统停死。
二. 代码:
void vTaskStartScheduler( void ) { BaseType_t xReturn; /* Add the idle task at the lowest priority. */ #if ( configSUPPORT_STATIC_ALLOCATION == 1 ) { StaticTask_t * pxIdleTaskTCBBuffer = NULL; StackType_t * pxIdleTaskStackBuffer = NULL; uint32_t ulIdleTaskStackSize; /* The Idle task is created using user provided RAM - obtain the * address of the RAM then create the idle task. */ vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize ); xIdleTaskHandle = xTaskCreateStatic( prvIdleTask, configIDLE_TASK_NAME, ulIdleTaskStackSize, ( void * ) NULL, /*lint !e961. The cast is not redundant for all compilers. */ portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */ pxIdleTaskStackBuffer, pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */ if( xIdleTaskHandle != NULL ) { xReturn = pdPASS; } else { xReturn = pdFAIL; } } #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */ { /* The Idle task is being created using dynamically allocated RAM. */ xReturn = xTaskCreate( prvIdleTask, configIDLE_TASK_NAME, configMINIMAL_STACK_SIZE, ( void * ) NULL, portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */ &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 ) { /* freertos_tasks_c_additions_init() should only be called if the user * definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is * the only macro called by the function. */ #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT { freertos_tasks_c_additions_init(); } #endif /* 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 ) || ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 ) ) { /* Switch C-Runtime's TLS Block to point to the TLS * block specific to the task that will run first. */ configSET_TLS_BLOCK( pxCurrentTCB->xTLSBlock ); } #endif xNextTaskUnblockTime = portMAX_DELAY; xSchedulerRunning = pdTRUE; xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT; /* 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. NOTE: If configGENERATE_RUN_TIME_STATS * is set to 0 and the following line fails to build then ensure you do not * have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your * FreeRTOSConfig.h file. */ portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); traceTASK_SWITCHED_IN(); /* Setting up the timer tick is hardware specific and thus in the * portable interface. */ xPortStartScheduler(); /* In most cases, xPortStartScheduler() will not return. If it * returns pdTRUE then there was not enough heap memory available * to create either the Idle or the Timer task. If it returned * pdFALSE, then the application called xTaskEndScheduler(). * Most ports don't implement xTaskEndScheduler() as there is * nothing to return to. */ } 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; /* OpenOCD makes use of uxTopUsedPriority for thread debugging. Prevent uxTopUsedPriority * from getting optimized out as it is no longer used by the kernel. */ ( void ) uxTopUsedPriority; }