加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

【FreeRTOS操作系统教程】第28章 FreeRTOS动态内存管理

(2016-09-09 16:32:57)
标签:

fatfs

freertos

stemwin

stm32

usb

分类: FreeRTOS

第28章      FreeRTOS动态内存管理




本章节为大家讲解FreeRTOS动态内存管理,动态内存管理是FreeRTOS非常重要的一项功能,前面章节讲解的任务创建、信号量、消息队列、事件标志组、互斥信号量、软件定时器组等需要的RAM空间都是通过动态内存管理从FreeRTOSConfig.h文件定义的heap空间中申请的。

本章节整理的部分内容整理自官网地址:http://www.freertos.org/a00111.html

本章教程配套的例子含Cortex-M3内核的STM32F103Cortex-M4内核的STM32F407以及F429

28.1 动态内存管理介绍

28.2 动态内存和静态内存比较

28.3 动态内存API函数

28.4 实验例程说明

28.5       总结

 

 

28.1  动态内存管理介绍

FreeRTOS支持5种动态内存管理方案,分别通过文件heap_1heap_2heap_3heap_4heap_5实现,这5个文件在FreeRTOS软件包中的路径是:FreeRTOS\Source\portable\MemMang用户创建的FreeRTOS工程项目仅需要5种方式中的一种。

下面将这5种动态内存管理方式分别进行讲解。

28.1.1 动态内存管理方式一heap_1

heap_1动态内存管理方式是五种动态内存管理方式中最简单的,这种方式的动态内存管理一旦申请了相应内存后,是不允许被释放的。尽管如此,这种方式的动态内存管理还是满足大部分嵌入式应用的,因为这种嵌入式应用在系统启动阶段就完成了任务创建、事件标志组、信号量、消息队列等资源的创建,而且这些资源是整个嵌入式应用过程中一直要使用的,所以也就不需要删除,即释放内存。FreeRTOS的动态内存大小在FreeRTOSConfig.h文件中进行了定义:

#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )  //单位字节

用户通过函数xPortGetFreeHeapSize就能获得FreeRTOS动态内存的剩余,进而可以根据剩余情况优化动态内存的大小。heap_1方式的动态内存管理有以下特点:

1、项目应用不需要删除任务、信号量、消息队列等已经创建的资源。

2、具有时间确定性,即申请动态内存的时间是固定的并且不会产生内存碎片。

3、确切的说这是一种静态内存分配,因为申请的内存是不允许被释放掉的。

28.1.2 动态内存管理方式二heap_2

heap_1动态内存管理方式不同,heap_2动态内存管理利用了最适应算法,并且支持内存释放。但是heap_2不支持内存碎片整理,动态内存管理方式四heap_4支持内存碎片整理。FreeRTOS的动态内存大小在FreeRTOSConfig.h文件中进行了定义:

#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )  //单位字节

用户通过函数xPortGetFreeHeapSize就能获得FreeRTOS动态内存的剩余,但是不提供动态内存是如何被分配成各个小内存块的信息。另外,就是用户可以根据剩余情况优化动态内存的大小。heap_2方式的动态内存管理有以下特点:

1、不考虑内存碎片的情况下,这种方式支持重复的任务、信号量、事件标志组、软件定时器等内部资源的创建和删除。

2、如果用户申请和释放的动态内存大小是随机的,不建议采用这种动态内存管理方式,比如:

    (1)项目应用中需要重复的创建和删除任务,如果每次创建需要动态内存大小相同,那么heap_2比较适合,但每次创建需要动态内存大小不同,那么方式heap_2就不合适了,因为容易产生内存碎片,内存碎片过多的话会导致无法申请出一个大的内存块出来,这种情况使用heap_4比较合适。

    (2)项目应用中需要重复的创建和删除消息队列,也会出现类似上面的情况,这种情况下使用heap_4比较合适。

     (3)直接的调用函数pvPortMalloc() vPortFree()也容易出现内存碎片。如果用户按一定顺序成对的申请和释放,基本没有内存碎片的,而不按顺序的随机申请和释放容易产生内存碎片。

3、如果用户随机的创建和删除任务、消息队列、事件标志组、信号量等内部资源也容易出现内存碎片。

4、heap_2方式实现的动态内存申请不具有时间确定性,但是比C库中的malloc函数效率要高。

大部分需要动态内存申请和释放的小型实时系统项目可以使用heap_2。如果需要内存碎片的回收机制可以使用heap_4

28.1.3动态内存管理方式三heap_3

这种方式实现的动态内存管理是对编译器提供的mallocfree函数进行了封装,保证是线程安全的。

heap_3方式的动态内存管理有以下特点:

1、需要编译器提供mallocfree函数。

2、不具有时间确定性,即申请动态内存的时间不是固定的。

3、增加RTOS内核的代码量。

另外要特别注意一点,这种方式的动态内存申请和释放不是用的FreeRTOSConfig.h文件中定义的heap空间大小,而是用的编译器设置的heap空间大小或者说STM32启动代码中设置的heap空间大小,比如MDK版本的STM32F103工程中heap大小就是在这里进行的定义:

【FreeRTOS操作系统教程】第28章 <wbr>FreeRTOS动态内存管理

 

28.1.4动态内存管理方式四heap_4

heap_2动态内存管理方式不同,heap_4动态内存管理利用了最适应算法,且支持内存碎片的回收并将其整理为一个大的内存块。FreeRTOS的动态内存大小在FreeRTOSConfig.h文件中进行了定义:

#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )  //单位字节

heap_4同时支持将动态内存设置在指定的RAM空间位置。

用户通过函数xPortGetFreeHeapSize就能获得FreeRTOS动态内存的剩余,但是不提供动态内存是如何被分配成各个小内存块的信息。使用函数xPortGetMinimumEverFreeHeapSize能够获取从系统启动到当前时刻的动态内存最小剩余,从而用户就可以根据剩余情况优化动态内存的大小。heap_4方式的动态内存管理有以下特点:

1、可以用于需要重复的创建和删任务、信号量、事件标志组、软件定时器等内部资源的场合。

2、随机的调用pvPortMalloc() vPortFree(),且每次申请的大小都不同,也不会像heap_2那样产生很多的内存碎片。

3、不具有时间确定性,即申请动态内存的时间不是确定的,但是比C库中的malloc函数要高效。

heap_4比较实用,本教程配套的所有例子都是用的这种方式的动态内存管理,用户的代码也可以直接调用函数pvPortMalloc() vPortFree()进行动态内存的申请和释放。

28.1.5动态内存管理方式五heap_5

有时候我们希望FreeRTOSConfig.h文件中定义的heap空间可以采用不连续的内存区,比如我们希望可以将其定义在内部SRAM一部分,外部SRAM一部分,此时我们就可以采用heap_5动态内存管理方式。另外,heap_5动态内存管理是在heap_4的基础上实现的。

heap_5动态内存管理是通过函数vPortDefineHeapRegions进行初始化的,也就是说用户在创建任务FreeRTOS的内部资源前要优先级调用这个函数vPortDefineHeapRegions,否则是无法通过函数pvPortMalloc申请到动态内存的。

函数vPortDefineHeapRegions定义不同段的内存空间采用了下面这种结构体:

typedef struct HeapRegion

{

    

    uint8_t *pucStartAddress;

 

    

     size_t xSizeInBytes;

} HeapRegion_t;

比如下面定义了两个内存块:

 

const HeapRegion_t xHeapRegions[] =

{

    { ( uint8_t * ) 0x80000000UL, 0x10000 },

    { ( uint8_t * ) 0x90000000UL, 0xa0000 },

    { NULL, 0 }

};

 

 

vPortDefineHeapRegions( xHeapRegions );

定义的时候要注意两个问题,一个是内存段结束时要定义NULL。另一个是内存段的地址是从低地址到高地址排列。

用户通过函数xPortGetFreeHeapSize就能获得FreeRTOS动态内存的剩余,但是不提供动态内存是如何被分配成各个小内存块的信息。使用函数xPortGetMinimumEverFreeHeapSize能够获取从系统启动到当前时刻的动态内存最小剩余,从而用户就可以根据剩余情况优化动态内存的大小。

28.1.6五种动态内存方式总结

五种动态内存管理方式简单总结如下,实际项目中,用户根据需要选择合适的:

(1)heap_1:五种方式里面最简单的,但是申请的内存不允许释放。

(2)heap_2:支持动态内存的申请和释放,但是不支持内存碎片的处理,并将其合并成一个大的内存块。

(3)heap_3:将编译器自带的mallocfree函数进行简单的封装,以支持线程安全,即支持多任务调用。

(4)heap_4:支持动态内存的申请和释放,支持内存碎片处理,支持将动态内存设置在个固定的地址。

(5)heap_5:在heap_4的基础上支持将动态内存设置在不连续的区域上。

28.2动态内存和静态内存比较

静态内存方式是从FreeRTOSV9.0.0版本才开始有的,而我们本次教程使用的版本是V8.2.3。所以静态内存方式我们暂时不做讲解,等FreeRTOS教程版本升级时再做讲解。关于静态内存方式和动态内存方式的优缺点可以看官方的此贴说明:

http://www.freertos.org/Static_Vs_Dynamic_Memory_Allocation.html

(制作此教程的时候,官方的FreeRTOS V9.0.0正式版本还没有发布,所以采用的是当前最新的V8.2.3

28.3动态内存API函数

动态内存的API函数在官方的在线版手册上面没有列出,其实使用也比较简单,类似C库的mallocfree函数,具体使用参看下面的实例说明。

28.4实验例程说明

28.4.1STM32F103开发板实验

配套例子:

V4-334_FreeRTOS实验_动态内存管理

实验目的:

1.     学习FreeRTOS的动态内存管理。

2.     FreeRTOS提供了5种内存管理方法,板子配套的都是用的方法4,即heap_4文件。

实验内容:

1.     K1按键按下,串口打印任务执行情况(波特率115200,数据位8,奇偶校验位无,停止位1)。

2.     K2键按下,向消息队列xQueue1发送数据, 要发送数据的变量空间是通过函数pvPortMallocvPortFree实现申请和释放。

3.     各个任务实现的功能如下:

              vTaskUserIF任务 :按键消息处理。

              vTaskLED任务   LED闪烁

              vTaskMsgPro任务:使用函数xQueueReceive接收任务vTaskTaskUserIF发送的消息队列数(xQueue1)

              vTaskStart任务   :启动任务,也是最高优先级任务,这里实现按键扫描。

FreeRTOS的配置:

FreeRTOSConfig.h文件中的配置如下:

 

#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)

 #include

 extern volatile uint32_t ulHighFrequencyTimerTicks;

#endif

 

#define configUSE_PREEMPTION         1

#define configUSE_IDLE_HOOK          0

#define configUSE_TICK_HOOK          0

#define configCPU_CLOCK_HZ           ( ( unsigned long ) 72000000 )  

#define configTICK_RATE_HZ           ( ( TickType_t ) 1000 )

#define configMAX_PRIORITIES         ( 5 )

#define configMINIMAL_STACK_SIZE     ( ( unsigned short ) 128 )

#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )

#define configMAX_TASK_NAME_LEN      ( 16 )

#define configUSE_TRACE_FACILITY      1

#define configUSE_16_BIT_TICKS        0

#define configIDLE_SHOULD_YIELD      1

 

 

#define configGENERATE_RUN_TIME_STATS                1

#define configUSE_STATS_FORMATTING_FUNCTIONS         1

#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()     (ulHighFrequencyTimerTicks = 0ul)

#define portGET_RUN_TIME_COUNTER_VALUE()             ulHighFrequencyTimerTicks

//#define portALT_GET_RUN_TIME_COUNTER_VALUE           1

 

 

#define configUSE_CO_ROUTINES            0

#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

 

 

 

#define INCLUDE_vTaskPrioritySet          1

#define INCLUDE_uxTaskPriorityGet         1

#define INCLUDE_vTaskDelete               1

#define INCLUDE_vTaskCleanUpResources      0

#define INCLUDE_vTaskSuspend              1

#define INCLUDE_vTaskDelayUntil           1

#define INCLUDE_vTaskDelay                1

 

 

#ifdef __NVIC_PRIO_BITS

    

     #define configPRIO_BITS              __NVIC_PRIO_BITS

#else

     #define configPRIO_BITS                   

#endif

 

 

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY              0x0f

 

 

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY         0x01

几个重要选项说明:

u  #define configUSE_PREEMPTION        1

使能抢占式调度器

u  #define configCPU_CLOCK_HZ     ( ( unsigned long ) 72000000 )   

系统主频72MHz

u  #define configTICK_RATE_HZ              ( ( TickType_t ) 1000 )

系统时钟节拍1KHz,即1ms

u  #define configMAX_PRIORITIES          ( 5 )

定义可供用户使用的最大优先级数,如果这个定义的是5,那么用户可以使用的优先级号是0,1,2,3,4不包含5,对于这一点,初学者要特别的注意。

u  #define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )

定义堆大小,FreeRTOS内核,用户动态内存申请,任务栈等都需要用这个空间。

u  configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY          0x01

定义受FreeRTOS管理的最高优先级中断。简单的说就是允许用户在这个中断服务程序里面调用FreeRTOSAPI的最高优先级。为了进一步说明这个宏定义的的作用,解释如下:

l  使用CM内核的MCU,官方强烈建议将NVIC的优先级分组配置为全抢占式优先级,全部配置为抢占式优先级的好处就是方便管理。

l  对于STM32来说,设置NVIC的优先级分组为4时,NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)就是全部配置为抢占式优先级。又因为STM32的优先级设置仅使用CM内核8bit中的高4bit,即只能区分2^4 = 16种优先级。因此当优先级分组设置为4的时候可供用户选择抢占式优先级为015,共16个优先级,配置为0表示最高优先级,配置为15表示最低优先级,不存在子优先级。

l  这里配置configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY0x01表示用户可以在抢占式优先级为115的中断里面调用FreeRTOSAPI函数,抢占式优先级为0的中断里面是不允许调用的。

更多关于这个参数说明请参看第12章。

FreeRTOS任务调试信息(按K1按键,串口打印):

【FreeRTOS操作系统教程】第28章 <wbr>FreeRTOS动态内存管理

 

上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:

#define tskBLOCKED_CHAR          ( 'B' )  任务阻塞

#define tskREADY_CHAR           ( 'R' )  任务就绪

#define tskDELETED_CHAR           ( 'D' )  任务删除

#define tskSUSPENDED_CHAR   ( 'S' )  任务挂起

程序设计:

u  任务栈大小分配:

vTaskUserIF任务   2048字节

vTaskLED任务     2048字节

vTaskMsgPro任务 :2048字节

vTaskStart任务    2048字节

任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的

#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )

u  系统栈大小分配:

【FreeRTOS操作系统教程】第28章 <wbr>FreeRTOS动态内存管理

 

u  FreeROTS初始化:

 

int main(void)

{

    

     __set_PRIMASK(1); 

    

    

     bsp_Init();

    

    

     vSetupSysInfoTest();

    

    

     AppTaskCreate();

 

    

     AppObjCreate();

    

   

    vTaskStartScheduler();

 

    

     while(1);

}

u  硬件外设初始化

硬件外设的初始化是在bsp.c文件实现:

 

void bsp_Init(void)

{

    

    

          NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

 

     bsp_InitUart();   

     bsp_InitLed();    

     bsp_InitKey();    

}

u  FreeRTOS任务创建:

 

static void AppTaskCreate (void)

{

    xTaskCreate( vTaskTaskUserIF,  

                 "vTaskUserIF",    

                 512,               

                 NULL,             

                 1,                

                 &xHandleTaskUserIF );  /* 任务句柄  */

    

    

     xTaskCreate( vTaskLED,          

                 "vTaskLED",        

                 512,               

                 NULL,              

                 2,                 

                 &xHandleTaskLED );

    

     xTaskCreate( vTaskMsgPro,           

                 "vTaskMsgPro",          

                 512,                    

                 NULL,                   

                 3,                      

                 &xHandleTaskMsgPro );  /* 任务句柄  */

    

    

     xTaskCreate( vTaskStart,            

                 "vTaskStart",           

                 512,                    

                 NULL,                   

                 4,                      

                 &xHandleTaskStart );   

}

u  FreeRTOS消息队列创建:

 

static void AppObjCreate (void)

  

    

     xQueue1 = xQueueCreate(10, sizeof(struct Msg *));

    if( xQueue1 == 0 )

    {

        

    }

}

u  四个FreeRTOS任务的实现:

 

static void vTaskTaskUserIF(void *pvParameters)

{

     MSG_T *ptMsg;

     uint8_t ucCount = 0;

     uint8_t ucKeyCode;

     uint8_t pcWriteBuffer[500];

 

    while(1)

    {

         ucKeyCode = bsp_GetKey();

        

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                  

                   case KEY_DOWN_K1:          

                       printf("=================================================\r\n");

                       printf("任务名      任务状态 优先级   剩余栈 任务序号\r\n");

                       vTaskList((char *)&pcWriteBuffer);

                       printf("%s\r\n", pcWriteBuffer);

                  

                       printf("\r\n任务名       运行计数         使用率\r\n");

                       vTaskGetRunTimeStats((char *)&pcWriteBuffer);

                       printf("%s\r\n", pcWriteBuffer);

                       break;

                  

                  

                   case KEY_DOWN_K2:

                       printf("=================================================\r\n");

                       printf("当前动态内存大小 = %d\r\n", xPortGetFreeHeapSize());

                       ptMsg = (MSG_T  *)pvPortMalloc(sizeof(MSG_T));

                       printf("申请动态内存后剩余大小 = %d\r\n", xPortGetFreeHeapSize());

                  

                       ptMsg->ucMessageID = ucCount++;

                       ptMsg->ulData[0] = ucCount++;

                       ptMsg->usData[0] = ucCount++;

                  

                      

                       if(xQueueSend(xQueue1,                  

                                      (void *) &ptMsg,           

                                      (TickType_t)10) != pdPASS )

                       {

                           

                            printf("K2键按下,向xQueue2发送数据失败,即使等待了10个时钟节拍\r\n");

                            vPortFree(ptMsg);

                            printf("释放申请的动态内存后大小 = %d\r\n", xPortGetFreeHeapSize());

                       }

                       else

                       {

                           

                            printf("K2键按下,向xQueue2发送数据成功\r\n");

                                               

                            vPortFree(ptMsg);

                            printf("释放申请的动态内存后大小 = %d\r\n", xPortGetFreeHeapSize());                         

                       }

                  

                  

                   default:                    

                       break;

              }

         }

        

         vTaskDelay(20);

     }

}

 

 

static void vTaskLED(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 200;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

         bsp_LedToggle(2);

         bsp_LedToggle(3);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

 

 

static void vTaskMsgPro(void *pvParameters)

{

     MSG_T *ptMsg;

     BaseType_t xResult;

     const TickType_t xMaxBlockTime = pdMS_TO_TICKS(200);

    

    while(1)

    {

         xResult = xQueueReceive(xQueue1,                   

                                 (void *)&ptMsg,          

                                 (TickType_t)xMaxBlockTime);

        

        

         if(xResult == pdPASS)

         {

             

              printf("接收到消息队列数据ptMsg->ucMessageID = %d\r\n", ptMsg->ucMessageID);

              printf("接收到消息队列数据ptMsg->ulData[0] = %d\r\n", ptMsg->ulData[0]);

              printf("接收到消息队列数据ptMsg->usData[0] = %d\r\n", ptMsg->usData[0]);

         }

         else

         {

             

              bsp_LedToggle(1);

              bsp_LedToggle(4);

         }

    }

}

 

 

static void vTaskStart(void *pvParameters)

{

    while(1)

    {

        

         bsp_KeyScan();

        vTaskDelay(10);

    }

}

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有