Nginx内存池源码剖析
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
能看出来Nginx小块内存和大块内存分界线就是一个页面4kx86
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)//能分配的最大内存
#define NGX_DEFAULT_POOL_SIZE (16 * 1024)//默认池的大小 16k
#define NGX_POOL_ALIGNMENT 16//字节对齐
#define NGX_MIN_POOL_SIZE //最小的大小 \
ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), \
NGX_POOL_ALIGNMENT)
进到ngx_align看到其实就是调整倍数和SGI STL的做法是一样的
他的数据成员和函数
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;
};
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};
typedef struct {
u_char *last;
u_char *end;
ngx_pool_t *next;
ngx_uint_t failed;//记录分配失败的次数<4后面写死了
} ngx_pool_data_t;
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 {
ngx_fd_t fd;
u_char *name;
ngx_log_t *log;
} ngx_pool_cleanup_file_t;
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);//创建内存池
void ngx_destroy_pool(ngx_pool_t *pool);
void ngx_reset_pool(ngx_pool_t *pool);
void *ngx_palloc(ngx_pool_t *pool, size_t size);//分配内存
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);
void ngx_pool_cleanup_file(void *data);
void ngx_pool_delete_file(void *data);
创建内存池Create
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);//根据用户指定size大小内存对齐不同平台
if (p == NULL) {
return NULL;
}
//初始化
p->d.last = (u_char *) p + sizeof(ngx_pool_t);//指向除ngx_pool_t结构的头信息之后的位置
p->d.end = (u_char *) p + size;//指向内存池的末尾
p->d.next = NULL;
p->d.failed = 0;
size = size - sizeof(ngx_pool_t);//内存池能使用的大小size-头部信息
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
//4095 开辟比一个页面小就用size比一个页面大就用一个页面
p->current = p;//指向内存起始地址
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;//日志
return p;
}
分配内存
当分配小块内存的时候如果剩的还够分配
static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
u_char *m;
ngx_pool_t *p;
p = pool->current;
do {
m = p->d.last;//可分配内存的地址
if (align) {
m = ngx_align_ptr(m, NGX_ALIGNMENT);//根据平台 调整倍数
}
if ((size_t) (p->d.end - m) >= size) {//剩余的大于要申请的size
p->d.last = m + size;//把m指针偏移个size字节
return m;
}
p = p->d.next;
} while (p);
return ngx_palloc_block(pool, size);
}
当剩的不够分配了进入 return ngx_palloc_block(pool, size);
我们看看ngx_palloc_block
第二块内存块之后的里面头信息就不用存maxlarge那些了只存用于分配内存的last next那些指针
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);//头只存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;
}
下面看看要是分配大块内存>4knginx怎么分配的
整理逻辑就是先malloc把内存分配出来然后遍历large链表找大块内存的头信息看看哪个里面是空的被free了如果找到了就把新分配的内存地址填进去就行但是遍历了三个还没找到那就不找了直接退出利用小块内存分配移动last指针把大小内存头信息分配出来然后把刚刚分配大块内存填进去就行。nginx处处体现了效率就看三下没有就用小块因为小块的效率高直接移动指针就行
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);//调malloc
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);//通过小块内存把large头分配进去
if (large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;//大块内存起始地址
large->next = pool->large;//头插 连接起来
pool->large = large;
return p;
}
Nginx对于大块内存提供了free函数
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;
}
对于小块内存没有提供释放函数
所以小块内存的释放只能用reset重置整个内存池nginx源码重置里面对于小块内存的处理有点浪费他全部按照第一块内存那样处理了但是第二块往后小块内存的头部信息都没有ngx_pool_s这个结构里的成员了所以更好的应该分别处理
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;
}*/ 这块nginx源码自己写的
//下面我改的处理小块内存
//处理第一个内存块
p=pool;
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
p->d.failed = 0;
//处理第二个开始到剩下的
for (p = p->d.next; p; p = p->d.next) {//小块
p->d.last = (u_char *) p + sizeof(ngx_pool_data_t);
p->d.failed = 0;
}
pool->current = pool;
pool->chain = NULL;
pool->large = NULL;
}
对于内存池外部资源的释放
在小块内存内存池创建个clean头信息开辟完之后把这一块内存起始地址返回回来
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);
}
}
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;
}
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;
}
整个释放流程先释放外部资源调用外部资源清楚函数回调再释放大块最后小块。因为小块里面会有前面的头信息