Nginx源码阅读1-内存池
首先我们来看一下他的一个基础组件:内存池组件。为什么先从内存池开始呢,因为后面 nginx
的内置数据结构,如:array,string 等都是从内存池分配的。
为什么需要内存池呢?在高并发的前提下,会大量地申请和释放小块的内存;虽然内核中也有相关的内存优化操作,但是还是容易出现大量地内存碎片,内存利用效率很低。如果我们一开始就申请一块大内存,自己对这一块大内存进行操作和管理,就可以高效的利用内存。
nginx
的内存池设计是十分巧妙的,我们一起来看一下,最好是后面手动实现一下。
数据结构
内存池结构体
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; // 存放大块数据的链表
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;
大数据块结构体
struct ngx_pool_large_s {
ngx_pool_large_t *next; // 指向下一个大数据块
void *alloc; // 大块数据
};
自定义清理函数结构体
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 清理函数
void *data; // 存储的数据
ngx_pool_cleanup_t *next; // 下一个自定义清理函数结构
};
结构图
相关函数
创建内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
// 申请一块内存池的内存
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
if (p == NULL) {
return NULL;
}
// 将可用内存的首部偏移到结构体头之后
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;
// 可用内存
size = size - sizeof(ngx_pool_t);
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;
}
销毁内存池
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;
}
}
}
重置内存池
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);
}
}
// 将可用指针指向结构体内存之后,重新开始写后面的内存
for (p = pool; p; p = p->d.next) {
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
p->d.failed = 0;
}
// 初始化
pool->current = pool;
pool->chain = NULL;
pool->large = NULL;
}
向内存池申请内存
void *
ngx_palloc(ngx_pool_t *pool, size_t size) // 有字节对齐
{
#if !(NGX_DEBUG_PALLOC)
if (size <= pool->max) {
return ngx_palloc_small(pool, size, 1);
}
#endif
// 如果大小超过max,就申请大内存块
return ngx_palloc_large(pool, size);
}
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size) // 无字节对齐
{
#if !(NGX_DEBUG_PALLOC)
if (size <= pool->max) {
return ngx_palloc_small(pool, size, 0);
}
#endif
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;
// 当前内存池的总可用大小
psize = (size_t) (pool->d.end - (u_char *) pool);
// 创建一个新内存池
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
if (m == NULL) {
return NULL;
}
new = (ngx_pool_t *) m;
// 末位置
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
// 初始位置要在需要的存储数据的内存之后
new->d.last = m + size;
// 加入链表
for (p = pool->current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
pool->current = p->d.next;
}
}
p->d.next = new;
return m;
}
开辟大内存块
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;
return p;
}
if (n++ > 3) {
break;
}
}
// 创建一个未使用的大内存块
large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
if (large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
释放大内存块
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;
}
添加自定义清理函数
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;
}
// 回调函数初始化为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;
}
删除链表上的 cleanup 块
void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
ngx_pool_cleanup_t *c;
ngx_pool_cleanup_file_t *cf;
for (c = p->cleanup; c; c = c->next) {
if (c->handler == ngx_pool_cleanup_file) {
cf = c->data;
if (cf->fd == fd) {
// 执行回调函数
c->handler(cf);
c->handler = NULL;
return;
}
}
}
}
这个 cleanup 块可以存储任何需要清理的东西,所以他就可以操作文件,内存设备等,给这个内存池提供了很大的灵活性。
删除文件回调函数
// 可以看到上面的函数有调用这个函数的if,如果调用这个,那都会被清理
void
ngx_pool_cleanup_file(void *data)
{
ngx_pool_cleanup_file_t *c = data;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
c->fd);
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " \"%s\" failed", c->name);
}
}
删除文件
void
ngx_pool_delete_file(void *data)
{
ngx_pool_cleanup_file_t *c = data;
ngx_err_t err;
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
c->fd, c->name);
// 删除文件
if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
err = ngx_errno;
if (err != NGX_ENOENT) {
ngx_log_error(NGX_LOG_CRIT, c->log, err,
ngx_delete_file_n " \"%s\" failed", c->name);
}
}
// 关闭文件
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " \"%s\" failed", c->name);
}
}