Nginx内存池源码分析

Nginx是轻量级web服务器/反向代理服务器及电子邮件代理服务器。特点是占有内存少,并发能力强。现在主要分析其内存池的实现的原理:


Nginx内存池

内存池结构有一个的头部,其中包含一个数据部,头部(除数据部)主要是用来为用户分配大块内存(通过链表来实现)、管理外部资源、日志信息等。而数据部而用于分配小块内存,和指向下一个内存池。

头部信息结构为:

struct ngx_pool_s {
    ngx_pool_data_t       d;//指向数据部
    size_t                max;//内存池小块的内存最大值
    ngx_pool_t           *current;//指向当前内存池
    ngx_chain_t          *chain;//挂接一个ngx_chain_t结构体
    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;

两个结构构成的结构的模型图:
在这里插入图片描述


内存池的创建 :ngx_create_pool

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);
//底层封装  ngx_alloc(size, log);实际上也是封装了malloc为内存池开辟size大小空间

/*
	void *
	ngx_alloc(size_t size, ngx_log_t *log)
	{
		void  *p;

	p = malloc(size);//实际上也是使用malloc开辟内存
	if (p == NULL) {
		ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
					  "malloc(%uz) failed", size);
	}

	ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);

	return p;
}

*/

if (p == NULL) {
    return NULL;
}

//将新建的指向内存池的数据部进行初始化
p->d.last = (u_char *) p + sizeof(ngx_pool_t);//last指向内存池分配内存的尾部
p->d.end = (u_char *) p + size;//指向小块内存块的尾部
p->d.next = NULL;//指向下一个小块
p->d.failed = 0;//小块内存分配失败的次数

size = size - sizeof(ngx_pool_t);//减去头部为小块内存的大小   ngx-pool_t 是ngx_pool_s别名
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
//#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)  这是一个宏定义,ngx_pagesize 是4k大小,一个页大小,给小块内存最大只能是一块页大小的内存

p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
   //初始化其它成员
    return p;
}

由此可以看出,内存池的创建实际上就是,开辟内存池的内存和内存池头部数据的初始化,且,一旦内存池创建完成了,就不能修改内存池的大小,需要拓展的话,需要在创建一个新的内存池。且内存池的实现离不开Glibc的malloc函数,就离不开ptmalloc的内存管理机制。


内存申请:ngx_palloc

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);//小块内存分配,传1表示需要内存对齐
    }
#endif

    return ngx_palloc_large(pool, size);//大块内存分配
}
//可以看出来,内存池的分配有两种,当所需要的内存大小大于max的时候,就就进行大块内存的分配,如果小于等于的话,就进行小块内存的分配

//ngx_pnalloc函数不需要内存对齐
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);//传0表示需要内存对齐
    }
#endif

    return ngx_palloc_large(pool, size);
}

内存分配的方式有两种,分别是大块内存和小块内存,分配的方式的采用是通过所需分配的内存大小来决定,且还为是否需要内存对齐,给出了两个函数。

ngx_pcalloc函数是将开辟的内存并初始化

void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;

    p = ngx_palloc(pool, size);
    if (p) {
        ngx_memzero(p, size);//对内存初始化为0
        //这是一个宏函数   #define ngx_memzero(buf, n)     (void) memset(buf, 0, n)
    }

    return p;
}

小块内存的分配: ngx_palloc_small

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);//宏函数,进行内存对齐
        }
/*
#define ngx_align_ptr(p, a)                                                   \
			(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

	这是一个用来内存地址取整的宏,非常精巧,一句话就搞定了。作用不言而喻,取整可以降低CPU读取内存的次数,提高性能。

*/
        if ((size_t) (p->d.end - m) >= size) {
            p->d.last = m + size;

            return m;
        }//如果剩余的内存足够的话,就直接将last指针往下移动size大小。

        p = p->d.next;//否则就前往下一块内存

    } while (p);//当p不为空的时候进行

    return ngx_palloc_block(pool, size);//没有找到有剩余空间的块,就在开辟一个新块,
}

有此可知,小块内存的分配是在内存池结构的里面进行的,通过移动last指针来分配空间,如果没有空间或者是空间不足的话,就在开辟新块。


分配新块:ngx_palloc_block

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指向数据部的尾部
    m = ngx_align_ptr(m, NGX_ALIGNMENT);//进行内存对齐
    new->d.last = m + size;//last指向当前内存的尾部
//每一个p的failed都要+1,因为这些块的内存都不够分配,都失败了一次
    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;  
            //当分配内存失败超过5次后,改变current的指向,就不要每次都从第一个块开始遍历
        }
    }
    

    p->d.next = new;

    return m;
}

分配的内存池块是和第一块一样的大的,然后放在内存池链表的后面。


分配大块内存 :ngx_palloc_large

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);//分配size大小的内存
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }//找到空闲的large块,将新开辟的内存交给它

        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;//内存域指向p
    large->next = pool->large;
    pool->large = large;
    //采用头插法
    return p;
}

大块内存的分配是需要开辟内存的通过malloc在堆上开辟,先是寻找空闲的块,然后将新开辟的内存给它,如果三次没有找到,就使用小块内存分配的方式,来开辟内存。然后使用头插法插入到大块链表中。


大致的内存分配图:
在这里插入图片描述


内存重置:ngx_reset_pool

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);//ngx_free  是一个宏 实质就是free,释放掉大块内存
        }
    }

    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);//重置小块内存就是重置last指针和failed
        p->d.failed = 0;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

内存清理:ngx_pfree

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;
        }//寻找地址为P的大块内存将其释放
    }
    return NGX_DECLINED;
}

重置和清理都是对大块内存进行清除,因为我们大块内存的分配过程就是检查3次有没有空闲的内存块, 没有就直接调用malloc去堆上直接申请。所以系统资源使用要使用后尽快返回,小块内存就不需要释放,保留等下次调用


回调函数结构体:

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;//回调函数
    void                 *data;//数据域
    ngx_pool_cleanup_t   *next;//指向链表的下一结点
};

nginx内存池还通过回调函数对外部资源的清理。ngx_pool_cleanup_t是外部资源管理的结构体节点,内存池中有一个链表用来链接这些节点。我们可以通过ngx_pool_cleanup_add获取一个新的节点的指针,然后通过该指针来设置要管理的外部资源地址以及清理这些资源要用到的方法(回调函数)

ngx_pool_cleanup_add函数:

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));//开辟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;//这里暂时设为NULL,返回ngx_pool_cleanup_t后在外部设置回调函数
    c->next = p->cleanup;
 
    p->cleanup = c;
 
    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
 
    return c;
}

大致模型图:
在这里插入图片描述

内存池的销毁:ngx_destroy_pool

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);//循环调用handler函数对外部资源进行清理
        }
    }

#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;
        }
    }//对小块内存进行释放内存
}

先调用回调函数,对外部资源进行释放,然后先后对大块内存和小块内存进行释放。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值