歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux基礎知識

linux內存源碼分析 - 內存池

  內存池是用於預先申請一些內存用於備用,當系統內存不足無法從伙伴系統和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(5*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), 0, 0, 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);
Copyright © Linux教程網 All Rights Reserved