外部资源的释放
什么是外部资源?
若存在指针在该内存池中,指针指向堆区获得的空间(这块空间不包括在内存池中),如果直接对内存池清理,就会丢失外部资源的地址(因为指针被清理),导致内存泄漏。
示例:在内存池中的char*指针指向堆内存。如果释放p,那么地址丢失,内存泄漏
所以Nginx针对这种情况,预置一个资源释放的函数(通过回调函数,函数指针来实现)。
就是这个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; // 链表,相当于把外部资源头信息串起来
};
而且为了效率,这些头信息也和large信息一样,都是在小块内存池上分配的:
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)); // 1
if (c == NULL) {
return NULL;
}
if (size) {
c->data = ngx_palloc(p, size); // 2
if (c->data == NULL) {
return NULL;
}
} else {
c->data = NULL;
}
c->handler = NULL;
c->next = p->cleanup; // 3
p->cleanup = c;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
return c; // 4
}
- 使用ngx_palloc()开辟一个cleanup的头信息。
- 需要的内存大小通过malloc()开辟。
- 将该头信息串在链表上,从起始的cleanup开始。
- 返回头信息的起始位置。
ngx_destroy_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) { // 1
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) { // 2
if (l->alloc) {
ngx_free(l->alloc);
}
}
// 3
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_free(p);
if (n == NULL) {
break;
}
}
}
destroy的顺序
- 先遍历cleanup,释放每个外部资源data。
- 遍历大块内存,释放每个alloc。
- 再对小块内存池遍历,释放每个小块内存。