该文是针对NGINX 1.13.6这个版本进行分析。内存管理的源文件集中在以下几个文件中:
-
src/core/ngx_palloc.h
-
src/core/ngx_palloc.c
NGINX内存管理相对于REDIS或者MC简单一些,分为大内存和小内存管理。小内存管理,采用分块的模式,块内采用的是连续存储分配方式,块间采用的离散存储分配方式。大内存管理则采用离散存储分配方式。核心数据结构如下:
-
小内存数据区数据结构
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; // 存放当前可用的小内存数据区,取决于failed计数器
ngx_chain_t *chain; // 预留
ngx_pool_large_t *large; // 大内存的单向链表的头节点
ngx_pool_cleanup_t *cleanup; //单向链表,可以向内存池注册一系列回调函数,当内存池销毁的时候,会遍历该回调链表进行回调。
ngx_log_t *log; // 日志句柄
};
-
大内存数据区数据结构
struct ngx_pool_large_s {
ngx_pool_large_t *next; // 指向下一个大内存节点的地址,单向链表
void *alloc; // 大内存区
};
-
这些主要的数据结构的关系如下
![](https://i-blog.csdnimg.cn/blog_migrate/30cf45d8350c4973c9c65a277f459dab.png)
内存池ngx_pool_t管理着一个一个小内存区的单向链表,同时也管理着一个大内存区的单向链表。值得注意的是,内存池也包含一个小内存区域,同内存池同属于一个连续内存区域。如果该内存区域内存不够使用,则在堆上再申请一块连续区域,对于这些新申请的小内存区域的首地址是ngx_pool_t指针以及ngx_pool_data_t指针复用。为什么要复用呢?因为能确保内存池的current指针能寻址到新申请的小内存区域的首地址。
-
创建内存池源码分析
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t*log)
{
ngx_pool_t *p;
// 申请一块连续的内存块,并且16个字节对齐,从寻址效率考虑。有两种选择,POSIX
// 提供的函数,另外一个选择是采用C标准库提供的函数
p =ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
// C语言编程的一个好习惯,内存分配不一定每次都是成功的,需要做判断
if (p == NULL) {
return NULL;
}
//设置last指针和end指针的位置
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;
//遍历清理回调链表,回调清理函数
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;
}
}
}
-
内存分配源码分析
// 如果申请的大小超过了一个小内存区域的最大值,就从大内存区域申请内存
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);
}
// 从小内存区申请内存
static ngx_inline void * ngx_palloc_small(ngx_pool_t *pool, size_tsize, ngx_uint_t align)
{
u_char *m;
ngx_pool_t *p;
//从当前可用的小内存区块开始遍历
p= pool->current;
do {
// 将要分配内存的首地址指向最近一次分配的位置
m = p->d.last;
//对即将要分配的内存进行内存对齐,C语言编程习惯
if (align) {
m = ngx_align_ptr(m, NGX_ALIGNMENT);
}
//如果剩余的内存能满足分配,修改last指针,返回分配内存的首地址
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_tsize)
{
u_char *m;
size_t psize;
ngx_pool_t *p, *new;
//计算该区块的大小,注意pool的地址其实是ngx_pool_t和ngx_pool_data_t复用
psize = (size_t) (pool->d.end - (u_char *) pool);
//16个字节内存对齐,在堆中申请内存
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;
}
// 大内存区块的分配
static void * ngx_palloc_large(ngx_pool_t *pool, size_tsize)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
//在堆中申请内存
p= ngx_alloc(size, pool->log);
if (p == NULL) {
return NULL;
}
n= 0;
//如果当前大内存区链表有可用的节点存放刚分配好的内存,那么分配流程就结束了。
//如果连续三个节点都是unavailable的,那么就试图从小内存区域中为大内存区域的
//链表节点分配空间,并将该节点链入大内存区块的链表的链表头。
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;
}
-
内存释放源码分析
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;
}
-
内存池重置源码分析
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;
}