先了解一下Nginx:
Nginx是一個高性能的HTTP反向代理服務器,接收瀏覽器請求。其特點是佔有內存少,併發能力強,穩定性高。
nginx有什麼功能?
接收http服務,正向反向代理(負載均衡)。
正向代理代理客戶端,反向代理代理服務器,反向代理也稱作負載均衡器
http協議本身就是一個短鏈接,無狀態的協議
內存池的好處:
減少向系統申請和釋放內存的時間開銷,解決內存頻繁分配產生的碎片,提示程序性能。
nginx有小塊內存和大塊內存的概念,小塊內存nginx在分配的時候會嘗試在當前的內存池節點中分配,而大塊內存會調用系統函數malloc向操作系統申請
區分小塊內存和大塊內存的原因有2個:
1、針對大塊內存 如果它的生命週期遠遠短於所屬的內存池,那麼提供一個單獨的釋放函數是十分有意義的,但不區分大塊內存和小塊內存,針對大的內存塊便會無法提前釋放了
2、大塊內存與小塊內存的界限是一頁內存4K(下文有體到),大於一頁的內存在物理上不一定是連續的,所以如果分配的內存大於一頁的話,從內存池中使用,和向操作系統重新申請效率差不多是等價的。
主要數據結構:
ngx_pool_data_t(內存池數據塊結構)
typedef struct 包含了所需要操作這個內存池數據的一些指針。
{
u_char *last; unsigned char類型指針,保存當前數據區的已經使用的數據的結尾。
u_char *end; 表示當前的內存池的結尾。end-last就是內存池未使用的大小。
ngx_pool_t *next; 指向下一塊內存池。
ngx_uint_t failed; 標記請求小塊內存由於內存池空間不夠,而需要重新分配一個小內存池的次數。
} ngx_pool_data_t;
ngx_pool_t(內存池頭部結構)
struct ngx_pool_t
{
ngx_pool_data_t d; 這就是上面內玩意
size_t max; 內存池所能容納的最大值
ngx_pool_t *current; 指向當前的內存池的頭
ngx_chain_t *chain; 將所有內存池都鏈接起來
ngx_pool_large_t *large; 鏈接大塊內存池
ngx_pool_cleanup_t *cleanup; 清理函數列表
ngx_log_t *log; 日誌
};
ngx_pool_large_s(大塊內存池類型)
struct ngx_pool_large_s
{
ngx_pool_large_t *next; 指向下一大塊內存
void *alloc; 本塊內存
};
ngx_pool_cleanup_s(內存池中的數據清理)
struct ngx_pool_cleanup_s
{
ngx_pool_cleanup_pt handler; 清理函數
void *data; 傳給清理函數的數據
ngx_pool_cleanup_t *next; 指向下一個清理函數 調用destroy時會遍歷清理函數鏈表,調用handler
}
主要函數:
1、ngx_create_pool() 內存池創建
宏定義的函數,就是對malloc進行簡單封裝。裏面寫入了錯誤日誌
#define ngx_memalign(alignment,size,log) ngx_alloc(size,log)
內存池的數據區的最大容量。在x86上ngx_pagesize=4096。
因爲尋址頁面大小爲4096,是一個相對合適的數字,保證size不會太大,導致尋址不到浪費空間
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
ngx_pool_t * 函數返回值類型
ngx_create_pool(size_t size, ngx_log_t *log) 傳進來的size並不是真正能使用的大小,應減去sizeof(ngx_poll_t)。
{
ngx_pool_t *p;
p = ngx_memalign(NGX_POOL_ALIGNMENT,size,log) 用宏定義了一個函數,第一個參數宏定義的16,用來內存對齊
if (p == NULL)
{
return NULL;
}
內存已經申請了,對結構體指針初始化。
由於一開始數據區爲空,因此last指向數據區的開始。
p->d.last = (u_char *) p + sizeof(ngx_pool_t); 可以證明上述實際使用空間並沒有size
end也就是數據區的結束位置
p->d.end = (u_char *) p + size;
p->d.next = NULL;
p->d.failed = 0;
這裏纔是我們真正能使用的大小。
size = size - sizeof(ngx_pool_t);
然後設置max。內存池的最大值也就是size和最大容量之間的最小值。
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
current表示當前的內存池。
p->current = p;
其他的域置NULL。
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
返回指針。
return p;
}
爲什麼要考慮內存對齊?
因爲內存對齊可以降低CPU讀取內存的次數,提高性能。主要爲減少內存的I/O操作。
2、內存申請:
ngx_palloc() 分配的內存會對齊
ngx_calloc() 用來分配一塊清0的內存
ngx_pnalloc() 分配的內存不會對齊
就對齊的計算,不必深究
#define ngx_align_ptr(p,a) (u_char *)(((uintptr_t)(p) + ((uintptr_t)a - 1)) &~((uintptr_t)a - 1))
#define NGX_ALIGNMENT sizeof(unsigned long)
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
//判斷當前申請的大小是否超過max,如果超過則說明是申請大塊內存,此時進入large
if (size <= pool->max) 如果申請小塊內存,進入
{
得到當前的內存池指針。
p = pool->current;
開始遍歷內存池
do {
首先對齊last指針。調用宏對內存地址進行對齊處理。
如果是ngx_pnalloc(),就省去這一步。
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
得到當前內存池中的可用大小。如果剩下的空間不夠,則直接返回當前的last,也就是數據的指針。
if ((size_t) (p->d.end - m) >= size) 如果當前夠存,進入
{
偏移last,然後返回前面保存的last。
p->d.last = m + size;
return m;
}
否則繼續遍歷
p = p->d.next;
} while (p);
到達這裏說明內存池已經滿掉,因此我們需要重新分配一個小塊內存然後鏈接到當前的data的next上。
return ngx_palloc_block(pool, size);
}
申請大塊。
return ngx_palloc_large(pool, size);
}
ngx_palloc_block() 申請小塊內存
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
u_char *m;
size_t psize;
ngx_pool_t *p, *new, *current;
計算當前的內存池的大小。
psize = (size_t) (pool->d.end - (u_char *) pool);
再申請一個與pool同樣大小的內存
m = ngx_memalign(NGX_POOL_ALIGNMENT,psize,pool->log)
if (m == NULL)
{
return NULL;
}
new = (ngx_pool_t *) m;
接下來和我們create一個內存池做的操作一樣。就是更新一些指針
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
讓m指向該塊內存ngx_pool_data_t結構體之後數據區的起始位置
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT); 對齊指針,前面說了
new->d.last = m + size; 設置last指針位置
設置current。
current = pool->current;
這裏遍歷所有的子內存池,這裏主要是通過failed來標記重新分配子內存池的次數,然後找出最後一個大於4的,標記它
for (p = current; p->d.next; p = p->d.next)
{
if (p->d.failed++ > 4)
{
當failed大於4說明我們至少請求了4次內存分配,都不能滿足我們的請求,
此時我們就假設老的內存都已經沒有空間了,因此我們就從比較新的內存塊開始。
current = p->d.next;
}
}
鏈接到最後一個內存池後面
p->d.next = new;
如果current爲空,則current就爲new。
pool->current = current ? current : new;
return m;
}
ngx_palloc_large() 申請大塊內存
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
分配一大塊內存。
p = ngx_memalign(NGX_POOL_ALIGNMENT,size,pool->log) 上面提了
if (p == NULL)
{
return NULL;
}
n = 0;
開始遍歷large鏈表,如果有alloc(也就是內存區指針)爲空,則直接指針賦值然後返回。一般第一次請求大塊內存都會
for (large = pool->large; large; large = large->next)
{
if (large->alloc == NULL)
{
large->alloc = p;
return p;
}
鏈表不能超過4個,只對前3個內存塊進行檢查,否則就直接分配內存。
if (n++ > 3)
{
break;
}
}
malloc一大內存塊。
large = ngx_palloc(pool, sizeof(ngx_pool_large_t)); 調用本身
if (large == NULL)
{
ngx_free(p); 從pool的large鏈表中找到p,然後free掉。
return NULL;
}
然後鏈接數據區指針p到large。
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
3、ngx_destroy_pool() 內存池的銷燬
void
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
首先調用所有的數據清理函數
for (c = pool->cleanup; c; c = c->next)
{
if (c->handler)
{
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,"run cleanup: %p", c);
c->handler(c->data); 說結構體的時候就提到了這個函數
}
}
free大塊內存 ngx_palloc_large申請的內容
for (l = pool->large; l; l = l->next)
{
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
if (l->alloc)
{
ngx_free(l->alloc);
}
}
遍歷小塊內存池。 ngx_create_pool申請的內存
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next)
{
直接free掉。
ngx_free(p);
if (n == NULL)
{
break;
}
}
}
補充:
1、在nginx內存池中只有大塊內存提供了free接口【ngx_pfree(ngx_pool_t *pool, void *p),這個函數就是從pool的large鏈表中找到p,然後free掉它】,可以回收釋放。
小塊數據是不釋放的,只有當整個內存池被destroy掉纔會被釋放,這樣就簡化了內存池的操作。爲什麼不釋放?原因:http服務器是間歇性處理,是一個短連接,無狀態的協議。處理完請求,服務器斷開。在處理完http請求完成之後,就可以發起內存池的重置函數。
2、ngx_pool_cleanup_t * ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
這個函數也就是添加一個ngx_pool_cleanup_t到當前的pool上,然後返回,我們此時就能通過返回的結構來給對應的handler賦值。當內存池destroy的時候我們就能添加這些清理函數到pool中,然後當內存池釋放的時候就會自動調用。
4、ngx_pfree() 內存池清理
ngx_int _t
ngx_pfree(ngx_pool_t *pool,void *p)
{
ngx_pool_large_s *l;
釋放大塊內存
for(l=pool->large ;l;l=l->next )
{
if(p==l->alloc)
{
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC,pool->log,0,"free:%p",l->alloc);
free(l->alloc );
l->alloc =NULL;
return NGX_OK;
}
}
return NGX_DECLINED;
}
注:因爲大塊內存只對前三個進行檢查,否則就直接分配,所以大塊內存必須及時釋放。
5、ngx_reset_pool() 內存池重置
void
ngx_reset_pool(ngx_pool_t *pool)
{
ngx_pool_large_s *l;
ngx_pool_s *p;
釋放所有大塊內存
for(l=pool->large ;l;l=l->next )
{
if(l->alloc )
{
free(l->alloc );
l->alloc =NULL;
}
}
pool->large = NULL;
重置所有小塊內存
for(p=pool;p;p=p->d.next )
{
p->d .last =(u_char *)p+sizeof(ngx_pool_t);
p->d .failed =0;
}
pool->current =pool;
}
在處理完一批http請求完成之後就可以發起內存池的重置函數