ngx_destroy_pool函数
先执行回调函数释放所有的外部资源,然后free释放所有的大块内存和小块内存。
// 释放外部资源,销毁内存池
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);
}
}
#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在处理一个HTTP请求时需要打开一个文件,并读取其中的内容。这个文件描述符就是一个外部资源,因为它不是由Nginx内存池直接管理的。Nginx可能会在内存池中分配一个结构体来存储与这个请求相关的状态信息,这个结构体中可能包含一个指向文件描述符的指针。
当请求处理完毕,Nginx准备释放与这个请求相关的内存池时,如果仅仅释放内存池中的内存块,那么文件描述符并不会被自动关闭,这就会导致资源泄漏。为了避免这种情况,Nginx可以在创建内存池时注册一个回调函数,这个回调函数的职责就是在内存池被销毁时关闭文件描述符。
若有一个如下图的结构体,其使用Nginx内存池存放在小块内存中,即指针p和其他成员在内存池中,但是p指向了一个用户自己开辟的内存地址并存储了“hello world”。这样在内存池销毁时,释放了存储变量stData的内存,指针p被释放,但是其指向的内存地址并未释放,导致了内存泄漏。
想要释放诸如此类的外部资源,需要对外部资源预置一个对应的资源释放函数,通过回调函数及函数指针来实现。
保存在内存池头信息中的 cleanup 链表
链表的结构体如下:
typedef void (*ngx_pool_cleanup_pt)(void *data); // 回调函数 函数指针
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t; // 预置一个资源释放的函数
struct ngx_pool_cleanup_s { // 本清理信息,也会分配在小块内存的内存池上
ngx_pool_cleanup_pt handler; // 回调函数 函数指针 保存预先设置的回调函数
void *data; // 资源地址
ngx_pool_cleanup_t *next; // 很多释放资源的动作,做成链表
};
存放示意图:
注:上图中的cleanup头信息应和大块内存头信息一样,都存放在小块内存池中。
回调函数链表使用ngx_pool_cleanup_add函数进行创建和插入。
ngx_pool_cleanup_add函数
代码及注释:
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)); // cleanup头信息放到小块内存池中
if (c == NULL) {
return NULL;
}
if (size) { // size是传递给回调函数的参数的大小
c->data = ngx_palloc(p, size); // 释放内存,通过用户手动赋值
if (c->data == NULL) {
return NULL;
}
} else { // 回调函数不要参数
c->data = NULL;
}
c->handler = NULL; // handler通过用户手动赋值
c->next = p->cleanup; // 新的清除信息节点,头插法串到cleanup节点上
p->cleanup = c;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
return c;
}
cleanup使用示例:
在destroy函数中, 通过函数指针调用release,并传入参数:
总结:
destroy的释放顺序:
1、通过回调函数链表,释放外部资源
2、释放大块内存
3、释放小块内存