6.nginx源码分析之数据结构:ngx_pool_t

来源:互联网 发布:闽江网络教学平台 编辑:程序博客网 时间:2024/06/06 14:04

nginx内存管理


nginx内存管理的结构体

关于nginx内存管理的方法在ngx_palloc.h文件中,定义了相关结构体和接口:

如下几个宏定义了内存池的默认大小和对齐大小。

/* * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86. * On Windows NT it decreases a number of locked pages in a kernel. */#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)//默认大小是1024个16B#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)#define NGX_POOL_ALIGNMENT       16#define NGX_MIN_POOL_SIZE                                                     \    ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),            \              NGX_POOL_ALIGNMENT)

定义了nginx的FREE函数指针,ngx_pool_cleanup_pt为一个函数指针,他可以指向用户希望采用的释放函数。


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 记录着资源的释放策略;
data是实际所指向的资源的地址;
next就是链表的下一个节点位置;

因为nginx的内存释放是在最后进行的,所以需要记录下每个资源的释放规则(由handler记录着函数地址)。

typedef struct ngx_pool_large_s  ngx_pool_large_t;struct ngx_pool_large_s {    ngx_pool_large_t     *next;    //指向下一个pool    void                 *alloc;    //指向分配的内存};  
typedef struct {    u_char               *last;      //内存池下一次分配的起始位置    u_char               *end;       //内存池结束位置    ngx_pool_t           *next;      //内存池里面有很多块内存,这些内存块就是通过该指针连成链表的    ngx_uint_t            failed;    //内存分配失败次数} ngx_pool_data_t;   //内存池数据块信息

内存池的控制信息如下,上述内存池数据块ngx_pool_data_t信息是当前这个内存池的描述信息,所以其实是ngx_pool_s的一部分,作为控制信息我们把它放在了ngx_pool_s的开始位置。

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 指向了一个chain结构
large 指向一个大块内存的地址(大小大鱼max的内存,nginx使用标准库malloc进行申请)
cleanup 指向nginx释放结构体的指针
log 指向日志结构体

各个结构体的关系如下图所示(该图由JULY绘制,借鉴过来):
这里写图片描述


nginx内存管理的接口

上述是内存管理的结构体定义,在C编程中,一旦结构体定义OK,剩下的接口实现基本都是围绕着结构体展开的。nginx向我们介绍了如下几个接口定义:

//nginx的内存申请void *ngx_alloc(size_t size, ngx_log_t *log);void *ngx_calloc(size_t size, ngx_log_t *log);//nginx创建内存池,销毁内存池,重置内存池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);void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);void ngx_pool_cleanup_file(void *data);void ngx_pool_delete_file(void *data);

下面我们对这些接口进行逐个分析,接口的实现在ngx_palloc.c文件中:


ngx_alloc

void * ngx_alloc(size_t size, ngx_log_t *log){    void  *p;    p = malloc(size);   //ngx_alloc申请内存只是对malloc的封装    //不管是申请成功还是失败都会写入日志    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

void * ngx_calloc(size_t size, ngx_log_t *log){       void  *p;    p = ngx_alloc(size, log);    if (p) {        ngx_memzero(p, size);    //将ngx_alloc申请的内存全部清空    }    return p;}   

ngx_create_pool 创建ngx内存池操作

ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log){           ngx_pool_t  *p;    //创建nginx内存池的控制结构    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);    if (p == NULL) {        return NULL;    }                  //对nginx控制结构的值进行初始化    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);    //可申请的内存大小要去除控制信息(ngx_pool_t)    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;   //判定大内存的大小    p->current = p;    //当前内存块的地址指向p本身    p->chain = NULL;     //链表指针地址    p->large = NULL;      //刚开始还没有大内存申请,所以指针为NULL    p->cleanup = NULL;     //清空模块的指针为空    p->log = log;         //日志结构体指针进行初始化    return p; }   

内存池的初始化没有采用malloc直接申请,而是使用了ngx_memalign函数,ngx_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;}

这个函数中真正起申请作用的是posix_memalign,posix_memalign的接口定义如下:

int posix_memalign (void **memptr,    //内存申请指针的地址                    size_t alignment,    //申请的内存是alignment的倍数,alignment的2的幂,这样设计是为了方便操作                    size_t size);    //size是我们申请的内存的大小

ngx_create_pool 调用最终会得到一个内存池的控制信息以及对其成员的初始化:


ngx_destroy_pool内存池的销毁操作

代码实现如下:

voidngx_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);        }    }#if (NGX_DEBUG)    /*     * we could allocate the pool->log from this pool     * so we cannot use this log while free()ing the pool     */    for (l = pool->large; l; l = l->next) {        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);    }    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 (l = pool->large; l; l = l->next) {        if (l->alloc) {            ngx_free(l->alloc);        }    }    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {        ngx_free(p);        if (n == NULL) {            break;        }    }}

在C语言中,因为没有类的概念,也就不存在类的销毁,所以资源的释放顺序一定要严格的把控。以ngx_pool_t为例,它其中包含了ngx_pool_large_t和ngx_pool_cleanup_t这些结构体指针,释放顺序如下:
(1)首先在清楚链表中使用用户指定的方式( handler
所指向的函数)释放节点的data数据。

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

(2)接着释放大块内存链表,其中的ngx_free就是标准库中的free函数封装:

for (l = pool->large; l; l = l->next) {        if (l->alloc) {            ngx_free(l->alloc);        }    }

(3)最后我们释放nginx的内存池链表,这里采用的是双指针联动方式,让p指向当前内存池地址,而n指向下一个内存池,先释放当前的内存池,再判断下一个内存池是否存在,如果不存在则循环结束:

for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {        ngx_free(p);        if (n == NULL) {            break;        }    }

ngx_reset_pool 重置内存池

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);        }    }    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;    pool->chain = NULL;    pool->large = NULL;}

这个流程和销毁内存池操作有着相同的部分,先清空大内存池的链表,之前的那个cleanup链表的销毁操作可以不进行,反正在内存池执行销毁的时候也会执行,然后把last指针指向控制信息的下一个字节地址。


ngx_palloc内存池申请内存

这里对申请的内存大小做判断,选择大内存申请或者小内存申请方式,注意这里申请的内存大小是经过对齐的,而ngx_pnalloc申请的内存大小是没有对齐的。

void *ngx_palloc(ngx_pool_t *pool, size_t size){#if !(NGX_DEBUG_PALLOC)    if (size <= pool->max) {        return ngx_palloc_small(pool, size, 1);    }#endif    return ngx_palloc_large(pool, size);}

这里主要关注两个函数ngx_palloc_small和ngx_palloc_large。

ngx_palloc_small

static ngx_inline void *ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align){    u_char      *m;         ngx_pool_t  *p;    p = pool->current;    do {        m = p->d.last;        if (align) {            m = ngx_align_ptr(m, 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);}   

在整个内存池链表中找到能够申请size大小的内存,把申请的起始地址返回,该内存池的可用内存大小减少size。如果遍历完当前的内存池链表都无法满足当前size大小的内存申请。则调用ngx_palloc_block(pool, size)函数,该函数的实现如下所示:

static void * ngx_palloc_block(ngx_pool_t *pool, size_t size){       u_char      *m;    size_t       psize;    ngx_pool_t  *p, *new;    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;    for (p = pool->current; p->d.next; p = p->d.next) {        if (p->d.failed++ > 4) {            pool->current = p->d.next;        }    }    p->d.next = new;    return m;}

该函数的作用就是申请了一个新的内存池(pool),首先计算了内存池的大小,然后进行申请,如果成功的话把地址返回给new指针,用new对内存池的结构体成员进行初始化,m是我们最后申请的内存块的首地址,但是现在需要把new这个新的内存池插入到其他的内存池的链表末尾。

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;    //大块内存的申请采用malloc直接申请    p = ngx_alloc(size, pool->log);    if (p == NULL) {        return NULL;    }    n = 0;    for (large = pool->large; large; large = large->next) {        //如果当前内存池还没有大块内存申请,则当前这个申请是第一个        if (large->alloc == NULL) {            large->alloc = p;            return p;        }        if (n++ > 3) {            break;        }    }    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);    if (large == NULL) {        ngx_free(p);        return NULL;    }    //把这个p所指向的大块内存的管理节点尾部添加到大块内存链表指针里    large->alloc = p;    large->next = pool->large;    pool->large = large;    return p;}

ngx_pnalloc不进行对其的内存池申请

该方法的实现和ngx_palloc类似


ngx_pcalloc对内存池申请的内存初始化为0

void * ngx_pcalloc(ngx_pool_t *pool, size_t size){    void *p;    p = ngx_palloc(pool, size);    if (p) {    //初始化申请的内存为0        ngx_memzero(p, size);    }    return p;}

ngx_pfree 内存池释放大块内存的方法

ngx_int_tngx_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_pool_run_cleanup_file清空文件描述符fd

void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd){    ngx_pool_cleanup_t       *c;    ngx_pool_cleanup_file_t  *cf;    for (c = p->cleanup; c; c = c->next) {        if (c->handler == ngx_pool_cleanup_file) {            cf = c->data;            if (cf->fd == fd) {                c->handler(cf);                c->handler = NULL;                return;            }        }    }}

小结

关于nginx的内存池管理如上所示,需要边看代码边进行画图才能加深理解,管理思路比较直接,不像c++的stl那样分配了多个自由链表。希望大家可以好好思考。

1 0
原创粉丝点击