尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Flipper Zero 固件中的 FreeRTOS-Kernel:内核结构、CM4F 移植层与 FreeRTOSConfig.h 配置详解

Flipper Zero 固件中的 FreeRTOS-Kernel:内核结构、CM4F 移植层与 FreeRTOSConfig.h 配置详解 Flipper Zero 固件中的 FreeRTOS-Kernel内核结构、CM4F 移植层与 FreeRTOSConfig.h 配置详解【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware本文以 FreeRTOS-Kernel 子库的 README.md 为主体结合 Flipper Zero 固件仓库的实际集成方式展开先梳理内核仓库三个核心文件 移植层 公共头文件的目录结构与各文件职责再深入分析固件如何通过 SCons 构建系统挑选portable/GCC/ARM_CM4F移植层进行编译最后逐段解读 targets/f7/inc/FreeRTOSConfig.h 中每一项关键宏配置及其在furi运行时中的落地证据。读完本文你将掌握 FreeRTOS 内核源码的完整布局、端口层选择逻辑以及一个真实嵌入式产品中内核配置参数的典型取值与验证方法。内核仓库定位与目录结构README.md 对仓库的定位做了明确说明本仓库只包含 FreeRTOS 内核的源文件、头文件和内核移植层kernel source/header files and kernel ports它本身被作为子模块引用到官方的 FreeRTOS/FreeRTOS 总仓库中后者在FreeRTOS/Demo目录下提供预先配置好的演示工程。README 建议的使用路径是先跑通一个配置好的 demo 工程这样源文件与包含路径都是正确的再删除 demo 应用文件、加入自己的应用源码。仓库根目录lib/FreeRTOS-Kernel/的文件清单与 README 描述完全吻合根目录的三个核心文件即整个内核README 原文指出The kernel is contained within these three files——list.c226 行内核通用的双向循环链表实现是任务就绪列表、延迟列表、事件列表的基础数据结构queue.c3087 行队列、二值信号量、计数信号量、互斥锁的实现任务间同步与通信的核心tasks.c5429 行全仓库最大的内核源文件任务创建/删除、调度、挂起、阻塞、任务通知task notifications等机制TCB任务控制块也定义于此。croutine.c363 行可选的协程co-routine功能。README 特别说明它normally only used on very memory limited systems通常只用于内存极度受限的系统。Flipper Zero 在配置中明确禁用了它configUSE_CO_ROUTINES 0因此这份代码不会参与固件运行。根目录还有 README 未单独点名、但同属每个移植层公共的模块event_groups.c事件组、stream_buffer.c字节流缓冲区、timers.c软件定时器内部依赖一个低优先级的定时器服务任务。include/ 目录包含实时内核的对外头文件如FreeRTOS.h总入口头、task.h、queue.h、semphr.h、timers.h、event_groups.h、stream_buffer.h、list.h、projdefs.h等。portable/ 目录包含与特定微控制器和/或编译器相关的文件。portable/readme.txt 进一步解释了组织规则每个目录名对应一个编译器GCC、IAR、ARMClang、RVDS、MPLAB、SDCC、MSVC-MingW、CCS 等其下的子目录对应目标架构如ARM_CM4F、ARM_CM33、RISC-V、Xtensa_ESP32MemMang子目录存放 5 个官方示例内存分配器。readme 提醒如果只关心某一个移植层其他目录都可以忽略——这正是 Flipper Zero 集成时采取的策略。版本信息可以在 manifest.yml 中确认当前仓库快照对应的内核版本为v10.5.1许可证为 MIT。Flipper Zero 的构建集成只编译一个移植层README 指出内核仓库面向所有平台而一个具体产品只需其中一份移植层。Flipper Zero 的 f7 目标芯片为 STM32WB55Cortex-M4F Cortex-M4 协处理器对应的 GCC 移植层位于 portable/GCC/ARM_CM4F/目录内只有两个文件port.c839 行调度器启动xPortStartScheduler()、vPortEndScheduler()、首次任务切换prvPortStartFirstTask()、以及关键的pxPortInitialiseStack()初始化任务栈、构造初始寄存器现场使任务被首次切换进入时从用户函数正常返回。xPortStartScheduler入口处首先断言configMAX_SYSCALL_INTERRUPT_PRIORITY非零——这是 Cortex-M 移植层对中断优先级分层的硬性要求对应 FreeRTOSConfig.h 中专门的注释!!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!。portmacro.hportBASE_TYPE、临界区进入/退出基于基优先级寄存器BASEPRI等端口级宏定义。SCons 构建脚本 lib/freertos.scons 完整地呈现了挑选移植层这一决策env.Append( CPPPATH[ #/lib/drivers, #/lib/FreeRTOS-Kernel/include, # 内核公共头文件 #/lib/FreeRTOS-Kernel/portable/GCC/ARM_CM4F, # 只取 CM4F 移植层 #/lib/FreeRTOS-glue, ], ) libenv env.Clone(FW_LIB_NAMEfreertos) libenv.ApplyLibFlags() sources libenv.Glob(FreeRTOS-Kernel/*.c, sourceTrue) # 根目录全部内核 .c sources [ FreeRTOS-Kernel/portable/GCC/ARM_CM4F/port.c, # 唯一加入的移植层源 ] lib libenv.StaticLibrary(${FW_LIB_NAME}, sources)从源码结构看集成策略非常简洁根目录下所有.clist/queue/tasks/croutine/event_groups/stream_buffer/timers加上 ARM_CM4F 移植层的port.c共 8 个源文件编成一个名为freertos的静态库${LIB_DIST_DIR}分发。注意这里把croutine.c也编译进了库但由于配置中configUSE_CO_ROUTINES为 0协程相关代码不会进入最终镜像的有效路径内存管理则没有使用portable/MemMang下随内核提供的分配器而是由配置宏USE_FreeRTOS_HEAP_4指定 heap_4 实现见下文。FreeRTOSConfig.hf7 目标的全量关键配置FreeRTOS 内核的可配置性全部通过一个工程必须自行提供的FreeRTOSConfig.h实现内核代码中config*宏均以此文件为前提。Flipper Zero 为 f7 目标提供了一份位于 targets/f7/inc/FreeRTOSConfig.h 的配置以下按主题拆解其中的关键参数并说明其在源码中的影响。抢占、分配与基础节拍宏取值含义configUSE_PREEMPTION1启用抢占式调度高优先级任务就绪后立即切换configTICK_RATE_HZ1000系统节拍 1kHz时间单位即毫秒configUSE_16_BIT_TICKS0节拍计数器为 32 位约 49.7 天回绕configMAX_PRIORITIES32优先级 0~31 共 32 级configMINIMAL_STACK_SIZE128动态创建任务时的最小栈深度字configSUPPORT_STATIC_ALLOCATION/configSUPPORT_DYNAMIC_ALLOCATION1 / 1静态、动态两种分配方式都启用configUSE_POSIX_ERRNO1每个任务拥有独立 errnoTCB 内增加iTaskErrno成员configCPU_CLOCK_HZSystemCoreClock运行时时钟频率取自 CMSIS 系统时钟变量其中configCPU_CLOCK_HZ用(SystemCoreClock)而非字面量说明固件接受运行期时钟校准STM32WB 的 HSE/PLL 配置决定最终主频配合configGENERATE_RUN_TIME_STATS见下节使运行时间统计与真实时钟保持一致。堆管理与运行时间统计/* Heap size determined automatically by linker */ #define configTOTAL_HEAP_SIZE ((uint32_t) __heap_end__ - (uint32_t) __heap_start__) ... #define configGENERATE_RUN_TIME_STATS 1 #define portGET_RUN_TIME_COUNTER_VALUE() (DWT-CYCCNT) #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()堆大小不由宏写死而是直接取链接脚本中的__heap_start__/__heap_end__符号差值来自 stm32wb55_linker.h 所引入的链接器头即内核堆 C 运行时堆由链接布局自动确定portGET_RUN_TIME_COUNTER_VALUE使用 Cortex-M4 的DWT 周期计数器DWT-CYCCNT作为运行时间统计基准且portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()为空实现——因为 DWT CYCCNT 无需额外定时器初始化。这使得xTaskGetRunTimeStats之类的统计精度达到 CPU 周期级configENABLE_HEAP_PROTECTOR 1与configHEAP_CLEAR_MEMORY_ON_FREE 1是 Flipper 定制的增强项前者在堆块边界放置保护页便于检测堆破坏后者在释放时将内存清零防止敏感数据残留在可被重新分配的内存中。定时器服务任务与空闲任务#define configUSE_TIMERS 1 #define configTIMER_TASK_PRIORITY (2) #define configTIMER_QUEUE_LENGTH 32 #define configTIMER_TASK_STACK_DEPTH 256 #define configTIMER_SERVICE_TASK_NAME TimersSrv #define configIDLE_TASK_NAME (-_-) #define configIDLE_TASK_STACK_DEPTH 128软件定时器启用后timers.c 会创建一个优先级为 2 的后台服务任务TimersSrv其命令队列深度 32、栈深 256 字。空闲任务的名字(-_-)是一个有辨识度的躺平表情空闲栈深 128 字。空闲任务在 tickless idle 模式下尤其重要见下一节。Tickless Idle低功耗睡眠的调度器配合#define configUSE_TICKLESS_IDLE 2 #define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 4configUSE_TICKLESS_IDLE 2表示启用基于vTaskStepTick()的无节拍空闲模式当所有任务都阻塞、且预计空闲时间超过configEXPECTED_IDLE_TIME_BEFORE_SLEEP4 个 tick 4ms时调度器可以停止节拍中断、让芯片进入低功耗睡眠唤醒后通过vTaskStepTick补记错过的节拍。固件侧在 targets/f7/furi_hal/furi_hal_os.c 中实现了vTaskStepTick的对应逻辑——其中包含do { ... vTaskStepTick(completed_ticks); } while(0)的补 tick 循环并在补记期间先关闭中断__enable_irq()收尾保证补记原子性。这是 Flipper Zero 作为电池供电设备降低待机功耗的关键机制之一。内核移植层的中断优先级分层#ifdef __NVIC_PRIO_BITS #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 4 #endif #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 #define configKERNEL_INTERRUPT_PRIORITY \ (configLIBRARY_LOWEST_INTERRUPT_PRIORITY (8 - configPRIO_BITS)) #define configMAX_SYSCALL_INTERRUPT_PRIORITY \ (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY (8 - configPRIO_BITS))这是 Cortex-M 移植层最容易被配错的一组参数。规则是数值越小优先级越高内核把可用中断划分为两段——高于configMAX_SYSCALL_INTERRUPT_PRIORITY即数值小于 5 的优先级的中断不允许调用任何带 From ISR 后缀的 FreeRTOS API低于该值的中断可以自由调用中断安全 API。configKERNEL_INTERRUPT_PRIORITY则把 PendSV/SVC 压到 15最低优先级保证任何配置为系统调用级别的硬件中断都能打断 PendSV 从而触发任务切换。port.c 中xPortStartScheduler开头的configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY )就是对不能为 0这一红线的运行时检查。任务通知数组一个定制化的精巧配置/* Workaround for various notification issues: * - First one used by system primitives * - Second one by thread event notification * - Third one by FuriEventLoop */ #define configTASK_NOTIFICATION_ARRAY_ENTRIES 3标准内核默认每个任务只有 1 个任务通知槽ulNotifiedValue[1]而任务通知是比信号量更轻量的同步原语。固件把它扩到 3 个并在配置里逐条注释了分配意图第 1 槽给系统原语、第 2 槽给线程事件通知、第 3 槽给FuriEventLoop。从源码结构看furi的事件循环 furi/core/event_loop.c 正是以任务通知作为底层同步机制的这个数组大小是事件循环设计的一部分。对应的 TCB 成员ulNotifiedValue[configTASK_NOTIFICATION_ARRAY_ENTRIES]/ucNotifyState[...]在 tasks.c 的 TCB 定义中按该宏展开。任务退出路径的接管configTASK_RETURN_ADDRESSextern __attribute__((__noreturn__)) void furi_thread_catch(void); #define configTASK_RETURN_ADDRESS (furi_thread_catch 2)在portable/GCC/ARM_CM4F移植中任务函数返回后 CPU 会返回到configTASK_RETURN_ADDRESS指定的地址继续执行该地址被写入任务初始栈帧的 PC。内核默认指向prvTaskExitError固件则把它重定向到自己的furi_thread_catch函数偏移 2 是为了跳过该函数首条非 Thumb 对齐指令从而把任务意外返回这一错误路径统一收口到 furi 线程框架处理。这与 furi/core/thread.c 中线程生命周期的管理创建、join、清理相互呼应。调试钩子MPU 栈保护与 per-task errno#define traceTASK_SWITCHED_IN() \ extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB-pxStack); \ errno pxCurrentTCB-iTaskErrno // ^^^^^ acquire errno directly from TCB because FreeRTOS assigns its FreeRTOS_errno _after_ our hook is called #define traceTASK_SWITCHED_OUT() FreeRTOS_errno errno这组宏展示了配置钩子如何承担系统级职责traceTASK_SWITCHED_IN在每次任务切入时调用furi_hal_mpu_set_stack_protection为当前任务的栈设置 MPU内存保护单元栈溢出保护。注意 FreeRTOSConfig.h 中configENABLE_MPU 0表示 MPU 不用于任务隔离无 MPU wrapper但栈保护仍借助 MPU 硬件单独启用属于轻量用法由于启用了configUSE_POSIX_ERRNO每个 TCB 持有iTaskErrno成员任务切换时把全局errno与 TCB 值互换实现每任务独立的 errno。配置中的两条注释非常关键地解释了顺序细节FreeRTOS 在调用traceTASK_SWITCHED_OUT之前读取全局 errno 写入 TCB所以 OUT 钩子里必须用全局errno而不是 TCB 值而在调用traceTASK_SWITCHED_IN之后才把 TCB 值写回全局 errno所以 IN 钩子里必须主动从 TCB 读取。这类钩子与内核执行时序的耦合是阅读此类配置时最需要注意的地方configASSERT在FURI_DEBUG构建下直接触发furi_crash(FreeRTOS Assert)让内核断言直接走设备的统一崩溃上报路径。TCB 与运行时任务检视调度器维护的全部任务状态都在 TCB任务控制块中内核在 tasks.c 中以旧命名约定tskTaskControlBlock定义该结构并typedef tskTCB TCB_t注释说明保留旧名是为了不破坏内核感知调试器。每个 TCB 至少包含pxTopOfStack必须为第一成员指向栈顶最后压入位置、xStateListItem/xEventListItem用于挂载到就绪/阻塞/挂起列表、uxPriority、pxStack、pcTaskName[configMAX_TASK_NAME_LEN]32 字节任务名以及按配置宏展开的可选成员——例如本配置启用的configRECORD_STACK_HIGH_ADDRESS记录pxEndOfStack、configUSE_MUTEXESuxBasePriority/uxMutexesHeld用于优先级继承、configGENERATE_RUN_TIME_STATSulRunTimeCounter累计运行周期数、configUSE_TASK_NOTIFICATIONS上面讨论的通知数组、configUSE_POSIX_ERRNOiTaskErrno。固件在 furi/core/thread.c 中直接操作这一结构一方面通过 lib/FreeRTOS-glue/task_control_block.h 提供的TaskControlBlock类型对运行中的任务做运行时检视例如遍历任务句柄数组并读取tcb信息另一方面线程创建走的是xTaskCreateStatic路径——furi_thread_start中把thread-stack_size换算成字深度stack_size / sizeof(StackType_t)后以静态栈和静态容器thread-container创建任务这要求configSUPPORT_STATIC_ALLOCATION为 1与前面配置表一致。线程安全方面furi_hal_os.c 实现了vApplicationStackOverflowHook打印溢出任务名后直接furi_crash(StackOverflow)。需要说明的是该配置中configCHECK_FOR_STACK_OVERFLOW为 0即不启用内核的自动栈金丝雀检测实际防线依赖上一节的 MPU 栈保护traceTASK_SWITCHED_IN钩子该溢出钩子在启用检测后或特定移植路径下作为最后兜底存在。代码规范格式与拼写检查README 最后两节描述了上游项目对代码风格的管理了解它们有助于阅读内核源码时理解命名与格式格式FreeRTOS 源文件使用uncrustify工具统一格式化配置文件随 FreeRTOS 总仓库的tools/uncrustify.cfg维护。内核代码中大量以prv前缀的私有函数、px/ux/pc/x匈牙利式前缀命名如pxTopOfStack、uxPriority、pcTaskName均出自这套约定拼写lexicon.txt收录代码库中非常规词汇术语、变量名等供拼写检查器使用README 特别提醒只有内核源文件被拼写检查portable 部分被忽略。小结从内核子库到产品固件的映射关系把 README 描述的仓库结构与 Flipper Zero 的实际集成对照可以得到一张清晰的映射表README 描述的结构仓库路径在固件中的角色三个公共内核文件list.c、queue.c、tasks.c全部编入freertos静态库可选协程croutine.c编译进库但配置禁用configUSE_CO_ROUTINES 0公共头文件include/加入CPPPATH编译器/架构移植层portable/GCC/ARM_CM4F/仅port.c一个源文件参与编译内存分配器示例portable/MemMang/未使用改用USE_FreeRTOS_HEAP_4配置契约由工程自行提供targets/f7/inc/FreeRTOSConfig.h构建集成—lib/freertos.scons这套集成方式体现了 FreeRTOS核心小、端口可插拔的设计内核逻辑集中在根目录少数几个文件中产品方只需贡献一份移植层选择、一个FreeRTOSConfig.h和少量钩子实现栈溢出钩子、tick 补记、MPU 栈保护、任务退出接管就能把内核完整接进自己的系统。若要继续深入建议按顺序阅读port.c 中pxPortInitialiseStack与prvPortStartFirstTask的上下文构造过程 → tasks.c 中 TCB 定义与prvAddTaskToReadyList的就绪列表机制 → furi/core/thread.c 中 furi 线程如何封装静态任务三者正好覆盖移植层—内核—上层框架三个层面。【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表