Nginx内存池__2018.06.13

  Nginx以高效,节省内存著称。到底如何高效,如何节省内存,这个得真正了解其设计原理才能知道,分析源码是了解其原理最直接的方法。Nginx对非常多的基础设施(红黑树 内存池 连接池 hash表)都重复造了轮子,我们来看看为什么要这么做。
  对于c系统,最难的常常是内存管理,随着系统复杂度的提高,各种内存问题都出来了,很难管理,对于系统的长期稳定运行构成影响。

所有的内存池都有一个共同的特点,那就是一开始就将申请大块内存,避免重复申请释放小区块造成内存碎片。

工作原理

    预先分配一大块内存,作为内存池,小块内存申请和释放时,从内存池中分配。大块内存另行分配
内存对齐:分配的内存块地址会进行内存对齐,提高IO效率

优点:
将大量小内存的申请聚集到一块,能够比malloc 更快
减少内存碎片,防止内存泄漏
减少内存管理复杂度
缺点:

造成内存空间浪费,以空间换时间

Src/core/ngx_palloc.h
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;//大块内存链表,分配空间超过max时使用
ngx_pool_cleanup_t *cleanup;//释放内存的callback
ngx_log_t *log;//日志信息
};
Src/core/ngx_palloc.h
typedef struct {
u_char *last;//已分配内存的末尾,下一次分配,从这里开始
u_char *end;//内存池结束位置
ngx_pool_t *next;//链表,指向下一块内存池
ngx_uint_t failed;//内存池分配失败次数
} ngx_pool_data_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next; //用链表组织,指向下一块较大内存
void *alloc;//实际内存地址
};
基本操作

Src/core/ngx_palloc.c
基本操作 函数头
创建内存池 ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log);
销毁内存池 void ngx_destroy_pool(ngx_pool_t *pool);
重置内存池 void ngx_reset_pool(ngx_pool_t *pool);
内存申请(对齐) void * ngx_palloc(ngx_pool_t *pool, size_t size);
void * ngx_palloc(ngx_pool_t *pool, size_t size); (清零)
内存申请(不对齐) void * ngx_pnalloc(ngx_pool_t *pool, size_t size);

内存释放 ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);


内存申请

内存申请ngx_palloc 如果分配较大内存,那么会调用ngx_palloc_large,否则在内存池中分配

分配较小内存后的内存池


或者


分配较大内存后的内存池


重置内存池

重置内存池ngx_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);
}
}
  pool->large = NULL;
  //重置所有较小内存块
for (p = pool; p; p = p->d.next) {
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
}

}

释放内存池

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;

}
销毁内存池

销毁内存池步骤
调用所有cleanup函数,清理数据
释放所有大块内存
释放所有内存池中的内存块
值得关注的是cleanup函数

为什么要有cleanup回调函数? 因为我们在释放内存的时候,常常伴随需要其他的释放操作,比如释放文件句柄,关闭网络连接等。这些需要在释放内存之前完成。

struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; //回调函数指针
void *data;//执行回调函数时,传入的数据
ngx_pool_cleanup_t *next;//下一个回调函数结构体
};
注册cleanup
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;

}

(1)内存池数据结构

在nginx的内存池结构有一个内存池头部,该头部又包含一个数据部。头部(除数据部)主要用来为用户分配大块内存(通过链表)、管理外部资源、日志信息以及内存池的一些其它信息。数据部主要用来为用户分配小块内存(通过移动内存池起始指针),以及链接到下一个内存池的指针。

注意,在nginx中内存池不止一个,而且内存池的大小一开始就固定下来了,所以当内存池中容量不够时,就要重新开辟一个内存池。

(3)内存池中内存分配(考虑内存对齐)
在内存池中分配内存时有三种情况(后面会分别详细说明):
1、申请的是小块内存,内存池中空间足够。
2、申请的是小块内存,内存池中空间不够。
3、申请的是大块内存。

(4)内存池中内存释放

对于nginx的内存池,小块内存申请后是不释放的,释放大块内存时,先通过要释放区域的地址在大块内存的管理链表中找到相应的节点将其释放掉。注意,这里只释放大块的内存,并不会释放其对应的头部结构,头部结构会做下一次申请大内存之用。

(5)外部资源管理

nginx内存池还通过回调函数对外部资源的清理。ngx_pool_cleanup_t是外部资源管理的结构体节点,内存池中有一个链表用来链接这些节点。我们可以通过ngx_pool_cleanup_add获取一个新的节点的指针,然后通过该指针来设置要管理的外部资源地址以及清理这些资源要用到的方法(回调函数)。

(6)销毁内存池
销毁内存池时主要是对外部资源进行清理,以及对大块数据内存进行清理。

二、nginx内存池 vs STL内存池
大数据块:nginx和STL一样,对于大的数据区块都是直接在堆中分配空间,只不过nginx将这些大的数据区块的链接起来进行管理,而STL中则是由申请数据块的程序自己负责管理。
小数据块:nginx和STL一样,都要对申请小的数据区块值上调到某个值的倍数,方便内存对齐。在nginx中是直接在内存池的那块连续空间中进行分配,而STL中内存池中的连续空间只能为空闲链表提供数据块,然后根据申请的数据块的大小找到相应的空闲链表取出相应数据块。
nginx内存池的独特之处:能够对外部资源进行管理、能够记录日志。

猜你喜欢

转载自blog.csdn.net/weixin_40316053/article/details/80684549