FreeRTOS--堆内存管理

由于项目须要,最近开始学习FreeRTOS,一开始有些紧张,由于两个星期以前对于FreeRTOS的熟悉度几乎为零,通过对FreeRTOS官网的例子程序的摸索,和项目中问题的解决,遇到了不少熟悉的身影,之前在Linux平台编程的经历给了我一些十分有用的经验,后悔当初没能在第一家公司待下去,浪费了大好时光。好吧,如今仍是潜下心来搞搞FreeRTOS吧。html

后续都是一系列FreeRTOS相关的随笔,先把FreeRTOS“圣经”--Mastering the FreeRTOS Real Time kernel -- A Hands On Tutorial Guide 20161204好好研读,接连的几个随笔都是我从这本“圣经”中翻译出来的。翻译不免有所疏漏、词不达意,你们凑合着看吧。算法

从FreeRTOS V9.0.0开始FreeRTOS应用程序能够彻底用静态分配内存,而没有必要引入堆内存管理。编程

章节引言和范围

前提

FreeRTOS是以C源文件的形式提供的,所以成为一名合格的C语言编程人员是使用FreeRTOS的必要条件,于是这个章节假定读者熟悉如下概念:api

  • C语言项目是如何构建的,包含不一样的编译和连接过程
  • 堆和栈分别是什么
  • 标准C库的malloc()free()函数

动态内存分配以及它和FreeRTOS的关系

从FreeRTOS V9.0.0开始内核对象既能够在编译的时候静态分配,也能够在运行时动态分配。本书随后的章节将会介绍如下内核对象:tasks, queues, semaphoresevent groups。为了尽量让FreeRTOS易于使用,这些内核对象并非在编译时静态分配的,而是在运行时动态分配的。内核对象建立时FreeRTOS分配RAM而在内核对象删除时释放内存。这样的策略减小了设计和计划上的努力,简化了API,而且减小了RAM的占用。数组

动态内存分配是C语言编程的概念,而不是针对FreeRTOS或者多任务编程的概念。它和FreeRTOS是相关的,由于内核对象是动态分配的,而且通用编译器提供的动态内存分配方案对于实时应用程序并不老是适合的。安全

内存可使用标准C库的malloc()free()函数来分配,但有可能不适合,或者恰当,由于下几点缘由:ide

  • 在小型嵌入式系统中并不老是可用的
  • 它们的实现可能很是的大,占据了至关大的一块代码空间
  • 他们几乎都不是线程安全的
  • 它们并非肯定的,每次调用这些函数执行的时间可能都不同
  • 它们有可能产生碎片
  • 它们有可能打乱连接器的配置
  • 若是容许堆空间的生长方向覆盖其余变量占据的内存,它们会成为debug的灾难

动态内存分配的可选项

从FreeRTOS V9.0.0开始内核对象既能够在编译时静态分配也能够在运行时动态分配。现在FreeRTOS把内存分配放在可移植层。这是认识到不一样的嵌入式操做有不一样的动态内存管理方法和时间要求,所以单个的动态内存分配算法将只适合于应用程序的一个子集。一样,从核心代码库中移除动态内存分配使得应用程序编写者提供本身的特定的实现,若是适合的话。函数

当FreeRTOS须要RAM的时候,并非调用malloc(),而是调用pvPortMalloc()。当须要释放RAM的时候,并非调用free(),而是调用vPortFree()pvPortMalloc()和标准C库的malloc()有一样的函数原型,vPortFree()和标准C库的free()有一样的函数原型。学习

pvPortMalloc()vPortFree()都是公共函数,所以可以被应用代码调用。优化

FreeRTOS对于pvPortMalloc()vPortFree()提供了5种实现,后续章节会讲到。FreeRTOS应用程序可使用其中的一种,或者使用本身的实现。5种实现分别在heap_1.c, heap_2.c, heap_3.c, heap_4.cheap_5.c文件中,都存在于文件夹 FreeRTOS/Source/portable/MemMang 下。

范围

本章节致力于让读者深刻理解:

  • FreeRTOS什么时候分配RAM
  • FreeRTOS 提供的5种内存分配方案
  • 选用哪种内存分配方案

内存分配方案示例

Heap_4 (其余几种暂不去了解)

heap_1, heap_2 同样,heap_4也是把数组切割成更小的块。和前面同样,数组是静态声明的,由宏configTOTAL_HEAP_SIZE指定大小,因此这就使得即使数组中的内存尚未被分配出去就让应用程序显得消耗了大量的RAM。

Heap_4使用了最早适应算法来分配内存。和heap_2不一样,heap_4把临近的空闲的存储空间拼凑成一个更大的内存块,这就减小了内存碎片化的风险。

最早适应算法确保了pvPortMalloc()使用第一块空闲的足够大的内存来知足要申请的字节数。考虑下面的情景:

  • 堆里有3块空闲内存块,它们的大小分别是5个字节,200个字节,100个字节
  • 调用pvPortMalloc()来申请20个字节的RAM
    知足字节数要求的第一块空闲RAM块是200个字节的RAM块,所以pvPortMalloc()把大小为200个字节的RAM块分割成两块,一块是20个字节,一块是180个字节,然会返回一个指向20个字节的指针。新的180个字节大小的RAM块将在后续的pvPortMalloc()调用中可用。

Figure 7 演示了 heap_4 最早适应算法如何拼接内存,一样也演示了内存的分配和释放:

Figure 7

  1. A演示了建立3个任务以后的数组的样子,一大块空的块存在于数组的顶端。
  2. B演示了删除1个任务以后的数组,一大块空的块存在于数组的顶端。被删除的那个任务占据的TCB和栈存储空间如今是空的,而且它们拼接成一个大的空的块。
  3. C演示了FreeRTOS建立了一个Queue。队列是经过xQueueCreate() API 建立的,它是调用pvPortMalloc() 来分配存储空间的。因为heap_4采用最早适应算法,pvportMalloc()将会使用第一块大的足够容纳队列的RAM块来分配,在Figure 7中就采用以前删除任务的那一块。然而队列并不彻底消耗那个空闲的区块,因此那个RAM块会分红两个部分,未使用的部分将会由后续的pvPortMalloc()占用。
  4. D演示了应用程序直接调用pvPortMalloc()而不是间接地由FreeRTOS API调用以后的情形。用户分配的区块足够小,可以放在第一个空闲的区块中,这个区块就是队列占用的区块和后面的TCB占用的区块之间的那一块。
    删除任务释放的内存,如今被分割成3个区块,第一个区块是队列,第二个区块是用户分配的,第三个区块仍是空的。
  5. E 演示了队列删除以后,存储空间也自动释放了。如今用户分配的区块两边都是空闲区块。
  6. F 演示了用户分配的存储空间释放的情形。这个区块如今和两边的空闲区块拼接成了一个更大的空闲区块。

Heap_4并非肯定性的,可是要比标准库函数实现的malloc()free()运行的更快。

设定Heap_4数组的起始地址

此章节包含更高阶的信息,仅仅为了使用Heap_4是没有必要阅读和理解此章节的。

某些时候应用程序开发者须要指定heap_4数组的起始地址位于某个特定的内存。例如,FreeRTOS 任务的栈是从堆中分配的,就有可能有必要保证堆是分配在快速的内存中,而不是慢速的外存。

默认状况下,heap_4数组是在heap_4.c源文件中声明的,它的起始地址是由连接器自动肯定的。然而,若是在文件FreeRTOSConfig.h中把编译时配置选项configAPPLICATION_ALLOCATED_HEAP设为常量1,那么数组必须由使用FreeRTOS的应用声明。若是把数组声明为应用的一部分,那么应用编写者能够指定数组的起始地址。

若是把文件FreeRTOSConfig.h中的configAPPLICATION_ALLOCATED_HEAP设定为1,那么应用程序源文件中必须声明一个名字为ucHeapuint8_t类型的数组,它的大小有configTOTAL_HEAP_SIZE设定。

把变量放在某个内存地址的语法取决于使用了哪一种编译器,下面演示了两种编译器的用法:

  1. Listing 2演示的是GCC编译器声明数组并把数组放在名字为.my_heap的段中。
  2. Listing 3演示的是IAR编译器把数组放在内存绝对地址0x20000000上。

uint8_t ucHeap [configTOTAL_HEAP_SIZE] attribute (( section(".my_heap") ));

Listing 2

uint8_t ucHeap [configTOTAL_HEAP_SIZE] @ 0x20000000;

Listing 3

和堆相关的实用函数

xPortGetFreeHeapSize() API

这个函数能够获取调用时堆中空闲内存的大小,以字节为单位。使用它能够优化堆的大小。例如,当内核对象都建立完毕后调用xPortGetFreeHeapSize()返回2000,那么能够把configTOTAL_HEAP_SIZE减少2000.

须要注意,当使用heap_3时是不能调用这个函数的。

xPortGetMinimumEverFreeHeapSize() API

此函数返回FreeRTOS应用程序开始运行以后曾经存在的最小的未被分配的存储空间的字节数。它的返回值指示了应用程序离将要耗尽堆空间的接近程度。例如xPortGetMinimunEverFreeHeapSize()返回200个字节,那么从应用程序开始运行以后的某个时间,在使用200个字节就会把堆空间用完。

须要注意,xPortGetMinimumEverFreeHeapSize()只在使用heap_4或者heap_5时生效。

Malloc 失败钩子函数

应用程序能够直接调用pvPortMalloc()。固然在FreeRTOS源文件中每当内核对象建立时也会调用这个函数。此类的内核对象包括任务,队列,信号量和事件组。

和标准库函数malloc()同样,若是pvPortMalloc()由于申请RAM的大小不能知足没能返回一块RAM空间就会返回NULL。若是编程人员调用pvPortMalloc()来建立内核对象,可是返回NULL就说明内核对象没有建立成功。

例子中的全部堆分配方案均可以给pvPortMalloc()配置一个钩子函数(也称做回调函数),当pvPortMalloc()返回NULL时调用这个钩子函数。

若是文件FreeRTOSConfig.h中的configUSE_MALLOC_FAILED_HOOK设置为1,那么应用程序必须提供一个内存分配失败时的钩子函数,它的名字和原型参见以下。只要对这个应用来讲是合适的,这个钩子函数能够用任何方法来实现。

void vApplicationMallocFailedHook( void );

声明

欢迎转载,请注明出处和做者,同时保留声明。 做者:LinTeX9527 出处:http://www.cnblogs.com/LinTeX9527/p/8007541.html 本博客的文章如无特殊说明,均为原创,转载请注明出处。如未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。

相关文章
相关标签/搜索