nginx的简单介绍
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等,下面,我们将对Nginx的内存池部分源码进行剖析。
nginx内存池简介?
nginx为每一个层级都会创建一个内存池,进行内存的管理,在对应的生命周期结束的时候会摧毁整个内存池,把分配的内存一次性归还给操作系统。
在分配的内存上,nginx有小块内存和大块内存的概念(下面的源码剖析中会介绍到),在释放内存的时候,nginx没有专门提供针对释放小块内存的函数,小块内存会在ngx_destory_pool 和 ngx_reset_pool的时候一并释放。
nginx内存池分配内存的大小内存块儿区分
if (size <= pool->max) {
return ngx_palloc_small(pool, size, 1);
}
当所申请的内存大小小于max时,采用小块儿内存分配机制,max<=4095。否则采用大块儿内存分配机制
ngx主要API
图:
由于上面的图是一个静态图,所以不能够完全体现出内存池分配时的动态,且听我下面细细道来。为了方便下面源代码的剖析,我们分别对不同的块儿进行了命名
nginx内存池之创建内存池
源代码:
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); //申请大小为size内存空间
if (p == NULL) {
return NULL;
}
p->d.last = (u_char *) p + sizeof(ngx_pool_t); //将last指向未使用内存的头部
p->d.end = (u_char *) p + size; //将end指针指向未使用内存的尾部
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;
}
创建内存池所用到的数据结构数据结构:
ngx_pool_t :
管理小块儿内存的分配
typedef struct {
u_char *last; //指向未使用内存的头部
u_char *end; //指向未使用内存的尾部
ngx_pool_t *next; //指向下一个内存快
ngx_uint_t failed; //此内存块儿分配小块内存时失败的次数
} ngx_pool_data_t;
为了方便使用,用typedef重命名
typedef struct ngx_pool_s ngx_pool_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; //nginx日志入口指针
};
内存池创建完成后,如上面图片中最右边的块儿1所示。
内存分配函数:
1、ngx_palloc(ngx_pool_t *pool, size_t size)
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);
}
2、ngx_pnalloc(ngx_pool_t *pool, size_t size)
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);
}
两个函数的区别:只是在分配小块儿内存上所传的参数有区别,palloc函数中nginx_palloc_small的第三个参数为1,而pnalloc的参数为0,意思是palloc函数分配内存得考虑内存对齐,而pnalloc函数分配内存时不用考虑内存对齐问题
我们就以palloc函数为例,来看看内存池是如何管理内存的:
小块儿内存的分配
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; //第一次分配内存时是从第内存块儿1进行分配的
do {
m = p->d.last;
if (align) {
m = ngx_align_ptr(m, NGX_ALIGNMENT); //内存对齐后m指向为分配内存的首部
}
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;
}
举个例子来说明一下小块儿内存的分配流程:
1、调用ngx_create_pool(124, log),将内存块儿的大小设置为124byte,那么块儿1剩余的大小小于124
2、第一次申请内存时调用palloc函数:palloc(pool,70),会调用ngx_palloc_small(pool,70)函数,直接在内存块儿1的第一个内存块进行分配。
3、当我们第二次再调用:palloc(pool,70),时会发现调用ngx_palloc_small(pool,70),内存块儿1中的第一个内存块儿不够用了,就会调用ngx_palloc_block函数进行内存块儿的申请,也就是申请内存块儿2,然后将其与内存池串联起来
4、当我们多次申请大小为70byte的内存时会发现每一次第一块儿内存都会分配失败,falid++,当加到4的时候就会将current指针指向faild<4的块儿1上,这也就是ngx_palloc_block函数中最后for循环的作用
以上就是小块儿内存分配的步骤。
大块儿内存的分配
通过内存池的large进行分配,通过块儿3对大块儿内存进行管理
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);//调用malloc函数进行内存分配
if (p == NULL) {
return NULL;
}
n = 0;
//第一次分配内存时不用进for循环,
多次分配和释放大块内存后,如果前三个块儿3有未挂载内存块儿的,就可以直接使用
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); //调用小块儿内存分配函数构造块儿3
if (large == NULL) {
ngx_free(p);
return NULL;
}
进行头插
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
所用到的数据结构
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next; 指向下一个内存管理块儿
void *alloc; 指向大块儿内存
};
大块儿内存的释放
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;
}
上面代码也就是将大块儿内存管理的某一块儿所管理的内存归还。
举例说明:(内存池还是上面申请的内存池,块儿的大小为70)
1、调用palloc(100,log),申请大小为100byte的内存块儿
2、内存块大小大于小块内存的max,通过内存池的large作为头指针的链表对大块儿内存进行管理,也就是通过图中的块儿3进行管理
3、大块内存的释放也就是将链表节点对应的内存块释放掉注意:这里管理大块儿内存的内存都是通过申请小块儿内存而来的,nginx内存池只有对大块儿内存的回收,没有对小块儿内存的回收,这是与其的使用场景有很大关系的,nginx的本质是是一个http服务器, 是一个短连接的服务器,发起一个request请求,到达nginx服务器以后,处理完成,nginx给客户端返回一个request响应,http就主动断开(http1.1 kepp_alive 60s),http返回响应以后,需要等待60s,60s之内客户端又发来请求,重置这个时间
,否则调用nginx_reset_pool重置内存池(下面会讲到),等待下一次客户端的请求。
内存池的重置函数
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;
}
此函数比较简单,就不再说详细说明,首先,先将大块内存释放,在将小块儿内存收回
内存池清理函数
我们在申请内存的时候,对应的可能会占有外部资源,比如文件或者malloc出来的外部资源,在destroy的时候,得将外部资源释放,所以在申请内存之前,可以写好对应的资源释放函数,同或函数指针添加到链表中,通过回调的方式释放资源
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;
}
destory函数
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);
}
}//便利clean为头节点的链表,释放对应的资源
#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
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;
}
}
}
以上就是nginx内存池源码的剖析,如有错误,可留言