Linux 記憶體池原始碼淺析
記憶體池(Memery Pool)技術是在真正使用記憶體之前,先申請分配一定數量的、大小相等(一般情況下)的記憶體塊留作備用。當有新的記憶體需求時,就從記憶體池中分出一部分記憶體塊,若記憶體塊不夠再繼續申請新的記憶體。這樣做的一個顯著優點是儘量避免了記憶體碎片,使得記憶體分配效率得到提升。
不僅在使用者態應用程式中被廣泛使用,同時在Linux核心也被廣泛使用,在核心中有不少地方記憶體分配不允許失敗。作為一個在這些情況下確保分配的方式,核心開發者建立了一個已知為記憶體池(或者是 “mempool” )的抽象,核心中記憶體池真實地只是相當於後備快取,它盡力一直保持一個空閒記憶體列表給緊急時使用,而在通常情況下有記憶體需求時還是從公共的記憶體中直接分配,這樣的做法雖然有點霸佔記憶體的嫌疑,但是可以從根本上保證關鍵應用在記憶體緊張時申請記憶體仍然能夠成功。
下面看下核心記憶體池的原始碼,核心記憶體池的原始碼在中,實現上非常簡潔,描述記憶體池的結構mempool_t在標頭檔案中定義,結構描述如下:
12345678910 | typedefstructmempool_s{spinlock_t lock;/*保護記憶體池的自旋鎖*/intmin_nr;/*記憶體池中最少可分配的元素數目*/intcurr_nr;/*尚餘可分配的元素數目*/void**elements;/*指向元素池的指標*/void*pool_data;/*記憶體源,即池中元素真實的分配處*/mempool_alloc_t *alloc;/*分配元素的方法*/mempool_free_t *free;/*回收元素的方法*/wait_queue_head_t wait;/*被阻塞的等待佇列*/ |
記憶體池的建立函式mempool_create的函式原型如下:
12345 | mempool_t *mempool_create(intmin_nr,mempool_alloc_t *alloc_fn,mempool_free_t *free_fn,void*pool_data){returnmempool_create_node(min_nr,alloc_fn,free_fn,pool_data,-1);} |
函式原型指定記憶體池可以容納元素的個數、申請元素的方法、釋放元素的方法,以及一個可選的記憶體源(通常是一個cache),記憶體池物件建立完成後會自動呼叫alloc方法從pool_data上分配min_nr個元素用來填充記憶體池。
記憶體池的釋放函式mempool_destory函式的原型很簡單,應該也能猜到是依次將元素物件從池中移除,再釋放給pool_data,最後釋放池物件,如下:123456789 | voidmempool_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的作用是從指定的記憶體池中申請/獲取一個物件,函式原型如下:
1234567891011121314151617 | void*mempool_alloc(mempool_t *pool,gfp_t gfp_mask){......element=pool->alloc(gfp_temp,pool->pool_data);if(likely(element!=NULL))returnelement;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();returnelement;}......} |
函式先是從pool_data中申請元素物件,當從pool_data無法成功申請到時,才會從池中提取物件使用,因此可以發現核心記憶體池mempool其實是一種後備池,在記憶體緊張的情況下才會真正從池中獲取,這樣也就能保證在極端情況下申請物件的成功率,單也不一定總是會成功,因為記憶體池的大小畢竟是有限的,如果記憶體池中的物件也用完了,那麼程序就只能進入睡眠,也就是被加入到pool->wait的等待佇列,等待記憶體池中有可用的物件時被喚醒,重新嘗試從池中申請元素:
12345 | 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的原型如下:
1234567891011121314 | voidmempool_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函式,分別適用於不同大小或者型別的元素的記憶體池,具體如下:
12345678910111213141516171819202122232425262728293031 | void*mempool_alloc_slab(gfp_t gfp_mask,void*pool_data){structkmem_cache *mem=pool_data;returnkmem_cache_alloc(mem,gfp_mask);}voidmempool_free_slab(void*element,void*pool_data){structkmem_cache *mem=pool_data;kmem_cache_free(mem,element);}void*mempool_kmalloc(gfp_t gfp_mask,void*pool_data){size_t size=(size_t)pool_data;returnkmalloc(size,gfp_mask);}voidmempool_kfree(void*element,void*pool_data){kfree(element);}void*mempool_alloc_pages(gfp_t gfp_mask,void*pool_data){intorder=(int)(long)pool_data;returnalloc_pages(gfp_mask,order);}voidmempool_free_pages(void*element,void*pool_data){intorder=(int)(long)pool_data;__free_pages(element,order);} |
總體上來講mempool的實現很簡約,但是不簡單,而且非常輕便易用,這也是核心奧妙之所在。