内存管理-内存池的实现

news/2024/7/20 13:40:07 标签: c语言, linux, 内存管理

内存池的实现

  • 1 前言
  • 2 内存池的原理
    • 2.1 内存利用链表进行管理
    • 2.2 分配固定大小
    • 2.3 按块进行内存管理
  • 3 内存池的实现
    • 3.1 内存池的创建
    • 3.2 内存池的销毁
    • 3.3 内存分配
    • 3.4 大块内存分配
    • 3.5 小块内存分配
    • 3.6 内存的对其问题

1 前言

内存池出现的意义比较重大,对于服务器这种需要长时间工作的设备内存池具有比较大的优势:一个是可以避免内存频繁的分配和释放,造成内存的碎片,久而久之内存分配失败。这里实现内存池比较简易主要是为了了解内存的管理策略,如果是商用建议采用开源框架ptmalloc、tcmalloc或者jemalloc等等。

2 内存池的原理

内存池的管理,首先需要有大块内存和小块内存管理的问题,大块内存基本对于内存没有影响,因为本来就比较大不存在分配后出现内存碎片的问题,小块内存,则不一样每次分配和释放都会容易造成内存的碎片问题。所以讨论内存管理主要是对小块内存的管理,下面探讨内存管理的几种方案:
注意: 内存大小区分:没有标准,一般4K一下为小块内存,当然实际情况也可以调整。

2.1 内存利用链表进行管理

每次分配将地址加入到链表中,释放的时候不去真正释放而是,将标志位置位0,供下一次分配。
存在问题:

  • 当下次分配的大小不是在链表中的大小,这时候需要将节点拆分为两个节点,这个过程比较复杂,
  • 后面的小节点太小无法再次利用。
  • 链表查找效率笔记低
    在这里插入图片描述

2.2 分配固定大小

按照固定大小进行分配,分别为32字节,64字节,128字节,256字节和512字节大小;所有配置利用将不足大小按照向上去近似进行分配,防止链表出现的小的碎片问题。
存在问题:

  • 速度慢,在插入前需要进行一次flag为0的查找;在删除需要做一次地址匹配的查找;
  • 内存浪费的问题,就是分配字节不足时每次分配的多分配内存;
  • 链表节点之间地址不连续,没办法组成一个大块内存进行分配
    在这里插入图片描述

2.3 按块进行内存管理

内存块管理采用的方式是进行将内存分配采用大块和小块两部分分别管理,大块比较简单,分配多少就多少,直接由内存malloc分配,释放也是直接free释放。对于小块内存管理则是采用一次性分配4k大小,一个指针指向分配的起始节点后面的起始位置,另一个指针指向节点的结束位置,中间分配内存时last指针跟随移动。
存在问题:

  • 小块内存没有办法单独回收只能用于特定的场景,比如会话的声明周期。

在这里插入图片描述

3 内存池的实现

这里即采用的是按块进行内存管理的内存池方案,用于特定的连接生命周期这种局部性的内存池操作。

3.1 内存池的创建

内存池初始化,首先分配第一块内存块,这里需要注意的是内存池对象和小块内存是连续内存,一般剩余空间按照4k-1的大小分配空间。

mp_pool *mp_pool_create(size_t size)
{

	mp_pool *pool = NULL;
	mp_small_node *small = NULL;

	int ret;
	
	ret = posix_memalign((void **)&pool, MP_ALIGNMENT, size + sizeof(mp_pool) + sizeof(mp_small_node));
	if(ret) return NULL;

	pool->max = size < MP_SMALL_NODE_SIZE ? size : MP_SMALL_NODE_SIZE-1;
	pool->smalls_cur = pool->head;
	pool->larges = NULL;

	small = pool->smalls_cur;
	small->last = (unsigned char *)(small + 1);
	small->end = small->last + size;
	small->next = NULL;
	small->failed_count = 0;

	return pool;
}

3.2 内存池的销毁

内存池销毁需要先释放大块内存,再释放小块内存,主要是由于大块内存结构体是在小块内存中分配的。

int mp_pool_destory(mp_pool *pool)
{

	if(pool == NULL) return 0;

	mp_large_node *larges = pool->larges;
	while(larges)
	{
		if(larges->addr)
			free(larges->addr);
		larges = larges->next;
	}

	mp_small_node *small = pool->head->next;
	mp_small_node *tmp = NULL;
	while(small)
	{

		tmp = small->next;
		free(small);
		small = tmp;
	}

	free(pool);

	return 0;
}

3.3 内存分配

内存分配比较简单,根据大块内存和小块内存交给不同的函数处理。

unsigned char* mp_malloc(mp_pool *pool, size_t size)
{
	if(pool == NULL) return NULL;

	if(size <=  pool->max)
	{
		
		return mp_malloc_small(pool, size);
	}

	return mp_malloc_large(pool, size);
}

3.4 大块内存分配

大块内存分配主要是利用malloc分配,需要注意的是将大块内存的结构体在小块内存分配,方便后面统一回收。

unsigned char* mp_malloc_large(mp_pool *pool, size_t size)
{
	if(pool == NULL) return -1;

	unsigned char *addr = (unsigned char *)calloc(1, size);
	if(addr == NULL) return NULL;

	
	mp_large_node *larges = (mp_large_node *)mp_malloc_small(pool, sizeof(mp_large_node));
	if(larges == NULL) 
	{
		free(addr);
		return NULL;
	}
	
	larges->addr = addr;
	larges->next = NULL;

	mp_large_node *head = pool->larges;
	if(head == NULL) 
	{
		head = larges;
	}
	else
	{
		while(head->next)
		{
			head->next = head->next->next;
		}
		head->next = larges;
	}
	
	return larges->addr;
}

3.5 小块内存分配

小块内存分配分为两种情况一种是,内存块还有足够空间,直接分配出去,然后后移last指针即可。当不足分配时需要新创建节点,加入到链表中,并将内存返回。这里有个加速操作,利用当前指针指向分配位置的起点防止每次从头开始遍历。

unsigned char* mp_malloc_small(mp_pool *pool, size_t size)
{
	if(pool == NULL) return NULL;
	
	mp_small_node *cur = pool->smalls_cur;
	mp_small_node *node = NULL;
	unsigned char *tmp = NULL;
	while(cur)
	{
		tmp = mp_align_ptr(cur->last, MP_ALIGNMENT);
		if((cur->end - tmp) >= size)
		{
			cur->last = tmp + size;
			return tmp;
		}

		cur->failed_count++;

		cur = cur->next;
	}

	mp_small_node *new_node = NULL;
	size_t node_size = (size_t)(pool->head->end-(unsigned char *)pool->head);
	printf("size(%d)p-d(%d)\n", pool->max + sizeof(mp_small_node), node_size);
	int ret = posix_memalign((void **)&new_node, MP_ALIGNMENT, node_size);
	if(ret) return NULL;

	new_node->end = (unsigned char*)new_node + node_size;
	new_node->failed_count = 0;
	new_node->next = NULL;

	tmp += sizeof(mp_small_node);
	tmp = mp_align_ptr(tmp, MP_ALIGNMENT);
	new_node->last = tmp + size;

	cur = pool->smalls_cur;

	for (node = cur; node->next; node = node->next) 
	{
		if (node->failed_count > 3) 
		{
			cur = node->next;
		}
	}
	node->next = new_node;

	pool->smalls_cur = cur ? cur : new_node;

	return tmp;

}

3.6 内存的对其问题

在分配内存过程中,每次返回前需要将指针位置进行对齐操作,防止后面内存使用过程出问题。

#define mp_align_ptr(p, alignment) (void *)((((size_t)p) +(alignment-1)) & ~(alignment - 1))

http://www.niftyadmin.cn/n/927468.html

相关文章

企业级请求池实现

企业级请求池实现1 前言2 异步请求池原理2.1 基本原理2.2 结构体设计3 请求池实现3.1 请求池创建3.2 请求池销毁3.3 请求响应函数3.3 请求函数1 前言 请求池出现的运用场景&#xff0c;主要是为了解决同步处理带来的阻塞导致效率的大大降低&#xff0c;异步请求池将请求事件和…

企业级连接池实现

企业级连接池实现1 前言2 连接池设计2.1 连接池原理2.2 数据结构设计3 mysql连接过程4 连接池的实现4.1 连接池创建4.2 连接池销毁4.3 连接获取4.4 连接归还5 测试1 前言 连接池主要解决的问题就是&#xff0c;服务器对数据库的后半段的连接问题&#xff0c;这个过程连接对象是…

多线程操作与安全

多线程操作与安全1 互斥锁1.1 使用场景1.2 基本操作2 自旋锁2.1 使用场景2.2 基本操作3 原子操作3.1 使用场景3.2 基本操作4 线程私有空间4.1 使用场景4.2 基本操作5 信号量5.1 使用场景5.2 基本操作1 互斥锁 1.1 使用场景 互斥锁的特性&#xff1a; 当遇到锁被占用&#xff…

地表最强队列-ZMQ无锁队列

1 前言 老规矩&#xff0c;介绍前先简单聊一下为啥需要无锁队列&#xff0c;主要解决了哪些问题。首先是为啥需要无锁队列&#xff0c;我们最常见的就是利用锁保护临界资源&#xff0c;在多线程中进行队列操作&#xff0c;当并发量起来会带来大量的线程切换开销&#xff0c;而…

企业级定时器实现

1 前言 定时器在工程项目中应该是使用最为频繁的一个组件&#xff0c;很多时候处理任务并不是外部触发的任务&#xff0c;而是需要定时执行一些常规任务&#xff0c;比如说心跳包&#xff1b;定时更新更新信息&#xff1b;定时发送邮件等功能都需要用到定时器。 2 定时器的实…

音视频协议-RTP协议

1 协议简介 音视频传输的基石&#xff1a;RTP和RTCP。对于协议的讲解主要是是对于RFC文档的阅读和理解。不同的使用场景用到的字段也有所侧重&#xff0c;RTP和RTCP定义在RFC3550中。其中RTP用于数据流的传输&#xff1b;RTCP用于数据流的控制。可以说rtp/rtcp协议是即时通讯不…

音视频协议-RTCP协议介绍

音视频协议-RTCP协议介绍1 协议简介2 协议格式介绍2.1 RTCP公共头2.2 SR协议2.3 RR协议2.4 SDES协议2.5 BYE协议2.6 APP协议3 RTCP协议限制1 协议简介 RTCP和RTP协议是配合使用的音视频协议-RTP协议&#xff0c;为RTP提供信道外的传输控制&#xff0c;RTCP不参与数据传输&…

音视频协议-RTCP协议实现原理

1 前言 RTCP作为RTP控制协议&#xff0c;涵盖的内容比较多&#xff0c;用法也比较灵活&#xff0c;对于弱网下音视频质量和会话控制具有重要的作用。RTCP协议格式见&#xff1a;音视频协议-RTCP协议介绍 2 RTCP协议定义 2.1 RTCP公共头 RTCP公共头包括32字节&#xff1a;版…