前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >内存池的实现

内存池的实现

原创
作者头像
ge3m0r
发布2024-05-26 15:13:16
870
发布2024-05-26 15:13:16

内存池

经过了线程池,连接池的作用,内存池也就好理解了。内存池是专门使用数据结构将内存分配的任务交给内存池,不用每次分配内存的时候都自己使用 malloc 之类的。

简要分析

内存池可以分为分配大块内存和小块内存,所以内存池应该维护两个链表,一个是负责小块内存的分配,另一个是大块内存的链表。 c 语言实现相对来说简单一些,先定义数据结构。

这里先把头文件和一些宏定义展示

代码语言:c
复制
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <fcntl.h>

#define MP_ALIGNMENT   32
#define MP_PAGE_SIZE   4096
#define MP_MAX_ALLOC_FROM_POOL (MP_PAGE_SIZE - 1)

#define mp_align(n, alignment) (((n) + (alignment - 1)) & ~(alignment - 1))  //求向上 32 位对齐
#define mp_align_ptr(p, alignment) ((void*)(((size_t)p) + (alignment - 1)) //求指针的向上 32 位对齐

内存分配跟其他相比就是需要进行内存对齐,小块内存的数据结构

代码语言:c
复制
struct mp_node_s //小块内存,一个指向小块内存开始位置,一个指向末端,一个链表,标志位标志分配结果
{
    unsigned char *last;  //小块内存的初始位置
	unsigned char *end; //小块内存的结尾位置
	struct mp_node_s *next; //下一个小块内存
	size_t failed;  //分配的次数
};

大块内存数据结构如下:

代码语言:c
复制
//大块内存组织方式,一个大块 alloc ,一个指向另一个大块的指针
struct mp_large_s
{
   struct mp_large_s *next;   //下一个大块内存链表
   void *alloc  //指向大块内存
};

而内存池的结构

代码语言:c
复制
struct mp_pool_s
{
   size_t max;
   struct mp_node_s *current; //指向 head[0] 这个第一个小块,最开始这个快较大,后边链表的块都是 mp_node_s
   struct mp_large_s *large;  // 大块就是直接的指针指向
   struct mp_node_s head[0]  //结构体动态数组,内存池第一个结构是 max 表示内存池分配最大的内存
   
};

有了数据结构,然后就是数据结构的操作方法,所以对于内存池的操作方法定义如下:

代码语言:c
复制
struct mp_pool_s *mp_create_pool(size_t size)
{
    struct mp_pool_s *p;
	//可以分配大块内存,第一个结构是两个结构体联合加上需要分配的内存
	int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
    if(ret){
		return NULL;
    }

	p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
	p->current = p->head;  //current 指向第一个node 值
	p->large = NULL;

	p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
	p->head->end = p->head->last + size;

	p->head->failed = 0;
	return p;
}

创建内存池操作,其中 posix_memalign 是 Linux 负责分配小块内存的方法,然后内存池中 current 指针最开始指向其一起申请的小块 node, 然后就是各种初始化。

代码语言:c
复制
void mp_destory_pool(struct mp_pool_s * pool)
{
   struct mp_node_s *h, *n;
   struct mp_large_s *l;

   //释放大块内存
   for(l = pool->large; l; l->next)
   {
   	   if(l->alloc){
	   	  free(l->alloc);
   	   	}
   	}
   //释放小块内存
   h = pool->head->next;
   while(h){
   	  n = h->next;
	  free(h);
      h = n;
   	}
   //释放内存池
   free(pool);
}

上述是销毁内存池,先是大块内存销毁,然后是小块内存销毁,最后线程池销毁。

代码语言:c
复制
//重置
void mp_reset_pool(struct mp_pool_s *pool)
{
   struct mp_node_s *h;
   struct mp_large_s *l;
   //大块内存释放
   if(l = pool->large; l; l->next)
   {
   	  if(l->alloc){
	  	free(l->alloc);
   	  }
   }

   pool->large = NULL;
   //小块内存内存初始化
   for(h = pool->head; h; h = h->next)
   { 
      h->last = (unsigned char *)h + sizeof(struct mp_node_s);
   	}
}

内存池的重置首先就是释放大块内存,然后将小块内存最开始位置指向初始的 node 最开始位置。

内存分配

内存分配可以分为小块内存分配和大块内存分配,两者是不一样的函数,需要分别进行实现。

代码语言:c
复制
//分配一个小块的内存,然后将每个遍历过的内存 failed 标志 +1 ,并且如果遍历此处超过 4 次,就将线程池的指向其中
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size)
{
    unsigned char *m;
	struct mp_node_s *h = pool->head;
	size_t psize = (size_t)(h->end - (unsigned char*)h);

    //小块内存分配大小
	int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
	if(ret) return NULL;

	struct mp_node_s *p, *new_node, *current;
	new_node = (struct mp_node_s*)m;

	new_node->next = NULL;
	new_node->failed = 0;

	current = pool->current;

    //分配次数超过 4 次就进行抛弃
    //遍历链表
	for(p = current; p->next; p = p->next){
		if(p->failed++ > 4){
			current = p -> next;
		}
	}
    //找到链表的末尾分配
	p->next = new_node;

    //当前指向是否为空,如果是空那么就指向新节点。
	pool->current = current ? current : new_node;
	return m;
}

大块内存分配,我们首先要知道,我们的大块内存是被我们 alloc 指向的,说白了我们定义的数据结构是一个指针,所以这个数据结构占据的内存和 *alloc 指向的内存是分离的,因此我们首先要分配一个指针的内存,然后再分配大块内存。而既然我们要做一个内存池,那么这个指针的数据结构在其他地方分配多少不太合适,因此我们的指针也要在我们内存池分配。

因此先定义一个分配内存的机制。

代码语言:c
复制
void *mp_alloc(struct mp_pool_s * pool, size_t size){
     unsigned char* m;
	 struct mp_node_s *p;

	 if(size <= pool->max){  //如果小于内存池最大值在小块内配
	 	p = pool->current;
		do{
			m = mp_align_ptr(p->last, alignment);
			if((size_t)(p->end - m) >= size){
				p->last = m + size;
				return m;
			}
			p = p->next;
		}while(p);

		return mp_alloc_block(pool, size); 
	 }

	 return mp_alloc_large(pool, size); //分配大块
}

而大块内存的定义如下:

代码语言:c
复制
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size)
{
    void *p = malloc(size);
	if(p == NULL) return NULL;

	size_t n = 0; 
	struct mp_large_s *large;

    //这里设计了分配的次数,内存池维护的大块最多为 3 块
	for(large = pool->large; large; large = large->next){
		if(large->alloc == NULL){
			large->alloc = p;
			return p;
		}
		if(n++ > 3) break;
	}

	large = mp_alloc(pool, sizeof(struct mp_large_s));  //分配这样的结构体 这边有点递归套娃了,我在分配结构体可能会调用大块内存分配
	if(large == NULL){
		free(p);
		return NULL;
	}

    //环形指针
	large->alloc = p;
	large->next = pool->large;
	pool->large = large;

	return p;
}

初次之外,我们需要释放内存可以如下使用:

代码语言:c
复制
void mp_free(struct mp_pool_s * pool, void * p){
    struct mp_large_s *l;
	for(l = pool->large; l; l = l -> next){
		if(p == l->alloc){
			free(l -> alloc);
			l->alloc = NULL;

			return ;
		}
	}
}

这个只是负责大块内存的分配。

结语

关于内存池的实现到这里基本就结束了,当然有部分可以优化的地方,比方说最后 mp_alloc 函数,它可以进行对齐分配,也可以分配后进行初始化,也可以多个分配,但是实现思路基本类似,因此不再赘述。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内存池
  • 简要分析
    • 内存分配
    • 结语
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档