nginx 學習四 內存池 ngx_pool_t 和內存管理操作

這幾天在看nginx,發現凡是有內存申請的地方都有pool這個東東出現,仔細看看,原來pool的類型是ngx_pool_t,是nginx用來做內存管理的,於是就決定看看他的實現。

1 nginx內存池相關的結構體

     ngx_pool_t定義在core/ngx_palloc.h ngx_palloc.c中,下面是幾個主要的結構體

     ngx_pool_data_t

typedef struct {    //內存池的數據結構模塊  
	u_char               *last;    //當前內存分配結束位置,即下一段可分配內存的起始位置  
	u_char               *end;     //內存池的結束位置  
	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_chain_t結構  
    ngx_pool_large_t     *large;     //指向大塊內存分配,nginx中,大塊內存分配直接採用標準系統接口malloc  
    ngx_pool_cleanup_t   *cleanup;   //析構函數,掛載內存釋放時需要清理資源的一些必要操作  
    ngx_log_t            *log;       //內存分配相關的日誌記錄  
}; 
    其他相關的結構體

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;//clean 函數
    void                 *data; //要銷燬的內存
    ngx_pool_cleanup_t   *next; //下一下clean函數
};


typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc; //大塊內存
};


typedef struct {
    ngx_fd_t              fd;
    u_char               *name;
    ngx_log_t            *log;
} ngx_pool_cleanup_file_t; //文件內存的銷燬

2 內存池操作的相關函數

下面是nginx內存操作的相關函數:

void *ngx_alloc(size_t size, ngx_log_t *log);//malloc的封裝
void *ngx_calloc(size_t size, ngx_log_t *log);//malloc的封裝,內存經過初始化

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);//創建內存池
void ngx_destroy_pool(ngx_pool_t *pool);//銷燬內存池,包括大塊內存
void ngx_reset_pool(ngx_pool_t *pool);//重置內存池,會釋放大塊內存

void *ngx_palloc(ngx_pool_t *pool, size_t size);//申請內存,內存經過對齊
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);//申請內存,內存沒有經過對齊
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);//申請內存,內存經過對齊,且申請的內存經過初始化
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);//申請大塊內存,內存經過對齊
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);//釋放所有大塊內存

ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);//添加cleanup大小爲size(data)
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);//清楚所有的cleanup
void ngx_pool_cleanup_file(void *data);//關閉文件,data指向ngx_pool_cleanup_file_t
void ngx_pool_delete_file(void *data);//刪除文件,data指向ngx_pool_cleanup_file_t


2.1ngx_alloc 和 ngx_calloc

ngx_alloc 和 ngx_calloc函數定義在src/os/unix/ngx_alloc.(h,c)

ngx_alloc函數實際上是對malloc函數的封裝:

void *
ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p;

    p = malloc(size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "malloc(%uz) failed", size);
    }

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);

    return p;
}
ngx_calloc函數和ngx_alloc一樣,只不過對申請的內存初始化:
void *
ngx_calloc(size_t size, ngx_log_t *log)
{
    void  *p;

    p = ngx_alloc(size, log);

    if (p) {
        ngx_memzero(p, size);//對申請的內存進行初始化
    }

    return p;
}
2.2內存池創建

先看一個宏定義

#define NGX_POOL_ALIGNMENT       16
這個宏定義是內存對齊大小

內存池創建函數:

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);//申請的內存按16對齊,內存大小必須是16的整數倍,並且大於等於size
    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;
    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_memalign函數,這個函數在src/os/unix/ngx_alloc.(h,c):
#if (NGX_HAVE_POSIX_MEMALIGN)

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
    void  *p;
    int    err;

    err = posix_memalign(&p, alignment, size);

    if (err) {
        ngx_log_error(NGX_LOG_EMERG, log, err,
                      "posix_memalign(%uz, %uz) failed", alignment, size);
        p = NULL;
    }

    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
                   "posix_memalign: %p:%uz @%uz", p, size, alignment);

    return p;
}

#elif (NGX_HAVE_MEMALIGN)

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
    void  *p;

    p = memalign(alignment, size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "memalign(%uz, %uz) failed", alignment, size);
    }

    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
                   "memalign: %p:%uz @%uz", p, size, alignment);

    return p;
}

#endif
這裏簡要介紹一下memalign函數

在GNU系統中,malloc或realloc返回的內存塊地址都是8的倍數(如果是64位系統,則爲16的倍數)。如果你需要更大的粒度,請使用memalign或valloc。這些函數在頭文件“stdlib.h”中聲明。

在GNU庫中,可以使用函數free釋放memalign和valloc返回的內存塊。但無法在BSD系統中使用,而且BSD系統中並未提供釋放這樣的內存塊的途徑。

函數:void * memalign (size_t boundary, size_t size)
函數memalign將分配一個由size指定大小,地址是boundary的倍數的內存塊。參數boundary必須是2的冪!函數memalign可以分配較大的內存塊,並且可以爲返回的地址指定粒度。

函數:void * valloc (size_t size)
使用函數valloc與使用函數memalign類似,函數valloc的內部實現裏,使用頁的大小作爲對齊長度,使用memalign來分配內存。它的實現如下所示:

void *
valloc (size_t size)
{
    return memalign (getpagesize (), size);
} 

2.3內存池銷燬函數

ngx_destroy_pool函數分爲三步:
首先清理ngx_pool_cleanup_t結構的內存,這個結構上掛載了許多清理hander
然後清理大塊內存,即ngx_poll_large_t這個結構體中存放的大塊內存
最後清理ngx_pool_t的內存,即ngx_create_pool申請的內存

void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

	//第一步調用ngx_pool_clean_t上掛載的清理句柄來清理相應的data,也可能是關閉文件,socket連接等等
    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);
        }
    }

	//第二步釋放大塊內存,實際上是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);
        }
    }

#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
    //第三步釋放ngx_create_pool申請的內存
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}

2.4重置內存池

重置內存池操作是把內存池恢復到初始狀態,即沒有把內存分配給用戶的狀態,這個操作釋放了大塊內存,
而把小塊內存恢復到原來的狀態,就是修改ngx_pool_data_t中的last成員的值,使其指向最初的可分配
給用戶的內存位置。

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);
        }
    }

    pool->large = NULL;

    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t); //修改last指針,使其指向最開始可分配的內存位置
    }
}

2.5分配內存的函數

內存分配函數主要是ngx_palloc、ngx_pnalloc以及不給外部用戶條用的ngx_palloc_block、ngx_palloc_large

2.5.1 ngx_palloc、ngx_pnalloc

這兩個函數的區別是ngx_palloc從pool內存池分配以NGX_ALIGNMENT對齊的內存,而ngx_pnalloc分配適合size大小的內存,不考慮內存對齊。這裏只看ngx_palloc函數:

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;

	//申請的內存小於max
    if (size <= pool->max) {

        p = pool->current;
        
        //在內存池鏈表中查找是否有>=size的空閒內存
        do {
			//執行對齊操作,  
            //即以last開始,計算以NGX_ALIGNMENT對齊的偏移位置指針
            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);

		//如果在內存池鏈表中沒有找到>=size的內存,就調用ngx_palloc_block函數來申請
        //申請之後把這塊內存連接到內存池鏈表的最後
        return ngx_palloc_block(pool, size);
    }
   
    //申請的內存大於max
    return ngx_palloc_large(pool, size);
}

如果size<max並且在內存池鏈表中沒有找到空閒內存大於等於size的內存就調用ngx_palloc_block。

然後我們來看看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);

   //執行按NGX_POOL_ALIGNMENT對齊方式的內存分配
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }

    new = (ngx_pool_t *) m;
    //初始化block
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);//m指向可分配內存的起始位置
    m = ngx_align_ptr(m, NGX_ALIGNMENT);//內存對齊
    new->d.last = m + size;//last指向下一次分配的位置,因爲m到m+size的內存已分配

    current = pool->current;

    for (p = current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            current = p->d.next;//移動current,失敗4次說明前面可用的內存塊空間很小,以後直接從current這個位置來分配內存
        }
    }

    p->d.next = new;//將新分配的block連接到內存池

    pool->current = current ? current : new;//如果current爲空的情況

    return m;
}
注意:ngx_palloc_block是個靜態函數,說明這個函數只能在當前的文件中使用,不能給外部用戶調用。


如果申請的size>max,說明用戶需要一塊很大的內存,就調用ngx_palloc_large內分配內存給用戶。

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_alloc(size, pool->log);//申請內存 
    if (p == NULL) {  
        return NULL;  
    }  
      
    n = 0;  
      
    //以下幾行,將分配的內存鏈入pool的large鏈中,  
    //這裏指原始pool在之前已經分配過large內存的情況。  
    for (large = pool->large; large; large = large->next) {  
        if (large->alloc == NULL) {  
            large->alloc = p;  
            return p;  
        }  
          
        if (n++ > 3) {  
            break;  
        }  
    }  
      
    //如果該pool之前並未分配large內存,則就沒有ngx_pool_large_t來管理大塊內存  
    //執行ngx_pool_large_t結構體的分配,用於來管理large內存塊。  
    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;  
} 
注意:這是一個static的函數,說明外部函數不會隨便調用,而是提供給內部分配調用的,  即nginx在進行內存分配需求時,不會自行去判斷是否是大塊內存還是小塊內存,  而是交由內存分配函數去判斷,對於用戶需求來說是完全透明的。

2.5.2  ngx_pcalloc 、ngx_free、ngx_pmemalign

ngx_pcalloc和ngx_palloc幾乎一樣,只不過對分配的內存進行了初始化

ngx_pcalloc:

void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;

    p = ngx_palloc(pool, size);
    if (p) {
        ngx_memzero(p, size);//初始化
    }

    return p;
}

ngx_free函數只釋放大塊內存這點要注意:
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;
}

ngx_pmemalign將在分配size大小的內存並按alignment對齊,然後掛到large字段下,當做大塊內存處理
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
    void              *p;
    ngx_pool_large_t  *large;

    p = ngx_memalign(alignment, size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    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.6 ngx_clean_add

這個函數就是想cleanup中添加一個內存塊和hander函數,不過這個handler還要我們自己設定,因爲初始值爲空

ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;

    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }

    c->handler = NULL;
    c->next = p->cleanup;

    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}


http://blog.csdn.net/xiaoliangsky/article/details/39523875

work hard


3測試列子

/*
author: smtl
date: 2014-09-24-23:17
<a target=_blank href="http://blog.csdn.net/xiaoliangsky/article/details/39523875">http://blog.csdn.net/xiaoliangsky/article/details/39523875</a>
*/

#include <stdio.h>
#include <stdlib.h>
#include "ngx_config.h"
#include "ngx_conf_file.h"
#include "nginx.h"
#include "ngx_core.h"
#include "ngx_string.h"
#include "ngx_palloc.h"

////////////////////////////////////////////////////////////////////////////////////
//不加下面這兩個定義編譯會出錯
volatile ngx_cycle_t  *ngx_cycle;

void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
            const char *fmt, ...)
{
}
////////////////////////////////////////////////////////////////////////////////////

int main()
{
	ngx_pool_t* pool = ngx_create_pool(1024, NULL);
	if (pool == NULL)
	{
		printf("create pool failed!\n");	
	}
	
	ngx_int_t* array = ngx_palloc(pool, 128*sizeof(ngx_int_t));
	if (array == NULL)
	{
		printf("array alloc is failed!\n");
		exit(1);
	}

	ngx_int_t i;
	for (i=10; i<20; ++i)
	{
		array[i] = rand()%10000;
		printf("i = %d\n", array[i]);
	}

	ngx_str_t* str = ngx_palloc(pool, sizeof(ngx_str_t));
	if (str == NULL)
	{
		printf("str alloc is failed!\n");
		exit(1);
	}

	str->data = ngx_pcalloc(pool, 26*sizeof(char));
    if (str->data == NULL)
	{
		printf("data alloc is failed!\n");
		exit(1);
	}

	str->len = 10;
	for (i=97; i<123; ++i)
	{
		str->data[i-97] = (char)i;
	}

	printf("%s\n", str->data);
        ngx_destroy_pool(pool);
        return 0;
}

編譯:

makefile:

gcc -g -Wall -Wextra -I. -I /home/wyp/桌面/testkNx/nginx-1.0.15/src/core \
-I /home/wyp/桌面/testkNx/nginx-1.0.15/src/event \
 -I /home/wyp/桌面/testkNx/nginx-1.0.15/src/event/modules \
-I /home/wyp/桌面/testkNx/nginx-1.0.15/src/os/unix \
-I /home/wyp/桌面/testkNx/nginx-1.0.15/objs  \
/home/wyp/桌面/testkNx/nginx-1.0.15/objs/src/core/ngx_palloc.o \
/home/wyp/桌面/testkNx/nginx-1.0.15/objs/src/core/ngx_string.o \
/home/wyp/桌面/testkNx/nginx-1.0.15/objs/src/os/unix/ngx_alloc.o ngx_pool.o \
-o ngx_pool

運行結果:

i = 9383
i = 886
i = 2777
i = 6915
i = 7793
i = 8335
i = 5386
i = 492
i = 6649
i = 1421
abcdefghijklmnopqrstuvwxyz






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章