FreeRTOS 内存管理源码解析


FreeRTOS 提供了 5 种不同的内存管理策略以应对不同的应用场景,本章将对这 5 种不同的内存管理策略的实现进行分析。

我参考的源码是:FreeRTOS-Kernel-10.4.3-LTS-Patch-3\portable\MemMang,该路径下记录了 heap_1.cheap_2.cheap_3.cheap_4.cheap_5.c,讲解会从这 5 个文件的源码入手。

一、heap_1

1、源码讲解

首先,FreeRTOS 将堆定义为一个大数组,并使用变量 xNextFreeByte 记录已用内存大小。

/* 字节对齐堆起始地址可能会丢失几个字节 */
#define configADJUSTED_HEAP_SIZE    ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

/* Index into the ucHeap array. */
static size_t xNextFreeByte = ( size_t ) 0;

核心代码 pvPortMalloc() 函数实现如下:

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

    /* 如果对齐字节数不为1,则对请求的字节数做调整
     * 这里portBYTE_ALIGNMENT等于8 */
    #if ( portBYTE_ALIGNMENT != 1 )
        {
   
   
            /* 检查 xWantedSize 是否满足字节对齐的要求 
             * 如果不满足才会进入这句判断语句 */
            if( xWantedSize & portBYTE_ALIGNMENT_MASK )
            {
   
   
                /* 计算调整后的内存大小,并检查可能的溢出
                 * 这句判断计算需要调整的字节数,即需要添加的字节,以确保对齐 */
                if ( (xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) )) > xWantedSize )
                {
   
   
                    // 没有溢出
                    xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
                } 
                else 
                {
   
   
                    // 如果在调整大小时发生了溢出,这意味着请求的大小不合法。
                    xWantedSize = 0;
                }
            }
        }
    #endif

    /* 挂起任务 */
    vTaskSuspendAll();
    {
   
   
        if( pucAlignedHeap == NULL )
        {
   
   
            /* 作用:确保堆从正确对齐的边界开始
             * 宏定义:portBYTE_ALIGNMENT == 8,portBYTE_ALIGNMENT_MASK == 0x0007,portPOINTER_SIZE_TYPE 为 uint32_t
             * 可以整理一下方便看:
             * ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) = x
             * ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) = y
             * (uint8_t *)( x & y );
             * 所以这里的操作为:
             * 第一句的作用:获取地址指向 ucHeap 数组中偏移 portBYTE_ALIGNMENT 的位置,
             *              并将指针转换为 portPOINTER_SIZE_TYPE 可以确保我们以适当的大小处理指针,
             *              避免潜在的数据丢失或不正当的操作。
             * 第二局的作用:将掩码转换为 portPOINTER_SIZE_TYPE 并按位取反,相当于变成了:0xFFF8(1111 1111 1111 1000)
             * 所以这一句的作用为清除 ucHeap 的低 3 位,使得分配的起始位置是满足 portBYTE_ALIGNMENT 对齐要求(8位)的 */
            pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
        }

        /* 检查是否有足够的空间分配 */
        if( ( xWantedSize > 0 ) && /* valid size */
            ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
            ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) /* 防止数值溢出 */
        {
   
   
            /* 返回首地址 */
            pvReturn = pucAlignedHeap + xNextFreeByte;
            /* 记录已分配空间大小 */
            xNextFreeByte += xWantedSize;
        }

        traceMALLOC( pvReturn, xWantedSize );
    }
    ( void ) xTaskResumeAll();  /* 恢复任务 */

    return pvReturn;
}

需要注意的是,并未使用 configTOTAL_HEAP_SIZE 代表堆大小,而是用 configADJUSTED_HEAP_SIZE 表示堆大小,configADJUSTED_HEAP_SIZE 定义如下

#define configADJUSTED_HEAP_SIZE  ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

这里简单粗暴的丢弃掉了对齐字节数个字节,以此来表示堆的起始地址对齐的操作中损失的字节数(最多不会损失掉对齐字节数个字节)

heap_1.c 剩下的还有如下 3 个函数:

void vPortFree( void * pv )
{
   
   
    /* heap_1 没有实现如何释放内存!!! */
    ( void ) pv;

    /* 强制 assert,因为调用此函数无效 */
    configASSERT( pv == NULL );
}

void vPortInitialiseBlocks( void )
{
   
   
    /* 仅在未清除静态内存时需要 */
    xNextFreeByte = ( size_t ) 0;
}

size_t xPortGetFreeHeapSize( void )
{
   
   
    return( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}

2、总结

heap_1.c 所实现的内存管理方法十分简单,其可以使用 pvPortMalloc() 函数来申请内存,一旦申请成功了,便无法被释放。其实现大致可以用一句话概括,在堆空间剩余时,按需分割出内存,并记录已用的内存大小heap_1.c 使用的内存管理算法虽然简单,但对于许多嵌入式应用场景是适用且有效的。

二、heap_2

1、源码讲解

/* 字节对齐堆起始地址可能会丢失几个字节 */
#define configADJUSTED_HEAP_SIZE    ( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

/* 定义链表结构,这用于按大小的顺序链接空闲块. */
typedef struct A_BLOCK_LINK
{
   
   
    struct A_BLOCK_LINK * pxNextFreeBlock; /* 指向列表中的下一个空闲块. */
    size_t xBlockSize;                     /* 空闲块的大小(包括BlockLink_t 头部大小) */
} BlockLink_t;

/* 确定结构 BlockLink_t 的大小,同时考虑到内存的字节对齐要求 */
static const uint16_t heapSTRUCT_SIZE = ( ( sizeof( BlockLink_t ) + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK );
#define heapMINIMUM_BLOCK_SIZE    ( ( size_t ) ( heapSTRUCT_SIZE * 2 ) )  /* 空闲块的最小大小 */

/* 创建两个列表链接以标记列表的开头和结尾. */
static BlockLink_t xStart, xEnd;

/* 跟踪剩余的空闲字节数,但不说明碎片 */
static size_t xFreeBytesRemaining = configADJUSTED_HEAP_SIZE;

然后 FreeRTOS 还定义了一个将内存块插入链表的宏:

/*
 * 将数据块插入到可用数据块列表中
 * 该列表按数据块大小排序。列表开头的块是小块和列表末尾的块是大块。
 */
#define prvInsertBlockIntoFreeList( pxBlockToInsert )                                                                               \
    {
     
                                                                                                                                    \
        BlockLink_t * pxIterator;                                                                                                   \
        size_t xBlockSize;                                                                                                          \
                                                                                                                                    \
        xBlockSize = pxBlockToInsert->xBlockSize;                                                                                   \
                                                                                                                                    \
        /* 遍历列表,直到找到一个比我们插入的块更大的块 */                                                                            \
        for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock ) \
        {
     
                                                                                                                                \
            /* 等待迭代到正确的位置 */                                                                                               \ 
        }                                                                                                                           \
                                                                                                                                    \
        /* 更新列表以包括插入到正确位置的块。 */                                                                                     \
        pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;                                                             \
        pxIterator->pxNextFreeBlock = pxBlockToInsert;                                                                              \
    }

简单来说就是先查找适当的位置,然后将内存块插入到链表中,非常简单,不多说。

BlockLink_t 只描述了内存块的大小和内存块的链接关系,具体分配出的内存表示方式如下图所示:

1.1 堆的初始化

static void prvHeapInit( void )
{
   
   
    BlockLink_t * pxFirstFreeBlock;
    uint8_t * pucAlignedHeap;

    /* 确保堆从正确对齐的边界开始 
     * 和刚才 heap_1 中讲的一模一样,不理解再回去看 */
    pucAlignedHeap =</
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值