FreeRTOS--堆内存管理
因为项目需要,最近开始学习FreeRTOS,一开始有些紧张,因为两个星期之前对于FreeRTOS的熟悉度几乎为零,经过对FreeRTOS官网的例子程序的摸索,和项目中问题的解决,遇到了很多熟悉的身影,以前在Linux平台编程的经历给了我一些十分有用的经验,后悔当初没能在第一家公司待下去,浪费了大好时光。好吧,现在还是潜下心来搞搞FreeRTOS吧。
后续都是一系列FreeRTOS相关的随笔,先把FreeRTOS“圣经”--Mastering the FreeRTOS Real Time kernel -- A Hands On Tutorial Guide 20161204好好研读,接连的几个随笔都是我从这本“圣经”中翻译出来的。翻译难免有所疏漏、词不达意,大家凑合着看吧。
从FreeRTOS V9.0.0开始FreeRTOS应用程序可以完全用静态分配内存,而没有必要引入堆内存管理。
章节引言和范围
前提
FreeRTOS是以C源文件的形式提供的,因此成为一名合格的C语言编程人员是使用FreeRTOS的必要条件,因而这个章节假定读者熟悉以下概念:
- C语言项目是如何构建的,包含不同的编译和链接过程
- 堆和栈分别是什么
- 标准C库的
malloc()和free()函数
动态内存分配以及它和FreeRTOS的关系
从FreeRTOS V9.0.0开始内核对象既可以在编译的时候静态分配,也可以在运行时动态分配。本书随后的章节将会介绍以下内核对象:tasks, queues, semaphores 和 event groups。为了尽可能让FreeRTOS易于使用,这些内核对象并不是在编译时静态分配的,而是在运行时动态分配的。内核对象创建时FreeRTOS分配RAM而在内核对象删除时释放内存。这样的策略减少了设计和计划上的努力,简化了API,并且减少了RAM的占用。
动态内存分配是C语言编程的概念,而不是针对FreeRTOS或者多任务编程的概念。它和FreeRTOS是相关的,因为内核对象是动态分配的,并且通用编译器提供的动态内存分配方案对于实时应用程序并不总是适合的。
内存可以使用标准C库的malloc()和free()函数来分配,但有可能不适合,或者恰当,因为下几点原因:
- 在小型嵌入式系统中并不总是可用的
- 它们的实现可能非常的大,占据了相当大的一块代码空间
- 他们几乎都不是线程安全的
- 它们并不是确定的,每次调用这些函数执行的时间可能都不一样
- 它们有可能产生碎片
- 它们有可能打乱链接器的配置
- 如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为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.c 和 heap_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 最先适应算法如何拼接内存,同样也演示了内存的分配和释放:

A演示了创建3个任务之后的数组的样子,一大块空的块存在于数组的顶端。B演示了删除1个任务之后的数组,一大块空的块存在于数组的顶端。被删除的那个任务占据的TCB和栈存储空间现在是空的,并且它们拼接成一个大的空的块。C演示了FreeRTOS创建了一个Queue。队列是通过xQueueCreate()API 创建的,它是调用pvPortMalloc()来分配存储空间的。由于heap_4采用最先适应算法,pvportMalloc()将会使用第一块大的足够容纳队列的RAM块来分配,在Figure 7中就采用之前删除任务的那一块。然而队列并不完全消耗那个空闲的区块,所以那个RAM块会分成两个部分,未使用的部分将会由后续的pvPortMalloc()占用。D演示了应用程序直接调用pvPortMalloc()而不是间接地由FreeRTOS API调用之后的情形。用户分配的区块足够小,能够放在第一个空闲的区块中,这个区块就是队列占用的区块和后面的TCB占用的区块之间的那一块。
删除任务释放的内存,现在被分割成3个区块,第一个区块是队列,第二个区块是用户分配的,第三个区块还是空的。E演示了队列删除之后,存储空间也自动释放了。现在用户分配的区块两边都是空闲区块。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,那么应用程序源文件中必须声明一个名字为ucHeap的uint8_t类型的数组,它的大小有configTOTAL_HEAP_SIZE设定。
把变量放在某个内存地址的语法取决于使用了哪种编译器,下面演示了两种编译器的用法:
- Listing 2演示的是GCC编译器声明数组并把数组放在名字为
.my_heap的段中。 - 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
本博客的文章如无特殊说明,均为原创,转载请注明出处。如未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
FreeRTOS--堆内存管理的更多相关文章
- FreeRTOS 动态内存管理
以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 动态内存管理,动态内存管理是 FreeRTOS 非常重要的一项功能,前面 ...
- Linux堆内存管理深入分析(下)
Linux堆内存管理深入分析 (下半部) 作者@走位,阿里聚安全 0 前言回顾 在上一篇文章中(链接见文章底部),详细介绍了堆内存管理中涉及到的基本概念以及相互关系,同时也着重介绍了堆中chunk分 ...
- Linux堆内存管理深入分析(上)
Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全 0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞 ...
- Linux堆内存管理深入分析
(上半部) 作者:走位@阿里聚安全 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞利用两种.国内关于栈溢出的资料相对较 ...
- C语言堆内存管理上出现的问题,内存泄露,野指针使用,非法释放指针
C语言堆内存管理上出现的问题,内存泄露,野指针使用,非法释放指针 (1)开辟的内存没有释放,造成内存泄露 (2)野指针被使用或释放 (3)非法释放指针 (1)开辟的内存没有释放.造成内存泄露,以下的样 ...
- 轻量级操作系统FreeRTOS的内存管理机制(一)
本文由嵌入式企鹅圈原创团队成员朱衡德(Hunter_Zhu)供稿. 近几年来,FreeRTOS在嵌入式操作系统排行榜中一直位居前列,作为开源的嵌入式操作系统之一,它支持许多不同架构的处理器以及多种编译 ...
- Linux堆内存管理深入分析 (上半部)【转】
转自:http://www.cnblogs.com/alisecurity/p/5486458.html Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全 0 前言 近年来,漏洞挖掘越来 ...
- FreeRTOS的内存管理
FreeRTOS提供了几个内存堆管理方案,有复杂的也有简单的.其中最简单的管理策略也能满足很多应用的要求,比如对安全要求高的应用,这些应用根本不允许动态内存分配的. FreeRTOS也允许你自己实现内 ...
- Linux C 堆内存管理函数malloc()、calloc()、realloc()、free()详解
C 编程中,经常需要操作的内存可分为下面几个类别: 堆栈区(stack):由编译器自动分配与释放,存放函数的参数值,局部变量,临时变量等等,它们获取的方式都是由编译器自动执行的 堆区(heap):一般 ...
- 栈 & 堆 |--> 内存管理
内存管理: 栈区 [stack]:由编译器自动分配并释放,一般存放函数的参数值,局部变量等 堆区 [heap]:由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 全局区(静态区) ...
随机推荐
- SSM框架+slf4j 以Gradle实现
环境:win10+jdk8+tomcat9+Intellij IDEA 首先,作为一个喜欢偷懒的人,管理jar之类的的事情太累,所以用了Gradle项目管理器 第一步: 新建一个gradle-web项 ...
- 启动关闭zookeeper集群的脚本
启动hadoop Ha集群是,每次都要手动启动每个zk节点,实在是太麻烦了.于是乎自己写了个脚本startAllZK.sh: 需要启动的节点,先在 NODENAME_ARR 数组中配置好,zkServ ...
- javascript的一些算法的实用小技巧
一.交换两个数字的值 我们交换两个数字的值想到的方法一般就是用一个新的变变量,让他把一个数存起来,然后在交换两个数字的值,看下面这种. var a = 1, b = 2; //交换两个数字的值 var ...
- javascript 之作用域链-07
复习作用域 上一节我们说到作用域:是指变量可以访问的范围,他规定了如何查找变量,以及确定当前执行代码对变量的访问权限:也说到静态作用域即词法作用域,是在编译阶段决定变量的引用(由程序定义的位置决定,和 ...
- 暑假练习赛 006 A Vanya and Food Processor(模拟)
Description Vanya smashes potato in a vertical food processor. At each moment of time the height of ...
- two.js之实现动画效果
一.什么是two.js? Two.js 是面向现代 Web 浏览器的一个二维绘图 API.Two.js 可以用于多个场合:SVG,Canvas 和 WebGL,旨在使平面形状和动画的创建更方便,更简洁 ...
- ASP.NET Core的身份认证框架IdentityServer4(9)-使用OpenID Connect添加用户认证
OpenID Connect OpenID Connect 1.0是OAuth 2.0协议之上的一个简单的身份层. 它允许客户端基于授权服务器执行的身份验证来验证最终用户的身份,以及以可互操作和类似R ...
- HTML5与phonegap接口对比
HTML5与phonegap接口对比 接口 HTML5 phonegap 差异 地理定位 geolocation 单次定位: navigator.geolocation.getCurrentPosit ...
- JAVAscript学习笔记 jsBOM 第七节 (原创) 参考js使用表
<html> <head> <title>day02_js</title> <script type="text/javascript& ...
- SpringMVC知识一锅烩
Spring简介 SpringMVC和Struts2一样都是属于表现层的框架,将前段发出的请求分发给对应的后端处理器即Controller 处理流程 用户请求被前端控制前拦截,然后根据对应的拦截路径去 ...