您的位置:时时app平台注册网站 > 时时app平台注册网站 > 基本内部存储器池管理技能完毕深入分析【转】

基本内部存储器池管理技能完毕深入分析【转】

2019-11-28 03:37

内存池(Memery Pool)技术是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。
不仅在用户态应用程序中被广泛使用,同时在Linux内核也被广泛使用,在内核中有不少地方内存分配不允许失败。作为一个在这些情况下确保分配的方式,内核开发者创建了一个已知为内存池(或者是 "mempool" )的抽象,内核中内存池真实地只是相当于后备缓存,它尽力一直保持一个空闲内存列表给紧急时使用,而在通常情况下有内存需求时还是从公共的内存中直接分配,这样的做法虽然有点霸占内存的嫌疑,但是可以从根本上保证关键应用在内存紧张时申请内存仍然能够成功。
下面看下内核内存池的源码,内核内存池的源码在中,实现上非常简洁,描述内存池的结构;
mempool_t在头文件中定义,结构描述如下:

调用kmem_cache_create函数从系统缓存区cache_cache中获取长度为RPC_BUFFER_MAXSIZE的缓存区大小的内存,作为rpc_buffer使用的缓存区。而以后对rpc操作的所有数据结构内存都是从这块缓存区申请,这是linux的slab技术的要点,而内存池也是基于这段缓存区进行的操作。

#include <vector>

内存池回收对象的函数mempool_free的原型如下:

       pool = kmalloc(sizeof(*pool), GFP_KERNEL);

 

void mempool_free(void *element, mempool_t *pool)
{
    if (pool->curr_nr < pool->min_nr) {
        spin_lock_irqsave(&pool->lock, flags);
        if (pool->curr_nr < pool->min_nr) {
            add_element(pool, element);
            spin_unlock_irqrestore(&pool->lock, flags);
            wake_up(&pool->wait);
            return;
        }
        spin_unlock_irqrestore(&pool->lock, flags);
        }
    pool->free(element, pool->pool_data);
}

 

对象内存池,这是最失败的一个内存池设计。

void mempool_destroy(mempool_t *pool)
{
    while (pool->curr_nr) {
        void *element = remove_element(pool);
        pool->free(element, pool->pool_data);
    }
    kfree(pool->elements);
    kfree(pool);
}

一旦rpc服务申请到了一个缓存区rpc_buffer_slabp以后,就可以创建一个内存池来管理这个缓存区了:

内存池可有效降低动态申请内存的次数,减少与内核态的交互,提升系统性能,减少内存碎片,增加内存空间使用率,避免内存泄漏的可能性,这么多的优点,没有理由不在系统中使用该技术。

内存池的创建函数mempool_create的函数原型如下:

 * @min_nr:       为内存池分配的最小内存成员数量

   student_pool.free(obj);

mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,
                mempool_free_t *free_fn, void *pool_data)
{
    return mempool_create_node(min_nr,alloc_fn,free_fn, pool_data,-1);
}

       mempool_alloc_t *alloc; /* 用户在创建一个内存池对象时提供的内存分配函数,这个函数可以用户自行编写(因为对于某个内存对象如何获取内存,其开发者完全可以自行控制),也可以采用内存池提供的分配函数 */

3)singleton_pool

值得注意的是内存池分配和回收对象的函数:mempool_allocmempool_freemempool_alloc的作用是从指定的内存池中申请/获取一个对象,函数原型如下:

       int gfp_nowait = gfp_mask & ~(__GFP_WAIT | __GFP_IO);

  boost::singleton_pool<boost::fast_pool_allocator_tag,sizeof(CStudent *)>::purge_memory();

函数原型指定内存池可以容纳元素的个数、申请元素的方法、释放元素的方法,以及一个可选的内存源(通常是一个cache),内存池对象创建完成后会自动调用alloc方法从pool_data上分配min_nr个元素用来填充内存池。
内存池的释放函数mempool_destory函数的原型很简单,应该也能猜到是依次将元素对象从池中移除,再释放给pool_data,最后释放池对象,如下:

 

typedef struct student_st

void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask){
......
    element = pool->alloc(gfp_temp, pool->pool_data);
    if (likely(element != NULL))
        return element;

    spin_lock_irqsave(&pool->lock, flags);
    if (likely(pool->curr_nr)) {
        element = remove_element(pool);/*从内存池中提取一个对象*/
        spin_unlock_irqrestore(&pool->lock, flags);
        /* paired with rmb in mempool_free(), read comment there */
        smp_wmb();
        return element;
    }
......
}

mempool_t * mempool_create(int min_nr, mempool_alloc_t *alloc_fn,

object_pool提供的函数主要有(继承至父类的略): malloc/free 复写pool的malloc/free,add_ordered_block/malloc/ordered_free实现

其实原则跟mempool_alloc是对应的,释放对象时先看池中的可用元素是否充足(pool->curr_nr

pool->min_nr),如果不是则将元素对象释放回池中,否则将元素对象还给pool->pool_data。
此外mempool也提供或者说指定了几对alloc/free函数,及在mempool_create创建池时必须指定的alloc和free函数,分别适用于不同大小或者类型的元素的内存池,具体如下:

void *mempool_alloc_slab(gfp_t gfp_mask, void *pool_data)
{
    struct kmem_cache *mem = pool_data;
    return kmem_cache_alloc(mem, gfp_mask);
}
void mempool_free_slab(void *element, void *pool_data)
{
    struct kmem_cache *mem = pool_data;
    kmem_cache_free(mem, element);
}

void *mempool_kmalloc(gfp_t gfp_mask, void *pool_data)
{
    size_t size = (size_t)pool_data;
    return kmalloc(size, gfp_mask);
}
void mempool_kfree(void *element, void *pool_data)
{
    kfree(element);
}

void *mempool_alloc_pages(gfp_t gfp_mask, void *pool_data)
{
    int order = (int)(long)pool_data;
    return alloc_pages(gfp_mask, order);
}
void mempool_free_pages(void *element, void *pool_data)
{
    int order = (int)(long)pool_data;
    __free_pages(element, order);
}

总体上来讲mempool的实现很简约,但是不简单,而且非常轻便易用,这也是内核奥妙之所在。

slab算法思路中最基本的一点被称为object-caching,即对象缓存。其核心做法就是保留对象初始化状态的不变部分,这样对象就用不着在每次使用时重新初始化(构造)及破坏(析构)。

{

typedef struct mempool_s {
    spinlock_t lock; /*保护内存池的自旋锁*/
    int min_nr; /*内存池中最少可分配的元素数目*/
    int curr_nr; /*尚余可分配的元素数目*/
    void **elements; /*指向元素池的指针*/
    void *pool_data; /*内存源,即池中元素真实的分配处*/
    mempool_alloc_t *alloc; /*分配元素的方法*/
    mempool_free_t *free; /*回收元素的方法*/
    wait_queue_head_t wait; /*被阻塞的等待队列*/
} mempool_t;

空闲块:没有分配对象,即整个块内对象空间均可分配。

2)object_pool

函数先是从pool_data中申请元素对象,当从pool_data无法成功申请到时,才会从池中提取对象使用,因此可以发现内核内存池mempool其实是一种后备池,在内存紧张的情况下才会真正从池中获取,这样也就能保证在极端情况下申请对象的成功率,单也不一定总是会成功,因为内存池的大小毕竟是有限的,如果内存池中的对象也用完了,那么进程就只能进入睡眠,也就是被加入到pool->wait的等待队列,等待内存池中有可用的对象时被唤醒,重新尝试从池中申请元素:

              kfree(pool);

 

init_wait(&wait);
prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE);
spin_unlock_irqrestore(&pool->lock, flags);
io_schedule_timeout(5*HZ);
finish_wait(&pool->wait, &wait);

 

pool的加锁版本。

在申请新的对象空间时,如果缓冲区中存在部分块,那么首先查看部分块寻找空闲对象空间,若未成功再查看空闲块,如果还未成功则为这个对象分配一块新的slab块。

 

       /*若成功创建内存池,则返回内存池对象的指针,这样就可以利用mempool_alloc和mempool_free访问内存池了。*/

{

                                       mempool_free_slab,

~object_pool  单独拿出这个说下,若析构的时候有对象未被destroy,可以检测到,释放内存前对其执行destroy

 

  std::vector<CStudent *,boost::fast_pool_allocator<CStudent *> > v(8);

       void *pool_data;     /* 内存池与内核缓冲区结合使用(上面的简介当中提到了,Linux采用slab技术预先为每一种内存对象分配了缓存区,每当我们申请某个类型的内存对象时,实际是从这种缓存区获取内存),这个指针一般是指向这种内存对象对应的缓存区的指针 */

malloc/free  基于add_block/malloc/free实现,高效

*/

{

              void *element;

}

 

Boost库的pool提供了一个内存池分配器,用于管理在一个独立的、大的分配空间里的动态内存分配。Boost库的pool主要适用于快速分配同样大小的内存块,尤其是反复分配和释放同样大小的内存块的情况。使用pool内存池主要有以下两个优点:

rpc_buffer_slabp = kmem_cache_create("rpc_buffers",

   char name[10];

 * @pool_data:     根据用户自定义内存分配函数所提供的可选私有数据,一般是缓存区指针

boost官方网站:  http://www.boost.org/

四.内存池的使用

 

上面提到,内存池的使用是与特定类型的内存对象缓存区相关联的。例如,在系统rpc服务中,系统初始化时,会为rpc_buffers预先分配缓存区,调用如下语句:

    stl::allocator的替换方案。两者都是基于singleton_pool实现,实现了stl::allocator要求的接口规范。两者的使用相同,区别在于pool_allocator的内部实现调用了ordered_malloc和ordered_free,可以满足对大量的连续内存块的分配请求。fast_pool_allocator 的内部实现调用了malloc和free,比较适合于一次请求单个大内存块的情况,但也适用于通用分配,不过具有一些性能上的缺点。因此推荐使用后者。

void * mempool_alloc(mempool_t *pool, int gfp_mask)

    singleton_pool为单例类,是对pool的加锁封装,适用于多线程环境,其中所有函数都是静态类型。它的模版参数有5个,tag:标记而已,无意义;RequestedSize:block的长度;UserAllocator:分配子,默认还是default_user_allocator_new_delete;Mutex:锁机制,默认值最终依赖于系统环境,linux下是pthread_mutex,它是对pthread_mutex_t的封装;NextSize:内存不足的时候,申请的block数量,默认是32。最全面的使用singleton_pool类似这样:typedef boost::singleton_pool<singleton_pool_tag,sizeof(CStudent),default_user_allocator_new_delete,details::pool::default_mutex,200> global;

       pool->alloc = alloc_fn;

 

/**

{

如果需要使用已经创建的内存池,则需要调用mempool_alloc从内存池中申请内存以及调用mempool_free将用完的内存还给内存池。

   int age;

              if (unlikely(!element)) {

  v[1]=pObj;

三.内核缓存区和内存池的初始化

   return 0;

                                        RPC_BUFFER_MAXSIZE,

  return 0;

一.Linux系统内核内存管理简介

{

       mb();

 

                                       mempool_alloc_slab,

   boost::pool<> student_pool(sizeof(CStudent));

                                        NULL, NULL);

   return 0;

              element = pool->alloc(GFP_KERNEL, pool->pool_data);

int main()

       /*如果池中的成员(空闲)的数量低于满时的一半时,需要额外从系统中申请内存,而不是从内存池中申请了。但是如果这段内存使用完了,则调用mempool_free将其存放到内存池中,下次使用就不再申请了。*/

    它暴露的函数和pool相同。

 * @alloc_fn:       用户自定义内存分配函数

release_memory/purge_memory 前者释放池中未使用内存,后者释放池中所有内存。另池析构也会释放内存

       mempool_free_t *free;   /* 内存释放函数,其它同上 */

 

       /*这里存在一些不明白的地方,先将用户传递进来的gfp掩码标志去掉__GFP_WAIT 和 __GFP_IO 两个标志,试图调用用户自定义分配函数从缓存区申请一个内存对象,而不是首先从内存池从分配,如果申请不到,再从内存池中分配。*/

 int age;

{

 

       pool->free = free_fn;

#include <boost/pool/pool_alloc.hpp>

 * mempool_create – 创建一个内存池对象

}

       DEFINE_WAIT(wait);

ordered_malloc/ordered_free  基于add_ordered_block/malloc/ordered_free实现,在pool中无任何意义,切勿使用。

                     return NULL;

1)pool

              return element;

    fast_pool_allocator的模版参数有四个:类型,分配子,锁类型,内存不足时的申请的block数量,后三者都有默认值,不再说了。它使用的singleton_pool的tag是boost::fast_pool_allocator_tag。

Linux内存池是在2.6版内核中加入的,主要的数据结构定义在mm/mempool.c中。

}CStudent;

       if (!pool)

int main()

转自:

基本的定长内存池

                                       rpc_buffer_slabp);

}CStudent;

                     free_pool(pool);

   A *const pA=obj_pool.construct();

       pool->pool_data = pool_data;

   boost::object_pool<A> obj_pool;

mempool_create函数就是内存池创建函数,负责为一类内存对象构造一个内存池,传递的参数包括,内存池大小,定制的内存分配函数,定制的内存析构函数,这个对象的缓存区指针。下面是mempool_create函数的具体实现:

   char name[10];

Linux采用“按需调页”算法,支持三层页式存储管理策略。将每个用户进程4GB长度的虚拟内存划分成固定大小的页面。其中0至3GB是用户态空间,由各进程独占;3GB到4GB是内核态空间,由所有进程共享,但只有内核态进程才能访问。

   obj_pool.destroy(pA);

 * @free_fn:       用户自定义内存释放函数

};

                                        0, SLAB_HWCACHE_ALIGN,

 

       return pool;

   A():data_(0){}

       /*根据内存池的最小长度为elements数组分配内存*/

   global::free(df);

完全块:没有空闲对象。

   int age;

       spin_lock_init(&pool->lock);

 char name[10];

rpc_buffer_mempool = mempool_create(RPC_BUFFER_POOLSIZE,

1、              不定长内存池。典型的实现有apr_pool、obstack。优点是不需要为不同的数据类型创建不同的内存池,缺点是造成分配出的内存不能回收到池中。这是由于这种方案以session为粒度,以业务处理的层次性为设计基础。

}

     

 

construct/destroy 基于本类的malloc/free实现,额外调用默认构造函数和默认析构函数。

       if (!pool->elements) {

   return 0;

              return NULL;

#include <boost/pool/object_pool.hpp>

 

int main()

} mempool_t;

内存池分类:

              add_element(pool, element);

      pool库主要提供了四种内存池接口,分别是pool、object_pool、singleton_pool和pool_allocator/fast_pool_allocator。

       void *element;

    pool的模版参数只有一个分配子类型,boost提供了两种default_user_allocator_new_delete/default_user_allocator_malloc_free,指明申请释放内存的时候使用new/delete,还是malloc/free,默认是default_user_allocator_new_delete。构造函数有2个参数:nrequested_size,nnext_size。nrequested_size是block的大小(因为void*保存序号,因此boost内置了block的最小值,nrequested_size过小则取内置值),nnext_size是simple_segregated_storage中内存不足的时候,申请的block数量,默认是32。最全面的实例化pool类似这样:boost::pool<boost::default_user_allocator_malloc_free> student_pool(sizeof(CStudent),255);

              }

}

       /*为内存池对象分配内存*/

 

       if (likely(element != NULL))

 

l         缓冲区(cache):一种对象的所有实例都存在同一个缓存区中。不同的对象,即使大小相同,也放在不同的缓存区内。每个缓存区有若干个slab,按照满,半满,空的顺序排列。在slab分配的思想中,整个内核态内存块可以看作是按照这种缓存区来组织的,对每一种对象使用一种缓存区,缓存区的管理者记录了这个缓存区中对象的大小,性质,每个slab块中对象的个数以及每个slab块大小。

   CStudent * const obj=(CStudent *)student_pool.malloc();

       init_waitqueue_head(&pool->wait);

private:

{

 

       wait_queue_head_t wait;/* 任务等待队列 */

public:

二.内存池的数据结构

    为什么boost::object_pool要设计成这样?能调用构造函数和析构函数显然不是boost::object_pool类设计的出发点,因为构造函数只能执行默认构造函数(首次发表错误:可以调用任意的构造函数,参见代码文件:boost/pool/detail/pool_construct.inc和boost/pool/detail/pool_construct_simple.inc,感谢eXile指正),近似于无,它的重点是内存释放时候的清理工作,这个工作默认的析构函数就足够了。apr_pool内存池中就可以注册内存清理函数,在释放内存的时刻执行关闭文件描述符、关闭socket等操作。boost::object_pool也想实现同样的功能,因此设计了destroy这个函数,而同时为了防止用户遗漏掉这个调用,而又在内存池析构的时候进行了检测回收。为了这个目的而又不至于析构object_pool的时间复杂度是O(n平方),boost::object_pool付出了沉重的代价,在每次的destoy都执行排序功能,时间复杂度O(n),最后析构的时间复杂度是O(n),同样为了这个目的,从simple_segregated_storage增加了add_ordered_block/ordered_free,pool增加了ordered_malloc/ordered_free等累赘多余的功能。

       element = pool->alloc(gfp_nowait|__GFP_NOWARN, pool->pool_data);

typedef struct singleton_pool_tag{}singleton_pool_tag;

*/

2、             定长内存池。典型的实现有LOKI、BOOST。特点是为不同类型的数据结构分别创建内存池,需要内存的时候从相应的内存池中申请内存,优点是可以在使用完毕立即把内存归还池中,可以更为细粒度的控制内存块。
    与变长的相比,这种类型的内存池更加通用,另一方面对于大量不同的数据类型环境中,会浪费不少内存。但一般系统主要的数据结构都不会很多,并且都是重复申请释放使用,这种情况下,定长内存池的这点小缺点可以忽略了。

              return NULL;

   int data_;

l         slab块:slab块是内核内存分配与页面级分配的接口。每个slab块的大小都是页面大小的整数倍,有若干个对象组成。slab块共分为三类:

    object_pool继承至pool,有两个模版参数,第一个就是对象类型,第二个是分配子类型,默认同pool是default_user_allocator_new_delete。构造函数参数只有nnext_size,意义以及默认值同pool。最全面的实例化object_pool类似这样:boost::pool<A,boost::default_user_allocator_malloc_free> obj_pool(255);

       memset(pool, 0, sizeof(*pool));

   typedef boost::singleton_pool<singleton_pool_tag,sizeof(CStudent)>  global;

       pool->min_nr = min_nr;

typedef struct student_st

       int min_nr;             /* elements数组中的成员数量 */

{

l         对象:将被申请的空间视为对象,使用构造函数初始化对象,然后由用户使用对象。

总结:boost::pool小巧高效,多多使用,多线程环境下使用boost::singleton_pool,不要使用两者的ordered_malloc/ordered_free函数。boost::object_pool不建议使用,可以改造后使用。pool_allocator/fast_pool_allocator推荐使用后者。

slab:在操作系统的运作过程中,经常会涉及到大量对象的重复生成、使用和释放问题。对象生成算法的改进,可以在很大程度上提高整个系统的性能。在Linux系统中所用到的对象,比较典型的例子是inode、task_struct等,都又这些特点。一般说来,这类对象的种类相对稳定,每种对象的数量却是巨大的,并且在初始化与析构时要做大量的工作,所占用的时间远远超过内存分配的时间。但是这些对象往往具有这样一个性质,即他们在生成时,所包括的成员属性值一般都赋成确定的数值,并且在使用完毕,释放结构前,这些属性又恢复为未使用前的状态。因此,如果我们能够用合适的方法使得在对象前后两次背使用时,在同一块内存,或同一类内存空间,且保留了基本的数据结构,就可以大大提高效率。slab算法就是针对上述特点设计的。

    pool提供的函数主要有:

       /*初始化内存池的相关参数*/

#include <boost/pool/pool.hpp>

typedef struct mempool_s {

  2.  告别程序内存泄漏的烦恼,pool库会在内部对内存自动进行管理,避免了程序员一不小心而造成的内存泄漏问题。

       spinlock_t lock;

class A{

       }

}

              /*将刚刚申请到的内存挂到elements数组的相应位置上,并修改curr_nr的值*/

    基于上面讨论的原因,boost::object_pool被设计成了现在的样子,成了一个鸡肋类。类的设计者似乎忘记了内存池使用的初衷,忘记了内存池中内存申请释放的频率很高,远远大于内存池对象的析构。如果你依然想使用类似于此的内存清理功能,可以在boost::object_pool上修改,不复写malloc/free即可,重写object_pool的析构,简单释放内存就好,因此析构object_pool前不要忘记调用destroy,这也是使用placement new默认遵守的规则,或者保持以前的析构函数,牺牲析构时的性能。placement new的作用是为已经申请好的内存调用构造函数,使用流程为(1)申请内存buf(2)调用placement new:new(buf)construtor()(3)调用析构destructor()(4)释放内存buf。#include<new>可以使用placement new。

                            mempool_free_t *free_fn, void *pool_data)

参考资料:

Linux将物理内存也划分成固定大小的页面,由数据结构page管理,有多少页面就有多少page结构,它们又作为元素组成一个数组mem_map[]。

  1. 能够有效地管理许多小型对象的分配和释放工作,避免了自己去管理内存而产生的内存碎片和效率低下问题。

       int curr_nr;            /* 当前elements数组中空闲的成员数量 */

{

       pool->elements = kmalloc(min_nr * sizeof(void *), GFP_KERNEL);

  CStudent *pObj=new CStudent();

       void **elements;    /* 用来存放内存成员的二维数组,其长度为min_nr,宽度是上述各个内存对象的长度,因为对于不同的对象类型,我们会创建相应的内存池对象,所以每个内存池对象实例的element宽度都是跟其内存对象相关的 */

int main()

       mempool_t *pool;

typedef struct student_st

       unsigned long flags;

}CStudent;

 时时app平台注册网站 1

4)pool_allocator/fast_pool_allocator

repeat_alloc:

 

       while (pool->curr_nr < pool->min_nr) {

   CStudent * const df=(CStudent *)global::malloc();

部分块:只分配了部分对象空间,仍有空闲对象。

#include <boost/pool/singleton_pool.hpp>

面向对象的slab分配中有如下几个术语:

       /*首先为内存池预先分配min_nr个element对象,这些对象就是为了存储相应类型的内存对象的。数据结构形入:

       }

 

本文由时时app平台注册网站发布于时时app平台注册网站,转载请注明出处:基本内部存储器池管理技能完毕深入分析【转】

关键词: