最近学习nginx,今天看了下有关内存池方面的东东,记录下来作为总结学习,不正确或者片面之处后续再修改。
内存池作用:
统一建立内存池,当需要进行内存分配的时候统一通过内存池中的内存进行分配,最后nginx会在适当的时候释放内存池的资源,开发者只要在需要 的时候对内存进行申请即可,不用过多考虑内存的释放等问题,大大提高了开发的效率。
内存池数据结构:
//管理和维护整个内存池头部信息,一个内存池只有一个。
struct ngx_pool_t {
ngx_pool_data_t d; //内存池的数据块
size_t max; //内存池数据块的最大值
ngx_pool_t *current; //指向当前可分配的内存池
ngx_chain_t *chain; //该指针挂接一个ngx_chain_t结构
ngx_pool_large_t *large; //指向大块内存分配(即超过max的内存请求),nginx中,大块内存分配直接采用标准系统接口malloc
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;//统计该内存池不能满足分配请求的次数,即分配失败次数
} ngx_pool_data_t;//内存池的数据块位置信息
其中,<span lang="zh-CN" style="font-family:'Lucida Console';">sizeof(ngx_pool_data_t)</span><span lang="en-US" style="font-family:'Lucida Console';">=16B</span><span lang="zh-CN" style="font-family:宋体;">,</span><span lang="zh-CN" style="font-family:'Lucida Console';">sizeof(ngx_pool_t)</span><span lang="en-US" style="font-family:'Lucida Console';">=40B</span><span lang="zh-CN" style="font-family:宋体;"></span>。
//大内存结构
struct ngx_pool_large_s {
ngx_pool_large_t *next; //下一个大块内存
void *alloc;//指向分配的大块内存
};
超过max的内存请求
以上结构逻辑关系如下:
内存池相关操作:
1)创建
//创建内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
// ngx_memalign 返回值为void*,void*可以执指向任何类型的数据
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); // 内存分配,该函数的实现在src/os/unix/ngx_alloc.c文件中,uinx,windows分开走
if (p == NULL) {
return NULL;
}
p->d.last = (u_char *) p + sizeof(ngx_pool_t); //last指向ngx_pool_t结构体之后数据的起始
p->d.end = (u_char *) p + size; //end指向分配的整个size大小的内存的末尾
p->d.next = NULL;
p->d.failed = 0;
//max中存放的数指所申请内存块中空闲的大小,因此,在计算max之前先减去了管理结点本身的大小。
size = size - sizeof(ngx_pool_t);
//最大不超过 NGX_MAX_ALLOC_FROM_POOL,也就是getpagesize()-1 大小,即4095B
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_create_pool有如下三步:
第一步,调用ngx_memalign()申请内存;
第二步,设置ngx_pool_t中的成员d(即类型ngx_pool_data_t)中的各个变量;
…
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;
d.last则指向未使用内存的开始处,而d.end指向内存块的结尾处。刚申请的内存中占用ngx_pool_t结构体作为管理单元。所以,此时d.last指向
(u_char *) p + sizeof(ngx_pool_t)处。
第三步,设置ngx_pool_t 除d变量的其它成员;
...
p->max =...
p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
物理机构图如下:
其中,ngx_memalign(NGX_POOL_ALIGNMENT, size, log)函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h #defineNGX_POOL_ALIGNMENT 16 因此,nginx的内存池分配,是以16字节为边界对齐的。
2)销毁
void
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
// 遍历 cleanup链表结构依次调用clenup的handler清理
//cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等.
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);
}
}
//清理大块内存,ngx_free实际上就是标准的free函数
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);
}
}
#if (NGX_DEBUG)
/**
* we could allocate the pool->log from this pool
* so we can not use this log while the free()ing the pool
*/
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;
}
}
//只有debug模式才会执行这个片段的代码,主要是log记录,用以跟踪函数销毁时日志记录。
#endif
//释放小内存块
for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {
ngx_free(p);
if (n == NULL) {
break;
}
}
}
ngx_destroy_pool() 释放内存池,一共分三步:
第一步、在释放前先对业务逻辑进行释放前的处理
for (c = pool->cleanup; c; c = c->next) {
...
}
第二步、释放large占用的内存(大内存块链表)
for (l = pool->large; l; l = l->next) {
....
}
第三步、释放所有的池子(即小内存块链表)
for (p = pool, n = pool->next; /* void */; p = n, n = n->next) {
...
}
3)内存申请
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
if (size <= pool->max) { //判断待分配内存与max值
p = pool->current; //小于max值,则从current节点开始遍历pool链表
do {
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); // 对齐内存指针,加快存取速度
if ((size_t) (p->d.end - m) >= size) { //找到合适内存块
p->d.last = m + size; //在该节点指向的内存块中分配size大小的内存
return m;
}
//根据内存池的相连,定义在内存池数据块中;如果不满足,则查找下一个节点
p = p->d.next;
} while (p);
//链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存
return ngx_palloc_block(pool, size);
}
//大于max值,则在large链表里分配内存
return ngx_palloc_large(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, *current;
psize = (size_t) (pool->d.end - (u_char *) pool); //计算pool的大小
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); //分配一块与pool大小相同的内存
if (m == NULL) {
return NULL;
}
new = (ngx_pool_t *) m;
new->d.end = m + psize; //设置end指针
new->d.next = NULL;
new->d.failed = 0;
m += sizeof(ngx_pool_data_t); //让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置
m = ngx_align_ptr(m, NGX_ALIGNMENT); //按NGX_ALIGNMENT字节对齐
new->d.last = m + size; //在数据区分配size大小的内存并设置last指针
current = pool->current;
//更新current
for (p = current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) { //failed的值只在此处被修改
current = p->d.next; //失败6次以上移动current指针
}
}
//将分配的block链入内存池
p->d.next = new; //尾插法
//如果直到最后节点failed计数>=6次,则current指向新分配的内存池节点block
pool->current = current ? current : new;
return m;
}
创建的内存池中分配200B的内存,调用ngx_palloc(pool, 200)后,该内存池物理结构如下图:
//链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存
//大块内存的申请
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);
if (p == NULL) {
return NULL;
}
n = 0;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;//把新分配的内存块设置在其空出的large的alloc指针字段下
return p;
}
if (n++ > 3) {//尝试5次仍未找到已释放大内存块后空出的ngx_pool_large_t头结构体
break;
}
}
// 重新分配ngx_pool_large_t结构体
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
// 采用头插法插入新分配的大内存块
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
内存池申请内存调用的系统函数:
大数据块申请内存使用ngx_alloc==》malloc;
创建内存池或者小数据分配新的内存池块使用ngx_memalign==》posix_memalign或者memalign;