在src/core/ngx_palloc.h中定义了内存池相关的结构体
回收内存相关的
//内存池回收结构体
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler;
void *data;
ngx_pool_cleanup_t *next;
};
//大块数据分配结构体
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;//记录内存分配不能满足需求的次数
} 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;
乍一看这些结构体的关系有点混乱啊,别急,咱来看一段内存池创建的代码
src/core/ngx_palloc.c文件中
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;
}
创建后结果如下图所示:
看完了创建内存池的代码,再来看看nginx是如何从内存池中分配所需要的内存的吧,代码如下:
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
/*首先检查pool池中可供分配的空闲地址是否比size小,若是,说明所要分配的空间需要从large pool中分配,
从而转向ngx_palloc_large(pool, size)。若否,则从pool池中分配
*/
if (size <= pool->max) {
p = pool->current;
/*直到找到可分配出size大小的pool为止,并将分配的首地址m返回
*/
do {
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
if ((size_t) (p->d.end - m) >= size) {
p->d.last = m + size;
return m;
}
p = p->d.next;
} while (p);
/*如果所有的Pool都没有可供分配的size大小空间,则新创建一个pool,并将该pool中分配空间的首地址返回。
*/
return ngx_palloc_block(pool, size);
}
return ngx_palloc_large(pool, size);
}
pool是内存池的首地址,size是所需分配的内存大小。看完了代码,是不是对pool与pool之间的关系比较模糊,别着急,Pool与pool之间的关系是这样子滴:
pool链表间通过d结构体中的next相链接(红线部分),而pool->current则指向目前可参与分配内存的起始pool(蓝线),貌似只有第一个Pool的curernt有指向相应的pool,其他的pool好像在create时并未赋值,具体怎样还要再读代码,这个疑问留待以后解决吧。
新创建Pool并从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, *current;
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;
current = pool->current;
/*将pool链表中从current所指向的pool开始到链表中的最后一个pool的failed值+1,代表该pool分配失败的次数,如果该次数大于4,则current指向其下一个pool节点。
*/
for (p = current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
current = p->d.next;
}
}
//将新Pool节点插入链表末尾
p->d.next = new;
pool->current = current ? current : 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;
//首先分配所申请的size空间,这里用的就是malloc动态分配,并非从pool中分配size空间,因为size>pool->max嘛,pool中的空///间是不够分配滴
p = ngx_alloc(size, pool->log);
if (p == NULL) {
return NULL;
}
n = 0;
//试图将p挂在原来已分配的large但large的alloc所指向的数据空间已被释放的地方。
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
//只检查large链表的前三个节点,若前三个节点不可挂p,则跳出循环
if (n++ > 3) {
break;
}
}
//从pool中分配large指针大小的空间(8字节)
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;
//将新分配的large节点放在链表的表头,next指向原来的表头节点,即从头插入新节点。
large->next = pool->large;
pool->large = large;
return p;
}
第一次申请large空间的结果如下图所示:
第二次申请large空间的结果为:
为何要把large节点插入为表头的位置呢,个人推理该方法避免了没插入一个新节点必须遍历真个large链表的不必要开销。
以下为加入将新的cleanup节点加入cleanup链表的代码:
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
ngx_pool_cleanup_t *c;
//先从pool池中分配cleanup空间
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
if (c == NULL) {
return NULL;
}
//从pool池中分配size大小的数据空间
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;
}
第一次加入cleanup的结果如下图所示:
nginx内存管理方面的核心代码已经解析完毕了,若有分析错误的地方望指正。