為什麼需要記憶體池?
a. 在大量的小塊記憶體的申請和釋放的時候,能更快地進行記憶體配置設定(對比malloc和free)
b.減少記憶體碎片,防止記憶體洩露。
記憶體池的原理非常簡單,用申請一塊較大的記憶體來代替N多的小記憶體塊,當有需要malloc一塊
比較小的記憶體是,直接拿這塊大的記憶體中的位址來用即可。
當然,這樣處理的缺點也是很明顯的,申請一塊大的記憶體必然會導緻記憶體空間的浪費,但是
比起頻繁地malloc和free,這樣做的代價是非常小的,這是典型的以空間換時間。
一個典型的記憶體池如下圖所示:
<a href="http://images.cnblogs.com/cnblogs_com/sld666666/WindowsLiveWriter/nginx3nginx_112BD/MemoryPool_Step5_2.png"></a>
圖一:一個典型的記憶體池。
首先定義這樣一個結構體:
一個記憶體池就是這樣一連串的記憶體塊組成。當需要用到記憶體的時候,調用此記憶體池定義好的接口
GetMemory(),而需要删除的時候FreeMemory()。
而GetMemory和FreeMemory幹了什麼呢?GetMemory隻是簡單傳回記憶體池中可用空間的位址。
而FreeMemory幹了兩件事情:一: 改變UsedSize 的值,二:重新初始化這一記憶體區域。
首先我們看一下nginx記憶體池的定義:
代碼
<a href="http://www.cnblogs.com/sld666666/archive/2010/06/27/1766255.html"></a>
struct ngx_pool_s {
ngx_pool_data_t d;//表示資料區域
size_t max;//記憶體池能容納資料的大小
ngx_pool_t * current;//目前記憶體池塊(nginx中的記憶體池是又一連串的記憶體池連結清單組成的)
ngx_chain_t* chain;//主要為了将記憶體池連接配接起來
ngx_pool_large_t* large;//大塊的資料
ngx_pool_cleanup_t* cleanup;//清理函數
ngx_log_t* log;//寫log
};
nginx中的記憶體池和普通的有比較大的不同。nginx中的記憶體池是由N個記憶體池連結清單
組成的,當一個記憶體池滿了以後,就會從下一個記憶體池中提取空間來使用。
對于ngx_pool_data_t的定義非常簡單:
typedef struct {
u_char *last;
u_char *end;
ngx_pool_t *next;
ngx_uint_t failed;
} ngx_pool_data_t;
其中last表示目前資料區域的已經使用的資料的結尾。
end表示目前記憶體池的結尾。
next表示下一個記憶體池,前面已經說過,再nignx中,當一個記憶體池空間
不足的時候,它不會擴大其空間,而是再建立一個記憶體池,組成一個記憶體池連結清單。
failed标志申請記憶體的時候失敗的次數。
在了解了這個結構體後面的就非常簡單了。
current 表示目前的記憶體池。
chain表示記憶體池連結清單。
large表示大塊的資料。
對于ngx_pool_large_t定義如下:
struct ngx_pool_large_s {
ngx_pool_large_t* next;
void* alloc;
此結構體的定義也是非常簡單的。一個記憶體位址的指針已經指向下一個位址的指針。
這裡再解釋下為什麼需要有large資料塊。當一個申請的記憶體空間大小比記憶體池的大小還要大的時候,
malloc一塊大的空間,再記憶體池用保留這個位址的指針。
Cleanup保持存着記憶體池被銷毀的時候的清理函數。
typedef void (*ngx_pool_cleanup_pt)(void *data);
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void* data;
ngx_pool_cleanup_t* next;
ngx_pool_cleanup_pt 是一個函數指針的典型用法,
在這個結果中儲存這需要清理的資料指針以及相應的清理函數, 讓記憶體池銷毀
或其他需要清理記憶體池的時候,可以調用此結構體中的handler。
下面是我畫的一張nginx的記憶體池的結構圖。
<圖1. ngx_pool 結構體>
要大體了解一個記憶體池,隻需要了解其池子的建立,記憶體的配置設定以及池子的銷毀即可。下面就分析下
ngx_pool_t的這個3個方面。注:其中有些代碼可能與ngx_pool中的源代碼有所差異,但是整體意思
絕對是一樣的,本人的修改,隻是為了更好的分析,比如 我就把所有寫log的過程都去掉了。
建立一個記憶體池
nginx記憶體池的建立非常簡單,申請一開size大小的記憶體,把它配置設定給 ngx_poo_t。
從記憶體池中配置設定記憶體.
這個函數從記憶體池用拿出記憶體,如果目前記憶體池已滿,到下一個記憶體池,如果所有的記憶體池已滿,
增加一個新的記憶體池,如果申請的記憶體超過了記憶體池的最大值,從*large中配置設定
記憶體池的銷毀
銷毀一個記憶體池其實就是幹了三件事, 調用清理韓式, 釋放大塊的記憶體,釋放記憶體池,需要注意的
一點是在nginx中, 小塊記憶體除了在記憶體池被銷毀的時候都是不能被釋放的。
前面說過,在nginx中,當記憶體池滿了以後,會增加一個新的記憶體池。這個動作就是靠ngx_palloc_block
函數實作的。
這個函數就是申請了一塊記憶體區域,變為一個記憶體池,然後把它連接配接到原來記憶體池的末尾。
在nginx中,小塊記憶體除了在記憶體池銷毀之外是不能釋放的,但是大塊記憶體卻可以,這兩個
函數就是用來控制大塊記憶體的申請和釋放, 代碼也非常簡單,調用malloc申請記憶體,連接配接到
ngx_pool_large_t中 和 調用free釋放記憶體。這裡就不貼上代碼了。