池式结构--内存池

内存管理

我们经常在堆上进行内存的分配和释放时,就会产生内存碎片问题。

  • 内部碎片的产生:因为所有的内存分配必须起始于可被 4、8 或 16 整除(视 处理器体系结构而定)的地址或者因为MMU的分页机制的限制,决定内存分配算法仅能把预定大小的内存块分配给客户。假设当某个客户请求一个 43 字节的内存块时,因为没有适合大小的内存,所以它可能会获得 44字节、48字节等稍大一点的字节,因此由所需大小四舍五入而产生的多余空间就叫内部碎片。
  • 外部碎片的产生: 频繁的分配与回收物理页面会导致大量的、连续且小的页面块夹杂在已分配的页面中间,就会产生外部碎片。假 设有一块一共有100个单位的连续空闲内存空间,范围是0-99。如果你从中申请一块内存,如10个单位,那么申请出来的内存块就为0-9区间。这时候你 继续申请一块内存,比如说5个单位大,第二块得到的内存块就应该为10-14区间。如果你把第一块内存块释放,然后再申请一块大于10个单位的内存块,比 如说20个单位。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。现在整个内存空间的状态是0-9空闲,10-14 被占用,15-24被占用,25-99空闲。其中0-9就是一个内存碎片了。如果10-14一直被占用,而以后申请的空间都大于10个单位,那么0~9就 永远用不上了,变成外部碎片。

从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。

1、brk是将数据段(.data)的最高地址指针_edata往高地址推;malloc分配的内存小于128kb,使用brk分配,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系)。

2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0)

这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

伙伴算法

**基本原理:**必须满足三个条件的称为伙伴。
1)两个块大小相同;
2)两个块地址连续;
3)两个块必须是同一个大块中分离出来的;

分配原理:
伙伴系统将所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。假如现在需要大小为6的内存块,该算法就到定位到大小为8的链表中查找,如果链表中有空闲块,就直接从中摘下并分配出去。如果没有,算法将顺着数组向上查找大小为16的数组,如果16中有空闲块,那么将该空闲块分成相等的两部分,一部分分配出去,另一部分放入8中,如此重复寻找和分配,若到达数组最后也没有空闲块,则放弃分配。
在这里插入图片描述

释放原理:
内存的释放就是分配的逆过程。当当释放一个块时,先在其对应的链表中考查是否有伙伴存在,如果没有伙伴块,就直接把要释放的块挂入链表头;如果有,则从链表中摘下伙伴,合并成一个大块,然后继续考察合并后的块在更大一级链表中是否有伙伴存在,直到不能合并或者已经合并到了最大的块。

缺点:
虽然伙伴系统能够减少内存碎片的产生,但是在linux内核中伙伴系统用来管理物理内存,其分配的单位是页(4k),如果针对一些经常分配并释放的对象如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。

slab算法

一般来说,伙伴算法的改进算法用于操作系统分配和回收内存,而且内存块的单位较大,利于Linux使用的伙伴算法以页为单位.对于小块内存的分配和回收,伙伴算法就显得有些得不偿失了.
对于小块内存,一般采用slab算法,或者叫做slab机制.
slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当以后又要请求新的对象时,就可以从内存直接获取而不用重复初始化。
在这里插入图片描述
2 0 具 有 若 干 个 1 b y t e s 的 块 , 2 1 具 有 若 干 个 2 b y t e s 的 块 , 2 2 具 有 若 干 个 4 b y t e s 的 块 , . . . . . . 2 n 具 有 若 干 个 n b y t e s 的 块 , 2^0 具有若干个 1 bytes的块, 2^1 具有若干个 2 bytes的块, 2^2 具有若干个 4 bytes的块, ...... 2^n 具有若干个 n bytes的块, 201bytes212bytes224bytes......2nnbytes

Linux 的slab 可有三种状态:
满的:slab 中的所有对象被标记为使用。
空的:slab 中的所有对象被标记为空闲。
部分:slab 中的对象有的被标记为使用,有的被标记为空闲。
slab 分配器首先从部分空闲的slab 进行分配。如没有,则从空的slab 进行分配。如没有,则从物理连续页上分配新的slab,并把它赋给一个cache ,然后再从新slab 分配空间。

与传统的内存管理模式相比, slab 缓存分配器提供了很多优点。
1、内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配。
2、slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题。
3、slab 分配器还支持通用对象的初始化,从而避免了为同一目的而对一个对象重复进行初始化。
4、slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。

内存池的实现

我们的内存池的实现采用了slab算法的思想,我们以页(4096 byte)为单位。创建内存池的时候,预先分配一个一页的内存大小,对于小于4096字节的分配,我们直接以这个block进行分配,大于4096的内存块,我们重新分配一个大内存的内存块。为了避免开销,我们需要内存的结构体分配都使用block进行分配。而内存池的实现主要是分配、释放和扩容。

1. 分配

分配内存的结构体主要有两个,分配4k以下的内存:

struct mp_node_s {
    unsigned char *last;  // 已使用的内存的地址
    unsigned char *end; // 一个大内存块的最后末尾地址

    struct mp_node_s *next; //下一个大的内存块
    size_t failed;           //尝试分配的失败的次数
};

分配一个大的内存

struct mp_large_s {   
    struct mp_large_s *next;   // 下一个内存块
    void *alloc;          //分配的内存块 指向的地址
};

内存池的结构体

struct mp_pool_s {
    size_t max;        //一个内存块的最大内存

    struct mp_node_s *current;  //当前内存块
    struct mp_large_s *large;  //指向的大的内存块

    struct mp_node_s head[0];  //指向的第一块内存
};

在这里插入图片描述
创建内存池,使用posix_memalign函数进行分配,
在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。在Linux中,这些函数返回的地址在32位系统是以8字节为边界对齐,在64位系统是以16字节为边界对齐的。有时候,对于更大的边界,例如页面,程序员需要动态的对齐。虽然动机是多种多样的,但最常见的是直接块I/O的缓存的对齐或者其它的软件对硬件的交互,因此,POSIX 1003.1d提供一个叫做**posix_memalign( )**的函数:

/* one or the other – either suffices */
#define _XOPEN_SOURCE 600
#define _GNU_SOURCE
#include <stdlib.h>
int posix_memalign (void **memptr, size_t alignment,size_t size);
调用posix_memalign( )成功时会返回size字节的动态内存,并且这块内存的地址是alignment的倍数。参数alignment必须是2的幂,还是void指针的大小的倍数。返回的内存块的地址放在了memptr里面,函数返回值是0.

调用失败时,没有内存会被分配,memptr的值没有被定义,返回如下错误码之一:
EINVAL
参数不是2的幂,或者不是void指针的倍数。
ENOMEM
没有足够的内存去满足函数的请求。
要注意的是,对于这个函数,errno不会被设置,只能通过返回值得到。
由posix_memalign( )获得的内存通过free( )释放。

把结构体mp_pool_s和mp_node_s分配的内存都考虑在内。

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;
    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;
}

分配一个4k以内的大小的内存块:

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->end = m + psize;
    new_node->next = NULL;
    new_node->failed = 0;

    m +=sizeof(struct mp_node_s);
    m = mp_align_ptr(m, MP_ALIGNMENT);
    new_node->last = m + size;

    current = pool->current;

    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;
}

分配一个超过4k的内存块:

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;

    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;
}

2. 释放

释放就比较简单了,我们只到最后的时候才会释放内存(程序结束)。

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 = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	h = pool->head->next;

	while (h) {
		n = h->next;
		free(h);
		h = n;
	}

	free(pool);

}

3. 扩容

我们会判断分配的size的大小,看需要是扩容4k以上的内存块或超过4k的大内存块。

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, MP_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);
}

最后完整的代码:


#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))
#define mp_align_ptr(p, alignment)  (void*)((((size_t)p) + (alignment - 1)) & ~(alignment -1)) 


struct mp_large_s {
    struct mp_large_s *next;
    void *alloc;
};

struct mp_node_s {
    unsigned char *last;
    unsigned char *end;

    struct mp_node_s *next;
    size_t failed;
};

struct mp_pool_s {
    size_t max;

    struct mp_node_s *current;
    struct mp_large_s *large;

    struct mp_node_s head[0];
};


struct mp_pool_s *mp_create_pool(size_t size);
void mp_destory_pool(struct mp_pool_s *pool);
void mp_reset_pool(struct mp_pool_s *pool);
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size);
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size);
void *mp_alloc(struct mp_pool_s *pool, size_t size);
void *mp_calloc(struct mp_pool_s *pool, size_t size);

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;
    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;
}

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 = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	h = pool->head->next;

	while (h) {
		n = h->next;
		free(h);
		h = n;
	}

	free(pool);

}

void mp_reset_pool(struct mp_pool_s *pool){
    struct mp_node_s *h, *n;
    struct mp_large_s *l;

    for (l = pool->large; l; 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);
    }
}

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->end = m + psize;
    new_node->next = NULL;
    new_node->failed = 0;

    m +=sizeof(struct mp_node_s);
    m = mp_align_ptr(m, MP_ALIGNMENT);
    new_node->last = m + size;

    current = pool->current;

    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;
}

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;

    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;
}

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, MP_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);
}

void *mp_calloc(struct mp_pool_s *pool, size_t size) {

	void *p = mp_alloc(pool, size);
	if (p) {
		memset(p, 0, size);
	}

	return p;
	
}

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 ;
		}
	}
	
}

int main(){
    int size = 1<<12;

    struct mp_pool_s *p = mp_create_pool(size);

    int i = 0;
    for(i =0; i < 10; ++i) {
        void *mp = mp_alloc(p, 512);
    }
    printf("mp_align(123, 32): %d, mp_align(17, 32): %d\n", mp_align(24, 32), mp_align(17, 32));

    int j = 0;
	for (i = 0;i < 5;i ++) {

		char *pp = mp_calloc(p, 32);
		for (j = 0;j < 32;j ++) {
			if (pp[j]) {
				printf("calloc wrong\n");
			}
			printf("calloc success\n");
		}
	}

	//printf("mp_reset_pool\n");

	for (i = 0;i < 5;i ++) {
		void *l = mp_alloc(p, 8192);
		mp_free(p, l);
	}

	mp_reset_pool(p);

	//printf("mp_destory_pool\n");
	for (i = 0;i < 58;i ++) {
		mp_alloc(p, 256);
	}

	mp_destory_pool(p);

	return 0;

}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值