linux内核工程师 3.07节 内核内存池--mempool

内存池(Memery Pool)技术是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。 
    不仅在用户态应用程序中被广泛使用,同时在Linux内核也被广泛使用,在内核中有不少地方内存分配不允许失败。作为一个在这些情况下确保分配的方式,内核开发者创建了一个已知为内存池(或者是 "mempool" )的抽象,内核中内存池真实地只是相当于后备缓存,它尽力一直保持一个空闲内存列表给紧急时使用,而在通常情况下有内存需求时还是从公共的内存中直接分配,这样的做法虽然有点霸占内存的嫌疑,但是可以从根本上保证关键应用在内存紧张时申请内存仍然能够成功。
    下面看下内核内存池的源码,内核内存池的源码在中,实现上非常简洁,描述内存池的结构mempool_t在头文件中定义,结构描述如下:
  1. typedef struct mempool_s {
  2.     spinlock_t lock; /*保护内存池的自旋锁*/
  3.     int min_nr; /*内存池中最少可分配的元素数目*/
  4.     int curr_nr; /*尚余可分配的元素数目*/
  5.     void **elements; /*指向元素池的指针*/
  6.     void *pool_data; /*内存源,即池中元素真实的分配处*/
  7.     mempool_alloc_t *alloc; /*分配元素的方法*/
  8.     mempool_free_t *free; /*回收元素的方法*/
  9.     wait_queue_head_t wait; /*被阻塞的等待队列*/
  10. } mempool_t;
内存池的创建函数mempool_create的函数原型如下:
  1. mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,
  2.                 mempool_free_t *free_fn, void *pool_data)
  3. {
  4.     return mempool_create_node(min_nr,alloc_fn,free_fn, pool_data,-1);
  5. }
函数原型指定内存池可以容纳元素的个数、申请元素的方法、释放元素的方法,以及一个可选的内存源(通常是一个cache),内存池对象创建完成后会自动调用alloc方法从pool_data上分配min_nr个元素用来填充内存池。
内存池的释放函数mempool_destory函数的原型很简单,应该也能猜到是依次将元素对象从池中移除,再释放给pool_data,最后释放池对象,如下:
  1. void mempool_destroy(mempool_t *pool)
  2. {
  3.     while (pool->curr_nr) {
  4.         void *element = remove_element(pool);
  5.         pool->free(element, pool->pool_data);
  6.     }
  7.     kfree(pool->elements);
  8.     kfree(pool);
  9. }
值得注意的是内存池分配和回收对象的函数:mempool_alloc和mempool_free。mempool_alloc的作用是从指定的内存池中申请/获取一个对象,函数原型如下:
  1. void * mempool_alloc(mempool_t *pool, gfp_t gfp_mask){
  2. ......
  3.     element = pool->alloc(gfp_temp, pool->pool_data);
  4.     if (likely(element != NULL))
  5.         return element;

  6.     spin_lock_irqsave(&pool->lock, flags);
  7.     if (likely(pool->curr_nr)) {
  8.         element = remove_element(pool);/*从内存池中提取一个对象*/
  9.         spin_unlock_irqrestore(&pool->lock, flags);
  10.         /* paired with rmb in mempool_free(), read comment there */
  11.         smp_wmb();
  12.         return element;
  13.     }
  14. ......
  15.     
  16. }
函数先是从pool_data中申请元素对象,当从pool_data无法成功申请到时,才会从池中提取对象使用,因此可以发现内核内存池mempool其实是一种后备池,在内存紧张的情况下才会真正从池中获取,这样也就能保证在极端情况下申请对象的成功率,单也不一定总是会成功,因为内存池的大小毕竟是有限的,如果内存池中的对象也用完了,那么进程就只能进入睡眠,也就是被加入到pool->wait的等待队列,等待内存池中有可用的对象时被唤醒,重新尝试从池中申请元素:
  1.     init_wait(&wait);
  2.     prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE);
  3.     spin_unlock_irqrestore(&pool->lock, flags);
  4.     io_schedule_timeout(5*HZ);
  5.     finish_wait(&pool->wait, &wait);
池回收对象的函数mempool_free的原型如下:
  1. void mempool_free(void *element, mempool_t *pool)
  2. {
  3. if (pool->curr_nr < pool->min_nr) {
  4. spin_lock_irqsave(&pool->lock, flags);
  5. if (pool->curr_nr < pool->min_nr) {
  6. add_element(pool, element);
  7. spin_unlock_irqrestore(&pool->lock, flags);
  8. wake_up(&pool->wait);
  9. return;
  10. }
  11. spin_unlock_irqrestore(&pool->lock, flags);
  12. }
  13. pool->free(element, pool->pool_data);
  14. }
其实原则跟mempool_alloc是对应的,释放对象时先看池中的可用元素是否充足(pool->curr_nr == pool->min_nr),如果不是则将元素对象释放回池中,否则将元素对象还给pool->pool_data。
    此外mempool也提供或者说指定了几对alloc/free函数,及在mempool_create创建池时必须指定的alloc和free函数,分别适用于不同大小或者类型的元素的内存池,具体如下:
  1. void *mempool_alloc_slab(gfp_t gfp_mask, void *pool_data)
  2. {
  3.     struct kmem_cache *mem = pool_data;
  4.     return kmem_cache_alloc(mem, gfp_mask);
  5. }
  6. void mempool_free_slab(void *element, void *pool_data)
  7. {
  8.     struct kmem_cache *mem = pool_data;
  9.     kmem_cache_free(mem, element);
  10. }

  11. void *mempool_kmalloc(gfp_t gfp_mask, void *pool_data)
  12. {
  13.     size_t size = (size_t)pool_data;
  14.     return kmalloc(size, gfp_mask);
  15. }
  16. void mempool_kfree(void *element, void *pool_data)
  17. {
  18.     kfree(element);
  19. }

  20. void *mempool_alloc_pages(gfp_t gfp_mask, void *pool_data)
  21. {
  22.     int order = (int)(long)pool_data;
  23.     return alloc_pages(gfp_mask, order);
  24. }
  25. void mempool_free_pages(void *element, void *pool_data)
  26. {
  27.     int order = (int)(long)pool_data;
  28.     __free_pages(element, order);
  29. }
    总体上来讲mempool的实现很简约,但是不简单,而且非常轻便易用,这也是内核奥妙之所在。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

内存池(memory pool)是linux 2.6的一个新特性。内核中有些地方的内存分配是不允许失败的。为了确保这种情况下的成功分配,内核开发者建立了一种称为内存池的抽象。内存池其实就是某种形式的后备高速缓存,它试图始终保存空闲的内存,以便在紧急状态下使用。下边是内存池对象的类型:

[cpp]  view plain copy
  1. typedef struct mempool_s {  
  2.     spinlock_t lock;        /*用来保护对象字段的自旋锁*/  
  3.     int min_nr;     /*内存池中元素的最大个数*/  
  4.     int curr_nr;        /*当前内存池中元素的个数*/  
  5.     void **elements;        /*指向一个数组的指针,该数组由指向保留元素的指针组成*/  
  6.   
  7.   
  8.     void *pool_data;        /*池的拥有者可获得的私有数据*/  
  9.     mempool_alloc_t *alloc; /*分配一个元素的方法*/  
  10.     mempool_free_t *free;   /*释放一个元素的方法*/  
  11.     wait_queue_head_t wait; /*当内存池为空时使用的等待队列*/  
  12. } mempool_t;  
下面介绍一些API,这些API的内核源码不过二百多行:

创建内存池对象,mempool_create():

[cpp]  view plain copy
  1. mempool_t * mempool_create(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data);  
min_nr参数表示的是内存池应始终保持的已分配对象的最少数目,对象的实际分配和释放由alloc_fn和free_fn函数处理,其原型如下:

[cpp]  view plain copy
  1. typedef void *(mempool_alloc_t)(int gfp_mask, void *pool_data);  
  2. typedef void mempool_free_t(void *element, void *pool_data);  
mempool_create的最后一个参数,即pool_data,被传入alloc_fn和free_fn。

在建立内存池之后,可如下所示分配和释放对象:

[cpp]  view plain copy
  1. void *mempool_alloc(mempool_t *pool, int gfp_mask);  
  2. void mempool_free(void *element, mempool_t *pool);  
在创建mempool时,就会多次调用分配函数为预先分配的对象创建内存池,创建的对象个数为min_nr的大小。之后,对mempool_alloc()的调用将首先通过分配函数获得该对象;如果该分配失败,就会返回预先分配的对象(如果存在的话)。如果使用mempool_free释放一个对象,则如果预先分配的对象数目小于要求的最低数目,就会将该对象保留在内存池中;否则,该对象会返回给系统。

可以调用下面的函数来调整mempool的大小:

[cpp]  view plain copy
  1. int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask);  
可以使用下面的函数将内存池返回给系统:
[cpp]  view plain copy
  1. void mempool_destroy(mempool_t *pool);  
在销毁mempool之前,必须将所有已分配的对象返回到内存池中,否则会导致内核oops。

下边是自己编写的一个测试程序:

[cpp]  view plain copy
  1. #include <linux/init.h>  
  2. #include <linux/module.h>  
  3. #include <linux/kernel.h>  
  4. #include <linux/slab.h>  
  5. #include <linux/errno.h>  
  6. #include <linux/mempool.h>  
  7. #include <linux/gfp.h>  
  8. #include <linux/delay.h>  
  9.   
  10. struct my_mempool{  
  11.     int val;  
  12. };  
  13. #define MY_MIN_MEMPOOL 20  
  14.   
  15. struct kmem_cache * mempool_cachep = NULL;  
  16. mempool_t * my_pool = NULL;  
  17. struct my_mempool * mempool_object = NULL;  
  18.   
  19. static int __init mempool_init(void){  
  20.     printk("memory pool test module init!\n");  
  21.     mempool_cachep = kmem_cache_create("mempool_cachep"sizeof(struct my_mempool), 0, 0, NULL);  
  22.     if(!mempool_cachep)  
  23.         return -ENOMEM;  
  24.     my_pool = mempool_create(MY_MIN_MEMPOOL, mempool_alloc_slab, mempool_free_slab, mempool_cachep);  
  25.     if(!my_pool)  
  26.         return -ENOMEM;  
  27.     mempool_object = (struct my_mempool *)mempool_alloc(my_pool, GFP_KERNEL);  
  28.     if(mempool_object)  
  29.         printk("one object has been allocated!\n");  
  30.     else  
  31.         goto fail;  
  32.     printk("memory pool curr_nr is %d, min_nr is %d\n", my_pool->curr_nr, my_pool->min_nr);  
  33.     mempool_free(mempool_object, my_pool);  
  34.     return 0;  
  35. fail:  
  36.     mempool_destroy(my_pool);  
  37.     kmem_cache_destroy(mempool_cachep);  
  38.     return -1;  
  39. }  
  40.   
  41. static void __exit mempool_exit(void){  
  42.     printk("memory pool test module exit!\n");  
  43.     if(my_pool){  
  44.         mempool_destroy(my_pool);  
  45.         printk("mempool has been destroy!\n");  
  46.     }  
  47.   
  48.     if(mempool_cachep){  
  49.         kmem_cache_destroy(mempool_cachep);  
  50.         printk("cache has been destroy!\n");  
  51.     }  
  52. }  
  53.   
  54. module_init(mempool_init);  
  55. module_exit(mempool_exit);  
  56. MODULE_AUTHOR("[email protected]");  
  57. MODULE_LICENSE("GPL");  
这里创建了一个名为mempool_cachep的cache,然后又创建了一个memory pool,它的pool_data传的是指向cache的指针,表示这个memory pool是被用来保存slab对象的。这里分配一个object,然后cat /proc/slabinfo发现mempool_cachep的被使用object增加了1个,说明memory pool并没有从自己的那20个object中取得对象,而是从slab对象的cache中。其实,memory pool就相当于一种家里攒点钱留着保命的策略,在cache里有对象的时候就从cache分配,如果cache没有空闲object了,会调用cache_grow()分配新的slab。如果连cache都无法从系统获得slab了,那才开始使用那些为了保命攒下的“钱”。memory pool使用的那20个object是从slab的cache中分配的,所以释放的时候,要现释放memory pool,这样那20个object才会释放回cache,然后cache没有对象被使用才可以正确的释放了。
文章标签:  linux内核 cache object module api linux

猜你喜欢

转载自blog.csdn.net/zjy900507/article/details/80704041