0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

嵌入式操作系统FreeRTOS内存如何管理和堆

黄工的嵌入式技术圈 来源:黄工的嵌入式技术圈 作者:黄工的嵌入式技术 2020-01-10 15:17 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

编辑:黄工 公众号:strongerHuang 素材来源:FreeRTOS网站 + 网络 前两年FreeRTOS被亚马逊收购之后,变化不大,应该是在规划物联网这一块。 前段时间FreeRTOS官网的界面发生了变化,接下来可能会有大的动作,感兴趣的朋友可以去看一下。 网址:

https://www.freertos.org

回读之前文章:谈谈FreeRTOS_V10版本FreeRTOS更新至V10.2.1 言归正传,回来说FreeRTOS的内存管理和堆的问题。从 V9.0.0 开始,FreeRTOS 应用程序可以完全静态分配,这意味着无需包含堆内存管理器。 FreeRTOS内存管理地址:
https://www.freertos.org/a00111.html 一、拓展知识动态内存分配及其与 FreeRTOS 的关联性:要让 FreeRTOS 对象(如任务、队列、信号量和事件组)变得尽可能易于使用,这些内核对象不是在编译时静态分配,而是在运行时动态分配。每次创建内核对象时,FreeRTOS 都会分配 RAM

每次删除内核对象时,都会释放 RAM。此策略可减少设计和规划工作量,简化了 API,并最大程度地减少 RAM 开销。

动态内存分配是一个 C 语言编程概念。它不是特定于 FreeRTOS 或多任务处理的概念。它与 FreeRTOS 相关,因为内核对象是动态分配的,通用编译器提供的动态内存分配方案并非始终适合实时应用程序。

可以使用标准 C 语言库函数 malloc() 和 free() 分配内存,但由于以下一个或多个原因,这些函数可能不适合或不适用:

•它们并非始终适用于小型嵌入式系统。•它们的实现可能相当大,占用宝贵的代码空间。•它们极少是线程安全的。•它们不是确定性的。执行函数所花的时间量将因调用不同而异。•它们可能会碎片化。如果堆中的可用 RAM 被拆分为若干较小且彼此分离的块,则认为堆已碎片化。当堆已碎片化时,如果堆中没有一个可用块的大小足以包含某个数据块,则尝试分配此数据块时将失败,即使堆中所有单独块的总大小比无法分配的数据块大小大很多倍,也是如此。•它们可能使链接器配置变得更复杂。•如果允许堆空间增长而占用了其他变量使用的内存,则这些函数可能成为一些难以调试的错误的来源。 二、动态内存分配的选项

早期版本的 FreeRTOS 使用内存池分配方案,在编译时预分配由不同大小的内存块组成的池,然后由内存分配函数返回。虽然这是实时系统中常用的方案,但它产生了大量支持请求。由于该方案使用 RAM 的效率无法满足非常小型的嵌入式系统的需要,因此已被放弃。

FreeRTOS 现在将内存分配视为可移植层的一部分(而不是核心代码库的一部分)。这样做的原因是我们已认识到嵌入式系统具有变化多端的动态内存分配和计时要求。单个动态内存分配算法仅适用于一部分应用程序。此外,通过从核心代码库中删除动态内存分配,应用程序编写者可以适时提供其自己的特定实现。

当 FreeRTOS 需要 RAM 时,它调用 pvPortMalloc() 而不是 malloc()。释放 RAM 时,内核调用 vPortFree(),而不是 free()。pvPortMalloc() 与标准 C 语言库函数 malloc() 具有相同的原型。vPortFree() 与标准 C 语言库函数 free() 具有相同的原型。

pvPortMalloc() 和 vPortFree() 是公共函数,因此,也可以从应用程序代码中调用它们。

FreeRTOS 附带了 pvPortMalloc() 和 vPortFree() 的五个示例实现,本指南将一一介绍。FreeRTOS 应用程序可以使用其中一个示例实现或提供自己的实现。

这五个示例在 heap_1.c、heap_2.c、heap_3.c、heap_4.c 和 heap_5.c 源文件中定义,这些源文件位于FreeRTOS/Source/portable/MemMang 目录中。

三、5种内存分配实现

heap_1:最简单,不允许释放内存。

heap_2:允许释放内存,但不能合并相邻的空闲块。

heap_3:简单包装标准的malloc()和free()以确保线程安全。

heap_4:合并相邻的空闲块以避免碎片。包括绝对地址放置选项。

heap_5:按照heap_4,具有跨多个不相邻的内存区域扩展堆的能力。

Heap_1

它常用于小型专用嵌入式系统,以便仅在启动计划程序之前创建任务和其他内核对象。在应用程序开始执行任何实时功能之前,内核动态分配内存,并且内存在应用程序的生命周期内保持已分配状态。这意味着所选分配方案不必考虑任何更复杂的内存分配问题(例如确定性和碎片化),而是可以考虑如代码大小和简单性等属性。

Heap_1.c 实现 pvPortMalloc() 的一个非常基本的版本。它不实现 vPortFree()。从不删除任务或其他内核对象的应用程序可以使用 heap_1。

某些商业关键和安全关键型系统可能禁止动态内存分配,这些系统也可能能够使用 heap_1。由于存在与非确定性、内存碎片化以及分配失败等相关的不确定性,因此,关键系统通常禁止动态内存分配,但 heap_1 始终是确定性的且无法对内存进行碎片化。

当调用 pvPortMalloc() 时,heap_1 分配方案将一个简单的数组细分成更小的块。此数组称为 FreeRTOS堆。

数组的总大小(以字节为单位)由定义 configTOTAL_HEAP_SIZE 在 FreeRTOSConfig.h 中设置。以这种方式定义大型数组可能让应用程序看起来会消耗大量 RAM,甚至从数组中分配任何内存之前就是如此。

每个创建的任务都要求从堆中分配一个任务控制块 (TCB) 和一个堆栈。

下图显示了在创建任务时 heap_1 如何细分简单的数组。每次创建任务时,都会从 heap_1 数组分配 RAM。

A 显示创建任何任务之前的数组。整个数组都可用。

B 显示已创建一个任务后的数组。

C 显示已创建三个任务后的数组。

Heap_2

Heap_2 包含在 FreeRTOS 发行版中以保持向后兼容性。建议不要用于新设计,而是考虑使用 heap_4,因为其中提供了更多功能。

也可以使用 Heap_2.c,但要细分由 configTOTAL_HEAP_SIZE 确定大小的数组。它使用最适合算法分配内存。与 heap_1 不同,它允许释放内存。再次说明,数组是静态声明的,因此应用程序看起来会消耗大量RAM,甚至在从数组中分配任何内存之前就是如此。

最适合算法可确保 pvPortMalloc() 使用的可用内存块在大小方面与所要求的字节数最接近。例如,考虑以下情形:

• 堆包含三个可用内存块,大小分别为 5 字节、25 字节和 100 字节。 • 调用 pvPortMalloc() 以请求 20 字节的 RAM。

适合所请求字节数的最小可用 RAM 块是 25 字节块,因此,pvPortMalloc() 将 25 字节块拆分成一个 20 字节块和一个 5 字节块,然后返回一个指向 20 字节块的指针。(上面是过于简化了,因为 heap_2 要存储堆区域中块大小的信息,因此两个拆分块的总和实际上将小于 25。) 新的 5 字节块保持可用于将来对pvPortMalloc() 的调用。

与 heap_4 不同,heap_2 不将相邻的可用块合并为单个更大的块。因此,它更容易碎片化。但是,如果分配的块与后续释放的块大小始终相同,则碎片化不是问题。Heap_2 适合反复创建和删除任务的应用程序,但前提是分配给所创建的任务的堆栈大小不发生变化。

下图显示在创建和删除任务时从 heap_2 数组中分配和释放 RAM 的过程。

图中显示当创建、删除以及后续再次创建任务时,最适合算法的工作原理

• A显示已创建三个任务后的数组。大型可用块保持在数组的顶部。

• B显示已删除其中一个任务后的数组。这些区域有:

数组顶部的大型可用块保持原样。此外,目前有两个较小的可用块,它们之前分配给了已删除任务的TCB 和堆栈。

• C显示已创建另一个任务后的数组。创建任务导致两次调用 pvPortMalloc():一次是分配新的 TCB,一次是分配任务堆栈。使用 xTaskCreate() API 函数创建任务,如创建任务 (p. 23)中所述。在 xTaskCreate() 内部发生两次 pvPortMalloc() 调用。

每个 TCB 的大小完全相同,因此,最适合算法可确保之前分配给已删除任务的 TCB 的 RAM 块可重用于分配新任务的 TCB。

分配给新创建任务的堆栈大小与分配给以前被删除任务的堆栈大小完全相同,因此,最适合算法可确保之前分配给已删除任务的堆栈的 RAM 块可重用于分配新任务的堆栈。

数组顶部的较大的未分配块保持不变。Heap_2 是非确定性的,但它比 malloc() 和 free() 的大多数标准库实现都要快。

Heap_3

Heap_3.c 使用标准库函数 malloc() 和 free(),因此堆大小由链接器配置定义。configTOTAL_HEAP_SIZE 设置不起作用。

Heap_3 通过临时暂停 FreeRTOS 计划程序使 malloc() 和 free() 成为线程安全的。

Heap_4

与 heap_1 和 heap_2 一样,heap_4 将数组细分成较小的块。数组是静态声明的并由configTOTAL_HEAP_SIZE 确定大小,因此应用程序看起来会消耗大量 RAM,甚至在从数组中分配任何内存之前就是如此。

Heap_4 使用首个适合算法分配内存。与 heap_2 不同,它会将相邻的可用内存块组合(合并)成一个较大的块。这样可以最大程度地减小内存碎片化风险。

首个适合算法可确保 pvPortMalloc() 使用第一个大小足以容纳所要求的字节数的可用内存块。例如,考虑以下情形:

• 堆包含三个可用内存块。它们在数组中以下面的顺序显示:5 字节、200 字节和 100 字节。 • 调用 pvPortMalloc() 以请求 20 字节的 RAM。

适合所请求字节数的第一个可用 RAM 块是 200 字节块,因此,pvPortMalloc() 将 200 字节块拆分成一个 20字节块和一个 180 字节块,然后返回一个指向 20 字节块的指针。(上面是过于简化了,因为 heap_4 要存储堆区域中块大小的信息,因此两个拆分块的总和将小于 200 字节。) 新的 180 字节块保持可用于将来对pvPortMalloc() 的调用。

Heap_4 会将相邻的可用内存块组合(合并)成一个较大的块,同时最大限度地降低碎片化风险。Heap_4 适用于反复分配和释放不同大小的 RAM 块的应用程序。

下图显示从 heap_4 数组中分配和释放 RAM 的过程。它演示 heap_4 首个适合算法(带内存合并)在分配和释放内存时的工作方式。

A显示已创建三个任务后的数组。大型可用块保持在数组顶部。

B显示已删除其中一个任务后的数组。

C显示已创建一个 FreeRTOS 队列后的数组。

D显示在从应用程序代码中直接调用 pvPortMalloc()(而不是通过间接调用 FreeRTOS API 函数)后的数组。

E显示删除队列后的数组,此时会自动释放分配给已删除队列的内存。此时,用户分配的块的两侧都有可用内存。

F显示的也是已释放用户分配的内存之后的数组。用户分配的块所用的内存已与两侧的可用内存组合成一个更大的可用块。

Heap_4 是非确定性的,但比 malloc() 和 free() 的大多数标准库实现都要快。

Heap_5

heap_5 用来分配和释放内存的算法与 heap_4 的完全相同。与 heap_4 不同,heap_5 不限于从单个静态声明的数组分配内存。Heap_5 可以从多个单独的内存空间分配内存。当运行 FreeRTOS 的系统提供的 RAM在系统的内存映射中未作为单个邻接的(没有空间)块出现时,Heap_5 很有用。

Heap_5 是唯一一个必须在调用 pvPortMalloc() 之前显式初始化的内存分配方案。它使用 vPortDefineHeapRegions() API 函数进行初始化。当使用 heap_5 时,必须先调用vPortDefineHeapRegions(),然后才可创建任何内核对象(任务、队列、信号等)。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式系统
    +关注

    关注

    41

    文章

    3716

    浏览量

    133070
  • 内存
    +关注

    关注

    9

    文章

    3173

    浏览量

    76099
  • FreeRTOS
    +关注

    关注

    14

    文章

    497

    浏览量

    66327
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    嵌入式系统软件架构通常划分

    嵌入式系统的软件架构通常划分如下分层设计: 应用层:环境温度监测、报警触发逻辑。 中间件层:支持MQTT协议的网络通信模块,用于将温度数据上传至云端。 操作系统层:基于FreeRTOS
    发表于 12-01 07:20

    CW32嵌入式软件开发的必备知识

    的数据手册及用户手册,查找所需外设工作原理。 4、 嵌入式操作系统(深入知识点) 掌握常用的嵌入式操作系统,如μC/OS、FREERTOS
    发表于 11-28 07:48

    单片机的操作系统

    单片机操作系统主要分为实时操作系统(RTOS)和嵌入式操作系统两类,以下是常见选择: 实时操作系统(RTOS) ‌
    发表于 11-14 06:18

    嵌入式实时操作系统的特点

    操作系统具备高效的中断处理机制,能够快速响应和处理系统的中断事件。 资源管理:实时嵌入式操作系统提供有效的资源
    发表于 11-13 06:30

    RusT-Thread:基于Rust面向资源受限嵌入式设备的操作系统的实践 | 技术集结

    摘要随着物联网和嵌入式系统的发展,实时操作系统(RTOS)的安全性和性能需求日益提高。传统基于C语言的RTOS在内存安全和并发控制方面存在局限,容易导致缓冲区溢出、数据竞争等问题。本项
    的头像 发表于 11-07 17:37 6458次阅读
    RusT-Thread:基于Rust面向资源受限<b class='flag-5'>嵌入式</b>设备的<b class='flag-5'>操作系统</b>的实践 | 技术集结

    嵌入式需要掌握哪些核心技能?

    : 1)C语言与底层编程 核心地位:C语言是嵌入式开发的基石,需精通指针操作内存管理、位运算,直接操控硬件资源。 延伸技能:C++用于复杂项目架构设计,汇编语言优化底层性能(如启动代
    发表于 10-21 16:25

    嵌入式达到什么水平才能就业?

    、LoRa、NB-IoT,能实现嵌入式设备与云端平台的数据交互了解 RTOS 实时操作系统:如 FreeRTOS、RT-Thread,能进行任务创建、信号量管理
    发表于 09-15 10:20

    嵌入式系统:国产化现状与趋势

    和模块,如FreeRTOS内核小巧,可在几KB内存的微控制器上运行,以最小的资源占用提供基本的任务调度和管理功能。轻量级的操作系统FreeRTOS
    的头像 发表于 06-23 17:26 392次阅读
    <b class='flag-5'>嵌入式</b><b class='flag-5'>系统</b>:国产化现状与趋势

    Linux嵌入式和单片机嵌入式的区别?

    ,开发工具包括GCC、Makefile等。 3.操作系统 : 单片机嵌入式 :一般不使用完整的操作系统,或者使用简单的实时操作系统(RTOS),如
    发表于 06-20 09:46

    OPENRTOS为FreeRTOS提供商业许可证

    嵌入式操作系统嵌入式系统的基石,是工业软件的基础。在市场占有率上,Eclipse基金会2024年物联网开发者调查表明,资源受限设备上的开发人员使用的
    的头像 发表于 06-06 09:43 580次阅读

    嵌入式开发入门指南:从零开始学习嵌入式

    基础 3. 学习路径推荐第一阶段:熟悉开发环境(如Keil、IAR、STM32)第二阶段:掌握裸机编程与驱动开发第三阶段:学习RTOS(实时操作系统)如FreeRTOS第四阶段:深入理解Linux嵌入式
    发表于 05-15 09:29

    RT-Thread嵌入式操作系统专业培训班-深圳站重磅开启!

    RT-Thread官方将在深圳举办为期三天的嵌入式操作系统专业培训班!本次培训将深入讲解RT-Thread嵌入式实时操作系统的核心技术与实战应用,助力开发者快速掌握RTOS开发精髓。无
    的头像 发表于 04-11 18:18 643次阅读
    RT-Thread<b class='flag-5'>嵌入式</b><b class='flag-5'>操作系统</b>专业培训班-深圳站重磅开启!

    2025嵌入式操作系统专业培训班正式启动!

    RT-Thread官方将在河南-郑州发起为期三天的嵌入式操作系统专业培训班!本次培训将会深入讲解RT-Thread嵌入式实时操作系统的核心概念、实战技巧和应用场景!无论企业团队/工程师
    的头像 发表于 01-09 18:46 1356次阅读
    2025<b class='flag-5'>嵌入式</b><b class='flag-5'>操作系统</b>专业培训班正式启动!

    ARM嵌入式实时操作系统比较

    嵌入式系统领域,实时操作系统(RTOS)是确保任务按时完成的关键技术。ARM架构因其低功耗、高性能的特点,在嵌入式系统中得到了广泛应用。本
    的头像 发表于 12-28 09:15 2437次阅读

    新手怎么学嵌入式?

    嵌入式系统的发展,嵌入式操作系统也变得越来越重要。学习嵌入式操作系统可以帮助你更好地
    发表于 12-12 10:51