ngnix之内存池:结构体简析 和 函数功能总览

感悟

感悟有两点,
第一点就是别人已经把这个内存池总结的非常好了,我只是在继续炒冷饭而已,但是我还觉得该写点什么,我打算写 结构体的含义相关函数的功能。源码文件:ngx_palloc.c和ngx_palloc.h
更多复杂、详细的内容建议您参考:
  1. Nginx源码剖析之内存池,与内存管理
  2. 深入剖析nginx  P65
第二点就是如何看源码,昨日不知在哪里看源码的经验,需要三步:
  1. 看懂大致框架结构、各个模块的功能、各个模块的结构体、函数、接口的功能是什么,用图表示更好
  2. 挑重点模块来精读源码、一句一句仔细分析
  3. 再次总览各个模块,整体框架,分析分析。
我觉得这样说法很有道理,昨天我算精读了ngnix 中hash部分的源码,调试着一句一句的看,一句一句的分析,最后有种累感不爱的感觉,想着那茫茫多的源码我得分析成什么样子呀。所以突然看到上述三个步骤,就发现自己方法错了。所以现在的目标,不是去读每一句源码,而是分析整体框架和各个模块的功能、结构体、函数、接口,具体函数怎么实现应该往后放。

结构体

首先我们要明白,nginx对内存的管理机制是:nginx会事先向操作系统申请很多内存,作为内存池,然后自己管理,如果nginx其他模块需要内存,不能直接像操作系统申请,必须向ngnix自己管理的内存池申请,内存池再将内存拨给需要的模块。

ngx_pool_s

接着,我们来看看有哪些结构体,在这些结构体的相互作用下共同维护着内存池。
必须首先知道的是 ngx_pool_s:描述着内存池的一些信息,而内存池里面有很多内存块、有大的,有小的。描述信息的 ngx_pool_s只需要 一个
另外,ngx_pool_t是它的别名:
struct ngx_pool_s {
    ngx_pool_data_t       d;//指向一个小的内存块,这个内存块接在了ngx_pool_s描述信息的后面
    size_t                max;//一个内存块的最大大小,最大是一页的大小(4096) 再减一。
                              //i.e. 4095 on x86 
    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_pool_s        ngx_pool_t;

虽然还有更多细节没有说到,但下面用一张图(来自 v_JULY_v)来总览一下比较好,
注意:我们的 ngx_pool_s结构体就在左上方的方块里,那是一个内存块,含有内存池的描述信息,也就是ngx_pool_s:

ngx_pool_data_t

上图左上方的第一个内存块,其实已经将ngx_pool_data_t结构体的具体内容直接展示出来了,所以来分析一下ngx_pool_data_t:
typedef struct {
    u_char               *last;//指向内存当前内存块中可以分配的开始位置
    u_char               *end;//当前内存块的末尾
    ngx_pool_t           *next;//下一个内存块
    ngx_uint_t            failed;//在这个内存块试图拨内存给别的模块,可惜内存不足,失败了的次数
} ngx_pool_data_t;
在上图中,我们可以很清楚的看到
  1. last指向未分配的起始处
  2. end指向当前内存块的结尾
  3. next指向下一个小的内存块

ngx_pool_large_t

图中也可以看到ngx_pool_large_t以链表的形式组织在了一起:
typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;//下一个大的内存块
    void                 *alloc;//大的内存块的起始处
};

注意:

  • 什么时候会产生ngx_pool_large_t结构体为首的大的内存块呢?
  • 答:申请的内存块大于ngx_pool_s的max时,因为小的内存块不够用,只有分大的给申请者。此时是向操作系统申请了。

从如下代码中我们可以看到,当size > pool->max时,直接调用ngx_palloc_large(pool, size),而ngx_palloc_large(pool, size)最终无非是调用了系统函数 malloc(size):

void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;

    if (size <= pool->max) {
     ...
    }

    return ngx_palloc_large(pool, size);
}

ngx_pool_cleanup_t

图中可以看出,ngx_pool_cleanup_t也是链表组织在了一起:
typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;//释放资源的函数
    void                 *data;//需要释放资源的数据
    ngx_pool_cleanup_t   *next;//下一个需要释放的结构体数据
};

那什么时候释放呢?我们可以看到在ngx_destroy_pool函数中,如下所示的for循环中逐步调用了释放资源的函数和传入了释放资源的数据:
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);//调用了释放资源的函数和传入了释放资源的数据
        }
    }
    ...
}

函数

一共有多少函数

先来张总览图,看看有多少函数,这些函数都可以被外界调用的:
此外还有两个static,处理内部逻辑的:


具体含义

ngx_alloc直接调用malloc向操作系统系统申请,在src/os/unix/ngx_alloc.c的文档中
ngx_calloc调用ngx_alloc后,将内存空间全部清零,在src/os/unix/ngx_alloc.c的文档中
ngx_create_pool创建一个内存块,作为内存池的第一个内存块,返回的结构体含有内存池的基本信息
ngx_destroy_pool摧毁一个内存池
ngx_reset_pool重置一个内存池,这个内存块内存放的信息都会清零                                                                                                                    
ngx_palloc向内存池里面申请size的空间,作了对齐处理,
如果小于p->max,但目前内存块内的空间不足,
那就会调用ngx_palloc_block重新申请新的内存块
如果大于p->max,则会调用ngx_palloc_large
ngx_pnalloc向内存池里面申请size的空间,不作对齐处理
ngx_pcalloc向内存池里面申请size的空间,作对齐处理,还会清零处理
ngx_pmemalign做了对齐处理,之后调用了ngx_alloc,
也就说无论申请的大小为多少,都会直接想操作系统申请
并且会挂载到内存池的ngx_pool_large_t 结构体的alloc字段下
ngx_pfree只能对大的内存块调用系统函数:free,也就是释放大的内存块
ngx_pool_cleanup_add注册需要在释放内存时一起释放的资源,返回ngx_pool_cleanup_t结构体后,
在这个结构体的相应字段填写就可以了
ngx_pool_run_cleanup_file会关闭挂载到ngx_pool_cleanup_t结构体内的所有文件
ngx_pool_cleanup_file对指定的文件的进行关闭,指定的文件存放在:ngx_pool_cleanup_file_t结构体内
ngx_pool_delete_file对指定的文件的进行删除,指定的文件存放在:ngx_pool_cleanup_file_t结构体内

ngnix内存的特点

  1. 小的内存统一向内存池申请,避免了内存碎片的产生
  2. 注意到唯一的释放内存的函数只有一个:ngx_pfree,且只能释放大块内存。
    小块内存的释放,严格说来不是释放,归还给操作系统,而且直接用ngx_pool_s结构体的 last 指针来改一下指的地方就可以了。
    如下,将p->d.last指向内存块的可分配的内存的起始位置即可:
    void
    ngx_reset_pool(ngx_pool_t *pool)
    {
        ...
        for (p = pool; p; p = p->d.next) {
            p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        }
    }
  3. 根据WebServer的特点,Ngnix是针对阶段来创建内存池,然后这个阶段过后,就销毁内存池就完了。
    具体说来就是如下:摘自(Nginx源码分析-内存池

    一个web server总是不停的接受connection和request,所以nginx就将内存池分了不同的等级,有进程级的内存池、connection级的内存池、request级的内存池。也就是说,创建好一个worker进程的时候,同时为这个worker进程创建一个内存池,待有新的连接到来后,就在worker进程的内存池上为该连接创建起一个内存池;连接上到来一个request后,又在连接的内存池上为request创建起一个内存池。这样,在request被处理完后,就会释放request的整个内存池,连接断开后,就会释放连接的内存池。因而,就保证了内存有分配也有释放。

    但是我有一个疑问
    上段中的划线+黑体:如何做到在一个内存池上创建另一个内存池,因为用到"创建"一词,我认为作者所说的方法是:ngx_create_pool。但是这个方法是直接向操作系统申请内存,那就不是在一个内存池上的申请。

    那如果是调用ngx_palloc,那就是在worker进程的内存池申请了一些内存,那么我认为就是和我2中所说的一样,要释放小块内存,那么直接改动p->d.last的指向即可。那么上段中所述“创建”一词不太标准。(当然,我也很好奇如何让p->d.last指对地方)

学习源码的画图工具

mind map:好像linux下面没有。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值