FreeRTOS手册 -- 第二章 . 堆管理

22 篇文章 0 订阅
16 篇文章 6 订阅
本文介绍了FreeRTOS的内存管理机制,包括动态内存分配的重要性以及从heap_1到heap_5的不同内存分配策略。heap_1适用于小型系统,不支持内存释放,而heap_4和heap_5通过优化减少碎片,heap_5允许从多个内存区域分配。文章还提到了内存碎片、线程安全和内存分配失败的回调函数等概念。
摘要由CSDN通过智能技术生成

如果我对本翻译内容享有所有权。允许任何人复制使用本文章,不会收取任何费用。如有平台向你收取费用与本人无任何关系

第二章. 堆管理

介绍和范围

先决条件

FreeRTOS是以C语言的文件集合提供的,因此一个合格的C程序员就是使用FreeRTOS的前提,这里也假设读者已经拥有下面的观念:

  • 一个C项目是如何被编译的,包括不同的编译和链接步骤
  • 堆和栈是什么
  • 标准C函数库的malloc()和free()函数

FreeRTOS相关的动态内存分配

自v9.0.0内核以来,对象就可以在编译时静态分配,也可以运行时动态分配;本书的后面章节会介绍任务,队列,信号量和事件组。为了让FreeRTOS能尽量使用简单,这些内核对象都不是在编译的时候静态分配,而是在运行时动态分配;每创建一个内核对象FreeRTOS就会分配相应RAM空间,每次删除对象时又会释放RAM空间。这个准则减少了设计和规划的工作,简单化接口函数,最小化RAM的占用。
本章讨论动态内部分配。动态内存分配是一个C语言的概念,而不是FreeRTOS或多任务特有的概念。但它却和FreeRTOS相关,因为内核对象是动态分配的,通常的编译器提供的动态内存分配一般都不适用于实时系统。
可以用标准C语言的malloc()和free()分配和释放内存,但可能不是很合适或者说恰当,原因如下:

  • 它们可能在小的嵌入式系统中不可用
  • 它们相关实现很大,需要很大的代码空间
  • 它们不是线程安全的
  • 它们是不确定的;不同情况下执行这个函数的时间差别很大
  • 容易引起碎片化
  • 它由编译链接器配置
  • 如果堆空间允许生长,占用其他变量空间,会导致调试错误变困难

动态内存管理选项

自V9.0.0以来就可以在编译时静态分配,也可以运行时动态分配。早期的FreeRTOS使用一个内存池进行分配调度,内存池中不同大小的内存块编译时就已经预先分配好,然后由内存分配函数返回。尽管它是实时系统的共用调度器,它用于给很多请求提供支持,然而它不能在小型嵌入式系统中足够高效的使用RAM空间,所以它被废弃了。
现在的FreeRTOS将内存分配,作为portable层的一部分(之前是属于内核层)。这是由于认识到,不同的嵌入式系统有不同动态内存分配和计时需求,一次一个单独动态内存分配算法只能适应部分程序。因此从内核的基础代码中移出了动态内存分配代码,而是让程序员选择合适的专属动态内存分配算法实现。这里我多说两句,FreeRTOS有提供几个现成的动态分配内存算法,就是heap_1.cheap_5.c这些文件,用户可以自己选择一个合适的。后面马上就会介绍它们区别,这里并不是必须用户自己写一个C语言的源文件实现这里的动态分配内存算法。
当FreeRTOS需要RAM,不需要调用malloc(),而是调用pvPortMalloc()。当准备释放时用vPortFree()代替free()。pvPortMalloc()函数像标准C语言的malloc()函数有相同的原型,vPortFree()也和free()有相同的原型。
pvPortMalloc()和vPortFree()是公用函数,因此可以在你的程序中直接调用。
如果堆中释放的RAM只有很小一块,而且它们彼此被分割开来,这样的堆就是碎片化的,到处都是碎片的可用RAM。这时就可能出现需要一个大的堆,但到处都是碎片空间,没有足够大的空间分配给这个块,即使碎片空间总的大小大于需要分配的空间,也不能顺利分配需要的空间块
FreeRTOS提供了5个pvPortMalloc()和vPortFree()的实现(heap_1.c到heap_5.c),它们都在本章介绍。用户可以直接使用其中一个,也可以自己写一个(首先你要够厉害)。这5个具体实现位于FreeRTOS/Source/portable/MemMang/目录中。

范围

这章为了教会你:

  • FreeRTOS什么时候分配RAM
  • FreeRTOS提供的5个自动分配内存算法的差异
  • 具体怎么选择内存分配方案

内存分配方案

heap_1

常用于小的嵌入式系统,只在开启调度器之前创建任务或其他内核对象。当用它时,只在程序开始处理实时函数之前,由内核动态分配内存,程序运行期间内存保持分配状态。意味着选择了这个方案就不用考虑一些更复杂的内存分配问题,比如确定性和碎片化,而是只需要考虑代码大小和简单性等属性。
heap_1.c是一个非常基础的pvPortMalloc()实现,没有实现vPortFree()。所以才会只在开启调度器之前分配,程序运行过程中都不会释放,考虑的情况相当基础。不能删除任务和其他对象。
一些商业为主或安全为主的系统可能会阻止使用动态内存分配,这时就可能会用到heap_1.c。关键系统不用动态内存分配是因为非确定性带来的不确定性,内存碎片化,和可能的分配失败。heap_1.c是确定的,而且不会使内存碎片化。
heap_1分配方案会将一个简单的数组细分为更小的块,通过调用pvPortMalloc()实现。这里的数组就是FreeRTOS的堆。
这个数组(堆)的总大小是在FreeRTOSConfig.h中由configTOTAL_HEAP_SIZE定义的。用这种方式定义一个大数组,会让程序看起来消耗很多RAM,甚至那些数组中没有被分配的内存也会被包含在这些RAM里面。
每个创建的任务都需要一个任务控制块(TCB)和从堆分配的栈。图5展示了创建新任务时,heap_1怎么样简单的细分数组。
图5包含:

  • A显示没有任务被创建之前的数组,这时数组没有使用
  • B显示创建一个任务后的情况
  • C显示创建3个任务后的情况
# 用heap_1创建任务,分配RAM的情况。图5

-------------   -------------   -------------
|     A     |   |     B     |   |     C     |
|           |   |           |   |           |
|           |   |           |   |           |
|           |   |           |   |           |
|           |   |           |   |           |
|           |   |           |   |           |
|           |   |           |   |-----------|
|           |   |           |   |  Stack    |
|           |   |           |   |   TCB     |
|           |   |           |   |-----------|
|           |   |           |   |  Stack    |
|           |   |           |   |   TCB     |
|           |   |-----------|   |-----------|
|           |   |   Stack   |   |  Stack    |
|           |   |    TCB    |   |   TCB     |
-------------   -------------   -------------

heap_2

heap_2仍然保留在发布包中是为了兼容旧的FreeRTOS程序,但新版本的已经不推荐使用。可以用heap_4替代heap_2heap_4heap_2的升级版本。
heap_4.c也是通过细分一个configTOTAL_HEAP_SIZE长度的数组。它用一个合适的算法分配内存,不像heap_1那么低效率,它允许释放内存。同样这里的待分配内存是静态声名的,所以看起来程序很费RAM空间,即使那些还没有被分配的空间也占用内存。
这里合适的算法确保pvPortMalloc()使用的空闲块空间和申请的大小最接近。比如下面情形:

  • 堆空间有3个空闲块,分别是5字节,25字节和100字节
  • pvPortMalloc().申请一个20字节的RAM空间
    这个请求可以匹配的最小空间就是那块25字节的空间,所以pvPortMalloc()会分割25字节块为20字节和5字节,然后返回20字节空间地址。新的5字节块依然是空闲状态。
    不像heap_4,heap_2不会合并邻近的空闲空间成一个大的空闲空间,因此它更容易碎片化。如果在分配后可以顺序释放,碎片化就不是问题。heap_2适用于重复的创建和删除任务,而且创建任务分配的栈大小不改变。
-------------   -------------   -------------
|     A     |   |     B     |   |     C     |
|           |   |           |   |           |
|           |   |           |   |           |
|           |   |           |   |           |
|           |   |           |   |           |
|-----------|   |-----------|   |-----------|
|    Stack  |   |    Stack  |   |  Stack    |
|     TCB   |   |     TCB   |   |   TCB     |
|-----------|   |-----------|   |-----------|
|    Stack  |   |           |   |  Stack    |
|     TCB   |   |           |   |   TCB     |
|-----------|   |-----------|   |-----------|
|    Stack  |   |   Stack   |   |  Stack    |
|     TCB   |   |    TCB    |   |   TCB     |
-------------   -------------   -------------
                 free a task

图6展示了创建,删除和再创建任务,这个算法如何工作的。图6中:

  1. A显示创建3个任务后的情况。一个大的空闲块在数组的顶部。
  2. B显示删除一个任务后的情况。哪个大的空闲空间还是在上面没变。现在就存在2个小的任务块和二个空闲任务块(一个是原来任务的TCB块,一个是原来任务的栈块。它们不会合并为一个大的空间块)。
  3. C显示再次创建一个任务块后的情况。创建一个任务会导致两次调用pvPortMalloc(),一个用于分配新的TCB,一个用于分配任务栈。任务创建使用xTaskCreate()函数,这个函数在3.4章节介绍。pvPortMalloc()会在xTaskCreate()的中调用。
    每个TCB的大小都一样,所以最佳算法确保之前分配给TCB的RAM,在任务删除后。再次创建TCB块时会再次使用这个RAM空间。
    新创建任务的堆栈大小同之前删除任务的堆栈一样,这样算法就能确保之前被删除任务分配的堆栈完全可以用于新任务的堆栈。
    那一块最大的未被使用的空闲块还是在哪里,没有改变。
    heap_2不是确定的,但它比大多数的标准C函数的malloc()和free()的实现快。

heap_3

heap_3.c使用标准库的malloc()和free()函数,所以堆的尺寸由链接器配置决定,configTOTAL_HEAP_SIZE不会对它造成影响。
heap_3.c通过短暂的暂停FreeRTOS调度器使malloc()和free()线程安全。线程安全和调度器暂停都是在第7章(资源管理)介绍。

heap_4

heap_1``heap_2类似,heap_4这是通过细分数组为更小的块。和前面一样,这里的数组是静态分配的,由configTOTAL_HEAP_SIZE决定。所以会让程序看起来好像很费内存,即使在其中很多内存没有被分配使用之前。
heap_4用第一个适当的算法分配内存。不像heap_2heap_4会将相邻的小空闲块合并成大的空闲块,这样可以减少内存的碎片化。
第一个合适的算法确保pvPortMalloc()使用第一个空闲块的内存,这个内存块只要求足够大的空间保存请求的尺寸。比如考虑下面情况:

  • 内存中有3个空闲块,分别按顺序是5字节,200字节和100字节
  • pvPortMalloc()用于请求20字节
    按顺序找可以满足请求20字节的空闲块,就是200字节哪个空闲块,所以pvPortMalloc()会将200字节块,分割为一个20字节块和一个180字节块,然后返回20字节块地址。新的180字节块对于pvPortMalloc()任然可用。
    heap_4合并相邻的空闲块为一个大空闲块,降低碎片化,让它适合程序再次分配给不同需求的情况。
# 用heap_4分配和释放内存。图7
---------  ---------  ---------  ---------  ---------  ---------
|   A   |  |   B   |  |   C   |  |   D   |  |   E   |  |       |
|       |  |       |  |       |  |       |  |       |  |       |
|       |  |       |  |       |  |       |  |       |  |       |
|       |  |       |  |       |  |       |  |       |  |       |
|       |  |       |  |       |  |       |  |       |  |       |
|-------|  |-------|  |-------|  |-------|  |-------|  |-------|
| Stack |  |  Stack|  | stack |  | Stack |  | Stack |  | Stack |
|       |  |       |  |       |  |       |  |       |  |       |
|  TCB  |  |   TCB |  |  TCB  |  |  TCB  |  |  TCB  |  |  TCB  |
|-------|  |-------|  |-------|  |-------|  |-------|  |-------|
| Stack |  |       |  |  free |  |  free |  |  free |  |  free |
|       |  |  free |  | space |  |  User |  |  User |  |       |
|  TCB  |  | space |  | Queue |  | Queue |  |  free |  | space |
|-------|  |-------|  |-------|  |-------|  |-------|  |-------|
| Stack |  | Stack |  |  Stack|  | Stack |  | Stack |  | Stack |
|       |  |       |  |       |  |       |  |       |  |       |
|  TCB  |  |  TCB  |  |   TCB |  |  TCB  |  |  TCB  |  |  TCB  |
---------  ---------  ---------  ---------  ---------  ---------

图7展示了heap_4中的算法在内存分配和释放过程中如何聚合内存的。在图7中:

  1. A展示数组中有3个已经创建的任务。和一个大的空闲空间在数组顶部。
  2. B显示的是删除一个任务后的情况。顶部的大空闲空间还是保持原样。现在留出了一个之前分配的TCB和任务栈空间。注意不像heap_2,释放TCB后空间空闲,释放任务栈后空间又空闲出来。这个时候两个空闲空间会合并为一个,而heap_2的算法不会进行合并操作。heap_4会把两个相邻的空闲空间合并为一个大的空闲空间。
  3. C显示FreeRTOS再创建一个队列后的样子。创建队列用xQueueCreate()函数,它会在4.3章节介绍。xQueueCreate()会调用pvPortMalloc()分配一块RAM给队列。因为使用heap_4的分配算法,pvPortMalloc()会从第一个足够大的空闲RAM分配空间,在图7中,就是删除任务后释放的TCB和任务堆栈合并起来的空闲空间。队列没有占满整个块,所以这块空闲空间分割为两个部分,剩下多余的空闲空间对于pvPortMalloc()依然可以使用。
  4. D显示用户程序直接调用pvPortMalloc()后的情况,这里不是有FreeRTOS内核调用的pvPortMalloc()。用户申请的空间比较小,小到C中剩余的空间都足够大,所以直接会匹配C中剩余的空闲空间。因为用户申请分配空间小,不足以占满C中剩余空间。所以这块剩余空间再次分割为2块。最后再次剩余的空间对于pvPortMalloc()还是可用的。
  5. E展示的是删除队列后的情况。队列删除后会自动释放分配给队列的空间。这时在用户申请空间上方和下方都有一块空闲空间了。
  6. F显示用户程序申请空间释放后的情况。一旦用户申请的那块空间被释放,之前的3块空间就会被合并为一块更大的空闲空间。这3块空闲空间分别为,按照D显示剩余的空闲空间,用户申请的那块空间,原来队列申请那块空间。
    heap_4是不确定的,但比标准C的malloc()和free()要快。

使用heap_4时设置起始地址

这一节包含高级信息。只是简单使用heap_4可以不用了解。
有时候程序需要使用heap_4在特定的内存地址写入数据。比如栈分配给FreeRTOS任务的空间在堆上,可能需要确保堆的位置是在高速的内部内存,而不是低速的外部内存。
默认情况下,heap_4使用的数组是在heap_4.c源文件中声名,它的起始地址自动有链接器设定。但如果FreeRTOSConfig.h中configAPPLICATION_ALLOC_HEAP宏在编译时设置为1,那么这个数组就需要使用FreeRTOS的程序定义。如果这个数组定义是程序的一部分,那么程序开发者就可以设定它的起始地址。
如果FreeRTOSConfig.h中的configAPPLICATION_ALLOC_HEAP设置为1,就需要在程序的源码文件中包括一个configTOTAL_HEAP_SIZE宏,指定ucHeap数组的长度。
放置特定内存地址的变量语法需要依赖于具体使用的编译器,因此需要查阅你使用编译器的手册。下面是两个编译器的例子:

  • 列表2显示了GCC编译器申明数组的语法,并且这个数组会被放在.my_heap的节点
  • 列表3显示了IAR编译器申明数组的语法,会将数组放在绝对地址0x20000000处
// GCC编译器申明数组的语法,这会被用于heap_4的数组声名。列表2
uint8_t ucheap[configTOTAL_HEAP_SIZE] __attribute((section(".my_heap")));
// IAR编译器数组申明。列表3
uint8_t ucHeap[configTOTAL_HEAP_SIZE]  @  0x20000000

heap_5

heap_5的算法分配和释放内存和heap_4相同。但heap_5使用的数组不限于只能在编译阶段静态分配;heap_5可以从多个和分开的空间分配内存。当在使用FreeRTOS系统时,没有一个单独连续的空间块给系统使用时heap_5就很有用。
在写本书的时候heap_5只提供了分配方案,而且显示的在调用pvPortMalloc()之前初始化。heap_5使用vPortDefineHeapRegions()函数初始化。当使用heap_5时,必须在创建任何内核对象(任务,队列,信号量等)之前调用vPortDefineHeapRegions()进行初始化。

vPortDefineHeapRegions()函数

vPortDefineHeapRegions()函数用来指定每个分开内存的开始地址和尺寸大小,这些不同的内存组合起来成为一个总的内存给heap_5使用。

// vPortDefineHeapRegions()函数原型。列表4
void vPortDefineHeapRegions(const HeapRegion_t * const pxHeapRegions);

每个单独的内存区域都用一个HeapRegion_t的结构体。所有可用内存区域的描述都通过传递给vPortDefineHeapRegions()作为一个HeapRegion_t的数组。

// HeapRegion_t结构体
typedef_t struct HeapRegion {
    /* 内存块的起始地址,它将会成为堆的一部分 */
    uint8_t *pucStartAdress;
    /* 用字节表示的内存块大小 */
    size_t xSizeInByte;
}HeapRegion_t;
# vPortDefineHeapRegions()的参数。表5
pxHeapRegions: 一个指向HeapRegion_t结构的数组指针。每个这个数组里面的结构体都描述了内存块的起始地址和内存长度,这个内存结构就是`heap_5`会用到的内存的一部分。
               这里数组中的HeapRegion_t结构必须按照起始地址排序;这个数组中起始地址最低的HeapRegion_t结构必须是这个数组中的第一个,数组中起始地址最高的HeapRegion_t结构必须是数组中的最后一个。
               数组结尾的哪个HeapRegion_t结构体的pucStartAdress成员需要设置为NULL。

用实例的方式考虑一个假设内存,展示在图8A中,其中包含3块单独的内存:RAM1,RAM2,RAM3。假设执行代码放在一块只读的内存,它没有展示出来。

# 图8,内存映射

      A                         B                       C
-------------0xffffffff  -------------0xffffffff  -------------0xffffffff
|           |            |           |            |           |
|           |            |           |            |           |
|           |            |           |            |           |
|           |            |           |            |           |
|           |            |           |            |           |
|-----------|0x037fff    |-----------|0x037fff    |-----------|0x037fff
|    RAM3   |            |    RAM3   |            |    RAM3   |
|   32字节  |            |   32字节  |            |   32字节  |
|-----------|0x030000    |-----------|0x030000    |-----------|0x030000
|           |            |           |            |           |
|-----------|0x027fff    |-----------|0x027fff    |-----------|0x027fff
|   RAM2    |            |    RAM2   |            |    RAMW2  |
|  32字节   |            |   32字节  |            |   32字节  |
|-----------|0x020000    |-----------|0x020000    |-----------|0x020000
|           |            |           |            |           |
|-----------|0x01ffff    |-----------|0x01ffff    |-----------|0x01ffff
|   RAM1    |            |  RAM1空闲 |            |    RAM1   |
|           |            |           |            |  中包含   |
|           |            |  RAM1中   |            |  变量,如 |
|  65字节   |            |  包含变量 |            |  ucHeap[] |
|-----------|0x010000    |-----------|0x010000    |-----------|0x010000
|           |            |           |            |           |
|-----------|0x000000    |-----------|0x000000    |-----------|0x000000

列表6展示了一个HeapRegion_t结构数组,它就描述了图8中的3个内存区域映射。

// 描述图8的3个HeapRegion_t内存区域结构数组。列表6

/* 定义3个内存区域和大小
#define RAM1_START_ADDRESS      ((uint8_t *) 0x00010000)
#define RAM1_SIZE               (65 * 1024)
#define RAM2_START_ADDRESS      ((uint8_t *) 0x00020000)
#define RAM2_SIZE               (32 * 1024)
#define RAM3_START_ADDRESS      ((uint8_t *) 0x00030000)
#define RAM3_SIZE               (32 * 1024)
/* 创建一个HeapRegion_t数组,每个索性元素代表3个内存区域中的一个,数组的结尾用NULL结束。HeapRegion_t结构数组最低起始地址需要是第一个,最高起始地址需要是最后一个*/
const HeapRegion_t xHeapRegions[] = {
    {RAM1_START_ADDRESS, RAM1_SIZE},
    {RAM2_START_ADDRESS, RAM2_SIZE},
    {RAM3_START_ADDRESS, RAM3_SIZE},
    {NULL,               0        }
};

int main(void){
    /* 初始化heap_5的数组*/
    vPortDefineHeapRegions(xHeapRegions);

    /* 这里加程序代码 */
}

列表6中正确描述了一个内存RAM,但不意味着它是一个可用的例子,因为它将所有的RAM分配为堆,没有给其他变量留下空间。
当构建一个项目时,编译的链接阶段会为每个变量分配RAM地址。链接器给用户分配的可用RAM通常使用一个链接配置文件表示,比如一个连接器脚本。在图8B,假设链接器脚本包含RAM1的信息,但没有RAM2,RAM3的信息。链接器就会将变量放进RAM1,只是让RAM1的内存部分(地址0x0001nnnn)可以被heap_5使用。这里的0x0001nnnn的具体值依赖于被链接的所有变量的合并值。连接器留下RAM2和RAM3没有使用,让整个RAM2和整个RAM3对heap_5都不可用。
如果使用列表6中的代码,分配给heap_5的0x0001nnnn地址空间会覆盖用于保存变量的地址空间。为了防止这种情况,xHeapRegions[]中的第一个HeapRegion_t结构应该使用的起始地址应当是0x0001nnnn,而不是01x00010000。但不推荐这么做,因为:

  1. 起始地址可能很难确定
  2. 连接器使用的RAM数量在实际编译时可能会改变,需要更新HeapRegion_t的起始地址
  3. 编译工具是未知的,不能通知程序员具体使用的是链接器还是被heap_5覆盖使用的RAM空间
    列表7展示了一个更适用和方便维护的例子。它声名了一个叫ucHeap的变量。ucHeap是一个普通的变量,因此连接器会在RAM1中为它分配一块区域。xHeapRegions数组中的第一个HeapRegion_t结构描述了起始地址和ucHeap的大小,这样ucHeap成为由heap_5管理的一部分内存。ucHeap可以增加到整个内存,就像图8C中那样。
// 一个数组HeapRegion_t结构,描述整个RAM2和整个RAM3。但只用RAM1。列表7
/* 定义2和RAM区域的起始地址和大小,但不被链接器使用*/
#define RAM2_START_ADDRESS      ((uint8_t *) 0x00020000)
#define RAM2_SIZE               (32 * 1024)
#define RAM3_START_ADDRESS      ((uint8_t *) 0x00030000)
#define RAM3_SIZE               (32 * 1024)
/* 定义一个会被heap_5管理的堆部分。这个数组会被链接器放在RAM1中 */
#define RAM1_HEAP_SIZE      (30*1024)
static uint8_t ucHeap[RAM1_HEAP_SIZE]; 

/* 创建一个HeapRegion_t结构体数组。列表6中第一个进入点是RAM1整个区域,所以heap_5使用整个RAM1,这次第一个进入点只有ucHeap数组,所以heap_5只用RAM1中ucHeap数组的哪个部分。因为HeapRegion_t结构体数组的起始地址必须排序,所以最低的起始地址必须排在第一个 */
const HeapRegion_t xHeapRegions[] = {
    {ucHeap            , RAM1_HEAP_SIZE},
    {RAM2_START_ADDRESS, RAM2_SIZE},
    {RAM3_START_ADDRESS, RAM3_SIZE},
    {NULL,               0        }
};

下面是列表7中技术的优点:

  1. 不需要使用一个硬件代码起始地址
  2. HeapRegion_t结构体中使用的地址会由链接器自动设置,因此不会出错,即使以后连接器编译时RAM数量改变
  3. 不可能出现连接器分配给heap_5的RAM覆盖RAM1中数据
  4. 如果ucHeap太大,程序就不会链接了

堆相关工具函数

xPortGetFreeHeapSize()函数

调用xPortGetFreeHeapSize()会返回堆中剩余的空闲空间大小。可以用它来优化堆大小。比如,如果内核完成所有对象创建后xPortGetFreeHeapSize()返回2000,那么configTOTAL_HEAP_SIZE就可以减少2000。
当用heap_3管理堆空间时不能使用xPortGetFreeHeapSize()。

// xPortGetFreeHeapSize()函数原型
size_t xPortGetFreeHeapSize(void);

/* 返回值:
 * 在调用xPortGetFreeHeapSize()时堆中还没有被分配出去的空间的字节数量
 */

xPortGetMinimumEverFreeHeapSize()函数

xPortGetMinimumEverFreeHeapSize()返回自FreeRTOS程序开始执行以来,没有被分配使用的堆空闲空间的最小字节数量。
xPortGetMinimumEverFreeHeapSize()返回值表示程序运行过程中使用的堆空间离堆的最大空间有多近。比如,如果xPortGetMinimumEverFreeHeapSize()返回200,那么就表示自程序执行以来,使用堆最多时还差200字节就将所有的堆空间占满。
只有在使用heap_4,heap_5管理堆空间时才可以使用xPortGetMinimumEverFreeHeapSize()函数。

// xPortGetMinimumEverFreeHeapSize()原型。列表7
size_t xPortGetMinimumEverFreeHeapSize(void);
/* 返回值:
 * 自FreeRTOS程序开始执行以来,堆空间空间的最小内存字节数
 */

自动分配失败勾子函数

pvPortMalloc()可以在用户空间直接调用。也可以在每次创建内核对象时由FreeRTOS源码调用。内核对象包括任务,队列,信号量和事件组。它们都会在后面的章节介绍。
就像标准函数库的malloc(),如果因为请求尺寸的内存块不存在,使pvPortMalloc()不能返回一个内存块,那么它就会返回NULL。如果因为创建内核对象调用pvPortMalloc()或用户直接调用pvPortMalloc()返回NULL,那么这个内核对象就不能成功创建。
所有使用堆自动分配的方案都可以设置一个pvPortMalloc()返回NULL时的回调函数。
如果将FreeRTOSConfig.h中的configUSE_MALLOC_FAILED_HOOK设置为1,那么程序就要提供一个自动分配失败回调函数,它的名字和原型展示在列表10中。这个函数可以用任何适合本程序的方式实现。

// 自动分配失败回调函数名字和原型。列表10
void vApplicationMallocFailedHook(void);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值