esp-idf的内存管理——tlsf之上的封装
目录
- 1 为什么要封装
- 2 先看结构
- 2.1 multi heap
- note1
- note2
- 2.2 heap caps
- 2.3 层次关系
- 3 再看接口
- 3.1 内存的申请
- 3.2 内存的释放
- 3.2 堆完整性检测
- 3.3 其它
- 参考
封装通常会降低效率,但能够带来诸如通用性提升等好处,idf在tlsf的基础上增加封装,其作用可以概括为以下两点
- 上下层接口分离,上层接口和底层实际使用的内存管理算法无关,这样,以后有更优秀的算法,也可以很方便移植
- 单纯的tlsf没方法满足idf的需要,比如不支持内存的caps,没有堆调试特性,因而增加上层封装以提供更多的功能
还是从结构来看上层封装的具体设计与实现。上层封装更具体的说,有两层,分别是multi heap层和更上层的heap caps层(不妨就这么称呼吧)。其实现分别位于multi_heap.c以及heap_caps.c。先看multi heap层,这一层提供了可选的堆调试配置项,这里仅介绍不含堆调试的部分(堆调试以及上层的堆初始化会在后面的博客中专门介绍)。
2.1 multi heapmulti heap译为多堆,因为esp32系列芯片不止一块内存,比如SRAM、RTC fast mem等,且每块内存也可能因为内存保留而被进一步分割。最终就会存在多个连续且相互之间不连续的内存区域,这些内存区域都会建堆进行管理,那么就会有多个堆,故而叫多堆(我猜的)。multi heap层定义了struct multi_heap_info来管理堆,这个结构的内容如下
typedef struct multi_heap_info { void lock; size_t free_bytes; size_t minimum_free_bytes; size_t pool_size; tlsf_t heap_data; } heap_t;
字段不是很多,下面一一介绍
- lock自旋锁的指针,指向保护multi heap的自旋锁
- free_bytes堆的空闲字节
- minimum_free_bytesfree_bytes的历史最小值(可以用于观察堆的历史占用情况)
- pool_size堆数据部分的大小
- heap_data指向堆数据部分(pool)
有个恶心的地方,struct multi_heap_info被重命名为heap_t(尽管只在源文件中,没有暴露出去),而heap caps层的元数据结构也叫heap_t,注意区分。
note2一个完整的堆包括元数据和数据部分,idf将其中的数据部分称为pool。需要注意的是,不同层次上来看,pool所表示的范围是不一样的。比如tlsf堆的pool就是真正的数据部分,而对于multi heap来说,pool还包括tlsf堆的元数据。
2.2 heap capsheap caps层定义了heap_t来管理堆,这个结构的内容如下
typedef struct heap_t_ { uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS]; intptr_t start; intptr_t end; multi_heap_lock_t heap_mux; multi_heap_handle_t heap; SLIST_ENTRY(heap_t_) next; } heap_t;
上述字段的含义如下
- caps从soc_memory_type_desc_t的caps字段拷贝而来,会影响内存申请的优先顺序
- start内存区域的起始地址(包含)
- end内存区域的结束地址(不包含)
- heap_mux保护堆的串行访问的自旋锁本体
- heap指向内存区域的起始位置
- next多堆串成单向链表
multi_heap.c以及heap_caps.c的层次关系如下
先梳理一下接口调用情况
- malloc
- heap_caps_malloc_default
- multi_heap_malloc
- tlsf_malloc
- multi_heap_malloc
- heap_caps_malloc_default
然后梳理一遍源码
malloc函数调用heap_caps_malloc_default实现功能,后者主要调用heap_caps_malloc_base实现功能
IRAM_ATTR void heap_caps_malloc_default( size_t size ) { if (malloc_alaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) { return heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL); } else { void r; if (size <= (size_t)malloc_alaysinternal_limit) { r=heap_caps_malloc_base( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL ); } else { r=heap_caps_malloc_base( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM ); } if (r==NULL) { r=heap_caps_malloc_base( size, MALLOC_CAP_DEFAULT ); } if (r==NULL){ heap_caps_alloc_failed(size, MALLOC_CAP_DEFAULT, __func__); } return r; } }
heap_caps_malloc_base
IRAM_ATTR static void heap_caps_malloc_base( size_t size, uint32_t caps) { void ret = NULL; if (size > HEAP_SIZE_MAX) { return NULL; } if (caps & MALLOC_CAP_EXEC) { if ((caps & MALLOC_CAP_8BIT) || (caps & MALLOC_CAP_DMA)) { return NULL; } caps |= MALLOC_CAP_32BIT; // IRAM is 32-bit aessible RAM } if (caps & MALLOC_CAP_32BIT) { size = (size + 3) & (~3); // int overflo checked above } for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) { heap_t heap; SLIST_FOREACH(heap, ®istered_heaps, next) { if (heap->heap == NULL) { continue; } if ((heap->caps[prio] & caps) != 0) { if ((get_all_caps(heap) & caps) == caps) { if ((caps & MALLOC_CAP_EXEC) && esp_ptr_in_diram_dram((void )heap->start)) { ret = multi_heap_malloc(heap->heap, size + 4); if (ret != NULL) { return dram_alloc_to_iram_addr(ret, size + 4); } } else { ret = multi_heap_malloc(heap->heap, size); if (ret != NULL) { return ret; } } } } } } return NULL; }
在未使能堆调试功能时,multi_heap_malloc的实现很简单
void multi_heap_malloc(multi_heap_handle_t heap, size_t size) __attribute__((alias("multi_heap_malloc_impl"))); ...... void multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size) { if (size == 0 || heap == NULL) { return NULL; } multi_heap_internal_lock(heap); void result = tlsf_malloc(heap->heap_data, size); if(result) { heap->free_bytes -= tlsf_block_size(result); heap->free_bytes -= tlsf_alloc_overhead(); if (heap->free_bytes < heap->minimum_free_bytes) { heap->minimum_free_bytes = heap->free_bytes; } } multi_heap_internal_unlock(heap); return result; }3.2 内存的释放
同样的,先梳理一下接口调用情况
- free
- heap_caps_free
- multi_heap_free
- heap_caps_free
然后梳理一遍源码
free函数调用heap_caps_free实现功能,heap_caps_free主要调用multi_heap_free实现功能
IRAM_ATTR void heap_caps_free( void ptr) { if (ptr == NULL) { return; } if (esp_ptr_in_diram_iram(ptr)) { uint32_t dramAddrPtr = (uint32_t )ptr; ptr = (void )dramAddrPtr[-1]; } heap_t heap = find_containing_heap(ptr); assert(heap != NULL && "free() target pointer is outside heap areas"); multi_heap_free(heap->heap, ptr); }
在未使能堆调试功能时,multi_heap_free的实现很简单
void multi_heap_free(multi_heap_handle_t heap, void p) __attribute__((alias("multi_heap_free_impl"))); ...... void multi_heap_free_impl(multi_heap_handle_t heap, void p) { if (heap == NULL || p == NULL) { return; } assert_valid_block(heap, block_from_ptr(p)); multi_heap_internal_lock(heap); heap->free_bytes += tlsf_block_size(p); heap->free_bytes += tlsf_alloc_overhead(); tlsf_free(heap->heap_data, p); multi_heap_internal_unlock(heap); }3.2 堆完整性检测
堆的完整性检测接口主要有
- heap_caps_check_integrity
- heap_caps_check_integrity_all
- heap_caps_check_integrity_addr
这些接口逻辑很简答,没什么好说的,它们主要调用multi_heap_check实现功能,而multi_heap_check会先后调用tlsf堆的tlsf_check和tlsf_check_pool对tlsf堆进行完整性检查,不多赘述了。
3.3 其它搞明白元数据和层次关系,其它接口应该也就不难理解了,在这里不赘述了。
参考[1] esp-idf
空调维修
- 海信电视维修站 海信电视维修站点
- 格兰仕空调售后电话 格兰仕空调维修售后服务电
- 家电售后服务 家电售后服务流程
- 华扬太阳能维修 华扬太阳能维修收费标准表
- 三菱电机空调维修 三菱电机空调维修费用高吗
- 美的燃气灶维修 美的燃气灶维修收费标准明细
- 科龙空调售后服务 科龙空调售后服务网点
- 华帝热水器维修 华帝热水器维修常见故障
- 康泉热水器维修 康泉热水器维修故障
- 华凌冰箱维修电话 华凌冰箱维修点电话
- 海尔维修站 海尔维修站点地址在哪里
- 北京海信空调维修 北京海信空调售后服务
- 科龙空调维修 科龙空调维修故障
- 皇明太阳能售后 皇明太阳能售后维修点
- 海信冰箱售后服务 海信冰箱售后服务热线电话
- 海尔热水器服务热线