nginx內(nèi)存池
拿來主義:https://www.cnblogs.com/xiekeli/archive/2012/10/17/2727432.html
參考:http://blog.csdn.net/houjixin/article/details/56294550
1集灌、基本結(jié)構(gòu)
先來學(xué)習(xí)一下nginx內(nèi)存池的幾個主要數(shù)據(jù)結(jié)構(gòu):[見:./src/core/ngx_palloc.h/.c]
ngx_pool_data_t(內(nèi)存池數(shù)據(jù)塊結(jié)構(gòu))
typedef struct {
u_char *last;
u_char *end;
ngx_pool_t *next;
ngx_uint_t failed;
} ngx_pool_data_t;
ngx_pool_s(內(nèi)存池頭部結(jié)構(gòu))
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;
};
可以說袖订,ngx_pool_data_t和ngx_pool_s基本構(gòu)成了nginx內(nèi)存池的主體結(jié)構(gòu)拉一,下面詳細介紹一下nginx內(nèi)存池的主體結(jié)構(gòu):
如上圖,nginx的內(nèi)存池實際是一個由ngx_pool_data_t和ngx_pool_s構(gòu)成的鏈表拱绑,其中:
ngx_pool_data_t中:
last:是一個unsigned char 類型的指針,保存的是/當前內(nèi)存池分配到末位地址摹量,即下一次分配從此處開始季春。
end:內(nèi)存池結(jié)束位置;
next:內(nèi)存池里面有很多塊內(nèi)存热康,這些內(nèi)存塊就是通過該指針連成鏈表的召耘,next指向下一塊內(nèi)存。
failed:內(nèi)存池分配失敗次數(shù)褐隆。
ngx_pool_s
d:內(nèi)存池的數(shù)據(jù)塊;
max:內(nèi)存池數(shù)據(jù)塊的最大值剖踊;
current:指向當前內(nèi)存池庶弃;
chain:該指針掛接一個ngx_chain_t結(jié)構(gòu);
large:大塊內(nèi)存鏈表德澈,即分配空間超過max的情況使用歇攻;
cleanup:釋放內(nèi)存池的callback
log:日志信息
以上是內(nèi)存池涉及的主要數(shù)據(jù)結(jié)構(gòu),為了盡量簡化梆造,其他涉及的數(shù)據(jù)結(jié)構(gòu)將在下面實際用到時候再做介紹缴守。
2、內(nèi)存池基本操作
內(nèi)存池對外的主要方法有:
創(chuàng)建內(nèi)存池 ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log);
銷毀內(nèi)存池 void ngx_destroy_pool(ngx_pool_t *pool);
重置內(nèi)存池 void ngx_reset_pool(ngx_pool_t *pool);
內(nèi)存申請(對齊) void * ngx_palloc(ngx_pool_t *pool, size_t size);
內(nèi)存申請(不對齊) void * ngx_pnalloc(ngx_pool_t *pool, size_t size);
內(nèi)存清除 ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
2.1镇辉、內(nèi)存池創(chuàng)建(ngx_create_pool)
ngx_create_pool用于創(chuàng)建一個內(nèi)存池屡穗,我們創(chuàng)建時,傳入我們的初始大泻龈亍:
//通過ngx_create_pool可以創(chuàng)建一個內(nèi)存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
// #define NGX_POOL_ALIGNMENT 16
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); // 分配內(nèi)存函數(shù)村砂,uinx,windows分開走
if (p == NULL) {
return NULL;
}
p->d.last = (u_char *) p + sizeof(ngx_pool_t); //初始指向 ngx_pool_t 結(jié)構(gòu)體后面
p->d.end = (u_char *) p + size; //整個結(jié)構(gòu)的結(jié)尾后面
p->d.next = NULL;
p->d.failed = 0;
//sizeof(ngx_pool_t)用來存儲自身
size = size - sizeof(ngx_pool_t);
// #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
// 最大不超過 NGX_MAX_ALLOC_FROM_POOL,也就是getpagesize()-1 大小
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;
}
nginx對內(nèi)存的管理分為大內(nèi)存與小內(nèi)存,當某一個申請的內(nèi)存大于某一個值時屹逛,就需要從大內(nèi)存中分配空間础废,否則從小內(nèi)存中分配空間汛骂。
nginx中的內(nèi)存池是在創(chuàng)建的時候就設(shè)定好了大小,在以后分配小塊內(nèi)存的時候评腺,如果內(nèi)存不夠帘瞭,則是重新創(chuàng)建一塊內(nèi)存串到內(nèi)存池中,而不是將原有的內(nèi)存池進行擴張蒿讥。當要分配大塊內(nèi)存是蝶念,則是在內(nèi)存池外面再分配空間進行管理的,稱為大塊內(nèi)存池诈悍。
2.2祸轮、內(nèi)存申請
ngx_palloc
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
// pool->max 數(shù)據(jù)塊大小,即小塊內(nèi)存的最大值
if (size <= pool->max) {
// pool->current 保存當前內(nèi)存值
p = pool->current;
do {
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); // 對齊內(nèi)存指針侥钳,加快存取速度
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);
}
//size過大适袜,需要在大數(shù)據(jù)塊內(nèi)存鏈表上申請內(nèi)存空間
return ngx_palloc_large(pool, size);
}
這里需要說明的幾點:
1、ngx_align_ptr舷夺,這是一個用來內(nèi)存地址取整的宏苦酱,非常精巧,一句話就搞定了给猾。作用不言而喻疫萤,取整可以降低CPU讀取內(nèi)存的次數(shù),提高性能敢伸。因為這里并沒有真正意義調(diào)用malloc等函數(shù)申請內(nèi)存扯饶,而是移動指針標記而已,所以內(nèi)存對齊的活池颈,C編譯器幫不了你了尾序,得自己動手。
#define ngx_align_ptr(p, a) \
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
2躯砰、ngx_palloc_block(ngx_pool_t *pool, size_t size)
這個函數(shù)是用來分配新的內(nèi)存塊每币,為pool內(nèi)存池開辟一個新的內(nèi)存塊,并申請使用size大小的內(nèi)存琢歇;
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);
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
if (m == NULL) {
return NULL;
}
new = (ngx_pool_t *) m;
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size;
current = pool->current;
for (p = current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
current = p->d.next;
}
}
p->d.next = new;
pool->current = current ? current : new;
return m;
}
3兰怠、ngx_palloc_large(ngx_pool_t *pool, size_t size)
在ngx_palloc中首先會判斷申請的內(nèi)存大小是否超過內(nèi)存塊的最大限值,如果超過李茫,則直接調(diào)用ngx_palloc_large揭保,進入大內(nèi)存塊的分配流程;
//控制大塊內(nèi)存的申請
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
// 直接在系統(tǒng)堆中分配一塊空間
p = ngx_alloc(size, pool->log); // 在大數(shù)據(jù)塊鏈表中申請調(diào)用了ngx_alloc
if (p == NULL) {
return NULL;
}
n = 0;
// 查找到一個空的large區(qū)涌矢,如果有掖举,則將剛才分配的空間交由它管理
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ > 3) {
break;
}
}
// 為了提高效率, 如果在三次內(nèi)沒有找到空的large結(jié)構(gòu)體,則創(chuàng)建一個
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
2.3塔次、內(nèi)存池重置
ngx_reset_pool
//重置方篮,將內(nèi)存池恢復(fù)到初始分配的狀態(tài)
void
ngx_reset_pool(ngx_pool_t *pool)
{
ngx_pool_t *p;
ngx_pool_large_t *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
ngx_free(l->alloc); //釋放大塊內(nèi)存
}
}
pool->large = NULL;
for (p = pool; p; p = p->d.next) {
p->d.last = (u_char *) p + sizeof(ngx_pool_t); //小塊內(nèi)存結(jié)尾指針指向剛分配的位置,其中的數(shù)據(jù)將被覆蓋
}
}
2.4励负、內(nèi)存池清理
ngx_pfree
//控制大塊內(nèi)存的釋放藕溅。注意,這個函數(shù)只會釋放大內(nèi)存继榆,不會釋放其對應(yīng)的頭部結(jié)構(gòu)巾表,遺留下來的頭部結(jié)構(gòu)會做下一次申請大內(nèi)存之用
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
ngx_pool_large_t *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);
ngx_free(l->alloc);
l->alloc = NULL;
return NGX_OK;
}
}
return NGX_DECLINED;
}
所以說Nginx內(nèi)存池中大內(nèi)存塊和小內(nèi)存塊的分配與釋放是不一樣的。我們在使用內(nèi)存池時略吨,可以使用ngx_palloc進行分配集币,使用ngx_pfree釋放。而對于大內(nèi)存翠忠,這樣做是沒有問題的鞠苟,而對于小內(nèi)存就不一樣了,分配的小內(nèi)存秽之,不會進行釋放当娱。因為大內(nèi)存塊的分配只對前3個內(nèi)存塊進行檢查,否則就直接分配內(nèi)存考榨,所以大內(nèi)存塊的釋放必須及時跨细。
ngx_pool_cleanup_s
Nginx內(nèi)存池支持通過回調(diào)函數(shù),對外部資源的清理河质。ngx_pool_cleanup_t是回調(diào)函數(shù)結(jié)構(gòu)體冀惭,它在內(nèi)存池中以鏈表形式保存,在內(nèi)存池進行銷毀時掀鹅,循環(huán)調(diào)用這些回調(diào)函數(shù)對數(shù)據(jù)進行清理云头。
typedef void (*ngx_pool_cleanup_pt)(void *data);
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void *data;
ngx_pool_cleanup_t *next;
};
其中
handler: // 是回調(diào)函數(shù)指針;
data: // 回調(diào)時淫半,將此數(shù)據(jù)傳入回調(diào)函數(shù);
next: // 指向下一個回調(diào)函數(shù)結(jié)構(gòu)體匣砖;
如果我們需要添加自己的回調(diào)函數(shù)科吭,則需要調(diào)用ngx_pool_cleanup_add來得到一個ngx_pool_cleanup_t,然后設(shè)置handler為我們的清理函數(shù)猴鲫,并設(shè)置data為我們要清理的數(shù)據(jù)对人。這樣在ngx_destroy_pool中會循環(huán)調(diào)用handler清理數(shù)據(jù);
比如:我們可以將一個開打的文件描述符作為資源掛載到內(nèi)存池上拂共,同時提供一個關(guān)閉文件描述的函數(shù)注冊到handler上牺弄,那么內(nèi)存池在釋放的時候,就會調(diào)用我們提供的關(guān)閉文件函數(shù)來處理文件描述符資源了宜狐。
2.5势告、內(nèi)存池銷毀
ngx_destroy_pool
ngx_destroy_pool這個函數(shù)用于銷毀一個內(nèi)存池:
//銷毀內(nèi)存池
void
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
// 遍歷節(jié)點上的各個節(jié)點
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); //釋放節(jié)點占用的內(nèi)存
}
}
//對大塊數(shù)據(jù)內(nèi)存的清理
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_free蛇捌,宏,本質(zhì)是free
}
}
#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);
if (n == NULL) {
break;
}
}
}