Nginx大牛分析博文-(都牛的不得了)

nginx中hash表的设计与实现

https://blog.csdn.net/brainkick/article/details/7816420

 

 


aweth0me - linux skills :

https://blog.csdn.net/dingyujie/article/category/913021

 

高性能server :

https://blog.csdn.net/dingyujie/article/category/782920

架构与设计 :

https://blog.csdn.net/dingyujie/article/category/950585

 

腾讯协程库libco的原理分析 :

https://blog.csdn.net/brainkick/article/details/48676403

 

一张脑图说清 Nginx 的主流程 :

https://www.cnblogs.com/yjf512/p/9255975.html

https://www.cnblogs.com/yjf512/p/9177562.html

https://blog.csdn.net/fzy0201/article/details/17683883

CSDN产品公告:APP新增大厂在线刷题功能、博主排名规则更新、MD编辑器优化

https://blogdev.blog.csdn.net/article/details/101446074

Nginx学习笔记系列 :

https://blog.csdn.net/fzy0201/article/category/9262198

nginx源码分析之模块初始化

https://www.cnblogs.com/chengxuyuancc/p/3792258.html

 

Nginx 源码分析:从模块到配置(上)

https://segmentfault.com/a/1190000002778510

 

Nginx学习笔记(十三):Nginx启动流程

https://blog.csdn.net/fzy0201/article/details/19416463

 

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

https://blog.csdn.net/fzy0201/article/details/24309787

 

 

leveldb设计分析之log

https://blog.csdn.net/brainkick/article/details/48730049

LevelDB源码分析之十:LOG文件

https://blog.csdn.net/caoshangpa/article/details/78925719

LevelDB源码分析之十三:table

https://blog.csdn.net/caoshangpa/article/details/79021568

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值