stm32单片机与FreeRTOS、LwIP堆栈内存使用分析
作为一个工业通信产品研发工程师,经常会遇到需要在单片机上使用中间件FreeRTOS、LwIP的情况。本篇文章主要是梳理各中间件内存使用方式,以及在结合使用时需要注意的内存管理问题。
首先说结论,单片机和中间件FreeRTOS、LwIP的堆栈是互相独立的。具体如图。
单片机总内存(以下称为RAM)包括已使用的内存和未使用的RAM。
红色区域代表已使用内存,由RW和ZI-Data(包括FreeRTOS堆栈、LwIP堆栈以及其他程序占用的RAM)还有单片机堆栈(HEAP、STACK)组成。白色区域是未使用的RAM。
如上信息,可以通过编译生成的.map文件得到证实。
单片机堆栈内存使用分析
在单片机中,堆和栈都是内存区域,它们都具有存储数据的作用。下面分别介绍一下它们的作用:
堆(HEAP):在单片机中,堆的作用与在计算机上运行的程序的堆类似,用于动态分配内存。当程序需要在运行时创建一个新的变量或对象时,会从堆中申请一块内存空间,并返回该内存空间的地址。
栈(STACK):在单片机中,栈的作用与在计算机上运行的程序的栈类似,用于存储函数调用的局部变量、参数和返回地址等信息。当程序执行函数调用时,会从栈中分配一段内存空间,用于存储函数的参数和局部变量等信息。当函数返回时,栈中的这些信息会被弹出,并恢复函数调用前的状态。
堆内存的使用仅与以下函数关联。
malloc();
free();
也就是说,堆内存的大小只需要满足程序中使用上述函数分配的内存空间就可以了。
栈内存的使用则较为简单。经测试在使用FreeRTOS的情况下,栈内存空间基本用不到(后面会分析原因)。
已keil5为例,单片机堆栈大小的设置方法通常是编辑工程文件夹下的MDK-ARM/{单片机型号}.s文件。如图:
FreeRTOS堆栈内存使用分析
FreeRTOSConfig.h 中有定义如下。
#if defined(__GNUC__)
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 25 * 1024 ) )
#else
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 50 * 1024 ) )
#endif
FreeRTOS 创建任务、消息队列及FreeRTOS的API函数都是从configTOTAL_HEAP_SIZE中申请内存。
FreeRTOS 的堆空间使用情况如图。
由图可见,任务、队列、用户使用 pvPortMalloc() 分配的空间都从heap堆上面划分。
此外,由于任务使用的栈内存实际上是在FreeRTOS 的堆空间的。所以不需要其他的栈空间来存放。
此外,函数xPortGetFreeHeapSize() 可以获取调用堆中空闲内存的大小,以Byte为单位。函数xPortGetMinimumEverFreeHeapSize() 可以返回FreeRTOS应用程序开始运行之后曾经存在的最小的未被分配的存储空间的字节数(只有heap_4和heap_5有效)。运用这两个函数可以有效的减少内存浪费。
LwIP堆栈内存使用分析
LWIP默认的内存策略包括内存池(POOL)和内存堆(HEAP)两种。
内存池是指为了满足特定内存分配的需要以及优化内存占用,制定了各种尺寸大小的内存池(每种内存池管理的内存块大小固定)。内存堆本质上是一大块连续内存(可以理解为数组),当需要内存时就从这个数组中按照特定算法切分一块所需大小的内存块,将这块内存的地址提供出去;剩余内存仍然存放在内存堆中以便继续分配。
关于内存池(POOL)和内存堆(HEAP)的详细讲解可以参考LwIP系列–内存管理(内存池)详解和LwIP系列–内存管理(堆内存)详解。
在内存池模式下,memp_std.h头文件中的定义了各种内存池。
/*
* A list of internal pools used by LWIP.
*
* LWIP_MEMPOOL(pool_name, number_elements, element_size, pool_description)
* creates a pool name MEMP_pool_name. description is used in stats.c
*/
#if LWIP_RAW
LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb), "RAW_PCB")
#endif /* LWIP_RAW */
#if LWIP_UDP
LWIP_MEMPOOL(UDP_PCB, MEMP_NUM_UDP_PCB, sizeof(struct udp_pcb), "UDP_PCB")
#endif /* LWIP_UDP */
#if LWIP_TCP
LWIP_MEMPOOL(TCP_PCB, MEMP_NUM_TCP_PCB, sizeof(struct tcp_pcb), "TCP_PCB")
LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN, sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN")
LWIP_MEMPOOL(TCP_SEG, MEMP_NUM_TCP_SEG, sizeof(struct tcp_seg), "TCP_SEG")
#endif /* LWIP_TCP */
#if LWIP_ALTCP && LWIP_TCP
LWIP_MEMPOOL(ALTCP_PCB, MEMP_NUM_ALTCP_PCB, sizeof(struct altcp_pcb), "ALTCP_PCB")
#endif /* LWIP_ALTCP && LWIP_TCP */
#if LWIP_IPV4 && IP_REASSEMBLY
LWIP_MEMPOOL(REASSDATA, MEMP_NUM_REASSDATA, sizeof(struct ip_reassdata), "REASSDATA")
#endif /* LWIP_IPV4 && IP_REASSEMBLY */
#if (IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF) || (LWIP_IPV6 && LWIP_IPV6_FRAG)
LWIP_MEMPOOL(FRAG_PBUF, MEMP_NUM_FRAG_PBUF, sizeof(struct pbuf_custom_ref),"FRAG_PBUF")
#endif /* IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF || (LWIP_IPV6 && LWIP_IPV6_FRAG) */
#if LWIP_NETCONN || LWIP_SOCKET
LWIP_MEMPOOL(NETBUF, MEMP_NUM_NETBUF, sizeof(struct netbuf), "NETBUF")
LWIP_MEMPOOL(NETCONN, MEMP_NUM_NETCONN, sizeof(struct netconn), "NETCONN")
#endif /* LWIP_NETCONN || LWIP_SOCKET */
可以通过修改宏定义来实现内存池内容的增减。
内存堆的实现同理,两者可以并存。他们所需要的内存(统称LwIP堆栈)空间是独立的,参考编译生成的.map文件如下。
此外,LwIP也给我们提供了其他可选的内存策略。
- 当定义宏MEM_LIBC_MALLOC为1,那么与内存堆相关的代码就不会被编译的,内存堆中的mem_malloc()和mem_free()就会被ANSI
C编译器自带的malloc()和free()替代。(相当于把mem.o占用的空间放到了单片机HEAP中) - 当定义宏MEMP_MEM_MALLOC为1,那么内存池文件memp.c就不会被编译。(相当于把memp.o占用的空间放到了单片机HEAP中)