nginx源码分析2———基础数据结构三(内存池)

来源:互联网 发布:java如何防止sql注入 编辑:程序博客网 时间:2024/05/16 15:55

内存池介绍

内存池数据结构主要是ngx_pool_t结构体,很多重要的其他数据结构也都在使用它,包括前面提到的数组链表。我们可以把它想象成一种提供内存的容器,它提供了一种机制,帮助管理一系列的资源(如内存,文件等),使得对这些资源的使用和释放统一进行,免除了使用过程中考虑到对各种各样资源的什么时候释放,是否遗漏了释放的担心。

源码分析

内存池结构

内存池的数据块位置信息

typedef struct {    u_char *last;    u_char *end;       ngx_pool_t *next;    ngx_uint_t  failed; } ngx_pool_data_t;    

last 当前内存池分配到此处,即下一次分配从此处开始
end 内存池结束位置
next 内存池里面有很多块内存,这些内存块就是通过该指针连成链表的
failed 在分配内存的时候,因为该block上的内存不够而失败的次数。

内存池头部结构

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 指向当前内存池链表中的某一块内存池,位于current在链表向前的内存池,均因为内存不足分配失败超过4次(好好理解,好好看代码)
chain 该指针挂接一个ngx_chain_t结构
large 大块内存链表,即分配空间超过max的内存,下面给出结构

struct ngx_pool_large_s {    ngx_pool_large_t     *next;    void                 *alloc;};

cleanup 释放内存池的callback,下面给出结构

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

log 日志信息(这个我们先不做具体讨论)

内存池的创建

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log){    ngx_pool_t  *p;    /*采用内存对齐方式分配内存,此处是16位对齐,分配大小为size    的内存*/    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);    if (p == NULL) {        return NULL;    }    //可以看出,ngx_pool_t结构体占用了内存块的首部    /*将内存的未使用内存的起始地址设置在紧跟ngx_pool_t结构体的    后面*/    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_palloc_block和ngx_palloc_large

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;    }    //将新的ngx_pool_t结构放在新申请内存的首部    //并给ngx_pool_t中的首字段ngx_pool_data_t赋值    new = (ngx_pool_t *) m;    //设置新内存结束的地方    new->d.end = m + psize;    //将新内存的next设置为空    new->d.next = NULL;    new->d.failed = 0;    //获取ngx_pool_data_t后的内存首地址    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){        //failed的值只在此处被修改          if (p->d.failed++ > 4) {                        //failed的值大于4时候,就移动current指针            pool->current = p->d.next;        }    }    //将新分配的内存挂到p->d的next上面    p->d.next = new;    return m;}

该函数分配一块内存后,p->d.last指针指向的是ngx_pool_data_t结构体(大小16B)之后数据区的起始位置。而创建内存池时时,last指针指向的是ngx_pool_t结构体(大小40B)之后数据区的起始位置。

//该函数主要是ngx_palloc_large函数调用,分配大小为size的内存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;}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;    //查找large列表中,是否有alloc指针为空的,查找超过三次就放弃    for (large = pool->large; large; large = large->next) {        if (large->alloc == NULL) {            large->alloc = p;            return p;        }        if (n++ > 3) {            break;        }    }    /*调用palloc在ngx_pool_t的成员d(ngx_pool_data_t)上分配存储    结构体的内存*/    large = ngx_palloc(pool, sizeof(ngx_pool_large_t));    if (large == NULL) {        //如果结构体分配失败,直接调用free(系统原生的)        ngx_free(p);        return NULL;    }    //对新创建的large结构体赋值    large->alloc = p;    //头插到pool->large    large->next = pool->large;    pool->large = large;    return p;}

在上述函数中调用了ngx_palloc,下面让我们来看一看ngx_palloc函数

void *ngx_palloc(ngx_pool_t *pool, size_t size){    u_char      *m;    ngx_pool_t  *p;    /*比较如果size小于等于max的大小。那么就在ngx_pool_data_t    链表上分配*/    if (size <= pool->max) {        p = pool->current;        do {            //p->d.last内存对齐矫正之后得到m指针            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);            //如果此块ngx_pool_data_t上面有足够的空间可以分配            if ((size_t) (p->d.end - m) >= size) {                //移动游标last                p->d.last = m + size;                //直接返回                return m;            }            //如果此块ngx_pool_data_t内存不够,就接着遍历下一块            p = p->d.next;        } while (p);        //如果每一块都不够,就分配一块新的        return ngx_palloc_block(pool, size);    }    //比较如果size大于max的大小。那么采用large分配    return ngx_palloc_large(pool, size);}

可以看到,在ngx_palloc_large上分配的时候,会调用ngx_palloc,并让它分配存储结构体的内存,接着如果要结构体存储的内存大于max,ngx_palloc函数也会调用ngx_palloc_large的,这样将一直循环,不过这是不可能的。结构体占用的大小不会大于max的值,所以结构体ngx_pool_large_t只会在ngx_palloc_block这个代码块中分配。
当然还有其他的内存分配函数,逻辑都大致相似。它们的区别在于内存对齐,是否清零buff,还有是否采用large分配,详细的可以自己下去了解,这边不在赘述。

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

增加cleanup

cleanup是让用户提供一个handle,在对内存进行释放的时候进行调用,可以释放一些系统相关,以及使用内核维护的相关数据。

ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){    ngx_pool_cleanup_t  *c;    //在该pool上面分配结构体内存    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));    if (c == NULL) {        return NULL;    }    /*对于该handle的处理,是否需要其他的内存,这个内存是handle函数的参数*/    //如果需要就继续在pool上面分配    if (size) {        c->data = ngx_palloc(p, size);        if (c->data == NULL) {            return NULL;        }    } else {        c->data = NULL;    }    //挂到cleanup链表上面去    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;}

内存池的释放

最后我们将介绍内存池的销毁,先依次调用cleanup链表的各个handle,然后再销毁large内存,最后销毁block内存。

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);        }    }    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);        }    }    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {        ngx_free(p);        if (n == NULL) {            break;        }    }}

总结

内存池的作用显而易见,减少了内存释放和申请的次数,这样就减少了内存碎片,而且对于内存的释放和分配都几种处理,减少了内存泄漏的可能,这些思想是值得我们学习的。

0 0