目录
[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);
}
}