Nginx源码分析-内存池

Nginx的内存池实现得很精巧,代码也很简洁。总的来讲,全部的内存池基本都一个宗旨:申请大块内存,避免“细水长流”。nginx

1、建立一个内存池web

nginx内存池主要有下面两个结构来维护,他们分别维护了内存池的头部和数据部。此处数据部就是供用户分配小块内存的地方。数据结构

//该结构用来维护内存池的数据块,供用户分配之用。函数

typedef struct {ui

u_char                    *last;                 //当前内存分配结束位置,即下一段可分配内存的起始位置spa

u_char                    *end;                //内存池结束位置操作系统

ngx_pool_t              *next;                //连接到下一个内存池指针

ngx_uint_t                failed;                //统计该内存池不能知足分配请求的次数server

} ngx_pool_data_t;接口

//该结构维护整个内存池的头部信息。

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;

};

有了上面的两个结构,就能够建立一个内存池了,nginx用来建立一个内存池的接口是:ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log)(位于src/core/ngx_palloc.c中);调用这个函数就能够建立一个大小为size的内存池了。这里,我用内存池的结构图来展现,就不作具体的代码分析了。

ngx_create_pool接口函数就是分配上图这样的一大块内存,而后初始化好各个头部字段(上图中的彩色部分)。红色表示的四个字段就是来自于上述的第一个结构,维护数据部分,由图可知:last是用户从内存池分配新内存的开始位置,end是这块内存池的结束位置,全部分配的内存都不能超过end。蓝色表示的max字段的值等于整个数据部分的长度,用户请求的内存大于max时,就认为用户请求的是一个大内存,此时须要在紫色表示的large字段下面单独分配;用户请求的内存不大于max的话,就是小内存申请,直接在数据部分分配,此时将会移动last指针。

2、分配小块内存(size <= max)

上面建立好了一个可用的内存池了,也提到了小块内存的分配问题。nginx提供给用户使用的内存分配接口有:

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);

ngx_palloc和ngx_pnalloc都是从内存池里分配size大小内存,至于分得的是小块内存仍是大块内存,将取决于size的大小;他们的不一样之处在于,palloc取得的内存是对齐的,pnalloc则否。ngx_pcalloc是直接调用palloc分配好内存,而后进行一次0初始化操做。ngx_pmemalign将在分配size大小的内存并按alignment对齐,而后挂到large字段下,当作大块内存处理。下面用图形展现一下分配小块内存的模型:

上图这个内存池模型是由上3个小内存池构成的,因为第一个内存池上剩余的内存不够分配了,因而就建立了第二个新的内存池,第三个内存池是因为前面两个内存池的剩余部分都不够分配,因此建立了第三个内存池来知足用户的需求。由图可见:全部的小内存池是由一个单向链表维护在一块儿的。这里还有两个字段须要关注,failed和current字段。failed表示的是当前这个内存池的剩余可用内存不能知足用户分配请求的次数,便是说:一个分配请求到来后,在这个内存池上分配不到想要的内存,那么就failed就会增长1;这个分配请求将会递交给下一个内存池去处理,若是下一个内存池也不能知足,那么它的failed也会加1,而后将请求继续往下传递,直到知足请求为止(若是没有现成的内存池来知足,会再建立一个新的内存池)。current字段会随着failed的增长而发生改变,若是current指向的内存池的failed达到了4的话,current就指向下一个内存池了。猜想:4这个值应该是做者的经验值,或者是一个统计值。

3、大块内存的分配(size > max)

大块内存的分配请求不会直接在内存池上分配内存来知足,而是直接向操做系统申请这么一块内存(就像直接使用malloc分配内存同样),而后将这块内存挂到内存池头部的large字段下。内存池的做用在于解决小块内存池的频繁申请问题,对于这种大块内存,是能够忍受直接申请的。一样,用图形展现大块内存申请模型:

注意每块大内存都对应有一个头部结构(next&alloc),这个头部结构是用来将全部大内存串成一个链表用的。这个头部结构不是直接向操做系统申请的,而是当作小块内存(头部结构没几个字节)直接在内存池里申请的。这样的大块内存在使用完后,可能须要第一时间释放,节省内存空间,所以nginx提供了接口函数:ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);此函数专门用来释放某个内存池上的某个大块内存,p就是大内存的地址。ngx_pfree只会释放大内存,不会释放其对应的头部结构,毕竟头部结构是当作小内存在内存池里申请的;遗留下来的头部结构会做下一次申请大内存之用。

4、cleanup资源

能够看到全部挂载在内存池上的资源将造成一个循环链表,一路走来,发现链表这种看似简单的数据结构却被频繁使用。由图可知,每一个须要清理的资源都对应有一个头部结构,这个结构中有一个关键的字段handler,handler是一个函数指针,在挂载一个资源到内存池上的时候,同时也会注册一个清理资源的函数到这个handler上。便是说,内存池在清理cleanup的时候,就是调用这个handler来清理对应的资源。好比:咱们能够将一个开打的文件描述符做为资源挂载到内存池上,同时提供一个关闭文件描述的函数注册到handler上,那么内存池在释放的时候,就会调用咱们提供的关闭文件函数来处理文件描述符资源了。

5、内存的释放

只提供给了用户申请内存的接口,却没有释放内存的接口,那么nginx是如何完成内存释放的呢?总不能一直申请,用不释放啊。针对这个问题,nginx利用了web server应用的特殊场景来完成;一个web server老是不停的接受connection和request,因此nginx就将内存池分了不一样的等级,有进程级的内存池、connection级的内存池、request级的内存池。也就是说,建立好一个worker进程的时候,同时为这个worker进程建立一个内存池,待有新的链接到来后,就在worker进程的内存池上为该链接建立起一个内存池;链接上到来一个request后,又在链接的内存池上为request建立起一个内存池。这样,在request被处理完后,就会释放request的整个内存池,链接断开后,就会释放链接的内存池。于是,就保证了内存有分配也有释放。

总结:经过内存的分配和释放能够看出,nginx只是将小块内存的申请汇集到一块儿申请,而后一块儿释放。避免了频繁申请小内存,下降内存碎片的产生等问题