LwIP系列(2):动态内存池管理(memp.c)详细分析

news2025/1/15 23:35:52

前言

我们在学习Lwip源码时,内存管理是绕不开的一个重点,我们在看相关的代码时,经常会看到memp_mallocmem_malloc, 其中:
(1)memp_malloc是从内存池中申请内存,具体实现在memp.c + memp.h。
(2)mem_malloc则是从内存堆中申请内存,具体实现在mem.c + mem.h中。
这两个API的区分也很容易,“p”是pool的简称,所以memp代表从内存池,mem是从内存堆。

内存池与内存堆的区别?

内存堆

内存堆其实很好理解,可以简单的认为编辑器默认的malloc就是从内存堆中申请的,与**【堆】** 对应的是 【栈】 ,堆的生长方向是从低地址->高地址,栈的生长方向是从高地址->低地址。使用malloc申请的变量都是从【堆】中申请,临时变量或局部变量是从【栈】中默认申请。更详细的区别本文就不赘述了,后面可以单独的文章说明。
所以内存堆,就是自己实现一套malloc程序。

内存池

内存池的理解重点在“池”,所谓【池】其实就是要提前挖好坑,提前占位,类似线程池,都是提前先定义或申请好,等到需要申请使用时,直接从内存池中拿出1块内存使用即可。再具体一些就是,提前申请好固定内存类型数组,这个内存数组就是内存池,从内存池中申请内存,就是从内存数组中拿出1个可用的成员。释放内存到内存池,相当于内存再恢复到内存数组中(这样说可能还是有点不严谨,凑合理解吧)。

为什么需要内存池

因为内存池管理有优点,比如:

  1. 速度块,因为内存池在程序编译时,就自动在内存栈中提前申请了内存池数组,所以再申请的时候,就可以很快的从内存池中取处一片内存使用。这一点对于以太网通信就非常重要了,因为以太网的速率是非常块的,而lwip又是在资源比较弱的单片机上实用,所以内存池的管理,是一种以空间换时间的方法。
  2. 避免内存泄漏。由于内存池是提前从栈中申请的内存数组,申请和释放都是围绕着这个内存数组的,所以顶多会有一点浪费(比如内存数组申请的个数多了),而不会造成内存泄漏。这一点内存堆就不能保证。

当然内存池也有缺点,比如:

  1. 内存池必须是固定结构、固定大小,所以不够灵活,在lwip中,一般是通过opt.h 进行配置的。
  2. 可能会造成使用浪费,注意这里说的是“浪费”而不是“泄漏”,浪费的含义是,我们一半会分配内存池数组稍微大一些,防止不够用。

内存池,内存堆的使用场景

  1. 内存池一般用在PBUF_POOL类型的需求,比如以太网原始数据接收。还有就是各种固定的tcb、pcb使用,比如tcp_pcb、udb_pcb、pbuf_pcb 等等。
  2. 内存堆一般用在PBUF_RAM,还有一些不太固定格式、不太常用的内存使用上。

LwIP 动态内存池管理分析

如果我们直接查看memp.c ,可能很多人开始都会一脸懵逼,起码我是的,因为这个文件中,作者用了大量的宏定义高级用法,我们很难一下子看懂内存池管理的具体逻辑,所以我们可以借助IDE的预编译功能,将memp.c 通过预编译,翻译成没有宏定义的文件,方便我们查看。这里我们可以借助MDK的输出预编译文件的功能,具体设置如下图:
在这里插入图片描述

简化版程序分析

上述的方法能够输出预编译 memp.i ,应该就能很容易分析memp.c的实现原理了,这里我们再通过一个类似的、简化版、方便理解的示例程序,进一步分析memp.c的原理。
示例程序code如下:

#include <stdio.h>
#include <string.h>


typedef struct slist_s{
    struct slist_s *next;
} slist_t;

struct memp_desc{
    int size;
    int num;
    char *pool_buf;
    slist_t **list;
};

struct test_pcb{
    int a;
    int b;
    int c;
};

static char memp_test_pcb_base[4 * (sizeof(struct test_pcb))];
static slist_t *memp_list_test_pcb;
const struct memp_desc memp_test_pcb_desc = {
    sizeof(struct test_pcb),
    4, 
    memp_test_pcb_base,
    &memp_list_test_pcb
};

void memp_pool_init(struct memp_desc *desc)
{
    int i = 0;
    slist_t *list;

    *desc->list = 0;

    list = (slist_t *)(void *)(desc->pool_buf);
    for(i = 0; i < desc->num; ++i){
        list->next = *(desc->list);
        *(desc->list) = list;

        list = (slist_t *)(void *)((char *)list + desc->size);
    }
}

void *memp_malloc_pool(const struct memp_desc *desc)
{
    slist_t *list;

    list = *(desc->list);

    if(list != NULL){
        *(desc->list) = list->next;
        return (char *)list;
    }

    return 0;
}

void memp_free_pool(const struct memp_desc *desc, void *mem)
{
    slist_t *list;

    list = (slist_t *)(void *)((char *)mem);

    list->next = *(desc->list);
    *(desc->list) = list;
}



int main(void)
{
    int i;
    struct test_pcb *pcb1, *pcb2;

    printf("hello world.\n");

    memp_pool_init(&memp_test_pcb_desc);

    for(i = 0; i < 100; i++){
        pcb1 = memp_malloc_pool(&memp_test_pcb_desc);
        if(pcb1 != NULL){
            printf("malloc [%d] succ.\n", i*2 + 1);
            memset(pcb1, 0, sizeof(struct test_pcb));

            pcb1->a = i + 1;
            pcb1->b = i + 2;
            pcb1->c = i + 3;
            printf("pcb1 a = %d, b = %d, c = %d \n", pcb1->a, pcb1->b, pcb1->c);
        }

        pcb2 = memp_malloc_pool(&memp_test_pcb_desc);
        if(pcb2 != NULL){
            printf("malloc [%d] succ.\n", i*2 + 2);
            memset(pcb2, 0, sizeof(struct test_pcb));

            pcb2->a = i + 4;
            pcb2->b = i + 5;
            pcb2->c = i + 6;
             printf("pcb2 a = %d, b = %d, c = %d \n", pcb2->a, pcb2->b, pcb2->c);
        }

        printf("free [%d] .\n", i*2 + 1);
        memp_free_pool(&memp_test_pcb_desc, pcb1);

        printf("free [%d] .\n", i*2 + 2);
        memp_free_pool(&memp_test_pcb_desc, pcb2);
    }
    return 0;
}

上述代码可以直接运行。
需要说明的是,即便是上面简化版的程序,我们理解起来还是有一定的门槛的,因为上面涉及了很多中C语言中高级用法,比如:

  1. 结构体中包含了该结构体指针类型的成员变量。
  2. 结构体指针也是一种指针,大小也类似于int *指针。
  3. 结构体地址与结构体的首个成员函数的地址是相同的。
  4. 二级指针,二级指针的值是一级指针的地址。
  5. 指针的间接引用是通过【*】符号实现的。
  6. 数据类型的强转,意味着我们可以使用转换后的数据类型顺序来访问原来的数据内存,这一点非常重要,比如我们将char *转换为 struct slist_s *, 这就意味着,我们可以通过操作struct slist_s 类型的成员函数来向char *的内存中写入数据。

附录:Lwip 内存池初始化后的示例图

在这里插入图片描述
简单的说:

  1. memp_pools类似于内存池的head + 状态存储单元。
  2. 内存池就是内存数组,每种类型的内存池,对应一个memp_pools的成员变量。
  3. 内存池的读取和释放都是通过memp_pools来实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/658888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

木马攻击与防护

目录 一、初识Trojan木马 1.1 木马概念 1.2 木马特点 1.2.1 欺骗性 1.2.2 隐蔽性 1.2.3 非授权性 1.3 病毒和木马 1.3.1 病毒的特点 1.3.2 病毒的主要目的 1.3.3 病毒例子 1.3.4 木马程序企图 1.3.5 木马危害 1.3.6 病毒与木马的区别 1.4 木马种类 1.4.1 远程访…

机器学习之KNN算法:基于pytorch在MNIST数据集上实现数据分类预测

1 KNN算法介绍 KNN算法又叫做K近邻算法&#xff0c;是众多机器学习算法里面最基础入门的算法。KNN算法是最简单的分类算法之一&#xff0c;同时&#xff0c;它也是最常用的分类算法之一。KNN算法是有监督学习中的分类算法&#xff0c;它看起来和Kmeans相似&#xff08;Kmeans是…

CMake中的find_package(xxx REQUIRED)在windows平台怎么解

最近在编译FastDDS时&#xff0c;遇到了这个问题&#xff0c;使用CMake构建时提示找不到库。 下载的源代码不能一次性编过是最让人头疼的问题&#xff0c;这种开源代码通常都是迭代了很多版本&#xff0c;各种配置信息如果不在文档中说明&#xff0c;全靠自己去摸索确实会让人头…

idea运行java项目提示异常: java.security.InvalidKeyException: Illegal key size

idea运行java项目提示异常&#xff1a;java.lang.IllegalArgumentException: java.security.InvalidKeyException: Illegal key size 参考&#xff1a;java.security.InvalidKeyException: Illegal key size_gqltt的博客-CSDN博客 产生错误原因&#xff1a;为了数据代码在传输过…

4、做什么类型的产品经理

1、如何选择适合自己的产品经理岗位 怎么选择适合自己的这个产品经理岗位呢&#xff1f;建议大家是先考虑行业&#xff0c;再考虑其他的。 考虑行业就是说我要做什么行业的产品经理,然后再考虑在这个行业里面具体的你要做前端还是后端或者是APP端&#xff0c;还是web端&#x…

【MySQL】不就是MySQL——索引

前言 嗨&#xff01;小伙伴们周末快乐呀&#xff01;想必你们周末都在家里边呆着吧&#xff0c;外面实在是太热了&#xff01;在家里吹着空调做着自己喜欢做的事情吧&#xff01;本期我们主要学习的是MySQL中的约束条件。 目录 前言 索引概述 外键约束 1.概念 2.语法 1.添加…

【HTML界面设计(二)】说说模块、登录界面

记录很早之前写的前端界面&#xff08;具体时间有点久远&#xff09; 一、说说模板 采用 适配器&#xff08;Adapter&#xff09;原理 来设计这款说说模板&#xff0c;首先看一下完整效果 这是demo样图&#xff0c;需要通过业务需求进行修改的部分 这一部分&#xff0c;就是dem…

ch8_2_CPU的指令周期,流水线技术

1.  指令周期 指令周期是指_ CPU从主存取出一条指令, 分析指令&#xff0c;加上执行这条指令的时间。 1.1指令周期 指令周期&#xff1a; 是指cpu&#xff0c;从内存中取出指令&#xff0c;并且执行一条指令所需要的全部时间。 比如 从内存单元中&#xff0c;取出操作数&…

【使用Neo4j进行图数据可视化】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

“面试造火箭,入职拧螺丝”2023最新最全的Java开发八股文合集来了

前言 金三银四招聘旺季马上就到了&#xff0c;不知道大家是否准备好了&#xff0c;面对金三银四的招聘旺季&#xff0c;如果没有精心准备那笔者认为那是对自己不负责任&#xff1b;就我们 Java 程序员来说&#xff0c;多数的公司总体上面试都是以自我介绍项目介绍项目细节/难点…

Java016——Java输入输出语句

一、输出语句 Java常用的输出语句有三种&#xff1a; 1&#xff09;System.out.println(); 换行输出&#xff0c;输出后会自动换行。 //示例 System.out.println("Hello"); System.out.println("World");//输出 Hello World2&#xff09;System.out.pri…

LIN-物理层(收发器)

文章目录 一、显性和隐性二、LIN的供电电压说明三、LIN通道数3.1 单通道3.2 双通道3.3 四通道 一、显性和隐性 LIN总线协议规定其物理层收发器的显性&#xff08;Dominant , 逻辑 “ 0”&#xff0c;电气特性为GND(0V)&#xff09;和隐性电平&#xff08;Recessive , 逻辑 “ …

cgi接口原理(boa服务器)

CGI&#xff1a;通用网关接口&#xff08;Common Gateway Interface&#xff09;是一个Web服务器主机提供信息服务的标准接口。通过CGI接口&#xff0c;Web服务器就能够获取客户端提交的信息&#xff0c;转交给服务器端的CGI程序进行处理&#xff0c;最后返回结果给客户端。 b…

字符串概述

字符串 一、API二、字符串2.1字符串的构造方法2.2 字符串构造时的内存2.2.1 直接赋值时的内存模型2.2.2 由new创建时的内存模型 2.3 字符串的比较三、StringBuilder 一、API 目前已学过的两个API&#xff1a;Random和Scanner。 对记不清的API可以去JDK-API帮助文档进行查找。 …

基于matlab对现代相控阵系统中常用的子阵列进行建模分析

一、前言 本示例说明如何使用相控阵系统工具箱对现代相控阵系统中常用的子阵列进行建模并进行分析。 相控阵天线与传统碟形天线相比具有许多优势。相控阵天线的元件更容易制造;整个系统受组件故障的影响较小;最重要的是&#xff0c;可以向不同方向进行电子扫描。 但是&#xff…

分布式系统学习第一天 fastDFS框架学习

目录 1. 项目架构图 1.1 一些概念 1.2 项目架构图 2. 分布式文件系统 2.1 传统文件系统 3. FastDFS 3.1 fastDFS介绍 3.2 fastDFS安装 3.3 fastDFS配置文件 3.4 fastDFS的启动 3.5 对file_id的解释 4. 上传下载代码实现 5. 源码安装 - 回顾 1. 项目架构图 1.1 一…

JDK8-2-流(2)- 流操作

JDK8-2-流&#xff08;2&#xff09;- 流操作 上篇 JDK8-2-流&#xff08;1&#xff09;-简介 中简单介绍了什么是流以及使用流的好处&#xff0c;本篇主要介绍流的操作类型以及如何操作。 如何返回一个流 ① collection.stream 即调用集合 java.util.Collection 下的 stre…

大学生如何申请一台免费服务器?

大学生如何申请一台免费服务器&#xff1f;阿里云学生服务器免费申请&#xff1a;高效计划&#xff0c;可以免费领取一台阿里云服务器&#xff0c;如果你是一名高校学生&#xff0c;想搭建一个linux学习环境、git代码托管服务器&#xff0c;或者创建个人博客网站记录自己的学习…

【小米技术分享】MySQL:一条数据的存储之旅

大家好&#xff0c;我是你们的小米&#xff0c;一个热爱技术分享的活泼小伙伴&#xff01;今天&#xff0c;我来给大家揭开一个神秘的面纱&#xff0c;带你们深入了解一下MySQL数据库是如何保存一条数据的。 客户端 首先&#xff0c;让我们从客户端&#xff08;Client&#x…

【雕爷学编程】Arduino动手做(114)---US-015高分辨超声波模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…