后续的章节涉及这些内核对象:task、queue、semaphores和event group等。为了让FreeRTOS更容易使用,这些内核对象一般都是动态分配:用到时分配,不使用时释放。使用内存的动态管理功能,简化了程序设计:不再需要小心翼翼地提前规划各类对象,简化API函数的涉及,甚至可以减少内存的使用。
在C语言的库函数中,有mallc、free等函数,但是在FreeRTOS中,它们不适用:
- 不适合用在资源紧缺的嵌入式系统中
- 这些函数的实现过于复杂、占据的代码空间太大
- 并非线程安全的(thread- safe)
- 运行有不确定性:每次调用这些函数时花费的时间可能都不相同
- 内存碎片化
- 使用不同的编译器时,需要进行复杂的配置
- 有时候难以调试
FreeRTOS的5中内存管理方法
FreeRTOS中内存管理的接口函数为:pvPortMalloc
、vPortFree
,对应于C库的malloc、free。 文件在FreeRTOS/Source/portable/MemMang下,它也是放在portable目录下,表示你可以提供自己的函数。
源码中默认提供了5个文件,对应内存管理的5种方法。
文件 | 优点 | 缺点 |
---|---|---|
heap_1.c | 分配简单,时间确定 | 只分配、不回收 |
heap_2.c | 动态分配、最佳匹配 | 碎片、时间不定 |
heap_3.c | 调用标准库函数 | 速度慢、时间不定 |
heap_4.c | 相邻空闲内存可合并 | 可解决碎片问题、时间不定 |
heap_5.c | 在heap_4基础上支持分隔的内存块 | 可解决碎片问题、时间不定 |
以上均是引用韦东山老师教程:第8章 内存管理 | 百问网
我们一直讲内存,堆栈,以前是裸机现在引入了任务,每个任务有自己的栈空间,保存任务的变量等等,那么这些内存是怎么在ram、rom里面分布的呢?以及编译完成的可执行文件放在flash上面,又是什么情况?这么多内存之间什么关系?一直一来这里都没理清,接下来参考佬的文章,来详细看一下。
STM32的内存管理相关(内存架构,内存管理,map文件分析)
STM32的内存管理相关(内存架构,内存管理,map文件分析)_stm32内存管理有什么用-CSDN博客
嵌入式RTOS的 任务栈 和 系统栈 - 知乎
这里就不再继续贴原文了,原文写的非常详细,请移步学习。
在嵌入式系统中,程序的内存布局通常分为 Flash(或 ROM)和 RAM。Flash 用于存储程序代码和只读数据,而 **RAM 用于存储程序运行时的数据,包括全局变量、栈和堆。**下面是分布图示例:
flash:
RAM:
其实上面这个图片并不全:
RAM(数据存储器)–这里参考Linux系统的应用程序在内存上的分布
- .data:已初始化的全局变量和静态变量的运行时存储位置。这些变量在程序启动时从 Flash 的
.data
段复制到 RAM 中。- .bss:未初始化的全局变量和静态变量的运行时存储位置。这些变量在程序启动时在 RAM 中被自动清零。
- 堆(Heap):用于动态内存分配,如使用
malloc
和free
函数。- 栈(Stack):用于存储函数调用时的局部变量、参数和返回地址。每个任务或线程通常有自己的栈。
RAM和ROM对CPU的编址方式:
在一些嵌入式系统中,RAM和ROM对于CPU可以是统一编址的,也可以是独立编址的,这取决于具体的硬件设计和架构。统一编址时,CPU通过相同的地址总线来访问RAM和ROM,它们在同一个地址空间内,只是地址范围不同。独立编址则使用不同的控制信号和地址空间来分别访问RAM和ROM。
程序代码的存储和执行:
程序代码通常是存储在Flash(或ROM)中,而不是直接在RAM中运行。这是因为Flash是一种非易失性存储器,可以永久保存数据,而RAM是易失性存储器,断电后数据会丢失。
程序代码(包括 .text
段、.rodata
段、.data
段和 .bss
段)在编译后被存储在Flash中。尽管程序代码存储在Flash中,但实际执行时,CPU是从RAM中读取指令的。这是因为CPU直接从RAM中读取指令的速度比从Flash中读取要快得多。
这些段通常在编译后的可执行文件中以ELF(Executable and Linkable Format)格式存在。ELF是一种常见的可执行文件格式,它不仅用于Linux系统,也被许多其他系统使用。ELF文件包含了程序的代码、数据、符号表、重定位信息等,这些信息在程序加载到内存中时会被操作系统或引导加载程序(如BootLoader)使用。
在嵌入式系统中,ELF文件可能会被进一步处理,例如通过链接器脚本将其分割成适合特定硬件架构和内存布局的段。这些段最终会被加载到目标硬件的Flash和RAM中。因此,虽然ELF文件是程序的最终形式,但它内部的结构和段的组织方式是由编译器和链接器根据链接器脚本和目标平台的要求来确定的。
启动过程
- 启动时的代码复制:
- 在系统启动时,Bootloader(或系统启动代码)会将Flash中的
.text
段和.data
段复制到RAM中。.text
段包含程序的可执行代码,.data
段包含已初始化的全局变量和静态变量。.bss
段的处理:
.bss
段包含未初始化的全局变量和静态变量。- 在系统启动时,Bootloader会将
.bss
段在RAM中清零。- 程序开始执行:
- 一旦
.text
段和.data
段被复制到RAM中,并且.bss
段被清零,程序就可以开始在RAM中执行了。- CPU从RAM中的
.text
段开始执行指令。
上电汇编到kernel入口地址的编址:
从系统上电到进入内核入口地址的过程中,涉及到的内存编址通常是基于整个系统的内存映射来进行的。如果是统一编址,那么这一过程中对RAM和ROM的访问都遵循统一的地址空间规则。在启动过程中,会根据硬件的初始化设置和引导程序的逻辑,将程序从ROM加载到RAM中合适的位置,并跳转到内核入口地址开始执行。
寻址范围与处理器的关系:
寻址范围确实与SoC的架构以及处理器的位数有关。一般来说,n位的处理器理论上的寻址空间为2^n 。例如,32位处理器的寻址范围是2^32,即4GB的地址空间。但实际的寻址范围还可能受到硬件设计、内存管理单元(MMU)的配置以及系统中其他设备占用地址空间等因素的限制。