一、内存池原理
平时我们直接所使用的 malloc,new,free,delete 等等 API 申请内存分配,这做缺点在于,由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。
memory pool 是一种内存分配方式,又被称为固定大小区块规划。内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等的内存块留作备用。当有新的内存需要的时候,就直接从内存池中分出一部分内存块,若内存块不够再继续申请新的内存,这样做优势,使得内存分配效率得到提升。
二、内存池源码分析
1.内存池数据结构源码
先看看内存池主要的数据结构,结构体位于include/linux/mempool.h文件中:
typedef struct mempool_s {
spinlock_t lock;//防止多处理器并发而引入的锁
int min_nr; //elements数组中的成员数量
int curr_nr;//当前elements数组中空闲的成员数量
void **elements;//用来存放内存成员的二维数组,等于elements[min_nr][内存对象的长度]
//内存池与内核缓冲区结合使用的指针(这个指针专门用来指向这种内存对象对应的缓存区的指针)
void *pool_data;
mempool_alloc_t *alloc;//内存分配函数
mempool_free_t *free;//内存释放函数
wait_queue_head_t wait;//任务等待队列
} mempool_t;
2.内存池创建函数源码
内核里使用mempool_create()创建一个内存池,使用mempool_destroy()销毁一个内存池,使用mempool_alloc()申请内存和mempool_free()是否内存。mempool_create,函数位于mm/mempool.c文件中:
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,
GFP_KERNEL, NUMA_NO_NODE);
}
EXPORT_SYMBOL(mempool_create);
/******************
创建一个内存池对象
参数:
min_nr : 为内存池分配的最小内存成员数量
alloc_fn : 用户自定义内存分配函数(可以使用系统定义函数)
free_fn : 用户自定义内存释放函数(可以使用系统定义函数)
pool.data :根据用户自定义内存分配函数所提供的可选私有数据,一般是缓存区指针
gfp_mask : 内存分配掩码
node_id : 内存节点的id
******************/
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;
//初始化内存池
if (mempool_init_node(pool, min_nr, alloc_fn, free_fn, pool_data,
gfp_mask, node_id)) {
kfree(pool);
return NULL;
}
return pool;//返回内存池结构体
}
EXPORT_SYMBOL(mempool_create_node);
mempool_create主要通过mempool_create_node来创建内存池,mempool_create_node首先分配内存池对象的内存后使用mempool_init_node初始化内存池,mempool_init_node:
int mempool_init_node(mempool_t *pool, int min_nr, mempool_alloc_t *alloc_fn,
mempool_free_t *free_fn, void *pool_data,
gfp_t gfp_mask, int node_id)
{
//初始化内存池的相关参数
spin_lock_init(&pool->lock);//初始化锁
pool->min_nr = min_nr;
pool->pool_data = pool_data;
pool->alloc = alloc_fn;
pool->free = free_fn;
init_waitqueue_head(&pool->wait);//初始化等待队列
//分配一个长度为min_nr的数组用于存放申请后对象的指针
pool->elements = kmalloc_array_node(min_nr, sizeof(void *),
gfp_mask, node_id);
if (!pool->elements)
return -ENOMEM;
/*
* First pre-allocate the guaranteed number of buffers.
*/
//首先保证预分配的缓冲区数量
while (pool->curr_nr < pool->min_nr) {
void *element;
//调用pool->alloc函数min_nr次
element = pool->alloc(gfp_mask, pool->pool_data);
if (unlikely(!element)) {//如果申请不到element,则直接销毁此内存池
mempool_exit(pool);
return -ENOMEM;
}
add_element(pool, element);//添加到elements指针数组中
}
return 0;
}
EXPORT_SYMBOL(mempool_init_node);
3.内存池销毁函数源码
我们再看看mempool_destroy,mempool_destroy:
//销毁一个内存池
void mempool_destroy(mempool_t *pool)
{
if (unlikely(!pool))
return;
mempool_exit(pool);//释放内存池中的内存块
kfree(pool);//释放内存池结构体
}
EXPORT_SYMBOL(mempool_destroy);
void mempool_exit(mempool_t *pool)
{
while (pool->curr_nr) {
void *element = remove_element(pool);//把elements指针数组中的内存移除
pool->free(element, pool->pool_data);//释放elements数组中的所有对象
}
kfree(pool->elements);//销毁elements指针数组
pool->elements = NULL;
}
EXPORT_SYMBOL(mempool_exit);
mempool_destroy也很简单,首先调用mempool_exit直接将elements存放的内存依个释放掉再释放elements指针数组结构体,后将mempool_t结构也释放掉。
4.内存池分配内存函数
现在我们看mempool_alloc()函数
//内存池分配对象
void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask)
{
void *element;
unsigned long flags;
wait_queue_entry_t wait;
gfp_t gfp_temp;
//形参gfp_mask中不能包含_GFP_ZERO
VM_WARN_ON_ONCE(gfp_mask & __GFP_ZERO);
//如果有__GFP_WAIT标志,则会先阻塞,切换进程
//#define might_sleep_if(cond) do { if (cond) might_sleep(); } while (0)
might_sleep_if(gfp_mask & __GFP_DIRECT_RECLAIM);
gfp_mask |= __GFP_NOMEMALLOC;//不使用预留内存
gfp_mask |= __GFP_NORETRY;//分配页时如果失败则返回,不进行重试
gfp_mask |= __GFP_NOWARN;//分配失败不提供警告
//gfp_mask只保留__GFP_DIRECT_RECLAIM和__GFP_IO标志
gfp_temp = gfp_mask & ~(__GFP_DIRECT_RECLAIM|__GFP_IO);
repeat_alloc:
//使用内存池中的alloc函数进行分配对象
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();//写内存屏障,保证之前的写操作已经完成
/*
* Update the allocation stack trace as this is more useful
* for debugging.
*/
kmemleak_update_trace(element);//用于debug
return element;
}
/*
* We use gfp mask w/o direct reclaim or IO for the first round. If
* alloc failed with that and @pool was empty, retry immediately.
*/
//这里是内存池中也没有空闲内存对象的时候进行的操作
//如果gfp_temp != gfp_mask
if (gfp_temp != gfp_mask) {
spin_unlock_irqrestore(&pool->lock, flags);
gfp_temp = gfp_mask;
goto repeat_alloc;//跳到repeat_alloc重新获取一次
}
/* We must not sleep if !__GFP_DIRECT_RECLAIM */
//传入的参数gfp_mask不允许回收的等待,分配不到内存则直接退出
if (!(gfp_mask & __GFP_DIRECT_RECLAIM)) {
spin_unlock_irqrestore(&pool->lock, flags);
return NULL;
}
/* Let's wait for someone else to return an element to @pool */
init_wait(&wait);//初始化wait等待进程
//加入到内存池的等待队列中,等待当内存池中有空闲对象或者等待超时
prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE);
spin_unlock_irqrestore(&pool->lock, flags);
/*
* FIXME: this should be io_schedule(). The timeout is there as a
* workaround for some DM problems in 2.6.18.
*/
io_schedule_timeout(5*HZ);//阻塞等待5秒
finish_wait(&pool->wait, &wait);//从内存池的等待队列删除此进程
goto repeat_alloc;//跳转到repeat_alloc,重新尝试获取内存对象
}
EXPORT_SYMBOL(mempool_alloc);
当模块从此内存池中获取内存对象时,会调用此函数,此函数优先从伙伴系统或slab缓冲区获取需要的内存对象,当内存不足导致无法获取内存对象时,才会从内存池elements数组中获取,如果elements也没有空闲的内存对象,根据传入的分配标识进行相应的处理,最终会每5秒进行一次重新请求分配。
5.内存池释放内存函数
/ 内存池释放内存对象操作
void mempool_free(void *element, mempool_t *pool)
{
unsigned long flags;
//传入的对象为空,则直接退出
if (unlikely(element == NULL))
return;
/*
* Paired with the wmb in mempool_alloc(). The preceding read is
* for @element and the following @pool->curr_nr. This ensures
* that the visible value of @pool->curr_nr is from after the
* allocation of @element. This is necessary for fringe cases
* where @element was passed to this task without going through
* barriers.
*
* For example, assume @p is %NULL at the beginning and one task
* performs "p = mempool_alloc(...);" while another task is doing
* "while (!p) cpu_relax(); mempool_free(p, ...);". This function
* may end up using curr_nr value which is from before allocation
* of @p without the following rmb.
*/
smp_rmb();//读内存屏障
/*
* For correctness, we need a test which is guaranteed to trigger
* if curr_nr + #allocated == min_nr. Testing curr_nr < min_nr
* without locking achieves that and refilling as soon as possible
* is desirable.
*
* Because curr_nr visible here is always a value after the
* allocation of @element, any task which decremented curr_nr below
* min_nr is guaranteed to see curr_nr < min_nr unless curr_nr gets
* incremented to min_nr afterwards. If curr_nr gets incremented
* to min_nr after the allocation of @element, the elements
* allocated after that are subject to the same guarantee.
*
* Waiters happen iff curr_nr is 0 and the above guarantee also
* ensures that there will be frees which return elements to the
* pool waking up the waiters.
*/
//如果当前内存池中空闲的内存对象少于内存池中应当保存的内存对象的数量时,优先把释放的对象加入到内存池空闲数组中
if (unlikely(pool->curr_nr < pool->min_nr)) {
spin_lock_irqsave(&pool->lock, flags);
if (likely(pool->curr_nr < pool->min_nr)) {
add_element(pool, element);//将用户释放的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);
mempool_free将空闲内存对象释放到内存池中,当内存池中空闲对象不足时,优先将空闲内存对象放到elements数组中,把mempool填满,否则直接释放掉,让内存返回到伙伴系统或slab缓冲区中。