esp32 heap 内存管理简析

嵌入式系统运行时的内存情况是非常值得关注的。本文档用于分析乐鑫ESP32 SDK(版本esp-idf-v3.0-rc1) Heap (堆内存)管理的实现。

 

1:Heap管理主要函数接口与数据结构

 

1.1主要函数接口

ESP32的SDK对于heap部分管理的源码位于路径\esp-idf-v3.0-rc1\components\heap下,可以简单的认为分为两层:heap_caps_init.c与heap_caps.c构成一层函数接口封装;multi_heap.c等文件内是具体函数接口的实现。主要接口:

Heap初始化: voidheap_caps_init(void)

Heap分配:   void *heap_caps_malloc( size_t size,uint32_t caps )

Heap释放:   void heap_caps_free(void *ptr)

ESP32 SDK中的malloc/calloc/free等系统调用,最终都是调用以上函数执行(参见syscall_stub_table)。

 

1.2重要数据结构

Heap_t是Heap管理代码的核心数据结构。但是在heap_private.h与multi_heap.c下各有一个该类型定义。其实并不矛盾,前者是Heap管理中用于描述某个“Heap分区”的;后者定义仅限于multi_heap.c文件内部,作为“Heap分区”下”MetaData头”,示意如下:

esp32 heap 内存管理简析_第1张图片

由数据结构heap_t可以初步探知ESP32SDK的Heap管理的结构:

<1>整个Heap空间被作为多个Heap分区进行管理,描述Heap分区的结构体定义在heap_private.h文件下的heap_t,分区之间用链表关联;

<2> 每个Heap分区实体由MedaData头以及Data区构成:MedaData头定义在multi_heap.c文件下的heap_t;Data区分为已用空间(used space)以及可用空间(free space)。

<2> 每个Heap分区实体由MedaData头以及Data区构成:MedaData头定义在multi_heap.c文件下的heap_t;Data区分为已用空间(used space)以及可用空间(free space)。Data区以HeapBlock(heap_block_t)作为管理单元,通过链表关联。

 

2: heap_caps_init与Heap空间的初始化

2.1Heap空间初始化

系统调用heap_caps_init实现Heap空间初始化的过程,实质就是初始化1.2节所示数据结构的过程。简单的看可以分为三个步骤:

<1> 遍历当前系统的可用内存区域,并对类型相同的相邻区域进行合并

<2> 将可用内存区域通过register_heap初始化为Heap分区的结构(建立Meta Data头以及数据区的链表)

<3> 将所有Heap分区信息通过链表连接到registered_heaps

 

2.2heap_caps_init

比对2.1节三个步骤,截取主要代码如下:

<1>遍历与合并当前系统内存可用区域,内存区域见数组soc_memory_regions

esp32 heap 内存管理简析_第2张图片

<2>将所有可用内存区初始化为Heap分区

esp32 heap 内存管理简析_第3张图片

<3>将所有Heap分区信息通过链表连接到registered_heaps

esp32 heap 内存管理简析_第4张图片

以上都比较好理解。

 

2.3register_heap

register_heap将普通的内存区域初始化为Heap分区结构,multi_heap_register_impl是其最终实现功能的接口。下图示意这个过程:

esp32 heap 内存管理简析_第5张图片

<1>一段可用的连续内存被初始化为Heap分区时,分区起始地址会写入一个MetaData头(即是multi_heap_info类型)。MetaData头后紧接着是第一个真正可以用来分配的内存块block0,block0包含一个信息头heap_block_t以及实际数据区。在Heap分区的最后,有一个heap_block_t的信息头作为整个分区的结尾。heap_block_t结构中有指向下个节点的指针,因此分区所有block组成链表用于访问。

<2>当一段可用的连续内存被初始化Heap分区,它的实际可用空间是:

esp32 heap 内存管理简析_第6张图片

对应上图的“实际数据区”


3: heap_caps_malloc与Heap空间分配

Heap空间是系统运行期间交给用户申请的,如何分配与释放是关键问题。本节描述关于Heap空间如何分配的问题。

 

3.0次要问题

为了突出“Heap空间分配机制”这个主干,先对几个Heap分配的次要问题进行说明:

<1> Malloc Cap:ESP32 SDK的Heap源码中,经常出现函数接口的参数是Malloc Caps。该参数的涵义是“待分配内存的特性”。例如:8/16/32位对齐,内存能否执行程序,是内部/外部内存等等。这个其实非常重要,与ESP32特性紧密相关,但本文档不做分析,先从通用角度分析Heap空间的分配机制。

<2>内存临界区与跨界问题:某些时刻Heap的分配可能会涉及不同位置的内存空间。这里由于对ESP32的内存分配也并不是很了解,因此暂时略去。后期作为特别关注点与内存以及Flash的情况一起描述,主要针对OTA升级功能。

 

3.1Heap空间分配(用户Heap空间申请)

对于Heap空间的分配过程,通过对代码的理解,可以简单的概括为三个步骤:

当用户向系统通过malloc申请Heap内存空间时:

<1>系统会遍历每个Heap分区(通过registered_heaps),找到满足申请内存类型(例如:3.0所述Malloc Cap)的分区;再依次遍历分区下所有可用的Heap Block,找到可用空间满足申请内存大小的Heap Block。

<2>遍历所有Heap Block期间,会出现几种可能:

A:若Heap Block的可用空间小于用户申请的内存大小,显然不能使用,继续查找下一个Heap Block;

B:若Heap Block的可用空间正好等于用户申请的内存大小,这时系统出现了最佳的分配选择,策略选择该Heap Block作为本次分配的Best Heap Block,停止遍历。

C:若Heap Block的可用空间大于用户申请的内存大小,则先行记录下这个Heap Block的位置以及它的实际可用空间大小,然后继续遍历查找;在找到下一个满足条件的Heap Block时,比较两者,选取可用空间较小的记录下来。策略的目的即是保证最终得到一个既满足用户申请需求又最不浪费空间的Best Heap Block。

<3>对应步骤<2>选出的Best Heap Block:

A:若Best HeapBlock不存在,意味着用户申请失败;

B:若Best HeapBlock空间正好满足用户申请的需求,则无需另做处理,直接给到用户即可;

C:若Best HeapBlock的空间除去用户申请的需求还有盈余,则需要进行分割。盈余部分需要分割成为新的可用(空闲)Heap Block,等待下次内存申请。

 

下图是关于Heap空间分配的示意,体现出HeapBlock的连接变化:

esp32 heap 内存管理简析_第7张图片

        esp32 heap 内存管理简析_第8张图片

3.2Heap空间分配相关函数

ESP32 SDK Heap空间分配的相关函数以及调用过程:(未实际调试,猜测,应该不会错)

Malloc ->

_malloc_r ->

heap_caps_malloc_default ->

heap_caps_malloc ->

multi_heap_malloc ->

multi_heap_malloc_impl

另外的重要函数,用于分割Best Heap Block: split_if_necessary

 

4: heap_caps_free与Heap空间释放

Heap空间的释放是分配的逆过程。释放某个Block时同时还会探查该Block的前后Block是否可用;若可用则合并前/后Block:

esp32 heap 内存管理简析_第9张图片

esp32 heap 内存管理简析_第10张图片

ESP32 SDK Heap空间释放的相关函数以及调用过程:

_free_r->

heap_caps_free->

multi_heap_free->

multi_heap_free_impl->

merge_adjacent

 

5: 其它的一些补充与问题

5.1

以上关于Hesp空间分配/释放的描述,隐去了一些细节。事实上可以认为Heap分区的Block管理使用了两个链表:一个用于Block的连接,另一个用于连接空闲的Block,两者被同一个结构体包括了。这里稍微关注一下HeapBlock头的定义就知道了;


5.2

关于Heap空间释放的描述是有问题的,代码中发现,系统只是合并当前被释放的Block以及其“前一个”和“后一个”空闲可用的Block,这些Block可能并不是地址连续的。(例如第4节第6个示意图,Block0与Block3可能在空闲Block的链表上相邻)这样就会有问题,如果地址不连续,空间是怎么被访问的?这个问题还需要继续看代码或者调试理解。


6: ESP32Heap空间使用情况的观测

根据第1节对于Heap管理数据结构的描述就可以知道,查看Heap空间的使用情况,就是遍历各个分区,遍历各个分区下的Block链表。

相关函数有:

heap_caps_dump

heap_caps_get_minimum_free_size

heap_caps_get_free_size

heap_caps_print_heap_info

 

这个地方也有疑问,使用heap_caps_get_free_size获取的Heap空间居然比用链接文件链接时生成的数据处理后得到的Heap空间大很多。所以肯定是有个人没弄明白的地方,等探讨ESP32 Flash与内存分布再做理解。

 

7: Heap空间管理机制的问题与优化

TBD


你可能感兴趣的:(物联网)