Nginx 内存池

目录

零、基本框架

一、基础结构

二、对外接口

三、函数实现

1、ngx_create_pool

2、ngx_destroy_pool

3、ngx_reset_pool

4、ngx_palloc

5、ngx_pnalloc

6、ngx_pmemalign

7、ngx_pfree

8、ngx_pcalloc

9、ngx_pool_cleanup_add

10、ngx_pool_run_cleanup_file

[11、相关内存 handler](#11、相关内存 handler)


零、基本框架

nginx 内存池模块基于链表结构设计,每个链表节点都是一个内存池单元。每个内存池单元通过从操作系统内存中预申请一整片空间,通过移动指针进行内存分配。内存池模块支持动态扩容,在内存池空间不足时,往内存池链表中新增内存池节点。

对于大内存申请需求,内存池模块设计了 large 链表进行管理。内存池模块 reset 或 destroy 时都会清理 large 链表(调用 free 释放掉)。

内存池模块分配内存时支持内存对齐,提高 CPU 寻址效率。内存池模块也支持自定义对文件句柄等类似资源的 close handler。

一、基础结构

cpp 复制代码
/*
 * 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)

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


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

/*
	大块内存片结构(典型链表结构)
*/
typedef struct ngx_pool_large_s  ngx_pool_large_t;
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;

typedef struct {
    ngx_fd_t              fd;
    u_char               *name;
    ngx_log_t            *log;
} ngx_pool_cleanup_file_t;

/*
	内存池链表主结构
*/
struct ngx_pool_s {
    ngx_pool_data_t       d;    // 内存池链头节点
    size_t                max;  // 内存池可分配的最大内存片大小,超出 max 的分配 ngx_pool_large_t
    ngx_pool_t           *current;  // 当前正在使用的内存池指针(可直接分配内存空间)
    ngx_chain_t          *chain;  //
    ngx_pool_large_t     *large;  // 保存超出 max 大小的内存链
    ngx_pool_cleanup_t   *cleanup; // 自带释放 handler(例如文件句柄的close) 的内存链
    ngx_log_t            *log;
};

二、对外接口

cpp 复制代码
// 新建内存池
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
// 销毁内存池
void ngx_destroy_pool(ngx_pool_t *pool);
// 重置内存池,释放 large 链表
void ngx_reset_pool(ngx_pool_t *pool);

/*
	从内存池中申请 size 大小的内存,内存按平台位数对齐。
	小于 max 的内存,直接从内存池中分配;
	大于 max 的内存,动态申请,并记录在 large 链表中
*/
void *ngx_palloc(ngx_pool_t *pool, size_t size);
// 同 ngx_palloc,但不进行内存对齐
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);

/*
	调用 ngx_palloc 分配内存,并初始化为 0 
*/
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

/*
	从系统申请一块内存对齐的内存,并保存在 large 链表中
*/
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

/*
	释放保存在 large 链表中的 p 地址内存
*/
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);

/*
    从内存池中申请 size 大小的内存,并可以通过调用自定义的回调函数进行释放
*/
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);

/*
	释放 cleanup 中包含文件句柄 fd 的节点
*/
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);

/*
    cleanup 节点中的资源释放 handler,同 close,释放句柄
*/
void ngx_pool_cleanup_file(void *data);

/*
    cleanup 节点中资源释放 handler,同 unlink,删除文件
*/
void ngx_pool_delete_file(void *data);

三、函数实现

1、ngx_create_pool

新建内存池

cpp 复制代码
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;
	// 申请按 NGX_POOL_ALIGNMENT 内存对齐 size 大小的内存空间
    // linux 下主要依赖 posix_memalign 和 memalign 两个函数
    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);
	// 内存池可分配的最大内存为 pagesize - 1
    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;
}

2、ngx_destroy_pool

销毁内存池链表

cpp 复制代码
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);
        }
    }
// 一些 debug 信息,用于确认内存池的使用状况
#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
	// 释放全部大块内存(申请超过 max 的内存均在此链表)
    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;
        }
    }
}

3、ngx_reset_pool

重置内存池,不是释放该空间,而且调整内存池可用范围,重复使用内存池空间。

nginx 内存池模块没有设计类似 free 的内存返还函数,这边避免了繁琐的内存碎片管理逻辑。在需要时直接重置 ngx_reset_pool 即可。

cpp 复制代码
/*
	需要注意,内存池空间是预申请的一整块空间。
	重置内存池,不是释放该空间,而且调整内存池可用范围,重复使用内存池空间。
*/
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;
    }

	// 重置 current 指针
    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

4、ngx_palloc

按调用顺序,从上到下看。

cpp 复制代码
/*
	从内存池中申请 size 大小的内存,内存按平台位数对齐。
	小于 max 的内存,直接从内存池中分配;
	大于 max 的内存,动态申请,并记录在 large 链表中
*/
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 的内存,直接从内存池中划分

cpp 复制代码
/*
	分配内存池中 size       大小的空间,align 标识是否进行内存对齐
*/
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;

		// 内存按平台字长对齐(64 bit)
        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);

	// 若当前内存池链都不存在满足 size 大小的空间,则新建内存池
    return ngx_palloc_block(pool, size);
}

新增内存池节点,并返回申请的内存指针

cpp 复制代码
/*
	新增内存池节点
*/
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);
	// 分配 psize 内存池空间
    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 内存池节点
		current 记录的内存池节点,是可直接使用分配内存的节点,不然每次遍历链表搜索是很耗时的。
		对于失败次数太多的节点(超过 4 次),需要封存,将 current 指针向下移动。
	*/
	
    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;
}

申请大于 max 的内存空间,则单独从操作系统内存中额外申请,并记录在 large 链表中

cpp 复制代码
/*
	申请大于 max 的内存空间,则单独从操作系统内存中额外申请,并记录在 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;
	/*
		将新申请的内存空间,保存在 large 链表中
	*/
	// 1、先遍历 large 链表最前面的三个节点,若存在内存已经被 free 的,则保存在该节点
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }
		// 往后找三个节点
        if (n++ > 3) {
            break;
        }
    }
	// 2、前三个都没有被 free,则新建 large 节点,将新申请的大内存保存在该节点
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
	// 3、将新建的 large 节点作为新的头节点
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

5、ngx_pnalloc

与 ngx_palloc 类似,唯一区别在于申请的小于 max 的内存不会对齐。

cpp 复制代码
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        // 不对齐
        return ngx_palloc_small(pool, size, 0);
    }
#endif

    return ngx_palloc_large(pool, size);
}

6、ngx_pmemalign

从系统申请一块内存对齐的内存,并保存在 large 链表中

cpp 复制代码
/*
	从系统申请一块内存对齐的内存,并保存在 large 链表中
*/
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
    void              *p;
    ngx_pool_large_t  *large;

	// 申请一块 size 大小的内存,内存对齐
    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 链表头部
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

7、ngx_pfree

释放保存在 large 链表中的 p 地址内存

cpp 复制代码
/*
	释放保存在 large 链表中的 p 地址内存
*/
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
		// 搜索地址为 p 的内存
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
			// 同 free
            ngx_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

8、ngx_pcalloc

调用 ngx_palloc 分配内存,并初始化为 0

cpp 复制代码
/*
	调用 ngx_palloc 分配内存,并初始化为 0 
*/
void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;

    p = ngx_palloc(pool, size);
    if (p) {
		// 同 memset(p, 0, size)
        ngx_memzero(p, size);
    }

    return p;
}

9、ngx_pool_cleanup_add

从内存池中申请 size 大小的内存,并可以通过调用自定义的回调函数进行释放,例如文件描述符等资源。

cpp 复制代码
/*
	区别与申请内存后只能调用 free 进行释放,
	该函数申请的内存可以通过调用定义的回调函数进行释放,例如文件描述符等资源
*/
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;
	// 新建 ngx_pool_cleanup_t 节点
    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }
	// 从内存池中申请 size 大小的内存空间
    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }
	
    c->handler = NULL;
	// 插到 cleanup 链表头节点
    c->next = p->cleanup;

    p->cleanup = c;

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

    return c;
}

10、ngx_pool_run_cleanup_file

释放 cleanup 中包含文件句柄 fd 的节点

cpp 复制代码
/*
	释放 cleanup 中包含文件句柄 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) {
		// 关闭文件句柄的 close 函数 handler
        if (c->handler == ngx_pool_cleanup_file) {

            cf = c->data;
			// 搜索 fd 句柄
            if (cf->fd == fd) {
                c->handler(cf);
                c->handler = NULL;
                return;
            }
        }
    }
}

11、相关内存 handler

cpp 复制代码
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);
	// close 函数
    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);
    }
}

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

	// unlink 函数
    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);
    }
}
相关推荐
雨雪飘零26 分钟前
Windows系统使用OpenSSL生成自签名证书
nginx·证书·openssl
yanwushu1 小时前
Xserver v1.4.2发布,支持自动重载 nginx 配置
mysql·nginx·php·个人开发·composer
DARLING Zero two♡1 小时前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
QAQ小菜鸟2 小时前
一、初识C语言(1)
c语言
何曾参静谧3 小时前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
互联网打工人no13 小时前
每日一题——第一百二十一题
c语言
朱一头zcy4 小时前
C语言复习第9章 字符串/字符/内存函数
c语言
此生只爱蛋4 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
何曾参静谧4 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
lulu_gh_yu5 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法