文章目录
- 前言
- 一、启动调度器
- 二、详细逻辑分析
- 三、逐行分析
- 3.1、traceENTER_vTaskStartScheduler
- 3.2、configASSERT( ( sizeof( UBaseType_t ) * taskBITS_PER_BYTE ) >= configNUMBER_OF_CORES );
- 3.3、xReturn = prvCreateIdleTasks();
- 3.4、xTimerCreateTimerTask();
- 3.5、freertos_tasks_c_additions_init
- 3.6、portDISABLE_INTERRUPTS
- 3.7、configSET_TLS_BLOCK
- 3.8、portCONFIGURE_TIMER_FOR_RUN_TIME_STATS
- 3.9、traceTASK_SWITCHED_IN 和 traceSTARTING_SCHEDULER
- 3.10、xPortStartScheduler
- 3.11、configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
- 3.12、xIdleTaskHandles
- 3.13、uxTopUsedPriority
- 3.14、traceRETURN_vTaskStartScheduler
- 四、启动调度器的作用
前言
操作系统,在入行嵌入式j接触它之前,感觉那是多么高深、神圣的技术,感觉它是高不可攀的。曾经也幻想过它有多么的复杂,在裸机编程时无数次想去应用这个技术,但无奈,无人指引,以致于每每都会望而却步。如今,也在多种OS的基础上做过各行各业的软件开发,所以想在闲暇之时,将使用过的OS内核软件逐行阅读,以提升自我编程能力、去了解更多软件编程思想。写此专栏文章的目的也仅仅是为了让这个过程留痕。
本文所有代码源于RTOS-Keenel
一、启动调度器
话不多说,直接上源码。
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
traceENTER_vTaskStartScheduler();
#if ( configUSE_CORE_AFFINITY == 1 ) && ( configNUMBER_OF_CORES > 1 )
{
/* Sanity check that the UBaseType_t must have greater than or equal to
* the number of bits as confNUMBER_OF_CORES. */
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 )
{
/* 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_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();
traceSTARTING_SCHEDULER( xIdleTaskHandles );
/* Setting up the timer tick is hardware specific and thus in the
* portable interface. */
/* The return value for xPortStartScheduler is not required
* hence using a void datatype. */
( void ) 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 xIdleTaskHandles are not used anywhere else. */
( void ) xIdleTaskHandles;
/* 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;
traceRETURN_vTaskStartScheduler();
}
二、详细逻辑分析
用于启动调度器,调度器是操作系统的核心部分,负责决定哪个任务应该运行。
主要步骤如下:
- 1、调用 prvCreateIdleTasks 函数创建空闲任务。空闲任务是系统中优先级最低的任务,当没有其他任务需要运行时,空闲任务才会运行。
- 2、如果启用了定时器,调用 xTimerCreateTimerTask 函数创建定时器任务。定时器任务是用于处理定时器事件的任务。
- 3、如果上述步骤都成功了,就会调用函数 freertos_tasks_c_additions_init 进行一些额外的初始化工作。这个函数是可选的,只是当定义了 FREERTOS_TASKS_C_ADDITIONS_INIT 宏才会调用。
- 4、禁用中断。这是为了确保在调用 xPortStartScheduler 之前不会发生中断。在创建的任务堆栈中,有一个状态,其中就包括了中断开关的状态。因此,当第一个任务开始运行时,中断会自动被重新启用。
- 5、如果启用了C运行时的TLS支持,函数会调用 configSET_TLS_BLOCK 函数,将C运行时的TLS块切换到第一个运行的任务的TLS块。
- 6、初始化一些全局变量
- 7、如果定义了 configGENERATE_RUN_TIME_STATS 宏,函数会调用 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS 宏,配置用于生成运行时间统计的定时器/计数器。
- 8、调用 xPortStartScheduler 函数启动调度器。
- 9、如果 xPortStartScheduler 函数返回,那么可能是由于没有足够的堆内存来创建空闲任务或定时器任务,或者应用程序嗲用了 xTaskEndScheduler 函数。在后一种情况下,函数会调用 vPortEndScheduler 函数。
三、逐行分析
3.1、traceENTER_vTaskStartScheduler
用于跟踪和调试的宏。
3.2、configASSERT( ( sizeof( UBaseType_t ) * taskBITS_PER_BYTE ) >= configNUMBER_OF_CORES );
检查 UBaseType_t 类型的位数大于核心数量
3.3、xReturn = prvCreateIdleTasks();
为每个核心创建一个空闲任务。
3.4、xTimerCreateTimerTask();
创建定时器任务。前提 configUSE_TIMERS 是启用了,并且 prvCreateIdleTasks 函数成功创建了空闲任务。
3.5、freertos_tasks_c_additions_init
进行一些额外的初始化工作。
3.6、portDISABLE_INTERRUPTS
禁用中断,是为了确保在调用 xPortStartScheduler 函数之前不会发生中断。在创建的任务的堆栈中,有一个状态字,其中包括中断开关的状态。因此,当第一个任务开始运行时,中断会自动被重新启用。
3.7、configSET_TLS_BLOCK
如果启用了TLS支持(configUSE_C_RUNTIME_TLS_SUPPORT宏打开),那么调用该函数将运行TLS块切换到第一个运行任务的TLS。
3.8、portCONFIGURE_TIMER_FOR_RUN_TIME_STATS
如果定义了 configGENERATE_RUN_TIME_STATS 宏,那么调用 8、portCONFIGURE_TIMER_FOR_RUN_TIME_STATS 宏会配置用于生成运行时间统计的定时器/计数器。
3.9、traceTASK_SWITCHED_IN 和 traceSTARTING_SCHEDULER
用于跟踪和调试。
3.10、xPortStartScheduler
启动调度器。
3.11、configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
如果 xPortStartScheduler 函数返回异常,那么可能是由于没有足够的堆内存来创建空闲任务或定时器任务。
3.12、xIdleTaskHandles
总是将 INCLUDE_xTaskGetIdleTaskHandle 设置为0,可避免编译器发出警告。意味着 xIdleTaskHandles 未在其他地方调用。
3.13、uxTopUsedPriority
防止被优化掉。
3.14、traceRETURN_vTaskStartScheduler
用于跟踪和调试。
四、启动调度器的作用
-
1、创建空闲任务,空闲任务是系统中优先级最低的任务,当没有其他任务需要运行时,空闲任务会运行。
-
2、创建定时器任务,如果启用了定时器,那么就会创建一个定时器任务,用于处理定时器事件。
-
3、启动调度器,调度器开始运行后,会根据任务的优先级和状态,决定哪个任务应该运行。
-
4、管理系统资源,如处理器、内存、文件和设备。
总的来说,该函数主要作用就是启动RTOS的调度器,开始执行任务并管理系统资源。