FreeRTOS内核学习
命名规则
-
变量:
- uint32_t 定义的变量都加上前缀 ul。 u 代表 unsigned 无符号, l 代表 long长整型
- uint16_t 定义的变量都加上前缀 us。 u 代表 unsigned 无符号,s 代表 short 短整型
- uint8_t 定义的变量都加上前缀 uc。 u 代表 unsigned 无符号,c 代表 char 字符型
- int 定义的变量加上前缀 i。i 代表 int
- stdint.h 文件中未定义的变量类型,在定义变量时需要加上前缀 x.
- stdint.h 文件中未定义的无符号变量类型,在定义变量时要加上前缀 u。
- 枚举变量会加上前缀 e
- 指针变量会加上前缀 p
- **char ** 定义的变量只能用于 ASCII 字符,前缀使用 c
- **char ** 定义的指针变量只能用于 ASCII 字符串,前缀使用 pc。
-
函数:
- 加上了 static 声明的函数,定义时要加上前缀 prv
- 带有返回值的函数,根据返回值的数据类型,加上相应的前缀,如果没有返回值,即 void 类型,函数的前缀加上字母 v
- 根据文件名,文件中相应的函数定义时可能会将文件名加到函数命名中,比如 tasks.c 文件中函数vTaskDelete,函数中的 task 就是文件名中的 task。
-
宏定义:
- 根据宏定义所在的文件,文件中的宏定义声明时也将文件名加到宏定义中,比如宏定义configUSE_PREEMPTION 是定义在文件 FreeRTOSConfig.h 里面。 宏定义中的 config 就是文件名中的 config。 另外注意,前缀要小写。
- 除了前缀,其余部分全部大写,同时用下划线分开。
-
缩进:Tab 制表符用于缩进,Tab 一次缩进 4 个字符空间。
-
注释:FreeRTOS 中注释不会超过 80 个字符宽度,除非对函数的参数进行注释时。源码中主要是采用/* */
的形式进行注释,不采用 C++中的双斜杠风格来注释。
堆栈
堆栈的空间均分配在RAM中
堆:cm4中堆向上生长
栈:cm4中栈向下生长
FreeRTOS创建任务使用的栈实际上使用的单片机的堆空间,实际上是在堆中开辟了一个数据结构为栈的空间。
task.c中portSTACK_GROWTH默认为-1,即先分配stack(栈空间)再分配TCB(任务控制块)
任务
任务创建流程:向堆申请任务栈空间和任务控制块,初始化相关链表,将任务加入就绪队列
同一个优先级下,共用一个链表,同一个xLIST_ITEM
函数
-
vTaskDelete(NULL);
- 慎用
NULL
:调用vTaskDelete(NULL);
会删除调用它的任务。这在任务完成其工作并希望自我删除时非常有用,但需要谨慎使用,以避免意外删除当前正在运行的任务。 - 资源回收:虽然任务被删除了,但是它所持有的任何同步原语(如信号量、互斥锁等)不会自动释放。你需要在删除任务之前手动释放这些资源。
- 删除阻塞任务:如果尝试删除一个处于阻塞状态的任务,那么任务将被删除,但任何等待该任务的同步原语也将被释放。
- 调度器行为:
vTaskDelete()
可以影响调度器的行为。例如,如果删除了一个高优先级任务,那么调度器可能会立即调度其他任务。 - 内存碎片:在某些配置下,频繁创建和删除任务可能会导致内存碎片。在这种情况下,考虑使用静态内存分配或调整内存管理策略。
- 慎用
-
vApplicationTickHook();
- 如果要使用需要在FreeRTOSConfig.h中将configUSE_TICK_HOOK 配置为1
- 触发时机:每次系统时钟节拍中断时
- 在SysTick_Handler()中调用,中断频率高,由于它运行在时钟中断上下文中,执行时间应尽量短,以避免影响实时性和其他中断的响应,只能使用"FromISR"的函数
- 用途:
- 计时操作:可以用来实现简单的计时功能。例如,周期性地检查某些条件或触发定时操作。
- 监控功能:在钩子中实现某些运行时监控或统计功能,例如统计系统运行时间或检查某些状态。
- 外设处理:处理与时钟节拍相关的外设操作(如软件定时器、周期性事件等)。
- 节拍同步:可以用来同步其他系统组件与 FreeRTOS 的时钟节拍。
-
vApplicationMallocFailedHook();
- 如果要使用需要在FreeRTOSConfig.h中将configUSE_MALLOC_FAILED_HOOK 配置为1
- 触发时机:动态内存分配失败时
- 用途:
- 系统重启:在内存分配失败时,可能需要系统重启或进入死循环等待外部干预。
- 日志记录:可以将内存分配失败的信息写入日志,以便于后续调试。
- 内存池管理:在内存管理较为复杂的系统中,可以通过此钩子进行内存池的调整或重新配置。
-
vApplicationIdleHook():
-
如果要使用需要在FreeRTOSConfig.h中将**configUSE_IDLE_HOOK ** 配置为1
-
触发时机:空闲任务执行时
-
用途:
- 低功耗模式:在嵌入式系统中,空闲状态下常常进入低功耗模式以节省电池。可以在此钩子中调用 CPU 的低功耗指令,如进入休眠模式(例如
__WFI()
)。 - 周期性任务:如果系统在空闲时需要执行一些周期性的任务(如状态检查、定时任务等),可以将这些任务放在此钩子中执行。
- 定期更新或维护:一些不频繁但必须执行的操作,例如刷新显示屏、执行系统自检等,可以放在此钩子中进行。
- 低功耗模式:在嵌入式系统中,空闲状态下常常进入低功耗模式以节省电池。可以在此钩子中调用 CPU 的低功耗指令,如进入休眠模式(例如
-
-
vApplicationStackOverflowHook();
-
如果要使用需要在FreeRTOSConfig.h中将configCHECK_FOR_STACK_OVERFLO 配置为1
-
触发时机:任务栈溢出时
-
用途:
- 错误检测和日志记录:栈溢出是程序中的严重错误,可以通过此钩子记录错误信息、进行日志分析,或进行系统状态转储。
- 防止系统崩溃:栈溢出通常会导致任务行为异常,调用此钩子后,可以采取重启系统、切换到安全模式或者其他恢复措施来防止系统崩溃。
- 调试工具:在开发阶段,栈溢出钩子可以用于收集调试信息,帮助开发人员识别任务栈的大小配置问题。
-
-
vApplicationDaemonTaskStartupHook();
- 如果要使用需要在FreeRTOSConfig.h中将**configUSE_IDLE_HOOK ** 配置为1
- 触发时机:当守护任务(通常是系统初始化任务)启动时会调用此钩子。
- 用途:
- 系统初始化:用于执行系统的初始化操作,加载必要的配置,进行硬件初始化等。
- 启动日志:守护任务启动时,可以用此钩子启动系统的日志记录、网络连接等操作。
- 资源分配:可以用此钩子进行外部资源(如内存、外设等)的配置。
-
vTaskDelay(),vTaskDelayUntil()函数
- 调用时,调度器不能处于挂起状态
状态类型
- 运行状态:任务正在执行,CPU 正在为该任务分配时间。
- 就绪状态:任务已准备好运行,等待调度器分配 CPU 时间。任务进入就绪状态通常是因为它已经满足了执行的条件,且没有被任何事件阻塞(如等待信号量、消息队列等)。
- 阻塞状态: 任务因等待某些资源或事件(如信号量、消息队列、时间延迟等)而无法执行。任务不会被调度,直到其等待的事件或资源变得可用。
- 挂起状态:任务被显式挂起,不会被调度执行。挂起状态与阻塞状态的区别是任务不再等待某个资源或事件,而是被手动暂停了。
- 删除状态:任务被删除后,其资源(包括任务栈和任务控制块)将被释放。任务进入删除状态意味着它不再被调度,且不再存在。
任务控制块
消息队列
消息队列 是基于先进先出的队列结构,用于任务间传递消息或数据,适合较小的数据包传递,支持消息的发送与接收。
中断
信号量
- 二值信号量(可以在任务和中断中使用)
- 计数信号量(可以在任务和中断中使用)
- 互斥信号量(可以在任务中使用,不可以或避免在中断中使用)
事件组
主要用于多任务间的同步,与信号量不同的是,它可以实现一对多,多对多的同步。
任务通知
使用限制
-
任务通知方式仅可以用在只有一个任务等待信号量,消息邮箱或者事件标志组的情况。(点对点)
-
仅可在使用 RTOS 任务通知代替 队列的情况下:当某个接收任务可在阻塞状态下等待通知 (因而不花费任何 CPU 时间)时,发送任务不能 在阻塞状态下等待发送完成(在发送不能立刻完成的情况下) 。
-
只能发送给任务:通知只能发送给任务,而不能用于中断或其他任务间的同步。
-
使用任务通知方式实现的消息邮箱替代消息队列时,发送消息的任务不支持超时等待,即消息队列中的数据已经满
了,可以等待消息队列有空间可以存新的数据,而任务通知方式实现的消息邮箱不支持超时等待。
-
无法解决优先级翻转问题
用例
参考链接
FreeRTOS消息队列、信号量、事件组、任务通知之间的区别_freertos事件和消息队列的区别-CSDN博客
流缓冲区
流缓冲区 专门用于字节流的传递,适合用于处理大量连续数据的流式传输,如音频、视频、传感器数据等,支持更高效的字节数据传输。
软件定时器
低功耗
内存管理
- heap_1:提前动态分配一大段内存空间,不管任务用与不用,用多少,内存占用是固定的,禁止动态分配内存,只有pvPortMalloc(),没有vPortFree()!
- heap_2:允许释放内存,但不会合并相邻的空闲块,使用Best Fit,可以减少剩余空间,降低外部碎片。同时搜索时间较长,可能导致更多的内部碎片。
- heap_3:简单包装了标准 malloc() 和 free(),以保证线程安全。对这两个函数进行了保护机制,采用的方式是操作内存前挂起调度器,完成后再恢复调度器
- heap_4:合并相邻的空闲块以避免碎片化。包含绝对地址放置选项,使用First Fit,空闲块链表按照地址大小进行排列,而不是按照碎片大小。。
- heap_5:如同 heap_4,能够跨越多个不相邻内存区域的堆。在申请内存前,它会调用vPortDefineHeapRegions,这意味着它允许内存堆跨越多个非连续区域,如果你需要同时使用内部RAM和外部SDRAM,毫无疑问,这种策略非常合适,但是用户需要指定每个内存堆的起始地址和大小。
- heap_1 不太有用,因为 FreeRTOS 添加了静态分配支持。(FreeRTOS v9.0及以上)
- heap_2 现在被视为旧版,因为较新的 heap_4 实现是首选。(heap_2 开销小,但是容易产生内存碎片,heap_4 开销大,但是能减少内存碎片)
head_4
- heap_4.c使用链表进行了空闲块的管理,heap_4.c使用了链表将空闲的内存块地址进行连接
- 1)链表使用的空间哪里来呢。答:链表本身使用的空间也是来自这个大数组,链表本身和用申请的空间本身是连在一起的。
- 2)已经被申请的内存块就不会被链表管理吗?答:回答第一个问题,链表本身使用的空间也是来自这个大数组,链表本身和用申请的空间本身是连在一起的。
- heap_4.c对相邻的空闲内存块进行合并处理。
- 1)空闲块在什么时候进行合并。答:空闲块在内存释放的时候变成空闲块的时候,和申请内存的时候会有新的空闲内存块,然后进行合并。
- 2)空闲块是如何进行合并的。答:如果两个空闲块都被连接进来,那么只要两个空闲块地址连接在一起就可以进行合并。
- 3)已经被申请的内存,已经不被链表进行管理,那么释放已经申请的内存是如何找到前面的空闲块的。答:释放内存时的空闲块会遍历所有链表,然后找到当前空闲块的前一个空闲块和后一个空闲块,如果地址重叠可以判定进行空闲块合并。
参考链接
FreeRTOS 堆内存管理 - FreeRTOS™
微型操作系统内核源码详解系列三(0):空间存储及内存管理篇(前置篇)-CSDN博客
FreeRTOS Heap 1_2_3_4_5 比较 - JerryZheng2020 - 博客园
FreeRTOS系列-- heap_4.c内存管理分析_freertos heap合并-CSDN博客
链表
-
pxDelayedTaskList:延迟任务链表。
-
pxOverflowDelayedTaskList:溢出后的延迟任务链表。
-
pxReadyTasksLists:就绪任务链表(按优先级划分)。
-
xPendingReadyList:等待处理的就绪任务链表。
-
xSuspendedTaskList:被挂起的任务链表。
-
xTasksWaitingTermination:等待终止的任务链表。
-
pxTaskToBeDeleted:待删除任务链表。
-
pxIdleTaskList:空闲任务链表。
-
xEventList:等待事件的任务链表。
-
xTimerList:软件定时器链表。
-
pxTimerQueue:定时器队列链表。
-
xMutexHolderList:互斥量持有者链表。
-
pxQueueList:队列链表。
-
pxSemaphoreList:信号量链表。
-
pxTaskList:所有任务的链表。
-
xTaskStateList:任务状态链表。
杂项
使用结构体时从大到小放置变量更能节省空间
1、不同任务之间,避免在不同任务里面操作同一个外设,除非用户自己手动在底层做了互斥处理。
2、任务和中断之间,都操作同一个外设,一般需要在任务里面设置临界段,也就是开关中断。
FromISR
不能在FreeRTOS不能管理的中断中使用FreeRTOS的API
PendSV
xPortPendSVHandler()在任务切换过程中保存和恢复任务的上下文,调用 vTaskSwitchContext()
进行任务切换
进行任务切换,保存当前任务现场,执行调度,恢复下一个任务现场,执行
PendSV的优势在于它可以像普通中断一样被悬起,当其他中断都执行完毕时,它可以开始工作了,这样就不会打断其他中断了。
在 PendSV Handler 执行过程中,如果遇到更高优先级的中断被打断,那么 PendSV 之前保存的上下文会保留在栈上。中断服务例程(ISR)会在栈上进行上下文保存与恢复,ISR 处理完毕后,CPU 会根据栈中的上下文返回到 PendSV Handler,继续完成任务切换。
SVC
freeRTOS仅在初始化时用到一次SVC中断
SysTick
作为FreeRTOS的心跳,默认为1khz,使用hal及其他框架需要心跳时需要指定其他定时器作为心跳时钟源
更新任务时钟节拍和链表更新
在 SysTick
中断发生时,FreeRTOS 会执行一些必要的操作,主要是更新 Tick计数器、检查任务的延时、检查任务是否需要切换、以及触发任务调度等,而实际的任务调度则被推迟到 PendSV 中断 中处理。
【STM32F407开发板用户手册】第10章 STM32F407的FLASH,RAM和栈使用情况(map和htm文件) - 硬汉嵌入式 - 博客园
FreeRTOS 任务栈大小确定及其溢出检测 - Crystal_Guang - 博客园
FreeRTOS - 随笔分类 - 李柱明 - 博客园
FreeRTOS消息队列、信号量、事件组、任务通知之间的区别_freertos事件和消息队列的区别-CSDN博客