本文为原创,转载请注明:http://www.cnblogs.com/tolimit/

  内存池是用于预先申请一些内存用于备用,当系统内存不足无法从伙伴系统和slab中获取内存时,会从内存池中获取预留的那些内存。内存池与特殊slab一样,需要使用的设备需要自己创建内存池,而不是系统会自动生成。书上形容得好,内存比作新鲜食物,内存池比作罐头食物,人比作拥有此内存池的模块,当无法吃到新鲜食物时,就需要打开罐头吃罐头食物。

  一般情况下,内存池建立在slab之上,也就是说池子里存放的是slab对象,当某个模块创建一个属于自己的内存池时,创建之前,需要设计好一个分配函数和一个释放函数,创建函数用于创建内存池时自动申请对应的slab对象,而释放函数则是用于将slab对象释放回到slab/slub中。当然经过看代码,感觉内存池中也可以存放页,这个可以写个模块测试一下。

  具体先看看内存池主要的数据结构:

 /* 内存池,用于拥有该内存池的"拥有者"进行内存储备,只有在常规情况下分配不到内存的时候才会使用自己的内存池 */
typedef struct mempool_s {
spinlock_t lock;
/* 最大元素个数,也是初始个数,当内存池被创建时,会调用alloc函数申请此变量相应数量的slab放到elements指向的指针数组中 */
int min_nr; /* nr of elements at *elements */
/* 当前元素个数 */
int curr_nr; /* Current nr of elements at *elements */
/* 指向一个数组,在mempool_create中会分配内存,数组中保存指向元素指针 */
void **elements; /* 内存池的拥有者的私有数据结构,当元素是slab中的对象时,这里保存的是slab缓存描述符 */
void *pool_data;
/* 当元素是slab中的对象时,会使用方法mempool_alloc_slab()和mempool_free_slab() */
/* 分配一个元素的方法 */
mempool_alloc_t *alloc;
/* 释放一个元素的方法 */
mempool_free_t *free;
/* 当内存池为空时使用的等待队列,当内存池中空闲内存对象为空时,获取函数会将当前进程阻塞,直到超时或者有空闲内存对象时才会唤醒 */
wait_queue_head_t wait;
} mempool_t;

  内核里使用mempool_create()创建一个内存池,使用mempool_destroy()销毁一个内存池,使用mempool_alloc()申请内存和mempool_free()是否内存。一切信息都在代码当中,我们直接看代码就清楚知道内存池是怎么实现的了。

  首先我们先看mempool_create(),mempool_create()流程很简单,它主要做的就是分配一个mempool_t结构体,然后根据参数初始化此结构体,最后调用传入的alloc()函数min_nr次,把申请到的内存全部存放到elements中,如下:

 mempool_t *mempool_create_node(int min_nr, mempool_alloc_t *alloc_fn,
mempool_free_t *free_fn, void *pool_data,
gfp_t gfp_mask, int node_id)
{
mempool_t *pool;
/* 分配一个内存池结构体 */
pool = kzalloc_node(sizeof(*pool), gfp_mask, node_id);
if (!pool)
return NULL;
/* 分配一个长度为min_nr的数组用于存放申请后对象的指针 */
pool->elements = kmalloc_node(min_nr * sizeof(void *),
gfp_mask, node_id);
if (!pool->elements) {
kfree(pool);
return NULL;
}
/* 初始化锁 */
spin_lock_init(&pool->lock);
pool->min_nr = min_nr;
/* 私有成员 */
pool->pool_data = pool_data;
/* 初始化等待队列 */
init_waitqueue_head(&pool->wait);
pool->alloc = alloc_fn;
pool->free = free_fn; /*
* First pre-allocate the guaranteed number of buffers.
*/
/* pool->curr_nr初始为0,因为pool使用kzalloc_node分配的,会清0 */
while (pool->curr_nr < pool->min_nr) {
void *element; /* 调用pool->alloc函数min_nr次 */
element = pool->alloc(gfp_mask, pool->pool_data);
/* 如果申请不到element,则直接销毁此内存池 */
if (unlikely(!element)) {
mempool_destroy(pool);
return NULL;
}
/* 添加到elements指针数组中 */
add_element(pool, element);
}
/* 返回内存池结构体 */
return pool;
}

  再看看mempool_destroy(),此函数也很简单,直接将elements存放的内存依个释放掉,然后将该释放的elements指针数组和mempool_t结构都释放掉

 /* 销毁一个内存池 */
void mempool_destroy(mempool_t *pool)
{
while (pool->curr_nr) {
/* 销毁elements数组中的所有对象 */
/* element = pool->elements[--pool->curr_nr] */
void *element = remove_element(pool);
pool->free(element, pool->pool_data);
}
/* 销毁elements指针数组 */
kfree(pool->elements);
/* 销毁内存池结构体 */
kfree(pool);
}

  现在我们看mempool_alloc()函数,当模块从此内存池中获取内存对象时,会调用此函数,此函数优先从伙伴系统或slab缓冲区获取需要的内存对象,当内存不足导致无法获取内存对象时,才会从内存池elements数组中获取,如果elements也没有空闲的内存对象,根据传入的分配标识进行相应的处理:

 /* 内存池分配对象 */
void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask)
{
void *element;
unsigned long flags;
wait_queue_t wait;
gfp_t gfp_temp; VM_WARN_ON_ONCE(gfp_mask & __GFP_ZERO);
/* 如果有__GFP_WAIT标志,则会先阻塞,切换进程 */
might_sleep_if(gfp_mask & __GFP_WAIT); /* 不使用预留内存 */
gfp_mask |= __GFP_NOMEMALLOC; /* don't allocate emergency reserves */
/* 分配页时如果失败则返回,不进行重试 */
gfp_mask |= __GFP_NORETRY; /* don't loop in __alloc_pages */
/* 分配失败不提供警告 */
gfp_mask |= __GFP_NOWARN; /* failures are OK */ /* gfp_temp等于gfp_mask去除__GFP_WAIT和__GFP_IO的其他标志 */
gfp_temp = gfp_mask & ~(__GFP_WAIT|__GFP_IO); repeat_alloc: /* 使用内存池中的alloc函数进行分配对象,实际上就是从伙伴系统或者slab缓冲区获取内存对象 */
element = pool->alloc(gfp_temp, pool->pool_data);
/* 在内存富足的情况下,一般是能够获取到内存的 */
if (likely(element != NULL))
return element; /* 在内存不足的情况,造成从伙伴系统或slab缓冲区获取内存失败,则会执行到这 */
/* 给内存池上锁,获取后此段临界区禁止中断和抢占 */
spin_lock_irqsave(&pool->lock, flags);
/* 如果当前内存池中有空闲数量,就是初始化时获取的内存数量保存在curr_nr中 */
if (likely(pool->curr_nr)) {
/* 从内存池中获取内存对象 */
element = remove_element(pool);
/* 解锁 */
spin_unlock_irqrestore(&pool->lock, flags); /* 写内存屏障,保证之前的写操作已经完成 */
smp_wmb();
/* 用于debug */
kmemleak_update_trace(element);
return element;
} /* 这里是内存池中也没有空闲内存对象的时候进行的操作 */
/* gfp_temp != gfp_mask说明传入的gfp_mask允许阻塞等待,但是之前已经阻塞等待过了,所以这里立即重新获取一次 */
if (gfp_temp != gfp_mask) {
spin_unlock_irqrestore(&pool->lock, flags);
gfp_temp = gfp_mask;
goto repeat_alloc;
} /* 传入的参数gfp_mask不允许阻塞等待,分配不到内存则直接退出 */
if (!(gfp_mask & __GFP_WAIT)) {
spin_unlock_irqrestore(&pool->lock, flags);
return NULL;
} init_wait(&wait);
/* 加入到内存池的等待队列中,并把当前进程的状态设置为只有wake_up信号才能唤醒的状态 ,也就是当内存池中有空闲对象时,会主动唤醒等待队列中的第一个进程,或者等待超时时,定时器自动唤醒 */
prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE); spin_unlock_irqrestore(&pool->lock, flags); /* 阻塞等待5秒 */
io_schedule_timeout(*HZ); /* 从内存池的等待队列删除此进程 */
finish_wait(&pool->wait, &wait);
/* 跳转到repeat_alloc,重新尝试获取内存对象 */
goto repeat_alloc;
}
EXPORT_SYMBOL(mempool_alloc);

  最后我们看看mempool_free()函数,此函数用于将空闲内存对象释放到内存池中,当内存池中空闲对象不足时,优先将空闲内存对象放到elements数组中,否则直接返回到伙伴系统或slab缓冲区中。

 /* 内存池释放内存对象操作 */
void mempool_free(void *element, mempool_t *pool)
{
unsigned long flags; /* 传入的对象为空,则直接退出 */
if (unlikely(element == NULL))
return; /* 读内存屏障 */
smp_rmb(); /* 如果当前内存池中空闲的内存对象少于内存池中应当保存的内存对象的数量时,优先把释放的对象加入到内存池空闲数组中 */
if (unlikely(pool->curr_nr < pool->min_nr)) {
spin_lock_irqsave(&pool->lock, flags);
if (likely(pool->curr_nr < pool->min_nr)) {
/* 加入到pool->elements[pool->curr_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);
}
EXPORT_SYMBOL(mempool_free);

  或许看完这些还是不太清楚怎么使用内存池,毕竟alloc和free函数需要我们自己去设计,如果内存池是使用slab缓冲区进行内存分配时,可将slab缓冲区描述符写入到mempool_t中的pool_data中,alloc和free函数可以直接指定mempool_alloc_slab()和mempool_free_slab(),如下:

 /* 创建一个slab缓冲区 */
drbd_request_cache = kmem_cache_create(
"drbd_req", sizeof(struct drbd_request), , , NULL);
if (drbd_request_cache == NULL)
goto Enomem; /* 创建一个内存池,私有成员设置为drbd_request_cache这个slab缓冲区,alloc和free函数设置为mempool_alloc_slab()和mempool_free_slab() */
drbd_request_mempool = mempool_create(number,mempool_alloc_slab,mempool_free_slab,drbd_request_cache);
if (drbd_request_mempool == NULL)
goto Enomem; /* 若内存池从slab缓冲区中获取内存对象,则内核提供的alloc函数 */
void *mempool_alloc_slab(gfp_t gfp_mask, void *pool_data)
{
struct kmem_cache *mem = pool_data;
return kmem_cache_alloc(mem, gfp_mask);
}
EXPORT_SYMBOL(mempool_alloc_slab); /* 若内存池从slab缓冲区中获取内存对象,则内核提供的free函数 */
void mempool_free_slab(void *element, void *pool_data)
{
struct kmem_cache *mem = pool_data;
kmem_cache_free(mem, element);
}
EXPORT_SYMBOL(mempool_free_slab);

linux内存源码分析 - 内存池的更多相关文章

  1. linux内存源码分析 - 内存回收(整体流程)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 当linux系统内存压力就大时,就会对系统的每个压力大的zone进程内存回收,内存回收主要是针对匿名页和文 ...

  2. linux内存源码分析 - 内存压缩(同步关系)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 最近在看内存回收,内存回收在进行同步的一些情况非常复杂,然后就想,不会内存压缩的页面迁移过程中的同步关系也 ...

  3. linux内存源码分析 - 内存压缩(实现流程)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 本文章最好结合linux内存管理源码分析 - 页框分配器与linux内存源码分析 -伙伴系统(初始化和申请 ...

  4. (转)linux内存源码分析 - 内存回收(整体流程)

    http://www.cnblogs.com/tolimit/p/5435068.html------------linux内存源码分析 - 内存回收(整体流程) 概述 当linux系统内存压力就大时 ...

  5. linux内存源码分析 - 内存回收(lru链表)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 对于整个内存回收来说,lru链表是关键中的关键,实际上整个内存回收,做的事情就是处理lru链表的收缩,所以 ...

  6. linux内存源码分析 - 内存回收(匿名页反向映射)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 看完了内存压缩,最近在看内存回收这块的代码,发现内容有些多,需要分几块去详细说明,首先先说说匿名页的反向映 ...

  7. (转)linux内存源码分析 - 内存回收(lru链表)

    原文:http://www.cnblogs.com/tolimit/p/5447448.html 概述 对于整个内存回收来说,lru链表是关键中的关键,实际上整个内存回收,做的事情就是处理lru链表的 ...

  8. linux内存源码分析 - 零散知识点

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 直接内存回收中的等待队列 内存回收详解见linux内存源码分析 - 内存回收(整体流程),在直接内存回收过程中, ...

  9. linux内存源码分析 - 伙伴系统(初始化和申请页框)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前的文章已经介绍了伙伴系统,这篇我们主要看看源码中是如何初始化伙伴系统.从伙伴系统中分配页框,返回页框于伙伴系 ...

随机推荐

  1. 数据筛选和API优化

    筛选数据 需求:如果数据库中存在OrderNum相同,且IsDefault不同的记录,那么IsDefault值为0的记录将替换值为1的记录(IsDefault值为1的记录不展示). 由于查出来的数据不 ...

  2. Arcgis去除Z,M值

    在arcgis中,我们常用的数据类型有点,线,面数据,但是有时候我们在转换数据的时候经常会带有ZM值,而带ZM值的数据在有些软件中是不会显示的,也就是说显示存在问题,所以我们需要去除掉ZM值 在arc ...

  3. SQL注入与防范

    首先给大家看个例子: 1)小编首先在数据库中建立了一张测试表logintable,表内有一条测试信息: 然后写了个测试程序: package com.java.SqlInject; import ja ...

  4. 解决OpenCV JavaCameraView相机preview方向问题

    网上找了很多解决都是有问题的,研究了半天源码解决了这个问题.我是从整个相机启动和数据传输的过程着手的,这里捡重点介绍一下,最后会贴上修改后的两个源文件. 首先要知道一个概念. 图里的小圆圈是Home按 ...

  5. EasyUI动画效果

    1.jQuery动画效果 a)基本效果 >show(speed),显示 >hide(speed),隐藏 >toggle(speed),切换 b)滑动的效果 >slideUp(s ...

  6. Hibernate设置事务的隔离级别

    方式有两种: 1)修改配置文件hibernate.cfg.xml实现 <hibernate-configuration> <session-factory> ...... &l ...

  7. MS SQL作业Schedule的限制注意事项

      最近遇到了一个关于MS SQL作业Schedule下有限制的特殊案例,有一个作业,用户要求执行的时间为:9:30,14:30,16:30, 19:00,于是我设置了两个Schedule,其中一个每 ...

  8. Oracle解锁scott用户

    解决: (1)conn sys/sys as sysdba;//以DBA的身份登录 (2)alter user scott account unlock;// 然后解锁 (3)conn scott/t ...

  9. 记一次zookeeper单机伪集群分布

    zookeeper的各版本(历史版本)下载地址:http://apache.org/dist/zookeeper/ 环境>:linux 下载的zookeeper解压成3个

  10. [20180914]oracle 12c 表 full_hash_value如何计算.txt

    [20180914]oracle 12c 表 full_hash_value如何计算.txt --//昨天在12c下看表full_hash_value与11g的full_hash_value不同,不过 ...