栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

esp-idf的内存管理——tlsf之上的封装

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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 其它
  • 参考

1 为什么要封装

封装通常会降低效率,但能够带来诸如通用性提升等好处,idf在tlsf的基础上增加封装,其作用可以概括为以下两点:

  1. 上下层接口分离,上层接口和底层实际使用的内存管理算法无关,这样,以后有更优秀的算法,也可以很方便移植
  2. 单纯的tlsf没办法满足idf的需要,比如不支持内存的caps,没有堆调试特性,因而增加上层封装以提供更多的功能
2 先看结构

还是从结构来看上层封装的具体设计与实现。上层封装更具体的说,有两层,分别是multi heap层和更上层的heap caps层(不妨就这么称呼吧)。其实现分别位于multi_heap.c以及heap_caps.c。先看multi heap层,这一层提供了可选的堆调试配置项,这里仅介绍不含堆调试的部分(堆调试以及上层的堆初始化会在后面的博客中专门介绍)。

2.1 multi heap

multi 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;

字段不是很多,下面一一介绍:

  1. lock:自旋锁的指针,指向保护multi heap的自旋锁
  2. free_bytes:堆的空闲字节
  3. minimum_free_bytes:free_bytes的历史最小值(可以用于观察堆的历史占用情况)
  4. pool_size:堆数据部分的大小
  5. heap_data:指向堆数据部分(pool)
note1

有个恶心的地方,struct multi_heap_info被重命名为heap_t(尽管只在源文件中,没有暴露出去),而heap caps层的元数据结构也叫heap_t,注意区分。

note2

一个完整的堆包括元数据和数据部分,idf将其中的数据部分称为pool。需要注意的是,不同层次上来看,pool所表示的范围是不一样的。比如tlsf堆的pool就是真正的数据部分,而对于multi heap来说,pool还包括tlsf堆的元数据。

2.2 heap caps

heap 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;

上述字段的含义如下:

  1. caps:从soc_memory_type_desc_t的caps字段拷贝而来,会影响内存申请的优先顺序
  2. start:内存区域的起始地址(包含)
  3. end:内存区域的结束地址(不包含)
  4. heap_mux:保护堆的串行访问的自旋锁本体
  5. heap:指向内存区域的起始位置
  6. next:多堆串成单向链表
2.3 层次关系

multi_heap.c以及heap_caps.c的层次关系如下:

3 再看接口 3.1 内存的申请

先梳理一下接口调用情况:

  • malloc
    • heap_caps_malloc_default
      • multi_heap_malloc
        • tlsf_malloc

然后梳理一遍源码:

malloc函数调用heap_caps_malloc_default实现功能,后者主要调用heap_caps_malloc_base实现功能:

IRAM_ATTR void *heap_caps_malloc_default( size_t size )
{
    
    
    if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) {
        return heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
    } else {
        void *r;
        
        if (size <= (size_t)malloc_alwaysinternal_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 accessible RAM
    }

    if (caps & MALLOC_CAP_32BIT) {
        
        size = (size + 3) & (~3); // int overflow 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

然后梳理一遍源码:

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 堆完整性检测

堆的完整性检测接口主要有:

  1. heap_caps_check_integrity
  2. heap_caps_check_integrity_all
  3. heap_caps_check_integrity_addr

这些接口逻辑很简答,没什么好说的,它们主要调用multi_heap_check实现功能,而multi_heap_check会先后调用tlsf堆的tlsf_check和tlsf_check_pool对tlsf堆进行完整性检查,不多赘述了。

3.3 其它

搞明白元数据和层次关系,其它接口应该也就不难理解了,本文就不赘述了。

参考

[1] esp-idf

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/913294.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号