初識nginx——內存池篇
為了自身使用的方便,Nginx封裝了很多有用的數據結構,比如ngx_str_t ,ngx_array_t, ngx_pool_t 等等,對於內存池,nginx設計的十分精煉,值得我們學習,本文介紹內存池基本知識,nginx內存池的結構和關鍵代碼,並用一個實際的代碼例子作了進一步的講解
一、內存池概述
內存池是在真正使用內存之前,預先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠用時,再繼續申請新的內存。
內存池的好處有減少向系統申請和釋放內存的時間開銷,解決內存頻繁分配產生的碎片,提示程序性能,減少程序員在編寫代碼中對內存的關注等
目前一些常見的內存池實現方案有STL中的內存分配區,boost中的object_pool,nginx中的ngx_pool_t,google的開源項目TCMalloc等
二、nginx內存池綜述
nginx為每一個層級都會創建一個內存池,進行內存的管理,比如一個模板,tcp連接,http請求等,在對應的生命周期結束的時候會摧毀整個內存池,把分配的內存一次性歸還給操作系統。
在分配的內存上,nginx有小塊內存和大塊內存的概念,小塊內存 nginx在分配的時候會嘗試在當前的內存池節點中分配,而大塊內存會調用系統函數malloc向操作系統申請
在釋放內存的時候,nginx沒有專門提供針對釋放小塊內存的函數,小塊內存會在ngx_destory_pool 和 ngx_reset_pool的時候一並釋放
區分小塊內存和大塊內存的原因有2個,
1、針對大塊內存 如果它的生命周期遠遠短於所屬的內存池,那么提供一個單獨的釋放函數是十分有意義的,但不區分大塊內存和小塊內存,針對大的內存塊 便會無法提前釋放了
2、大塊內存與小塊內存的界限是一頁內存(p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL,NGX_MAX_ALLOC_FROM_POOL的值通過調用getpagesize()獲得),大於一頁的內存在物理上不一定是連續的,所以如果分配的內存大於一頁的話,從內存池中使用,和向操作系統重新申請效率差不多是等價的
nginx內存池提供的函數主要有以下幾個
三、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
;
}
}
}
|
四、示例代碼
這里是直接替換了原有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創建的節點的頭部信息都已經被數據覆蓋。
五、總結
nginx的代碼設計的十分靈活,既方便我們開發,也方便我們復用其中的結構,其中內存池的使用 對我們學習nginx,了解nginx如何管理內存有着十分重要的意義。