当前位置: 首页 > news >正文

FreeRTOS源码分析一:task启动(RISCV架构)

系列文章目录

FreeRTOS源码分析一:task创建(RISCV架构)


文章目录

  • 系列文章目录
  • 前言
  • vTaskStartScheduler 调度器启动函数
    • xPortStartScheduler架构特定调度器启动函数
      • vPortSetupTimerInterrupt启动 RISCV 定时器中断
      • xPortStartFirstTask启动第一个任务
    • 空闲任务
  • 总结


前言

本文继续看 task 的运行。主要解析函数 vTaskStartScheduler

主函数中调用函数 vTaskStartScheduler 开始调度。

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),并启动第一个任务的上下文切换,从而进入多任务运行模式。

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架构特定调度器启动函数

检查中断服务的堆栈对齐并填充字节,初始化必要的硬件设置并启动多任务调度

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 时触发始终中断。
实际实现也是这样。

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返回即可执行任务

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

static BaseType_t prvCreateIdleTasks( void )
{BaseType_t xReturn = pdPASS;                    // 函数返回值,初始化为成功BaseType_t xCoreID;                             // 当前处理的CPU核心IDchar 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 是任务函数,具体内容非常简单:

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();}
}

简单来说就是循环检查是否需要回收任务空间。


总结

完结撒花!!!

http://www.lryc.cn/news/608412.html

相关文章:

  • 【图像处理基石】用Python实现基础滤镜效果
  • PCB铜浆塞孔工艺流程
  • 网页操作自动化解决方案:如何用Browser-Use+CPolar提升企业运营效率
  • openwrt下安装istore(基于pve)
  • CCF IVC 2025“汽车安全攻防赛” -- Crypto -- WriteUp
  • ESP2025年6月认证C++八级( 第三部分编程题(2)遍历计数)
  • 线程池的实现
  • 【python】转移本地安装的python包
  • 【语音技术】意图与语料
  • 从下单到发货:如何清晰表达发货时间
  • Python编程基础与实践:Python条件语句入门:掌握if, else, 和elif
  • Android动画实现控件形状、大小逐渐过渡
  • Agentic RAG:自主检索增强生成的范式演进与技术突破
  • Waterfox水狐浏览器、火狐浏览器外观修改
  • XGBoost三部曲:XGBoost参数详解
  • Store / Slice / Reducer
  • 利用DeepSeek将Rust程序的缓冲输出改写为C语言实现提高输出效率
  • Python爬虫实战:研究SimpleCV技术,构建图像获取及处理系统
  • vulnhub-ELECTRICAL靶场攻略
  • 基于OAuth2与JWT的微服务API安全实战经验分享
  • AbstractExecutorService:Java并发核心模板解析
  • Batch Normalization(BN):深度学习中的“训练加速器”与实践指南
  • Vue 详情模块 3
  • 洛谷 P3372 【模板】线段树 1-普及+/提高
  • 星际漫游闪耀2025LEC全球授权展,三大IP与文旅AI打印机共绘国潮宇宙新篇章
  • 【走遍美国精讲笔记】第 1 课:林登大街 46 号
  • 深入 Go 底层原理(一):Slice 的实现剖析
  • 波士顿咨询校招面试轮次及应对策略解析
  • PYTHON从入门到实践-18Django从零开始构建Web应用
  • 二叉搜索树(C++实现)