从代码的结构看,nginx可以分为三个部分:基础设施、并发模型和应用模块。前面已经介绍了nginx的模块化设计,剖析了nginx的并发模型,并发模型主要是核心类模块和event类模块实现的。应用模块主要是指http类模块和mail类模块,它们实现了nginx作为web服务器、反向代理服务器和邮件代理服务器的功能,应用模块多和相应的领域知识有关,放在最后剖析。
基础设施主要包括memory、log、string、time、configuration、file和一些基本数据结构(容器):Array,List,Hash Table,Queue,Red-black Tree,Radix Tree。
本篇文章分析内存相关的数据结构和操作API。nginx中所有内存分配都使用内存池技术,我们首先分析内存池。
nginx对c的内存分配函数进行了封装,主要是添加了一些日志点,方便监控所有的内存分配操作:
ngx_alloc使用malloc分配内存空间;
ngx_calloc使用malloc分配内存空间,并将分配的空间初始化为0;
ngx_memalign使用posix_memalign分配内存空间,按照指定的alignment数值对齐;
ngx_free释放内存空间。
为了方便模块对内存的使用,方便内存的管理,nginx实现了内存池机制进行内存的分配和管理,nginx在特定的生命周期统一创建内存池,需要内存分配的时候统一通过内存池进行,nginx会在适当的时候释放内存池的资源,开发者只需要调用适当的API申请内存即可,不用过多考虑内存的释放问题,提高了开发效率。
内存池的数据结构定义如下:
// 内存池
struct ngx_pool_s {
ngx_pool_data_t d;
size_t max;// 内存池最大可分配的普通内存(small blocks)尺寸
ngx_pool_t *current; // 指向当前可用于分配的内存池
ngx_chain_t *chain;
ngx_pool_large_t *large;// 指向大内存块(large blocks)
ngx_pool_cleanup_t *cleanup;// 指向内存回收器
ngx_log_t *log;
};
typedef struct {
u_char *last;// 指向已使用的内存的末尾后面
u_char *end; // 指向分配的内存的末尾后面
ngx_pool_t *next; // 指向下一个内存池,把内存池组织成一个链表
ngx_uint_t failed; // 在当前内存池中分配失败的次数,以此为依据重置内存池的current
} ngx_pool_data_t;
// 大内存块
struct ngx_pool_large_s {
ngx_pool_large_t *next; // 指向下一个大内存块,把大内存块组织成一个链表
void *alloc; // 指向实际分配的内存块
};
// 内存回收器
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 回调函数
void *data; // 回调函数参数
ngx_pool_cleanup_t *next; // 指向下一个内存回收器,把内存回收器组织成一个链表
};
// 函调函数指针
typedef void (*ngx_pool_cleanup_pt)(void *data);
接下来是几个重要的API:
1、创建内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
p = ngx_alloc(size, log); // 分配指定尺寸的内存,并把头部格式化为ngx_pool_t
if (p == NULL) {
return NULL;
}
// 初始化头部结构
p->d.last = (u_char *) p + sizeof(ngx_pool_t); // 指向ngx_pool_t结构体后面
p->d.end = (u_char *) p + size; // 指向分配的内存末尾后面
p->d.next = NULL;
p->d.failed = 0;
size = size - sizeof(ngx_pool_t);
// 从内存池可分配的最大普通内存不能超过NGX_MAX_ALLOC_FROM_POOL和已分配的内存扣除头部的尺寸
// 如果需要分配超过这个尺寸的内存则另外分配一个大内存块
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、销毁内存池
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) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
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、重置内存池
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);
}
}
pool->large = NULL;
// 初始化所有普通内存(所有内存池的last重新指向ngx_pool_t结构体后面 )
for (p = pool; p; p = p->d.next) {
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
}
}
4、从内存池分配内存,并对齐
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
// 普通内存尺寸
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);
}
另外还有三个内存分配的API:
ngx_pnalloc,分配,不对齐
ngx_pcalloc,分配,对齐,初始化为0
ngx_pmemalign
5、释放大内存块
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;
}
6、添加内存回收器,创建一个内存回收器并增加到内存池的回收器链表上
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;
}
从内存池的实现可以看出,nginx的内存是预先分配小部分,后面根据需要再增加分配,并且可以重用。另外还可以看出nginx实现中的一个特点:数据成员先增加到数据容器中,然后再根据需要设置数据成员,例如上面添加回收器的过程,先把创建的回收器增加到回收器链表上,然后根据需要设置回收器的回调函数。后面分析基本数据结构的时候会经常遇到这种情况。