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

article/2025/10/16 22:11:00

目录

  • 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_tcaps字段拷贝而来,会影响内存申请的优先顺序
  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 )
{/* 若未使能在外部RAM(指SPI RAM)中申请内存,则始终在内部RAM中申请 *//* 可以调用接口heap_caps_malloc_extmem_enable来使能,并指定malloc_alwaysinternal_limit */if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) {return heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);} else {void *r;/* 若使能在外部RAM(指SPI RAM)中申请内存,待申请内存小于阈值则在内部RAM申请 */if (size <= (size_t)malloc_alwaysinternal_limit) {r=heap_caps_malloc_base( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL );/* 否则在外部RAM申请(外部RAM容量比内部RAM大很多) */} else {r=heap_caps_malloc_base( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM );}if (r==NULL) {/* 申请失败的话就放款caps再挣扎一次 */r=heap_caps_malloc_base( size, MALLOC_CAP_DEFAULT );}/* 还是申请失败 */if (r==NULL){/* 则执行内存申请失败时的回调函数 *//* 回调函数可通过heap_caps_register_failed_alloc_callback注册 *//* 并且可以配置在回调函数执行完之后系统abort */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) {/* 指定的caps若含有MALLOC_CAP_EXEC,则意味着需要的内存是IRAM(I/DRAM也行,但地址得是ibus地址) *//* 此时caps不能再包含MALLOC_CAP_8BIT或MALLOC_CAP_DMA *//* MALLOC_CAP_8BIT或MALLOC_CAP_DMA应该用于数据(DRAM) */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) {/* 32-bit可访问的当然要求4字节对齐 */size = (size + 3) & (~3); // int overflow checked above}/* 遍历heap_t的caps数组 */for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {heap_t *heap;/* 遍历所有堆串成的单向链表 *//* heap_t的caps数组和堆注册(介绍堆初始化的时候会进一步说明)的顺序共同决定了内存申请的优先级 */SLIST_FOREACH(heap, &registered_heaps, next) {if (heap->heap == NULL) {continue;}if ((heap->caps[prio] & caps) != 0) {/* 若当前heap->caps[prio]全部或部分含有指定的caps *//* 则查看当前堆所有属性是否可以完全满足指定的caps */if ((get_all_caps(heap) & caps) == caps) {/* 可以满足的话,就可以进行内存申请了 *//* 若需要在IRAM中申请内存,但当前堆在I/DRAM *//* 那么申请也是可以申请的,但要额外做一些事情 */if ((caps & MALLOC_CAP_EXEC) && esp_ptr_in_diram_dram((void *)heap->start)) {/* 先申请内存,且多申请4字节 */ret = multi_heap_malloc(heap->heap, size + 4);if (ret != NULL) {/* 若能成功申请到,则将dbus地址转换为相应的ibus地址 *//* 且把dbus地址藏在位于开头的多申请出的4个字节里 *//* 最终返回给用户的是ibus地址 */return dram_alloc_to_iram_addr(ret, size + 4);}} else {/* 通常会在这个分支申请内存(没什么特殊情况) */ret = multi_heap_malloc(heap->heap, size);if (ret != NULL) {return ret;}}}}}}/* 没有申请到内存,则返回NULL */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;}/* 对于I/DRAM中的ibus地址,申请的时候是做了一些特殊操作的 */if (esp_ptr_in_diram_iram(ptr)) {/* 把藏起来的dbus地址取出 */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");/* 释放内存,地址是申请的时候藏起来的dbus地址 */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_checktlsf_check_pooltlsf堆进行完整性检查,不多赘述了。

3.3 其它

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

参考

[1] esp-idf


http://chatgpt.dhexx.cn/article/T5Wk2KRM.shtml

相关文章

SSL/TLS 证书管理

SSL 证书发现 随着组织的 IT 基础架构的扩展&#xff0c;他们为每台计算机获取证书以保护其资源和域。此外&#xff0c;开发人员通常会创建许多自签名证书&#xff0c;以便在产品的开发阶段保护内部网络。组织通常最终会拥有数千个证书。自动发现证书提供了对证书基础结构的完…

TLS协议。

IPSec通过安全关联实现IP分组安全关联两端之间的安全传输过程&#xff0c;TLS通过建立安全连接实现数据在两个应用进程之间的安全传输过程。TLS建立安全连接时&#xff0c;实现安全连接两端应用进程之间的双向身份鉴别过程&#xff0c;保证经过安全连接传输的数据的保密性和完整…

SSL/TLS协议

SSL/TLS协议 文章目录 SSL/TLS协议1 协议历史2 协议的目标3 SSL体系结构4 两个主要的协议5 SSL的两个重要概念6 会话状态参数7 连接状态参数8 SSL Record Protocol9 SSL记录协议中的操作10 SSL握手协议使用的消息11 SSL握手协议的流程 1 协议历史 1994年Netscape开发了SSL(Sec…

tls协议

tls 前言一、TLS 协议的组成二、ECDHE1.连接过程 三、TLS1.31.过程 前言 tls是ssl协议的标准化&#xff0c;处于应用层(5层架构)和会话层(OSI)&#xff0c;有着rsa方式和ecdhe两种&#xff0c; 一、TLS 协议的组成 握手协议&#xff1a;负责在客户端和服务器之间协商决定密码算…

TLS协议详解!

TLS简介 SSL 即安全套接字层&#xff0c;它在 OSI 七层网络模型中处于第五层&#xff0c;SSL 在 1999 年被 IETF(互联网工程组)更名为 TLS &#xff0c;即传输安全层&#xff0c;直到现在&#xff0c;TLS 一共出现过三个版本&#xff0c;1.1、1.2 和 1.3 &#xff0c;目前最广…

esp-idf的内存管理——tlsf算法

目录 1 最初还不是tlsf2 为什么要引入tlsf3 tlsf算法概览4 idf中使用的tlsf算法的设计与实现4.1 先看结构4.1.1 管理内存块的结构4.1.2 管理tlsf堆的结构 4.2 优化内存块的元数据开销4.3 一二级位图索引的计算4.4 tlsf堆的创建与销毁4.4.1 tlsf堆的创建4.4.2 tlsf堆的销毁 4.5 …

TLSF算法1:二级索引的计算

TLSF算法1:二级索引的计算 一、什么是TLSF算法二,f的确定三、s的确定四、实验结果一、什么是TLSF算法 在嵌入式系统中,内存需要在分配和释放时有一个确定的相应时间,才能进一步分析其实时任务的可调度性。因此TLSF算法是一个十分适用嵌入式领域的动态内存分配算法。在关于T…

TLSF 内存分配算法详解

文章目录 1. DSA 背景介绍1.1 mmheap1.2 mmblk 2. TLSF 原理2.1 存储结构2.2 内存池初始化2.3 free2.4 malloc 参考资料 1. DSA 背景介绍 动态内存管理算法 DSA&#xff0c;Dynamic storage allocation。RTOS 一般情况下动态内存使用malloc申请分配&#xff0c;但是存在两个缺…

tlsf算法-概念、原理、内存碎片问题分析

文章目录 一、tlsf算法介绍二、tlsf代码分析2.1 mapping_search2.2 search_suitable_block 三、参考链接 一、tlsf算法介绍 tlsf&#xff08;全称Two-Level Segregated Fit&#xff0c;内存两级分割策略算法&#xff09;&#xff0c;第一级&#xff08;first level&#xff0c…

数学建模系列-优化模型(三)---排队论模型

所谓排队论模型&#xff0c;就是指一个模型中可根据交易简单的需要分为三个部分&#xff1a; &#xff08;1&#xff09;顾客造访 &#xff08;2&#xff09;服务顾客时间 &#xff08;3&#xff09;若不空闲&#xff0c;则顾客需要排队 下面是对于排队论模型的建模以及解决方法…

无线传感器网络:排队论(Queueing Theory)模型

文章目录 The arrival ProcessQueueing SystemThe M/M/1 queueThe M/M/1/N queue References 排队理论已被用于评估通信网络的性能很多年了。早在1917年&#xff0c;丹麦数学家 Erlang 就将该理论用于电话交换机的设计&#xff0c;并开创了现在著名的 Erlang-B 和 Erlang-C 公式…

排队论(Queuing theory)简介

Preliminary Questions 1.What does affect the performance of ——a computer system? ——a computer network? ——an Internet service? 2 How do we measure the performance of ——a computer system? ——a computer network? ——an Internet service…

泊松分布,指数分布与排队论模型

转自http://www.ruanyifeng.com/blog/2015/06/poisson-distribution.html泊松分布和指数分布&#xff1a;10分钟教程 https://www.bilibili.com/video/BV1L5411x7vH?p44北京工业大学运筹学 泊松分布与指数分布 泊松分布 泊松分布就是描述某段时间内&#xff0c;事件具体的…

排队论模型之M/M/S模型

相关参数 模型推导 例题 代码实现&#xff1a; s3; %服务台的个数 mu0.4; %单位时间内能服务的顾客数 lambda0.9; %单位时间内到达的顾客数rolambda/mu; rosro/s; sum10; %求解P0时把其分成两部分计算&#xff0c;分别为sum1,sum2 for i0:(s-1) sum1sum1ro.^i/factorial(i); …

排队模型和排队系统仿真

排队模型和排队系统仿真 Gary哥哥 2021.1.31 排队论又称随机服务系统&#xff0c;是研究系统随机聚散现象和随机服务系统工作过程的数学理论和方法&#xff0c;是运筹学的一个分支。排队论的基本思想是 1909 年丹麦数学家 A.K. 埃尔朗在解决自动电话设计问题时开始形成的&#…

数学模型算法实现之排队论

排队问题 https://wenku.baidu.com/view/475f68cb65ce0508763213a7.html 排队论详解 排队论又叫随机服务系统理论或公用事业管理中的数学方法。它是研究各种各样的排队现象的。它所要解决的主要问题是:在排队现象中设法寻求能够达到服务标准的最少设备,使得在满足服务对象…

排队论模型(二):生灭过程 、 M / M /s 等待制排队模型、多服务台模型

排队论模型&#xff08;一&#xff09;&#xff1a;基本概念、输入过程与服务时间的常用概率分布 排队论模型&#xff08;二&#xff09;&#xff1a;生灭过程 、 M / M /s 等待制排队模型、多服务台模型 排队论模型&#xff08;三&#xff09;&#xff1a;M / M / s/ s 损失…

排队论模型(四):M / M / s 混合制排队模型

排队论模型&#xff08;一&#xff09;&#xff1a;基本概念、输入过程与服务时间的常用概率分布 排队论模型&#xff08;二&#xff09;&#xff1a;生灭过程 、 M / M /s 等待制排队模型、多服务台模型 排队论模型&#xff08;三&#xff09;&#xff1a;M / M / s/ s 损失…

浅谈排队论

排队论起源于 1909 年丹麦电话工程师 A. K&#xff0e;爱尔朗的工作&#xff0c;他对电话通话拥挤问 题进行了研究。1917 年&#xff0c;爱尔朗发表了他的著名的文章—“自动电话交换中的概率理 论的几个问题的解决”。排队论已广泛应用于解决军事、运输、维修、生产、服务、库…

超详细的排队论建模

排队系统 顾客输入过程 顾客源&#xff08;总体&#xff09;&#xff1a;有限/无限顾客到达方式&#xff1a;逐个/逐批&#xff08;主要是逐个&#xff09;顾客到达间隔&#xff1a;随机型/确定型顾客到达&#xff1a;相互独立/相互关联输入过程&#xff1a;平稳/非平稳 排队…