Nginx源码剖析之内存池和内存管理(2)

接下来,咱们来看内存池的销毁函数,pool指向需要销毁的内存池

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);
}
}
//前面讲到,cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等,
//清理函数是一个handler的函数指针挂载。因此,在这部分,对内存池中的析构函数遍历调用。

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);
}
}
//这一部分用于清理大块内存,ngx_free实际上就是标准的free函数,
//即大内存块就是通过malloc和free操作进行管理的。

#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;
}
}
}
//该片段彻底销毁内存池本身。

该函数将遍历内存池链表,所有释放内存,如果注册了clenup(也是一个链表结构),亦将遍历该cleanup链表结构依次调用clenup的handler清理。同时,还将遍历large链表,释放大块内存。 

2.3、重置内存池

void ngx_reset_pool(ngx_pool_t *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);
}
}

这里虽然重置了内存池,但可以看到并没有释放内存池中被使用的小块内存,而只是将其last指针指向可共分配的内存的初始位置。这样,就省去了内存池的释放和重新分配操作,而达到重置内存池的目的。上面我们主要阐述了内存池管理的几个函数,接下来我们深入到如何从内存池中去申请使用内存。

2.4、分配内存(重点)2.4.1、ngx_palloc 与ngx_pnalloc函数
    这两个函数的参数都为(ngx_pool_t *pool, size_t size),且返回类型为void*,唯一的区别是ngx_palloc从pool内存池分配以NGX_ALIGNMENT对齐的内存,而ngx_pnalloc分配适合size大小的内存,不考虑内存对齐。
    我们在这里只分析ngx_palloc,对于ngx_pnalloc其实现方式基本类似,便不再赘述。
文件:src/core/ngx_palloc.c
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;

//判断待分配内存与max值
//1、小于max值,则从current结点开始遍历pool链表
if (size <= pool->max) {

p = pool->current;

do {
//执行对齐操作,
//即以last开始,计算以NGX_ALIGNMENT对齐的偏移位置指针,
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);

//然后计算end值减去这个偏移指针位置的大小是否满足索要分配的size大小,
//如果满足,则移动last指针位置,并返回所分配到的内存地址的起始地址;
if ((size_t) (p->d.end - m) >= size) {
p->d.last = m + size;
//在该结点指向的内存块中分配size大小的内存

return m;
}

//如果不满足,则查找下一个链。
p = p->d.next;

} while (p);

//如果遍历完整个内存池链表均未找到合适大小的内存块供分配,则执行ngx_palloc_block()来分配。

//ngx_palloc_block()函数为该内存池再分配一个block,该block的大小为链表中前面每一个block大小的值。
//一个内存池是由多个block链接起来的。分配成功后,将该block链入该poll链的最后,
//同时,为所要分配的size大小的内存进行分配,并返回分配内存的起始地址。
return ngx_palloc_block(pool, size); //2.4.1节分析

}
//2、如果大于max值,则执行大块内存分配的函数ngx_palloc_large,在large链表里分配内存
return ngx_palloc_large(pool, size); //2.4.2节分析
}

例如,在2.1节中创建的内存池中分配200B的内存,调用ngx_palloc(pool, 200)后,该内存池物理结构如下图:

Nginx源码剖析之内存池和内存管理

a、待分配内存小于max值的情况

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/10920.html