初識nginx——內存池篇
爲了自身使用的方便,Nginx封裝了不少有用的數據結構,好比ngx_str_t ,ngx_array_t, ngx_pool_t 等等,對於內存池,nginx設計的十分精煉,值得咱們學習,本文介紹內存池基本知識,nginx內存池的結構和關鍵代碼,並用一個實際的代碼例子做了進一步的講解nginx
1、內存池概述
內存池是在真正使用內存以前,預先申請分配必定數量的、大小相等(通常狀況下)的內存塊留做備用。當有新的內存需求時,就從內存池中分出一部份內存塊,若內存塊不夠用時,再繼續申請新的內存。程序員
內存池的好處有減小向系統申請和釋放內存的時間開銷,解決內存頻繁分配產生的碎片,提示程序性能,減小程序員在編寫代碼中對內存的關注等數組
目前一些常見的內存池實現方案有STL中的內存分配區,boost中的object_pool,nginx中的ngx_pool_t,google的開源項目TCMalloc等數據結構
2、nginx內存池綜述
nginx爲每個層級都會建立一個內存池,進行內存的管理,好比一個模板,tcp鏈接,http請求等,在對應的生命週期結束的時候會摧毀整個內存池,把分配的內存一次性歸還給操做系統。tcp
在分配的內存上,nginx有小塊內存和大塊內存的概念,小塊內存 nginx在分配的時候會嘗試在當前的內存池節點中分配,而大塊內存會調用系統函數malloc向操做系統申請函數
在釋放內存的時候,nginx沒有專門提供針對釋放小塊內存的函數,小塊內存會在ngx_destory_pool 和 ngx_reset_pool的時候一併釋放性能
區分小塊內存和大塊內存的緣由有2個,學習
一、針對大塊內存 若是它的生命週期遠遠短於所屬的內存池,那麼提供一個單獨的釋放函數是十分有意義的,但不區分大塊內存和小塊內存,針對大的內存塊 便會沒法提早釋放了ui
二、大塊內存與小塊內存的界限是一頁內存(p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL,NGX_MAX_ALLOC_FROM_POOL的值經過調用getpagesize()得到),大於一頁的內存在物理上不必定是連續的,因此若是分配的內存大於一頁的話,從內存池中使用,和向操做系統從新申請效率差很少是等價的this
nginx內存池提供的函數主要有如下幾個
3、nginx內存池詳解
nginx使用了ngx_pool_s用於表示整個內存池對象,ngx_pool_data_t表示單個內存池節點的分配信息,ngx_pool_large_s表示大塊內存
它們的結構和含義以下
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};
next: 指向下一個大塊內存
alloc:指向分配的大塊內存
struct ngx_pool_s {
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;
};
d:內存池的節點的數據分配狀況
max: 單個內存池節點容量的最大值
current: 指向當前的內存池節點
chain: 指向一個ngx_chain_t結構
large: 指向大塊內存鏈表
cleanup:釋放內存池的callback
log: 用於輸出日誌
typedef struct {
u_char *last;
u_char *end;
ngx_pool_t *next;
ngx_uint_t failed;
} ngx_pool_data_t;
last: 內存池節點已分配的末位地址,下一次分配會嘗試今後開始
end: 內存池節點的結束位置
next:next指向下一個內存池節點
failed: 當前內存池節點分配失敗次數
nginx 內存池示意圖1
在分配內存的時候,nginx會判斷當前要分配的內存是小塊內存仍是大塊內存,大塊內存調用ngx_palloc_large進行分配,小塊內存nginx先會嘗試從內存池的當前節點(p->current)中分配,若是內存池當前節點的剩餘空間不足,nginx會調用ngx_palloc_block新建立一個內存池節點並從中分配,
若是內存池當前節點的分配失敗次數已經大於等於6次(p->d.failed++ > 4),則將當前內存池節點前移一個
(這裏有個須要注意的地方,噹噹前內存節點的剩餘空間不夠分配時,nginx會從新建立一個ngx_pool_t對象,而且將pool.d->next指向新的ngx_pool_t,新分配的ngx_pool_t對象只用到了ngx_pool_data_t區域,並無頭部信息,頭部信息部分已經被當作內存分配區域了)
nginx 內存池示意圖2(新建了一個內存池節點和分配了2個大塊內存,其中一個已經釋放)
關鍵代碼
建立內存池代碼
ngx_pool_t *
ngx_create_pool(
size_t
size, ngx_log_t *
log
)
{
ngx_pool_t *p;
p = ngx_memalign(NGX_POOL_ALIGNMENT, size,
log
);
//間接調用了posix_memalign分配內存
if
(p == NULL) {
return
NULL;
}
p->d.last = (u_char *) p +
sizeof
(ngx_pool_t);
//初始的前面幾個字節用於儲存內存池頭部信息,因此下一次分配的開始應該前移頭部的大小
p->d.end = (u_char *) p + size;
//內存池節點的結尾
p->d.next = NULL;
//因爲當前內存池只有一個節點因此next爲NULL
p->d.failed = 0;
size = size -
sizeof
(ngx_pool_t);
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
//設置小塊內存和大塊內存的判斷標準
p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->
log
=
log
;
return
p;
}
|
ngx_palloc分配函數代碼
void
*
ngx_palloc(ngx_pool_t *pool,
size_t
size)
{
u_char *m;
ngx_pool_t *p;
if
(size <= pool->max)
//判斷是小塊內存 仍是大塊內存
{
p = pool->current;
do
{
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
if
((
size_t
) (p->d.end - m) >= size) {
//嘗試在已有的內存池節點中分配內存
p->d.last = m + size;
return
m;
}
p = p->d.next;
}
while
(p);
return
ngx_palloc_block(pool, size);
//當前已有節點都分配失敗,建立一個新的內存池節點
}
return
ngx_palloc_large(pool, size);
//分配大塊內存
}
|
消耗內存池
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);
//調用須要在內存池釋放時同步調用的方法
}
}
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);
}
}
#if (NGX_DEBUG)
/*
* we could allocate the pool->log from this pool
* so we cannot use this log while free()ing the pool
*/
for
(p = pool, n = pool->d.next;
/* void */
; p = n, n = n->d.next) {
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->
log
, 0,
"free: %p, unused: %uz"
, p, p->d.end - p->d.last);
if
(n == NULL) {
break
;
}
}
#endif
for
(p = pool, n = pool->d.next;
/* void */
; p = n, n = n->d.next) {
ngx_free(p);
//間接調用free釋放內存
if
(n == NULL) {
break
;
}
}
}
|
4、示例代碼
這裏是直接替換了原有nginx代碼的main函數 (src/core/nginx.c)
void
print_pool(ngx_pool_t *pool)
{
if
(pool->large != NULL)
{
printf
(
"has large memory\n"
);
for
(ngx_pool_large_t* i = pool->large; i!=NULL; i = i->next)
{
printf
(
"\t\tlarge next=0x%x\n"
, i->next);
printf
(
"\t\tlarge alloc=0x%x\n"
, i->alloc);
}
}
int
i=1;
while
(pool)
{
printf
(
"pool=0x%x,index:%d\n"
, pool, i++);
printf
(
"\t\tlast=0x%x\n"
, (pool->d).last);
printf
(
"\t\tend=0x%x\n"
,(pool->d).end);
printf
(
"\t\tnext=0x%x\n"
,(pool->d).next);
printf
(
"\t\tfailed=%d\n"
,pool->d.failed);
printf
(
"\t\tmax=%d\n"
,pool->max);
printf
(
"\t\tcurrent=0x%x\n"
,pool->current);
printf
(
"\t\tchain=0x%x\n"
,pool->chain);
printf
(
"\t\tlarge=0x%x\n"
,pool->large);
printf
(
"\t\tcleanup=0x%x\n"
,pool->cleanup);
printf
(
"\t\tlog=0x%x\n"
,pool->
log
);
printf
(
"\t\tavailable pool memory=%d\n"
, pool->d.end-pool->d.last);
printf
(
"\n"
);
pool=pool->d.next;
}
}
void
print_array(
int
*a,
int
size)
{
for
(
int
i=0; i<size; i++)
{
printf
(
"%d,"
,a[i]);
}
printf
(
"\n"
);
}
int
main()
{
ngx_pool_t *pool;
int
array_size = 128;
int
array_size_large = 1024;
int
page_size = getpagesize();
//得到一頁的大小
printf
(
"page_size:%d\n"
, page_size);
printf
(
"----------------------------\n"
);
printf
(
"create a new pool"
);
pool = ngx_create_pool(1024, NULL);
//建立一個大小爲1024的內存池
print_pool(pool);
printf
(
"----------------------------\n"
);
printf
(
"alloc block 1 from the pool:\n"
);
int
*a1 = ngx_palloc(pool,
sizeof
(
int
) * array_size);
//分配第一塊內存 用於建立數組
for
(
int
i=0; i< array_size; i++)
{
a1[i] = i+1;
}
print_pool(pool);
printf
(
"----------------------------\n"
);
printf
(
"alloc block 2 from the pool:\n"
);
int
*a2 = ngx_palloc(pool,
sizeof
(
int
) * array_size);
//分配第二塊內存 用於建立數組,這個時候會建立第二個內存池節點
for
(
int
i=0; i< array_size; i++)
{
a2[i] = 12345678;
}
print_pool(pool);
printf
(
"----------------------------\n"
);
printf
(
"alloc large memory:\n"
);
printf
(
"\t\tlarge next before=0x%x\n"
, pool->current->d.last);
int
* a3 = ngx_palloc(pool,
sizeof
(
int
) * array_size_large);
//因爲大小超過了max的值 ngx_palloc中會調用ngx_palloc_large分配大塊內存
printf
(
"\t\tlarge next after=0x%x\n"
, pool->large);
for
(
int
i=0; i< array_size_large; i++)
{
a3[i] = i+1;
}
print_pool(pool);
print_array(a1,array_size);
print_array(a2,array_size);
print_array(a3,array_size_large);
ngx_destroy_pool(pool);
return
0;
}
|
運行結果:
經過紅框能夠看到ngx_pool_t中只有第一個內存池節點的頭部信息是有意義的,後續調用ngx_palloc_block建立的節點的頭部信息都已經被數據覆蓋。
5、總結
nginx的代碼設計的十分靈活,既方便咱們開發,也方便咱們複用其中的結構,其中內存池的使用 對咱們學習nginx,瞭解nginx如何管理內存有着十分重要的意義。