系列文章目录
文章目录
- 系列文章目录
- 前言
- [vTaskStartScheduler 调度器启动函数](#vTaskStartScheduler 调度器启动函数)
-
- xPortStartScheduler架构特定调度器启动函数
-
- [vPortSetupTimerInterrupt启动 RISCV 定时器中断](#vPortSetupTimerInterrupt启动 RISCV 定时器中断)
- xPortStartFirstTask启动第一个任务
- 附
- 总结
前言
本文继续看 task 的运行。主要解析函数 vTaskStartScheduler
。
主函数中调用函数 vTaskStartScheduler
开始调度。
c
int main_blinky( void )
{
vSendString( "Hello FreeRTOS!" );
/* Create the queue. */
xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );
if( xQueue != NULL )
{
/* Start the two tasks as described in the comments at the top of this
* file. */
xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE * 2U, NULL,
mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );
xTaskCreate( prvQueueSendTask, "Tx", configMINIMAL_STACK_SIZE * 2U, NULL,
mainQUEUE_SEND_TASK_PRIORITY, NULL );
}
vTaskStartScheduler();
return 0;
}
vTaskStartScheduler 调度器启动函数
初始化核心调度数据结构、创建必要的后台任务(如 idle task 和 timer service task),并启动第一个任务的上下文切换,从而进入多任务运行模式。
c
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
// 创建 Idle Task(空闲任务)
xReturn = prvCreateIdleTasks();
// 启用了软件定时器(configUSE_TIMERS)
#if ( configUSE_TIMERS == 1 )
{
if( xReturn == pdPASS )
{
// 创建 Timer Service Task。
xReturn = xTimerCreateTimerTask();
}
}
#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. */
// 关闭中断,防止 tick 提前进入,xPortStartScheduler中我们会开启中断
portDISABLE_INTERRUPTS();
// 设置 Tick 计数器、调度状态。
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
/* Setting up the timer tick is hardware specific and thus in the
* portable interface. */
// 开启调度
( void ) xPortStartScheduler();
// 大部分情况下不会返回,除非内存不足
}
else
{
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
( void ) xIdleTaskHandles;
( void ) uxTopUsedPriority;
}
我们暂时先跳过空闲任务创建和时钟服务创建这两个地方,着重看 xPortStartScheduler
这个架构特定的调度器启动函数的实现。
xPortStartScheduler架构特定调度器启动函数
检查中断服务的堆栈对齐并填充字节,初始化必要的硬件设置并启动多任务调度
c
BaseType_t xPortStartScheduler( void )
{
/* 声明外部函数:启动第一个任务的汇编函数 */
extern void xPortStartFirstTask( void );
/* 如果启用了断言检查 */
#if ( configASSERT_DEFINED == 1 )
{
/* 检查中断栈顶地址的字节对齐
* 中断栈与调度器启动前main()函数使用的栈是同一个
* 确保栈顶地址符合平台的字节对齐要求 */
configASSERT( ( xISRStackTop & portBYTE_ALIGNMENT_MASK ) == 0 );
/* 如果配置了中断栈大小(以字为单位) */
#ifdef configISR_STACK_SIZE_WORDS
{
/* 使用特定的填充字节初始化整个中断栈内存区域
* 这有助于调试时检测栈溢出和栈使用情况 */
memset( ( void * ) xISRStack, portISR_STACK_FILL_BYTE, sizeof( xISRStack ) );
}
#endif /* configISR_STACK_SIZE_WORDS */
}
#endif /* configASSERT_DEFINED */
/* 设置定时器中断 */
vPortSetupTimerInterrupt();
/* 如果配置了MTIME和MTIMECMP寄存器的基地址(RISC-V标准定时器) */
#if ( ( configMTIME_BASE_ADDRESS != 0 ) && ( configMTIMECMP_BASE_ADDRESS != 0 ) )
{
/* 启用machine模式下的定时器中断和外部中断
* 通过设置mie(Machine Interrupt Enable)寄存器:
* - 位7 (0x80): 启用定时器中断
* - 位11 (0x800): 启用外部中断
* 0x880 = 0x80 | 0x800 */
__asm volatile ( "csrs mie, %0" ::"r" ( 0x880 ) );
}
#endif /* ( configMTIME_BASE_ADDRESS != 0 ) && ( configMTIMECMP_BASE_ADDRESS != 0 ) */
/* 启动第一个任务
* 这个函数会切换到第一个就绪任务的上下文
* 从这点开始,系统进入多任务调度模式 */
xPortStartFirstTask();
/* 正常情况下不应该执行到这里
* 因为调用xPortStartFirstTask()后,只有任务应该在执行
* 如果执行到这里,说明调度器启动失败 */
return pdFAIL;
}
这里我们需要关注两个函数,一个是 vPortSetupTimerInterrupt
用于设置定时器中断。一个是 xPortStartFirstTask
,启动第一个任务。
vPortSetupTimerInterrupt启动 RISCV 定时器中断
简单来说,只需要读取 mtime 得到当前系统运行时间,并加上余量,设置 mtimecmp 即可在 mtime > mtimecmp 时触发始终中断。
实际实现也是这样。
c
void vPortSetupTimerInterrupt( void )
{
uint32_t ulCurrentTimeHigh, ulCurrentTimeLow;
/* 设置指向MTIME寄存器的指针,MTIME是64位寄存器,高32位在+4字节偏移处 */
volatile uint32_t * const pulTimeHigh = ( volatile uint32_t * const ) ( ( configMTIME_BASE_ADDRESS ) + 4UL ); /* 8-byte type so high 32-bit word is 4 bytes up. */
volatile uint32_t * const pulTimeLow = ( volatile uint32_t * const ) ( configMTIME_BASE_ADDRESS );
volatile uint32_t ulHartId;
/* 读取当前硬件线程ID(Hart ID),用于确定使用哪个定时器比较寄存器 */
__asm volatile ( "csrr %0, mhartid" : "=r" ( ulHartId ) );
/* 根据Hart ID计算对应的MTIMECMP寄存器地址,每个hart有独立的比较寄存器 */
pullMachineTimerCompareRegister = ( volatile uint64_t * ) ( ullMachineTimerCompareRegisterBase + ( ulHartId * sizeof( uint64_t ) ) );
/* 原子地读取64位MTIME寄存器值,防止在读取过程中高位发生变化 */
do
{
ulCurrentTimeHigh = *pulTimeHigh; /* 先读取高32位 */
ulCurrentTimeLow = *pulTimeLow; /* 再读取低32位 */
} while( ulCurrentTimeHigh != *pulTimeHigh ); /* 确保读取期间高位没有变化 */
/* 将32位的高低位组合成64位的当前时间值 */
ullNextTime = ( uint64_t ) ulCurrentTimeHigh;
ullNextTime <<= 32ULL; /* 高32位左移到正确位置 */
ullNextTime |= ( uint64_t ) ulCurrentTimeLow; /* 或上低32位 */
/* 计算下次定时器中断的时间点:当前时间 + 一个tick的时间增量 */
ullNextTime += ( uint64_t ) uxTimerIncrementsForOneTick;
/* 设置机器定时器比较寄存器,当MTIME达到这个值时触发中断 */
*pullMachineTimerCompareRegister = ullNextTime;
/* 预先计算下下次中断的时间,为下次中断处理做准备 */
ullNextTime += ( uint64_t ) uxTimerIncrementsForOneTick;
}
xPortStartFirstTask启动第一个任务
xPortStartFirstTask 只需按照堆栈中放置数据的约定,把数据放入合适的寄存器,把PC放入RA寄存器,调用ret返回即可执行任务
c
xPortStartFirstTask:
/* 任务启动函数 - 启动第一个FreeRTOS任务 */
load_x sp, pxCurrentTCB /* 将当前任务控制块(TCB)的地址加载到栈指针寄存器 */
load_x sp, 0( sp ) /* 从TCB的第一个成员读取该任务的栈指针值,更新sp */
/* 恢复任务的上下文 - 按照栈中保存的顺序恢复寄存器 */
load_x x1, 0( sp ) /* 恢复x1寄存器(ra - 返回地址),用作任务函数的返回地址 */
load_x x5, 1 * portWORD_SIZE( sp ) /* 恢复初始mstatus寄存器值到x5(t0) */
addi x5, x5, 0x08 /* 设置MIE位(Machine Interrupt Enable),使任务启动时中断使能 */
csrw mstatus, x5 /* 将修改后的mstatus写入控制状态寄存器,从此处开始中断使能! */
portasmRESTORE_ADDITIONAL_REGISTERS /* 恢复RISC-V实现特有的额外寄存器(在freertos_risc_v_chip_specific_extensions.h中定义) */
/* 恢复通用寄存器 - 临时寄存器和参数寄存器 */
load_x x7, 5 * portWORD_SIZE( sp ) /* 恢复t2寄存器 */
load_x x8, 6 * portWORD_SIZE( sp ) /* 恢复s0/fp寄存器(帧指针) */
load_x x9, 7 * portWORD_SIZE( sp ) /* 恢复s1寄存器 */
load_x x10, 8 * portWORD_SIZE( sp ) /* 恢复a0寄存器(第一个参数/返回值) */
load_x x11, 9 * portWORD_SIZE( sp ) /* 恢复a1寄存器(第二个参数) */
load_x x12, 10 * portWORD_SIZE( sp ) /* 恢复a2寄存器(第三个参数) */
load_x x13, 11 * portWORD_SIZE( sp ) /* 恢复a3寄存器(第四个参数) */
load_x x14, 12 * portWORD_SIZE( sp ) /* 恢复a4寄存器(第五个参数) */
load_x x15, 13 * portWORD_SIZE( sp ) /* 恢复a5寄存器(第六个参数) */
#ifndef __riscv_32e
/* 非RV32E架构(完整寄存器集)才需要恢复以下寄存器 */
load_x x16, 14 * portWORD_SIZE( sp ) /* 恢复a6寄存器(第七个参数) */
load_x x17, 15 * portWORD_SIZE( sp ) /* 恢复a7寄存器(第八个参数) */
load_x x18, 16 * portWORD_SIZE( sp ) /* 恢复s2寄存器(保存寄存器) */
load_x x19, 17 * portWORD_SIZE( sp ) /* 恢复s3寄存器(保存寄存器) */
load_x x20, 18 * portWORD_SIZE( sp ) /* 恢复s4寄存器(保存寄存器) */
load_x x21, 19 * portWORD_SIZE( sp ) /* 恢复s5寄存器(保存寄存器) */
load_x x22, 20 * portWORD_SIZE( sp ) /* 恢复s6寄存器(保存寄存器) */
load_x x23, 21 * portWORD_SIZE( sp ) /* 恢复s7寄存器(保存寄存器) */
load_x x24, 22 * portWORD_SIZE( sp ) /* 恢复s8寄存器(保存寄存器) */
load_x x25, 23 * portWORD_SIZE( sp ) /* 恢复s9寄存器(保存寄存器) */
load_x x26, 24 * portWORD_SIZE( sp ) /* 恢复s10寄存器(保存寄存器) */
load_x x27, 25 * portWORD_SIZE( sp ) /* 恢复s11寄存器(保存寄存器) */
load_x x28, 26 * portWORD_SIZE( sp ) /* 恢复t3寄存器(临时寄存器) */
load_x x29, 27 * portWORD_SIZE( sp ) /* 恢复t4寄存器(临时寄存器) */
load_x x30, 28 * portWORD_SIZE( sp ) /* 恢复t5寄存器(临时寄存器) */
load_x x31, 29 * portWORD_SIZE( sp ) /* 恢复t6寄存器(临时寄存器) */
#endif
/* 恢复任务的临界区嵌套计数器 */
load_x x5, portCRITICAL_NESTING_OFFSET * portWORD_SIZE( sp ) /* 从任务栈中获取该任务的临界嵌套计数值 */
load_x x6, pxCriticalNesting /* 将全局临界嵌套变量的地址加载到x6 */
store_x x5, 0( x6 ) /* 恢复该任务的临界嵌套计数值到全局变量 */
/* 恢复最后两个临时寄存器 */
load_x x5, 3 * portWORD_SIZE( sp ) /* 恢复x5(t0)寄存器的初始值 */
load_x x6, 4 * portWORD_SIZE( sp ) /* 恢复x6(t1)寄存器的初始值 */
/* 调整栈指针,释放上下文保存空间 */
addi sp, sp, portCONTEXT_SIZE /* 栈指针向上调整,跳过已恢复的上下文数据 */
/* 跳转到任务函数开始执行 */
ret /* 返回到x1(ra)寄存器中保存的任务函数地址,开始执行任务 */
总结调用链如下所示:
main_blinky() // 主函数:创建队列和任务,启动调度器
└── vTaskStartScheduler() // 调度器启动:初始化系统任务和调度状态
├── prvCreateIdleTasks() // 创建空闲任务(系统必需的后台任务)
├── xTimerCreateTimerTask() // 创建定时器服务任务(软件定时器功能)
└── xPortStartScheduler() // 架构相关启动:硬件初始化和任务切换
├── vPortSetupTimerInterrupt() // 设置RISC-V定时器中断(任务切换时基)
└── xPortStartFirstTask() // 启动第一个任务(上下文切换到用户任务)
附
这里我们简单看一下前面在开始第一次调度的时候,创建的空闲任务具体内容。
空闲任务
为每一个CPU创建空闲任务,当前仅一个CPU
c
static BaseType_t prvCreateIdleTasks( void )
{
BaseType_t xReturn = pdPASS; // 函数返回值,初始化为成功
BaseType_t xCoreID; // 当前处理的CPU核心ID
char cIdleName[ configMAX_TASK_NAME_LEN ] = { 0 }; // 空闲任务名称缓冲区
TaskFunction_t pxIdleTaskFunction = NULL; // 空闲任务函数指针
UBaseType_t xIdleTaskNameIndex; // 任务名称字符索引
// 第一步:构建空闲任务的基础名称
// 从配置文件中的空闲任务名称复制字符,直到遇到空字符或达到最大长度
for( xIdleTaskNameIndex = 0U; xIdleTaskNameIndex < ( configMAX_TASK_NAME_LEN - taskRESERVED_TASK_NAME_LENGTH ); xIdleTaskNameIndex++ )
{
// 逐字符复制配置的空闲任务名称
cIdleName[ xIdleTaskNameIndex ] = configIDLE_TASK_NAME[ xIdleTaskNameIndex ];
// 如果遇到字符串结束符,停止复制
if( cIdleName[ xIdleTaskNameIndex ] == ( char ) 0x00 )
{
break;
}
}
// 确保字符串以空字符结尾
cIdleName[ xIdleTaskNameIndex ] = '\0';
// 第二步:为每个CPU核心创建空闲任务
// 以最低优先级为每个核心添加空闲任务
for( xCoreID = ( BaseType_t ) 0; xCoreID < ( BaseType_t ) configNUMBER_OF_CORES; xCoreID++ )
{
// 根据系统配置选择合适的空闲任务函数
#if ( configNUMBER_OF_CORES == 1 )
{
// 单核系统:使用标准空闲任务函数
pxIdleTaskFunction = &prvIdleTask;
}
#else /* #if ( configNUMBER_OF_CORES == 1 ) */
{
/* 在FreeRTOS SMP中,除了主空闲任务外,还会创建 configNUMBER_OF_CORES - 1 个
* 被动空闲任务,确保每个核心在没有其他任务可运行时都有空闲任务可执行 */
if( xCoreID == 0 )
{
// 核心0:使用主空闲任务函数
pxIdleTaskFunction = &prvIdleTask;
}
else
{
// 其他核心:使用被动空闲任务函数
pxIdleTaskFunction = &prvPassiveIdleTask;
}
}
#endif /* #if ( configNUMBER_OF_CORES == 1 ) */
// 第三步:为多核系统更新空闲任务名称,添加核心ID后缀以区分不同核心的空闲任务
/* 在单核FreeRTOS中不需要此功能,因为只有一个空闲任务 */
#if ( configNUMBER_OF_CORES > 1 )
{
// 宏不成立
}
#else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
{
// 动态内存分配方式创建空闲任务
/* 空闲任务使用动态分配的RAM创建 */
xReturn = xTaskCreate( pxIdleTaskFunction, // 任务函数
cIdleName, // 任务名称
configMINIMAL_STACK_SIZE, // 最小栈大小
( void * ) NULL, // 任务参数
portPRIVILEGE_BIT, // 优先级(实际上是 tskIDLE_PRIORITY | portPRIVILEGE_BIT,但tskIDLE_PRIORITY为0)
&xIdleTaskHandles[ xCoreID ] ); // 任务句柄存储位置
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
}
return xReturn; // 返回创建结果(pdPASS表示成功,pdFAIL表示失败)
}
pxIdleTaskFunction
是任务函数,具体内容非常简单:
c
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
/* Stop warnings. */
( void ) pvParameters;
for( ; configCONTROL_INFINITE_LOOP(); )
{
/* See if any tasks have deleted themselves - if so then the idle task
* is responsible for freeing the deleted task's TCB and stack. */
prvCheckTasksWaitingTermination();
}
}
简单来说就是循环检查是否需要回收任务空间。
总结
完结撒花!!!