1. 内存池结构定义
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; //大块内存链表. 该链表中的元素申请内存大于max
ngx_pool_cleanup_t *cleanup; //用于释放内存的结构. 这个结构接触到就知道干嘛了.
ngx_log_t *log; //日志信息对象
};
typedef struct {
u_char *last; //如果需要分配内存出去, 那么就是从last开始分配
u_char *end; //整个内存块的结束位置
ngx_pool_t *next; //链接下一个内存块
ngx_uint_t failed; //内存分配失败的次数, 一定次数后就不再考虑向这块内存申请内存
} ngx_pool_data_t;
这两个结构体涵盖了Nginx内存池的大部分内容.
在我64位机器上 sizeof(ngx_pool_data_t) = 32; sizeof(struct ngx_pool_s) = 80;
我这里画的简陋, 又不好盗图, 主要参见此博客:http://blog.csdn.net/livelylittlefish/article/details/6586946
2. 内存池的一些操作
1、首先
创建内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
//利用memalign创建一段size大小的内存段.函数memalign将分配一个由size指定大小,地址是boundary的倍数的内存块。
//参数boundary必须是2的幂!函数memalign可以分配较大的内存块,并且可以为返回的地址指定粒度。
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大小需要减去为ngx_pool_t分配的部分
size = size - sizeof(ngx_pool_t);
//每块内存都有一个最大值
//#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1) . 一般为4095
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;
}
在这个创建内存池的函数中, 我们会发现一些陌生的被初始化的变量. 一些经过上面的注释后很容易理解, 一些却要通过更详细的解释才能明白.
ngx_create_pool函数最后的变量初始化中, current表示这个内存池当前指向的内存块. 因为现在只有一个块, 且这个块就在内存池头部里, 所以直接指向自身.
large在上面注释中是指大块内存链表, 大块是指申请的size大于内存池头部里的max变量从而导致申请的内存块, 通过这种方式申请的内存快都会被连在这里
cleanup会在之后的函数中提到.
2、接着
在已有内存池的前提下进行的内存申请动作
按照上面讲的关于large的变量来理解
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
//如果申请的size符合条件(小于max)
if (size <= pool->max) {
p = pool->current;
do {
//下面的ngx_align_ptr是一个宏, 用于将地址对齐, 这样对这段内存进行操作的时候, 可以减少CPU访问内存次数
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
//如果有足够的空间, 就返回这段空间的首地址, 记得调整last指针
if ((size_t) (p->d.end - m) >= size) {
p->d.last = m + size;
return m;
}
//否则, 一块一块内存的继续找
p = p->d.next;
} while (p);
//如果仍然没有找到我们希望的大小的内存, 那么此刻只能重新申请一个内存块来解决这个问题
return ngx_palloc_block(pool, size);
}
//申请的size大于我们指定的max, 此时就需要申请大内存了
return ngx_palloc_large(pool, size);
}
假如当初我们创建的第一块内存的大小为1024, 因为内存池头部占去80字节, 现在又因需求而申请200字节大小的内存. 内存池的现状如下所示.
//根据上面的函数, 下面给出申请新的内存块的函数
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;
//这里需要注意一下, 新申请的内存块, 虽然是通过ngx_pool_t指向的, 但是我们可以在这段添加内存快的函数中发现,
//并没有使用到ngx_pool_t中除了ngx_pool_data_t之外的内容, 且下面可以看到, m是从ngx_pool_data_t之后的地址开始的
//所以新增的内存块一般都比第一块内存块大一些
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size;
//下面的循环一个目的是找到内存块链表的最后一块
//另一个目的是查找是否有失败次数超过一定次数的内存块.一般分配新内存块的原因都是因为之前的内存块分配失败
// 所以下面会让链表中的每个内存快失败次数加一.
//最后一个目的是, 让pool->current修改为失败次数达到要求的下一个内存块, 这样一来, 不用每次都从头开始找
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;
}
申请过了一块200大小的内存后, 又有需求800大小的内存, 满足小于max的限制, 但是显然第一块内存已经无法分配出这么大一块的内存了, 所以此时需要重新申请一块同样大小的内存块用来分配
//之后, 就是关于大内存块的部分
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
//下面的ngx_alloc函数, 是对malloc的封装, 直接分配size大小的内存
p = ngx_alloc(size, pool->log);
if (p == NULL) {
return NULL;
}
n = 0;
//从pool中large指向的内存开始找, 看是否有挂着的结构体, 但是其结构体中的数据是空的的结构体. (因为释放产生的)
//有就直接把这段内存挂上去
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
//找3次后就不再继续找
if (n++ > 3) {
break;
}
}
//而是直接分配一个结构体
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
//且把该结构体挂在large头部
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
大块内存的数据结构如下所示:
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s{
ngx_pool_large_t *next; //指向下一块大块内存
void *alloc; //指向分配的大块内存
};
3、最后
内存池的后续操作, 比如内存池重置
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);
}
}
//然后将last指针重置, 所以要注意的是, 所有小内存是不释放的, 只是简单的指针重置
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;
}
比如内存池的清理, 删除掉某一块大内存
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;
}
这样看来, Nginx内存池中大内存块和小内存块的分配与释放是不一样的。我们在使用内存池时,可以使用ngx_palloc进行分配。使用ngx_pfree释放, 对于大内存,这样做是没有问题的,而对于小内存就不一样了,分配的小内存,不会进行释放。因为大内存块的分配只对前3个内存块进行检查,否则就直接分配内存,所以大内存块的释放必须及时。
4. Nginx对除了内存之外的资源的清理动作
首先, 看看这个数据的结构:
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; //对应这个资源的回收方式
void *data; //这个资源指向的指针
ngx_pool_cleanup_t *next; //下一个要回收的资源
};
对应的处理:
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;
}
}
}
}