12-FreeRTOS内存管理

简介: 12-FreeRTOS内存管理

如果RTOS对象是动态创建的,那么标准C库malloc()和free()函数有时可以用于完成这项任务,除了:

  • 在嵌入式系统中,它们并不总是可以使用的;
  • 它们会占用更多宝贵的代码空间;
  • 它们没有线程保护;
  • 它们不具有确定性(每次调用执行的时间可能会不同);
    因此,通常需要一个替代的内存分配实现方案。
    嵌入式/实时系统具有千差万别的RAM和时序要求,因此单个RAM内存分配算法永远仅属于一个应用的子集。
    为了解决这个问题,FreeRTOS在移植层保留内存分配API函数。移植层在RTOS核心代码源文件之外(不属于核心源代码),这使得不同的应用程序可以提供适合自己的应用实现。当RTOS内核需要RAM时,调用pvPortMallo()函数来代替malloc()函数。当RAM要被释放时,调用vPortFree()函数来代替free()函数。
    FreeRTOS下载包中提供5种简单的内存分配实现,本文稍后会进行描述。用户可以适当的选择其中的一个,也可以自己设计内存分配策略。


FreeRTOS提供了五种内存分配方案,用户可以根据自己的实际情况选择需要的方案。本篇将对这五种进行介绍。

FreeRTOS提供的内存分配方案分别位于不同的源文件(heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c)之中,源文件位于下载包\FreeRTOS\Source\portable\MemMang文件夹中。其它实现方法可以根据需要增加。如果要使用FreeRTOS提供的内存堆分配方案,选中的源文件必须被正确的包含到工程文件中。

如下所示:

  • heap_1 - 最简单的一个。一旦分配内存之后,它甚至不允许释放分配的内存。
  • heap_2 - 允许释放分配的内存块。但不会把相邻的空闲块合成一个更大的块(换句话说,这会造成内存碎片)。
  • heap_3 - 简单的包装了标准库中的malloc()和free()函数,包装后的malloc()和free()函数具备线程保护。
  • heap_4 - 相邻的空闲内存块合并成一个更大的块(包含一个合并算法),避免碎片化。
  • heap_5 - 实现了heap_4.c中的合并算法,并且允许堆栈跨越多个非连续的内存区。
    官方推荐的首选是heap_4。


1-heap_1.c


Heap_1是所有实现中最简单的。一旦分配了内存,就不允许释放内存。尽管如此,heap_1.c适用于大量嵌入式应用程序。这是因为许多小型和深度嵌入式应用程序在系统引导时创建所需的所有任务、队列、信号量等,然后在程序的生命周期内使用的所有这些对象(直到应用程序再次关闭或重新启动)。任何东西都不会被删除。

当需要分配RAM时,这个内存分配方案只是简单的将一个大数组细分出一个子集来。大数组的容量大小通过FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE宏来设置。configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h配置常量允许将堆放置在内存中的特定地址。

xPortGetFreeHeapSize() API函数返回未分配的堆空间总量,允许对configTOTAL_HEAP_SIZE设置进行优化。


heap_1实现:

  • 如果你的应用程序从来没有删除任务、队列、信号量、互斥量等,可以使用它(这实际上涵盖了使用FreeRTOS的大多数应用程序)。
  • 始终是确定的(始终需要相同的时间来执行),并且不会导致内存碎片化。
  • 实现和分配过程非常简单,需要的内存是从一个静态数组中分配的,意味着这种内存分配通常只是适用于那些不进行动态内存分配的应用。


2- heap_2.c

方案使用一个最佳匹配算法,与heap_1不同的是heap_2允许释放之前分配的内存块。它不会把相邻的空闲块合成一个更大的块(换句话说,这会造成内存碎片)。

有效的堆栈空间大小由位于FreeRTOSConfig.h文件中的configTOTAL_HEAP_SIZE宏来设置。

API函数xPortGetFreeHeapSize() 返回未分配的堆空间总量(允许configTOTAL_HEAP_SIZE设置进行优化),但不提供关于未分配内存如何被分割成更小块的碎片信息。

pvPortCalloc()函数具有与标准库calloc函数相同的功能。它为对象数组分配内存,并将分配的存储中的所有字节初始化为零。如果分配成功,它将返回一个指向已分配内存块中最低字节的指针。如果失败,它返回一个空指针。


2.1 heap_2功能实现

  1. 即使在应用程序重复删除任务、队列、信号量、互斥锁等情况下也可以使用,但需要注意内存碎片。
  2. 如果分配和释放的内存大小是随机的,则不应使用。例如:
  • 如果一个应用程序动态的创建和删除任务,并且分配给任务的堆栈空间总是同样大小,那么大多数情况下heap_2.c是可以使用的。但是,如果分配给任务的堆栈不总是相等,那么释放的有效内存可能碎片化,形成很多小的内存块。最后会因为没有足够大的连续堆栈空间而造成内存分配失败。在这种情况下,heap_4.c是一个很好的选择。
  • 如果一个应用程序动态的创建和删除任务,并且分配给任务的堆栈空间总是同样大小,那么大多数情况下heap_2.c是可以使用的。但是,如果分配给任务的堆栈不总是相等,那么释放的有效内存可能碎片化,形成很多小的内存块。最后会因为没有足够大的连续堆栈空间而造成内存分配失败。在这种情况下,heap_4.c是一个很好的选择。
  • 应用程序直接调用pvPortMalloc() 和 vPortFree()函数,而不仅是通过FreeRTOS API间接调用。
  1. 如果应用程序队列、任务、信号量、互斥等的顺序不可预知,可能会导致内存碎片问题。这种情况发生的几率是非常低的,但应该牢记。
  2. 不具有确定性,但比大多数标准C库malloc的实现更有效。
    Heap_2.c适用于需要动态创建对象的多数小型实时系统。

3-heap_3.c

简单的包装了标准库中的malloc()和free()函数,包装后的malloc()和free()函数具备线程保护作用。


3.1 功能实现

  1. 需要链接器设置一个堆栈,并且编译器库提供malloc()和free()函数。
  2. 不具有确定性
  3. 可能明显的增大RTOS内核的代码大小

注意,FreeRTOSConfig.h中的configTOTAL_HEAP_SIZE设置在使用heap_3时无效。


4-heap_4.c


可用堆空间的总量由configTOTAL_HEAP_SIZE设置(configTOTAL_HEAP_SIZE在FreeRTOSConfig.h中定义。)configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h配置常量允许将堆放置在内存中的特定地址。

xPortGetFreeHeapSize() API函数返回调用该函数时未分配的堆空间总量,xPortGetMinimumEverFreeHeapSize() API函数返回FreeRTOS应用程序启动时存在的最低空闲堆空间总量。这两个函数都没有提供关于如何将未分配的内存分割成更小的块的信息。

vPortGetHeapStats() API函数提供了附加信息。它填充heap_t结构的成员,如下所示。


1/* vPortGetHeapStats()函数的原型 */
 2void vPortGetHeapStats( HeapStats_t *xHeapStats );
 3
 4/* Heap_stats_t结构的定义。*/
 5typedef struct xHeapStats
 6{
 7size_t xAvailableHeapSpaceInBytes; /* 当前可用的堆总大小——这是所有空闲块的总和,而不是可以分配的最大块*/
 8size_t xSizeOfLargestFreeBlockInBytes; /* 调用vPortGetHeapStats()时堆中所有空闲块的最大大小(以字节为单位)。*/
 9size_t xSizeOfSmallestFreeBlockInBytes; /* 调用vPortGetHeapStats()时堆中所有空闲块的最小大小(以字节为单位)。*/
10size_t xNumberOfFreeBlocks;    /* 在调用vPortGetHeapStats()时,堆中空闲内存块的数量。 */
11size_t xMinimumEverFreeBytesRemaining;    /* 自系统启动以来,堆中的最小总空闲内存(所有空闲块的总和)。 */
12size_t xNumberOfSuccessfulAllocations;    /* 返回有效内存块的对pvPortMalloc()的调用次数。 */
13size_t xNumberOfSuccessfulFrees;    /* 成功释放内存块的对vPortFree()的调用次数。*/
14} HeapStats_t;

pvPortCalloc()函数具有与标准库calloc函数相同的功能。它为对象数组分配内存,并将分配的存储中的所有字节初始化为零。如果分配成功,它将返回一个指向已分配内存块中最低字节的指针。失败时,它返回一个空指针。


4.1 实现:

  • 可用于重复分配、删除任务、队列、信号量、互斥量等等的应用程序。
  • 可以用于分配和释放随机字节内存的情况,并不像heap_2.c那样产生严重碎片。
  • 不具有确定性,但是它比标准库中的malloc函数具有高得多的效率。
    heap_4.c对于希望在应用程序代码中直接使用可移植层内存分配方案的应用程序特别有用(而不是通过调用本身调用pvPortMalloc()和vPortFree()的API函数间接使用)。

5-heap_5.c

Heap_5是通过调用vPortDefineHeapRegions()来初始化的,并且在执行vPortDefineHeapRegions()之前不能使用。创建一个RTOS对象(任务、队列、信号量等)将隐式调用pvPortMalloc(),因此在使用heap_5时,必须在创建任何此类对象之前调用vPortDefineHeapRegions()。

vPortDefineHeapRegions()接受单个参数。参数是HeapRegion_t结构的数组。HeapRegion_t在portable.h中定义为:


1typedef struct HeapRegion
2{
3/* Start address of a block of memory that will be part of the heap.*/
4uint8_t *pucStartAddress;
5
6/* Size of the block of memory. */
7size_t xSizeInBytes;
8} HeapRegion_t;


The HeapRegion_t type definition

这个数组必须使用一个NULL指针和0字节元素作为结束,起始地址必须从小到大排列。下面的代码段提供一个例子。MSVCWin32模拟器演示例程使用了heap_5,因此可以当做一个参考例程。

/* 在内存中为内存堆分配两个内存块.第一个内存块0x10000字节,起始地址为0x80000000, 第二个内存块0xa0000字节,起始地址为0x90000000.起始地址为0x80000000的内存块的

起始地址更低,因此放到了数组的第一个位置.*/

1const HeapRegion_t xHeapRegions[] = 
2{ 
3{ ( uint8_t * ) 0x80000000UL, 0x10000 }, 
4{ ( uint8_t * ) 0x90000000UL, 0xa0000 }, 
5{ NULL, 0 } /* 数组结尾. */ 
6}; 
7/* 向函数vPortDefineHeapRegions()传递数组参数. */ 
8vPortDefineHeapRegions( xHeapRegions );


xPortGetFreeHeapSize() API函数返回调用该函数时未分配的堆空间总量,xPortGetMinimumEverFreeHeapSize() API函数返回FreeRTOS应用程序启动时存在的最低空闲堆空间总量。这两个函数都没有提供关于如何将未分配的内存分割成更小的块的信息。

pvPortCalloc()函数具有与标准库calloc函数相同的功能。它为对象数组分配内存,并将分配的存储中的所有字节初始化为零。如果分配成功,它将返回一个指向已分配内存块中最低字节的指针。失败时,它返回一个空指针。

相关文章
9-FreeRTOS之静态内存分配与动态内存分配
9-FreeRTOS之静态内存分配与动态内存分配
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
358 1
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
2月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
25 3
|
2月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
55 1
|
2月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
3月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
110 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
3月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
3月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
68 2

热门文章

最新文章