nginx源码之内存池

混江龙づ霸主 提交于 2020-04-12 18:53:26

Ngnix内存池结构体和接口定义主要在以下几个文件:

core文件夹下的:ngx_palloc.h ngx_palloc.c

os/unix文件夹下的:ngx_alloc.h ngx_alloc.c


内存池相关的核心结构体:

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


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


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


可以看出nginx内存池是个链表结构,每个内存池包括头部和数据部分。

 

其中数据部分又分成两部分:一个是小块数据分配ngx_pool_data_t, 另外一个是大数据块的分配:ngx_pool_large_t。一般的内存分配是在ngx_pool_data_t,但是如果分配的内存过大,则直接分配大内存块,挂在大内存块这个链表后。

 

内存池核心头部结构:

d:内存池数据块

max:小块内存分配的最大值

current:指向当前内存池

large:大块内存分配

 

 

内存块数据结构:

last:可分配内存开始位置

end:可分配内存结束位置

next:指向下一个内存池,链表结构嘛

failed:本内存池分配内存失败次数


内存池整体结构图:


一些主要的接口分析:

 

创建内存池:

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);//分配了一块内存
    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;
}

销毁内存池:

void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;
//cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,清理函数是一个handler的函数指针挂载。
    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_free实际上就是标准的free函数,  
//即大内存块就是通过malloc和free操作进行管理的。 
 
    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);
 
        if (n == NULL) {
            break;
        }
    }
}


***内存分配***核心***

看一下内存分配的核心两个接口

void *ngx_palloc(ngx_pool_t *pool, size_t size);

void *ngx_pnalloc(ngx_pool_t *pool, size_t size);

这两个接口其实区别不大,主要区别就是内存分配的时候用不用内存对齐,调用函数不同,其他没有区别,因此只分析其中一个

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m; 
    ngx_pool_t  *p; 
 
//首先判断需要分配的内存是否是大内存块,不是的话,从current节点开始遍历内存池,直到找到能够分配的内存池,如果没有找到,会新建内存池,再分配
    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);
}

看一下具体的函数:

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;
    
    //调整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;
}

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  list中
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }   
 
        if (n++ > 3) {
            break;
        }   
    }   
 
    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;
}


nginx只提供给了用户申请内存的接口,却没有释放内存的接口,那么nginx是如何完成内存释放的呢?总不能一直申请,用不释放啊。针对这个问题,nginx利用了web server应用的特殊场景来完成; 一个web server总是不停的接受connection和request,所以nginx就将内存池分了不同的等级,有进程级的内存池、connection级的内存池、request级的内存池。

  也就是说,创建好一个worker进程的时候,同时为这个worker进程创建一个内存池,待有新的连接到来后,就在worker进程的内存池上为该连接创建起一个内存池;连接上到来一个request后,又在连接的内存池上为request创建起一个内存池。

  这样,在request被处理完后,就会释放request的整个内存池,连接断开后,就会释放连接的内存池。因而,就保证了内存有分配也有释放。

 


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!