您的位置:首页 > 运维架构 > Nginx

Nginx学习笔记(二十):内存池分析

2014-04-22 15:27 405 查看

前言

Nginx事件模块中在分配存储配置项参数的结构体时需要从内存池中分配内存,然后看代码的时候觉得这一块可以好好研究下。网上一搜果然是有大量的文章,所以就学习笔记下。这里推荐阿里数据平台的博文《Nginx源码分析-内存池》

Nginx内存池的设计也是蛮漂亮的,大块内存以及小块内存都有考虑到,这里很需要学习其思想,以后非常有可能会用到。

内存池创建

对于内存池的结构,我们可以暂且将其分为头部以及数据域两部分,代码如下:

<span style="font-family:SimSun;font-size:18px;">struct ngx_pool_s {
    ngx_pool_data_t       d;        // 数据域部分,小块内存在此分配
    size_t                max;      // 整个数据块的大小,亦即能够分配的小块内存最大值
    ngx_pool_t           *current;  // 指向当前内存池
    ngx_chain_t          *chain;    // 可以挂载一个chain结构
    ngx_pool_large_t     *large;    // 当分配的内存超过max值时,即分配大块内存时,使用该成员,属于数据域部分
    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;</span>
内存池创建的代码如下:
<span style="font-family:SimSun;font-size:18px;">ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

	/* 创建数据域大小为size的内存池,地址以NGX_POOL_ALIGNMENT对齐,这里是16位 */
    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;
}</span>
按照上面的描述,可以简单用图示意下:



内存池分配

nginx提供给用户使用的内存分配接口有:
<span style="font-family:SimSun;font-size:18px;">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);</span>
ngx_palloc和ngx_pnalloc都是从内存池里分配size大小内存,至于分得的是小块内存还是大块内存,将取决于size的大小;他们的不同之处在于,palloc取得的内存是对齐的,pnalloc则否。ngx_pcalloc是直接调用palloc分配好内存,然后进行一次初始化操作。ngx_pmemalign将在分配size大小的内存并按alignment对齐,然后挂到large字段下,当做大块内存处理。

以ngx_palloc为例详细说明下:
<span style="font-family:SimSun;font-size:18px;">void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;

	/* 申请分配小内存块 */
    if (size <= pool->max) {

        p = pool->current;

        do {
        	/* 内存对齐宏,下面有注释 */
            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);

			/* 如果小内存块的数据域大于申请内存的size大小,就直接分配;
			 * 否则,通过next指针寻找下一个内存池,直到找到能够分配size大小内存的内存池为止
			 */
            if ((size_t) (p->d.end - m) >= size) {
                p->d.last = m + size;

                return m;
            }

            p = p->d.next;

        } while (p);

		/* 如果不存在能够分配size大小的内存池,则申请一个新的内存池,并分配。
		 * failed字段在该函数里增加,一个内存池当failed字段大于4之后就将current字段指向
		 * 下一个内存池,亦即当failed字段大于4之后意味着该内存池已经分配完毕。至于为什么大小
		 * 是4,可能就像网站说的,是基于统计或者测试的经验吧。。
		 */
        return ngx_palloc_block(pool, size);
    }

	/* 申请分配大的内存块 */
    return ngx_palloc_large(pool, size);
}</span>

关于内存对齐使用的宏注释
<span style="font-family:SimSun;font-size:18px;">#define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))
#define ngx_align_ptr(p, a)                                                   \
    (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
</span>
以ngx_align(d, a)为例,这里a是2的幂的数,如2,4,8...

以a=8为例,用二进制表示就是a = 0x1000;(a-1) = 0x00...00111,再取反后为 0x11...11000;

当d=0~8时,0 =< d+(a-1) <= 15,该数与[~(a-1)]按位与,取高位,结果为0x1000,十进制为8;

当d=9~16时,0 =< d+(a-1) <= 23,该数与[~(a-1)]按位与,取高位,结果为0x10000,十进制为16;

当d=17~24时,0 =< d+(a-1) <= 31,该数与[~(a-1)]按位与,取高位,结果为0x11000,十进制为24;

......

亦即,最后取值均为8的倍数,完成对齐的功能。

推而广之,一个数加上(a-1)后与a(2的幂)进行(a减一后取反再按位与)的操作,所得值即为这个数向上取a的整倍数。

具体的内存池分配示意图:



内存池重置

<span style="font-family:SimSun;font-size:18px;">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);
        }
    }

    pool->large = NULL;

	/* 只是将last指针指向可共分配的内存的初始位置。
	 * 这样,就省去了内存池的释放和重新分配操作,而达到重置内存池的目的
	 */
    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    }
}</span>

内存池释放

<span style="font-family:SimSun;font-size:18px;">void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

	/* 先释放cleanup上挂载的数据资源,调用cleanup自身的handler处理方法 */
    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);
        }
    }

	/* 然后释放大内存块 */
    for (l = pool->large; l; l = l->next) {

        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);

        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;
        }
    }
}</span>

总结

Nginx内存的设计初探很精妙,然后学习进去发现逻辑很清晰。这种设计思想要有印象,以后遇到类似内存的设计问题可以提供备选思路。

主要参考

网上资料
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: