Linux 內存池源碼淺析

vcvc 8年前發布 | 41K 次閱讀 Linux

內存池(Memery Pool)技術是在真正使用內存之前,先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠再繼續申請新的內存。這樣做的一個顯著優點是盡量避免了內存碎片,使得內存分配效率得到提升。 

不僅在用戶態應用程序中被廣泛使用,同時在Linux內核也被廣泛使用,在內核中有不少地方內存分配不允許失敗。作為一個在這些情況下確保分配的方式,內核開發者創建了一個已知為內存池(或者是 “mempool” )的抽象,內核中內存池真實地只是相當于后備緩存,它盡力一直保持一個空閑內存列表給緊急時使用,而在通常情況下有內存需求時還是從公共的內存中直接分配,這樣的做法雖然有點霸占內存的嫌疑,但是可以從根本上保證關鍵應用在內存緊張時申請內存仍然能夠成功。

下面看下內核內存池的源碼,內核內存池的源碼在中,實現上非常簡潔,描述內存池的結構mempool_t在頭文件中定義,結構描述如下:

typedef struct mempool_s {
    spinlock_tlock; /*保護內存池的自旋鎖*/
    int min_nr; /*內存池中最少可分配的元素數目*/
    int curr_nr; /*尚余可分配的元素數目*/
    void **elements; /*指向元素池的指針*/
    void *pool_data; /*內存源,即池中元素真實的分配處*/
    mempool_alloc_t *alloc; /*分配元素的方法*/
    mempool_free_t *free; /*回收元素的方法*/
    wait_queue_head_twait; /*被阻塞的等待隊列*/
} mempool_t;

內存池的創建函數mempool_create的函數原型如下:

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);
}

函數原型指定內存池可以容納元素的個數、申請元素的方法、釋放元素的方法,以及一個可選的內存源(通常是一個cache),內存池對象創建完成后會自動調用alloc方法從pool_data上分配min_nr個元素用來填充內存池。

內存池的釋放函數mempool_destory函數的原型很簡單,應該也能猜到是依次將元素對象從池中移除,再釋放給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);
}

值得注意的是內存池分配和回收對象的函數:mempool_alloc和mempool_free。mempool_alloc的作用是從指定的內存池中申請/獲取一個對象,函數原型如下:

void * mempool_alloc(mempool_t *pool, gfp_tgfp_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;
    }
......
    
}

函數先是從pool_data中申請元素對象,當從pool_data無法成功申請到時,才會從池中提取對象使用,因此可以發現內核內存池mempool其實是一種后備池,在內存緊張的情況下才會真正從池中獲取,這樣也就能保證在極端情況下申請對象的成功率,單也不一定總是會成功,因為內存池的大小畢竟是有限的,如果內存池中的對象也用完了,那么進程就只能進入睡眠,也就是被加入到pool->wait的等待隊列,等待內存池中有可用的對象時被喚醒,重新嘗試從池中申請元素:

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);

池回收對象的函數mempool_free的原型如下:

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);
}

其實原則跟mempool_alloc是對應的,釋放對象時先看池中的可用元素是否充足(pool->curr_nr == pool->min_nr),如果不是則將元素對象釋放回池中,否則將元素對象還給pool->pool_data。

此外mempool也提供或者說指定了幾對alloc/free函數,及在mempool_create創建池時必須指定的alloc和free函數,分別適用于不同大小或者類型的元素的內存池,具體如下:

void *mempool_alloc_slab(gfp_tgfp_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_tgfp_mask, void *pool_data)
{
    size_tsize = (size_t)pool_data;
    return kmalloc(size, gfp_mask);
}
void mempool_kfree(void *element, void *pool_data)
{
    kfree(element);
}
 
void *mempool_alloc_pages(gfp_tgfp_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的實現很簡約,但是不簡單,而且非常輕便易用,這也是內核奧妙之所在。

 

來自:http://blog.jobbole.com/107345/

 

 本文由用戶 vcvc 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!