栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

内存池原理与实现

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

内存池原理与实现

什么是内存池

应用程序直接对使用的内存进行管理,避免频繁的从堆上申请释放内存,可以做到一次申请,多次分配。内存池主要是针对小块。

内存池管理的是堆内存。进程开始运行的时候,划分出一块内存

为什么需要内存池?

对于服务器,如果不段有客户端连接,并且客户端不断发送消息,对于每个连接的每个消息,服务器都需要去malloc/free内存。如果服务器需要7*24运行,时间久了,就会产生很多内存碎片,没有整块,最后就有可能malloc比较大的内存的时候失败。最后进程就会coredump。

这种问题,出现了很难解,因为程序要运行很长时间才会出现。解决方案就是使用内存池。

频繁的malloc/free, 会造成哪些问题?

  1. 不利于内存管理
  2. 内存碎片
  3. 出现内存泄漏,不容易查出具体位置。

内存池使用场景

典型的场景,服务器处理网络io。message较大的时候,recv、parse后,需要push到另一个thread进行处理。使用栈内存不合适,需要申请堆内存。recv的时候申请buffer,send后,释放buffer。每来一个消息,都需要申请、释放内存。

内存池的实现

内存池的演化
  1. 最早的内存池雏形。

每次malloc内存后,加入到一个内存池链表中,free的时候不释放,只是修改flag标志,表示当前内存块是空闲的。一定程度解决内存碎片的问题。

存在的问题

1)内存块越分越小。

如果一个内存块64字节,现在应用程序需要54字节,那么剩余的10字节又会加入到内存池链表的尾部。就会造成出现很多小块内存,越分越小,出现很多没办法使用的小块内存。链表也会越来越长。

2. 分配固定大小的不同的块

内存池内分别有16 byte,32byte...,这种固定大小的内存块。应用程序要申请内存,到相应大小的块中去获取。如果要申请大于1024 byte的内存,直接就申请一整块。这样就能够解决出现内存块越分越小的问题。

3. 用hash table

这种方法有什么缺点?

1) 查找速度慢,

malloc的时候,需要查找空闲的内存块,即flag为0

free的时候需要查找,设置flag = 0.

free的时候,参数加上size,就可以在hash table中找到相应的slot。

解决方法:

a. 对于malloc时查找,可以将每一个slot中的内存块分为两个链表,一个是已使用的,一个是未使用的。

b. free时候的查找,可以用hash,rbtree。

2)内存存在浪费,会存在间隙。影响小块的回收。

小块有没有必要回收?小块内存回收是一个极其麻烦的事情。

如果一个连接对应一个内存池,连接的生命周期不会很长,可以不回收小块内存。

如果要使用全局内存池,可以使用jemalloc、tcmalloc。

解决小块回收的方法:

对于16bytes的slot,下面挂的是4k的内存块, 即一开始就分配一个整块,每一个从这个内存块里面分配16bytes,如果4k用完了,接着再申请4k内存块,挂到链表上。

实现一个内存池

我们的实现比较粗犷,对于小块内存,没有单独回收,要回收统一回收,不单独回收一个块内的某个小块内存。对于大块,进行回收。

内存池里面保持两个list,一个是小块,一个是大块。小块大小固定是4k,大块用于大于4k的内存。如果申请小于4k的内存,直接从小块进行分配,一次申请好4k,之后在这个基础上进行分配;申请大于4k的内存,则使用大块,用多少申请多少,大块的大小不是固定的。

对于一个网络连接,我们create一个内存池,等到连接断开,destroy内存池。

代码实现

 

#include 


#define ALIGNMENT 8


typedef struct _mp_node_s {

    unsigned char *last; // 内存块中未分配内存的首地址
    unsigned char *end; // 内存块的尾地址

    struct _mp_node_s *next;

} mp_node_s;


typedef struct _mp_large_s {

    struct _mp_large_s *next;
    void *alloc;

} mp_large_s;


typedef struct _mp_pool_s {

    mp_node_s *small;
    mp_large_s *large;

    int size;

} mp_pool_s;

void *mp_malloc(mp_pool_s *pool, int size);

mp_pool_s *mp_create_pool(int size) {

    mp_pool_s *pool;

    int ret = posix_memalign((void **)&pool, ALIGNMENT, size + sizeof(mp_pool_s));
    if (ret) return NULL;

    pool->small = (mp_node_s *)(pool + 1);

    pool->small->last = (unsigned char *)(pool->small + 1);
    pool->small->end = (unsigned char *)pool + size + sizeof(mp_pool_s);
    pool->small->next = NULL;

    pool->large = NULL;

    return pool;

}

void mp_destroy_pool(mp_pool_s *pool) {

    mp_large_s *large;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc) {
            free(large->alloc);
        }
    }

    mp_node_s *small, *next;

    for (small = pool->small; small;) {

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

    free(pool);
}


static void *mp_malloc_large(mp_pool_s *pool, int size) {

    mp_large_s *large = (mp_large_s *)mp_malloc(pool, sizeof(mp_large_s));

    int ret = posix_memalign((void **)&large->alloc, ALIGNMENT,  size);
    if (ret) return NULL;

    return large->alloc;

}

static void *mp_malloc_small(mp_pool_s *pool, int size) {

    mp_node_s *node = NULL;
    int ret = posix_memalign((void **)&node, ALIGNMENT,  pool->size);
    if (ret) return NULL;

    node->next = pool->small;
    pool->small = node;

    node->last = (unsigned char *)node + sizeof(mp_node_s);
    node->end = (unsigned char *)node + pool->size;

    return node->last;


}

void *mp_malloc(mp_pool_s *pool, int size) {

    if (size < pool->size) {
        mp_node_s *node = pool->small;

        if (size < node->end - node->last) { // 需要考虑字节对齐
            unsigned char *m = node->last;
            node->last = m + size;
            return m;

        } else {
            return mp_malloc_small(pool, size);
        }
    } else {
        return mp_malloc_large(pool, size);
    }

}

void *mp_free(mp_pool_s *pool, void *p) {

    mp_large_s *large = NULL;
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == ()p) {
            free(large->alloc);
            large->alloc = NULL;
            break;
        }
    }
}


int main() {



}

如何保证内存池线程安全?

加锁来保证内存池线程安全,锁的粒度需要把握好。

如果是单线程reactor,或者没个线程一个reactor,也就是一个连接只在一个线程处理,不需要考虑加锁,一个连接一个线程池,是线程安全的。

开源内存池

如果要使用全局内存池,可以使用开源内存池

  1. jemalloc
  2. tcmalloc

使用的时候,加上一个宏定义,不需要改源代码,malloc/free就被hook住,使用jemalloc/tcmalloc的函数。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/642235.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号