FreeRTOS学习笔记(十一)内存管理


前言

  本章是Free RTOS系列的终章,我们来讲述贯穿全系列的一个核心元素——内存管理。


一、内存管理

1.1 内存管理的引入

  内存管理是一个系统基本组成部分,FreeRTOS 中大量使用到了内存管理,比如创建任务、信号量、队列等会自动从堆中申请内存。用户应用层代码也可以 FreeRTOS 提供的内存管理函数来申请和释放内存。FreeRTOS 创建任务、队列、信号量等的时候有两种方法,一种是动态的申请所需的 RAM;一种是由用户自行定义所需的 RAM,这种方法也叫静态方法,使用静态方法的函数一般以“Static”结尾,比如任务创建函数 xTaskCreateStatic(),使用此函数创建任务的时候需要由用户定义任务堆栈,本章我们不讨论这种静态方法。
  使用动态内存管理的时候 FreeRTOS 内核在创建任务、队列、信号量的时候会动态的申请RAM。标准 C 库中的 malloc()和 free()也可以实现动态内存管理,但是出于种种原因限制了其使用,因此一个内存分配算法可以作为系统的可选选项。FreeRTOS 将内存分配作为移植层的一部分,这样 FreeRTOS 使用者就可以使用自己的合适的内存分配方法。
  动态内存分配需要一个内存堆,FreeRTOS 中的内存堆为ucHeap[] ,大小为configTOTAL_HEAP_SIZE,这个前面讲 FreeRTOS 配置的时候就讲过了。不管是哪种内存分配方法,它们的内存堆都为 ucHeap[],而且大小都是 configTOTAL_HEAP_SIZE。

1.2 内存碎片

  在学习 FreeRTOS 的内存分配方法之前我们先来看一下什么叫做内存碎片,看名字就知道是小块的、碎片化的内存。内存碎片是伴随着内存申请和释放而来的,如下图所示:
在这里插入图片描述

  可以看到经过很多次的申请和释放以后,内存块被不断的分割、最终导致大量很小的内存块!也就是图中 80B 和 50B 这两个内存块之间的小内存块,这些内存块由于太小导致大多数应用无法使用,这些没法使用的内存块就沦为了内存碎片!
  内存碎片是内存管理算法重点解决的一个问题,否则的话会导致实际可用的内存越来越少,最终应用程序因为分配不到合适的内存而奔溃!FreeRTOS 的 heap_4.c 就给我们提供了一个解决内存碎片的方法,那就是将内存碎片进行合并组成一个新的可用的大内存块。

二、内存分配的方法

  在Free RTOS的移植一章中,我们提到了其提供了 5 种内存分配方法这 5 种方法是 5 个文件,分别为:heap_1.c、heap_2.c、heap_3.c、heap_4.c 和heap_5.c。这部分我们FreeRTOS 使用者可以其中的某一个方法,或者自定义一个合适的分配方法。
在这里插入图片描述

2.1 heap_1

  heap_1 实现起来就是当需要 RAM 的时候就从一个大数组(内存堆)中分一小块出来,大数组(内存堆)的容量为 configTOTAL_HEAP_SIZE,上面已经说了。使用函数xPortGetFreeHeapSize()可以获取内存堆中剩余内存大小。在heap_1.c 文件就有如下定义:

#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; //需要用户自行定义内存堆
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; //编译器决定
#endif 

  当宏 configAPPLICATION_ALLOCATED_HEAP 为 1 的时候需要用户自行定义内存堆,否则的话由编译器来决定,默认都是由编译器来决定的。如果自己定义的话就可以将内存堆定义到外部 SRAM 或者 SDRAM 中。

2.1.1 实现原理

  1. heap_1.c 中使用了一个简单的静态数组作为堆空间,并按需从该数组中分配内存
  2. 该实现非常简单,只允许内存分配,不支持内存释放,内存的释放只能在任务结束或系统重启时实现。
  3. 主要适用于小型嵌入式系统中,内存需求相对简单、可预测的场景。

2.1.2 源码解析

  heap_1 的内存申请函数 pvPortMalloc()源码如下:

// 该函数(简化)用于动态分配内存,返回指向分配内存的指针
void *pvPortMalloc( size_t xWantedSize )
{
   
   
    void *pvReturn = NULL;
    
    // 确保 xWantedSize 是 8 字节对齐(xWantedSize是用户请求的内存大小,按照默认要求堆分配会将该大小调整为 8 字节对齐)
    if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 ) 
    {
   
   
        xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
    }

    // 检查是否有足够的空间,如果有返回堆中当前位置 xNextFreeByte 的地址,并将 xNextFreeByte 向前移动 xWantedSize 字节;不足返回NULL
    if( ( ( xNextFreeByte + xWantedSize ) < configTOTAL_HEAP_SIZE ) &&
        ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )
    {
   
   
    	// pucAlignedHeap表示起始地址(ucHeap的起始地址不一定是8字节对齐的,需要我们利用这个参数补齐)
    	// 如果内存够分配并且不会产生越界,那么就将申请到的内存首地址赋给 pvReturn
    	// 值得注意的是如果我们要申请 30 个字节的内存,字节对齐以后实际需要申请 32 字节
        pvReturn =  pucAlignedHeap + xNextFreeByte; 
        xNextFreeByte += xWantedSize;
    }
    
    return pvReturn;
}

  heap_1 的内存释放函数为 pvFree(),可以看出 vPortFree()并没有具体释放内存的过程,这说明使用一旦申请内存成功就不允许释放!

void vPortFree( void *pv )
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值