赞
踩
我们在学习Lwip源码时,内存管理是绕不开的一个重点,我们在看相关的代码时,经常会看到memp_malloc 和 mem_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个可用的成员。释放内存到内存池,相当于内存再恢复到内存数组中(这样说可能还是有点不严谨,凑合理解吧)。
因为内存池管理有优点,比如:
- 速度块,因为内存池在程序编译时,就自动在内存栈中提前申请了内存池数组,所以再申请的时候,就可以很快的从内存池中取处一片内存使用。这一点对于以太网通信就非常重要了,因为以太网的速率是非常块的,而lwip又是在资源比较弱的单片机上实用,所以内存池的管理,是一种以空间换时间的方法。
- 避免内存泄漏。由于内存池是提前从栈中申请的内存数组,申请和释放都是围绕着这个内存数组的,所以顶多会有一点浪费(比如内存数组申请的个数多了),而不会造成内存泄漏。这一点内存堆就不能保证。
当然内存池也有缺点,比如:
- 内存池必须是固定结构、固定大小,所以不够灵活,在lwip中,一般是通过opt.h 进行配置的。
- 可能会造成使用浪费,注意这里说的是“浪费”而不是“泄漏”,浪费的含义是,我们一半会分配内存池数组稍微大一些,防止不够用。
如果我们直接查看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语言中高级用法,比如:
简单的说:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。