首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >FreeRTOS-内存管理(一)

FreeRTOS-内存管理(一)

原创
作者头像
HaloMay
发布2025-06-16 11:41:56
发布2025-06-16 11:41:56
36400
代码可运行
举报
文章被收录于专栏:FreeRTOSFreeRTOS
运行总次数:0
代码可运行

前言

在FreeRTOS中,内存管理文件夹下存在着五个源文件,分别是heap_1~5.c,对应着五种不同的内存管理策略,准确来说,应该是堆内存的管理策略。

下面逐一讲解这些源码以及背后的思想。

内存分配策略

heap_1.c

这个文件的代码仅有150多行,实现的功能也十分有限,主要就是实现了pvPortMalloc()函数,用于申请内存。这个策略是不允许释放内存的,所以不存在内存碎片的问题。这个策略适用于:

  • 内存需求简单且固定。
  • 任务数量较少。
  • 不需要频繁地分配和释放内存。
代码语言:txt
复制
//堆的定义:
    #define configTOTAL_HEAP_SIZE		( ( size_t ) ( 17 * 1024 ) )
    static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

可以看到,所谓的堆其是就是一段连续的内存空间,就是一个巨大的数组,在F103中的FreeRTOSConfig.h中,将这个数组大小设置为了17KB。所以内存的分配和释放其实就是在这个数组上进行。

void* pvPortMalloc(size_t xWantedSize)

代码语言:txt
复制
void * pvPortMalloc( size_t xWantedSize )
{
    void * pvReturn = NULL;
    static uint8_t * pucAlignedHeap = NULL;

    /* Ensure that blocks are always aligned. */
    #if ( portBYTE_ALIGNMENT != 1 )
    {
        if( xWantedSize & portBYTE_ALIGNMENT_MASK )
        {
            /* Byte alignment required. Check for overflow. */
            if( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize )
            {
                xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
            }
            else
            {
                xWantedSize = 0;
            }
        }
    }
    #endif /* if ( portBYTE_ALIGNMENT != 1 ) */

    vTaskSuspendAll();
    {
        if( pucAlignedHeap == NULL )
        {
            /* Ensure the heap starts on a correctly aligned boundary. */
            pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
        }

        /* Check there is enough room left for the allocation and. */
        if( ( xWantedSize > 0 ) &&                                /* valid size */
            ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
            ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) /* Check for overflow. */
        {
            /* Return the next free byte then increment the index past this
             * block. */
            pvReturn = pucAlignedHeap + xNextFreeByte;
            xNextFreeByte += xWantedSize;
        }

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

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

    return pvReturn;
}

这个函数的参数是想要申请的空间大小,返回值是申请空间的地址。

函数的刚开始会进行一次字节对齐的判断,Cortex M3内核采取的是8字节对齐,这样可以加快cpu访问效率。如果xWantedSize=5,会在这个判断语句中补到8,以确保内存对齐。

接下来需要关闭调度器,防止分配内存时,被任务调度打断。

代码语言:javascript
代码运行次数:0
运行
复制
 vTaskSuspendAll();

接着,在第一个判断语句中,确保堆内存的起始地址也是对齐的,如果不是对齐的,pucAlignedHeap会指向调整之后的堆内存。

代码语言:javascript
代码运行次数:0
运行
复制
  if( pucAlignedHeap == NULL )
        {
            /* Ensure the heap starts on a correctly aligned boundary. */
            pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
        }

下面进行申请空间,先判断xWantedSize是否合法以及申请后,是否会出现堆溢出等条件,如果全部符合申请条件,则将申请的地址赋值给pvReturn并返回。

代码语言:javascript
代码运行次数:0
运行
复制
 /* Check there is enough room left for the allocation and. */
        if( ( xWantedSize > 0 ) &&                                /* valid size */
            ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
            ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) /* Check for overflow. */
        {
            /* Return the next free byte then increment the index past this
             * block. */
            pvReturn = pucAlignedHeap + xNextFreeByte;
            xNextFreeByte += xWantedSize;
        }

重新开启调度器。

代码语言:javascript
代码运行次数:0
运行
复制
 ( void ) xTaskResumeAll();

后面这里还有一个条件判断语句,是FreeRTOS中开放给用户的钩子函数,用于在内存分配失败后处理。

代码语言:javascript
代码运行次数:0
运行
复制
 #if ( configUSE_MALLOC_FAILED_HOOK == 1 )
    {
        if( pvReturn == NULL )
        {
            vApplicationMallocFailedHook();
        }
    }
    #endif

heap_1.c不支持释放内存,所以

void vPortFree( void * pv )这个函数没有实现

代码语言:txt
复制

void vPortFree( void * pv )
{
    /* Memory cannot be freed using this scheme.  See heap_2.c, heap_3.c and
     * heap_4.c for alternative implementations, and the memory management pages of
     * https://www.FreeRTOS.org for more information. */
    ( void ) pv;
    /* Force an assert as it is invalid to call this function. */
 configASSERT( pv == NULL );
}

还有两个函数用于当前堆指针初始化,和获取。

代码语言:txt
复制

void vPortInitialiseBlocks( void )
{
    /* Only required when static memory is not cleared. */
 xNextFreeByte = ( size_t ) 0;
}
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void )
{
 return( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}

heap_2.c

heap_2.c相当于在heap_1.c的基础上演进而来。2使用的同样是一个巨大的数组:

代码语言:txt
复制

PRIVILEGED_DATA static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

只不过为了实现能够动态释放内存,heap_2.c引入了新的算法和结构。

heap_1.c的分配策略
heap_1.c的分配策略

heap_2.c引入了一个块结构,用于表示分配的内存,其本质是一个链表,记录下一个块地址,并记录当前剩余可用空间:

代码语言:txt
复制
typedef struct A_BLOCK_LINK
{
    struct A_BLOCK_LINK * pxNextFreeBlock; /*<< The next free block in the list. */
    size_t xBlockSize;                     /*<< The size of the free block. */
} BlockLink_t;

我们来看,初始化函数做了什么?

代码语言:txt
复制
static void prvHeapInit( void ) /* PRIVILEGED_FUNCTION */
{
    BlockLink_t * pxFirstFreeBlock;
    uint8_t * pucAlignedHeap;

    /* Ensure the heap starts on a correctly aligned boundary. */
    pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT - 1 ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

    /* xStart is used to hold a pointer to the first item in the list of free
     * blocks.  The void cast is used to prevent compiler warnings. */
    xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
    xStart.xBlockSize = ( size_t ) 0;

    /* xEnd is used to mark the end of the list of free blocks. */
    xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
    xEnd.pxNextFreeBlock = NULL;

    /* To start with there is a single free block that is sized to take up the
     * entire heap space. */
    pxFirstFreeBlock = ( BlockLink_t * ) pucAlignedHeap;
    pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
    pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}

首先还是在刚开始,将当前可用的堆地址对齐,pucAlignedHeap指向当前对齐后的地址,也就是可用于分配的地址。

然后定义了三个指针,分别是头尾指针xStatr、xEnd和已分配头节点指针。初始函数将这两个指针分别指向了堆的开头和结尾,

然后将pxFirstFreeBlock指向当前可分配的地址:

初始化
初始化

下面先讲解内存释放函数:

代码语言:txt
复制
void vPortFree( void * pv )
{
    uint8_t * puc = ( uint8_t * ) pv;
    BlockLink_t * pxLink;

    if( pv != NULL )
    {
        /* The memory being freed will have an BlockLink_t structure immediately
         * before it. */
        puc -= heapSTRUCT_SIZE;

        /* This unexpected casting is to keep some compilers from issuing
         * byte alignment warnings. */
        pxLink = ( void * ) puc;

        configASSERT( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 );
        configASSERT( pxLink->pxNextFreeBlock == NULL );

        if( heapBLOCK_IS_ALLOCATED( pxLink ) != 0 )
        {
            if( pxLink->pxNextFreeBlock == NULL )
            {
                /* The block is being returned to the heap - it is no longer
                 * allocated. */
                heapFREE_BLOCK( pxLink );
                #if ( configHEAP_CLEAR_MEMORY_ON_FREE == 1 )
                {
                    ( void ) memset( puc + heapSTRUCT_SIZE, 0, pxLink->xBlockSize - heapSTRUCT_SIZE );
                }
                #endif

                vTaskSuspendAll();
                {
                    /* Add this block to the list of free blocks. */
                    prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
                    xFreeBytesRemaining += pxLink->xBlockSize;
                    traceFREE( pv, pxLink->xBlockSize );
                }
                ( void ) xTaskResumeAll();
            }
        }
    }
}

vPortFree 函数的主要功能是将之前分配的内存块返回到堆中,以便后续的内存分配可以使用。它的主要步骤包括:

  1. 检查传入的指针是否为 NULL
  2. 获取内存块的起始地址,并将其转换为 BlockLink_t 类型的指针。
  3. 检查内存块是否已被分配,并确保它不在空闲列表中。
  4. 标记内存块为未分配,并将其加入到空闲列表中。
  5. 更新堆的空闲字节数。
  6. 恢复任务调度。

也就是说,我们将要释放的地址传递给这个API,函数就会根据我们提供的地址,定位到指定块,然后将其连接到空闲链表上,并修改标志位,表示这块内存可用。

核心函数:

代码语言:txt
复制
  vTaskSuspendAll();
        {
            prvInsertBlockIntoFreeList((BlockLink_t *)pxLink);
            xFreeBytesRemaining += pxLink->xBlockSize;
            traceFREE(pv, pxLink->xBlockSize);
        }
        (void)xTaskResumeAll();

完成将释放的块挂载空闲链表上。

heap_2.c的内存分配函数原理与1差不多,也要考虑内存对齐问题,其接收到我们提供的参数(要分配内存的大小)后,遍历空闲链表,如果匹配到需要大小的块,就占用这个地址,并从空闲链表中移除。

具体可见这篇博客,我认为写的非常细致:

https://zhuanlan.zhihu.com/p/687409961

后续

下篇会继续分析heap_4.c也就是我们移植一直使用的这个内存管理模块。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 内存分配策略
    • heap_1.c
      • void* pvPortMalloc(size_t xWantedSize)
    • heap_2.c
  • 后续
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档