首页 > 文章列表 > nginx内存池如何实现

nginx内存池如何实现

nginx
211 2023-05-12

nginx内存池如何实现

一、简介

最新稳定版本nginx1.20.2。

为了能高效、快速的分配内存,以及减少内存碎片等,nginx实现了自己的内存池基础组件。

主要实现文件ngx_palloc.h, ngx_palloc.c

二、数据结构

2.1 内存池主要结构

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;

};

内存池中第一个成员是一个结构体:

使用ngx_pool_data_t结构体来表示当前内存池信息。

last :下次开始分配的地址

end: 内存池的结束地址

next: 内存池链表,将多个内存池连接起来

max

整个内存池的最大大小

current

指向从当前内存池开始查找可用内存

chain

buffer使用的,这里不涉及

large

当需要的内存大于内存池最大大小时,需要通过malloc直接分配,然后形成链表进行组织

cleanup

清理工作的回调链表

log

日志句柄

2.2 大内存链

当需要分配的内存比内存池的最大大小都大时,内存池无法满足分配,所以直接从系统中分配,然后构成一个链表进行维护。

typedef struct ngx_pool_large_s  ngx_pool_large_t;



struct ngx_pool_large_s {

    ngx_pool_large_t     *next;

    void                 *alloc;

};

2.3 清理任务链

有一个回调任务的链表,当内存池销毁时,将依次遍历此链表,逐一回调handler进行清理工作。

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;

};

三、内存结构图

3.1 逻辑

3.2 实际

可以看出,很多节点都是从内存池中分配的,所以可以把精力都放在实际的数据上而不必在意其他细节上。

四、实现

4.1 创建内存池

/*

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



#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)
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;

}

从代码中可以看到,内存池最大不超过pagesize的大小

4.2 从内存池中分配空间

分配函数分了内存对齐和内存不对齐,但这只控制了内存池中分配空间,不控制大内存分配。

(1)分配小空间

  • 内存对齐 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);

}

当需要分配的空间小于max时,将使用小内存分配方式(即从内存池中分配空间),而ngx_pnalloc和ngx_palloc相比只是调用ngx_palloc_small时的最后一个参数为0。

从pool->current指向的内存池开始遍历,寻找满足分配大小的空间,找到则返回首地址

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

}

当现有内存池中都无法满足分配条件时,创建新的内存池

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;

}

其中,创建好新的内存池后,又做了一次遍历,将failed计数加一,当大于4时,将跳过此内存池,下次就不从它开始查找。

即认为超过4次你都不能满足分配,以后都不能满足分配,不再用你了,减少遍历个数,加快成功分配效率

(2)分配大空间

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;



    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;

    }



    large->alloc = p;

    large->next = pool->large;

    pool->large = large;



    return p;

}

可以看出,为了避免分配空间,遍历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_small(pool, sizeof(ngx_pool_large_t), 1);

    if (large == NULL) {

        ngx_free(p);

        return NULL;

    }



    large->alloc = p;

    large->next = pool->large;

    pool->large = large;



    return p;

}

4.3 注册清理任务

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;

}

可以看出,这里只是分配了一个节点,并没有设置handler以及data数据,所以还得看具体的调用方进行设置,因为这里返回了分配的节点。

比如在函数ngx_create_temp_file

ngx_int_t

ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path, ngx_pool_t *pool,

    ngx_uint_t persistent, ngx_uint_t clean, ngx_uint_t access)

{

    ...



    cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));

    if (cln == NULL) {

        return NGX_ERROR;

    }



       ...

        file->fd = ngx_open_tempfile(file->name.data, persistent, access);

				...

        if (file->fd != NGX_INVALID_FILE) {



            cln->handler = clean ? ngx_pool_delete_file : ngx_pool_cleanup_file;

            clnf = cln->data;



            clnf->fd = file->fd;

            clnf->name = file->name.data;

            clnf->log = pool->log;



            return NGX_OK;

        }

			...

}

生成临时文件,将fd以及文件名注册到清理任务中,后续文件不使用了则不需要特殊处理,内存内存池释放时将统一清理。

4.4 重置内存池

  • 释放大内存

  • 重置内存中last

  • 重置failed计数

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;

}

这里有个现象:

在内存池中空间不足时,将调用ngx_palloc_block创建一个新的内存池,而last指向的是m += sizeof(ngx_pool_data_t);, 因此当前新分配的内存池将比第一个内存池可用大小多了(max,current,chain,large,cleanup,log)这几个字段大小(可能没有那么多,因为要对齐,可能对齐后就完全一样了),而现在重置时,p->d.last = (u_char *) p + sizeof(ngx_pool_t);每个内存池可用大小又变成一样的。

4.5 销毁内存池

  • 回调清理任务

  • 释放大内存

  • 释放内存池本身

void

ngx_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) {

        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;

        }

    }

}

4.6 大内存释放

通过遍历找到要释放的节点,将内存释放,并且将alloc设置成NULL,则有了节点重用的情况。

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;

}

4.7 分配并清空数据

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;

}

正常分配的空间中都是垃圾数据,所以当前函数在分配空间后,将分配的空间清零。

4.8 回调文件清理

(1) 手动关闭指定fd

遍历清理任务,找到ngx_pool_cleanup_file的handler,如果是要关闭的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;

            }

        }

    }

}

(2) 关闭fd

void

ngx_pool_cleanup_file(void *data)

{

    ngx_pool_cleanup_file_t  *c = data;



    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",

                   c->fd);



    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {

        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,

                      ngx_close_file_n " \"%s\" failed", c->name);

    }

}

(3) 删除文件并关闭fd

void

ngx_pool_delete_file(void *data)

{

    ngx_pool_cleanup_file_t  *c = data;



    ngx_err_t  err;



    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",

                   c->fd, c->name);



    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {

        err = ngx_errno;



        if (err != NGX_ENOENT) {

            ngx_log_error(NGX_LOG_CRIT, c->log, err,

                          ngx_delete_file_n " \"%s\" failed", c->name);

        }

    }



    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {

        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,

                      ngx_close_file_n " \"%s\" failed", c->name);

    }

}