问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

FreeRTOS内存管理详解:五种内存分配方法对比

创作时间:
作者:
@小白创作中心

FreeRTOS内存管理详解:五种内存分配方法对比

引用
CSDN
1.
https://m.blog.csdn.net/qq_44114055/article/details/145019654

FreeRTOS的内存管理是系统开发中的重要环节,它提供了多种内存分配策略以适应不同的应用场景。本文将详细介绍FreeRTOS中五种不同的内存分配方法(heap_1.c至heap_5.c),帮助开发者更好地理解和使用这些功能。

FreeRTOS 内存管理简介

FreeRTOS在创建任务、队列、信号量等资源时,提供了两种内存申请方式:动态申请和静态分配。动态申请通过pvPortMalloc()函数从堆中分配内存,而静态分配则由用户预先定义所需的RAM空间。

不同的嵌入式系统对内存分配和时间要求各异,因此FreeRTOS将内存分配作为移植层的一部分,允许用户选择最适合的内存分配方法。当内核需要RAM时,可以使用pvPortMalloc()替代标准的malloc()函数,释放内存时则使用vPortFree()替代free()函数。这两种函数的原型与标准C库中的malloc()和free()类似。

FreeRTOS提供了五种内存分配方法,分别对应五个文件:heap_1.c、heap_2.c、heap_3.c、heap_4.c和heap_5.c。这些文件位于FreeRTOS的源码目录中。

内存碎片问题

在深入探讨各种内存分配方法之前,我们先了解一下什么是内存碎片。内存碎片是指在内存分配和释放过程中,由于分配和释放的内存块大小不一致,导致内存空间被分割成许多小块,这些小块可能无法被有效利用。

如上图所示,当一个应用释放了80B和10B的内存块后,如果另一个应用需要50B的内存,它可以使用剩余的未分配内存或已释放的80B内存块。然而,10B的内存块由于太小,除非有需要小于等于10B内存的应用,否则无法被使用,从而成为内存碎片。

经过多次分配和释放后,内存会被分割成大量小块,这些小块可能无法满足大多数应用的需求,最终导致实际可用内存减少,甚至导致应用程序因无法分配到合适内存而崩溃。FreeRTOS的heap_4.c提供了一种解决方案,即通过合并相邻的空闲内存块来提高内存利用率。

五种内存分配方法对比

内存管理方法
简介
优点
缺点
heap_1.c
简单静态分配方式,提供单一的内存堆,分配后内存块不会被释放,内存块大小在编译时确定。
实现简单,占用资源少,无内存碎片问题,对于资源分配固定的简单系统可靠性高。
缺乏灵活性,不能动态释放内存,不适用于需要频繁分配和释放内存的复杂场景。
heap_2.c
基于单向链表管理固定大小的内存块,可动态分配和释放内存。
对于固定大小内存块的分配和释放操作相对简单高效,适用于内存块大小固定的频繁分配和释放场景,如相同大小任务栈的管理。
只能处理固定大小的内存块,存在内存碎片风险,当内存块大小需求不一致时,内存利用率可能较低。
heap_3.c
对标准C库的malloc()和free()函数进行简单包装。
利用标准C库的功能,易于理解和移植,在熟悉标准C库内存管理的情况下可以快速上手。
依赖标准C库的性能和特性,可能存在标准C库本身的内存碎片问题,对一些资源受限的嵌入式系统可能不太适用。
heap_4.c
采用双向链表管理可变大小的内存块,能合并相邻空闲内存块来提高利用率,可动态分配和释放。
能灵活处理不同大小的内存块分配,通过合并空闲内存块提高了内存利用率,适用于复杂多变的内存分配需求。
实现相对复杂,占用一定的系统资源用于管理内存链表,内存分配和释放操作可能比简单的方法耗时。
heap_5.c
基于heap_4.c的算法扩展到多个不连续的内存区域,可在这些区域间分配和释放内存。
能够有效利用分散的内存资源,适用于内存分布不连续的系统,提高了整个系统的内存可用性。
管理多个区域的内存增加了复杂性,对内存管理的开销进一步增大,实现和调试难度较高。

字节对齐的目的

字节对齐是为了优化内存访问效率。现代处理器在访问内存时,通常要求数据的起始地址与数据的大小对齐。例如,一个32位的整数应该存储在4字节对齐的地址上。如果不满足对齐要求,处理器可能需要进行额外的操作来访问数据,这会降低性能。因此,内存分配时通常需要进行字节对齐处理。

heap_1内存分配方法

heap_1是最简单的内存分配方法,它从一个大数组(内存堆)中分配一小块内存。内存堆在heap_x.c文件中定义,大小为configTOTAL_HEAP_SIZE。代码实现和内存分配过程都非常简单,适合那些不需要动态内存分配的应用。

#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
//需要用户自行定义内存堆
//当宏 configAPPLICATION_ALLOCATED_HEAP 为 1 的时候需要
//用户自行定义内存堆,否则的话由编译器来决定,默认都是由编译器
//来决定的。如果自己定义的话就可以将内存堆定义
//到外部 SRAM 或者 SDRAM 中
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; //编译器决定
#endif

void *pvPortMalloc( size_t xWantedSize )
{
    void *pvReturn = NULL;
    static uint8_t *pucAlignedHeap = NULL;

    //确保字节对齐
    #if( portBYTE_ALIGNMENT != 1 )
    {
        if( xWantedSize & portBYTE_ALIGNMENT_MASK )
        {
            //需要进行字节对齐
            xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize &
            portBYTE_ALIGNMENT_MASK ) );
        }
    }
    #endif

    vTaskSuspendAll();
    {
        if( pucAlignedHeap == NULL )
        {
            //确保内存堆的开始地址是字节对齐的
            pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE )&
            ucHeap[ portBYTE_ALIGNMENT ] ) &
            ( ~( ( portPOINTER_SIZE_TYPE )
            portBYTE_ALIGNMENT_MASK ) ) );
        }

        //检查是否有足够的内存供分配,有的话就分配内存
        if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
        ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )
        {
            pvReturn = pucAlignedHeap + xNextFreeByte;
            xNextFreeByte += xWantedSize;
        }

        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();

    #if( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
            extern void vApplicationMallocFailedHook( void );
            vApplicationMallocFailedHook();
        }
    }
    #endif

    return pvReturn;
}

关键概念解释

  • portBYTE_ALIGNMENT:指定字节对齐的规则,即数据存储时按照多少字节的边界进行对齐。例如,如果portBYTE_ALIGNMENT被定义为4,意味着数据存储时会按照4字节的边界进行对齐。
  • portBYTE_ALIGNMENT_MASK:与portBYTE_ALIGNMENT相关的掩码值。通常,portBYTE_ALIGNMENT是2的幂次方,例如2、4、8等。当portBYTE_ALIGNMENT是2的幂次方时,portBYTE_ALIGNMENT_MASK的值为portBYTE_ALIGNMENT - 1。例如,如果portBYTE_ALIGNMENT为4(二进制100),那么portBYTE_ALIGNMENT_MASK为3(二进制011)。

heap_2内存分配方法

heap_2提供了一个更好的分配算法,与heap_1不同的是,heap_2提供了内存释放函数。但是,heap_2不会将释放的内存块合并成一个大块,这会导致内存碎片问题。随着不断的内存分配和释放,内存堆会被分割成许多大小不一的内存块。

heap_3内存分配方法

heap_3是对标准C库中的malloc()和free()函数的简单封装。FreeRTOS对这两个函数进行了线程保护,使其在多任务环境中安全使用。

heap_4内存分配方法

heap_4提供了最优的匹配算法,与heap_2不同的是,heap_4会将内存碎片合并成一个大的可用内存块。它采用双向链表结构来管理内存,并能够合并相邻的空闲内存块,从而提高内存利用率。当一个任务结束并释放其占用的内存块后,heap_4可以将该内存块与相邻的空闲内存块合并为一个更大的空闲内存块,以便后续分配给需要较大内存空间的其他任务或资源。

heap_5内存分配方法

heap_5是在heap_4的基础上实现的,增加了管理多个非连续内存区域的能力。heap_5默认没有定义内存堆,需要用户手动指定内存区域的信息并进行初始化。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号