• nginx的内存管理


    先来看内存池的实现,nginx的内存池实现的非常简单。 

    这里内存池的一些图表可以看老朱同学的slides : 

    http://blog.zhuzhaoyuan.com/2009/09/nginx-internals-slides-video/ 

    当内存池初始化的时候(下面会分析到)ngx_poll_s只相当于内存池的一个头,保存了当前内存池的一些必要信息而已。 

    当从内存池存取数据的时候,nginx是分为两种类型来处理得,一种是小块数据,它是直接从内存池中取得数据,另一方面,当为大块数据时,它是直接malloc一块数据(也就是从内存池外部分配数据),然后保存这个指针到内存池。可以看到很多内存池,比如py的内存池实现,也基本是这个思想。这里的细节,我们将会在下面分析内存池相关函数的时候详细分析。 

    这里还有一个要注意就是这些子内存池和父内存池是不一样的,我们后面分析函数的时候会详细介绍。 


    大小块数据得分割线是你创建内存池时传递进来的size和页大小之间的最小值。 


    下面就是内存池的结构: 

    Java代码  收藏代码
    1. struct ngx_pool_s {  
    2. ///数据区的指针  
    3.     ngx_pool_data_t       d;  
    4. ///其实也就是内存池所能容纳的最大值。  
    5.     size_t                max;  
    6. ///指向当前的内存池的头。  
    7.     ngx_pool_t           *current;  
    8. ///这个主要是为了讲所有的内存池都链接起来。(他会创建多个内存池的)  
    9.     ngx_chain_t          *chain;  
    10. ///这个链表表示大的数据块  
    11.     ngx_pool_large_t     *large;  
    12. ///这个就是清理函数链表  
    13.     ngx_pool_cleanup_t   *cleanup;  
    14.     ngx_log_t            *log;  
    15. };  



    然后我们一个个来看上面的链表。首先是数据区的指针ngx_pool_data_t。 

    这个结构很简单,它就是包含了我们所需要操作这个内存池的数据的一些指针。 

    其中last表示当前的数据区的已经使用的数据的结尾。 

    end表示当前的内存池的结尾。也就是说end-last就是内存池未使用的大小。 

    我们要知道当我们从一个内存池请求一块内存时,如果此时内存池已经满掉,这是一般都是扩大内存池,而nginx中不是这么做的,它会直接再分配一个内存池,然后链接到data的next指针上。也就是说在nginx中,其实每个内存池都会包含一些子内存池。因此我们请求内存的时候都会遍历这些子内存池。 

    failed域主要是为了标记我们请求内存(小块内存)由于内存池空间不够,我们需要重新分配一个子内存池的次数。 下面分析函数的时候会再会看到这个域。 

    Java代码  收藏代码
    1. typedef struct {  
    2.     u_char               *last;  
    3.     u_char               *end;  
    4. //指向下一块内存池  
    5.     ngx_pool_t           *next;  
    6. ///失败标记  
    7.     ngx_uint_t            failed;  
    8. } ngx_pool_data_t;  



    ngx_chain_t这里就先不介绍了,我们现在只需要知道它是与buf相关的。 

    然后是ngx_pool_large_s,它表示了大块的内存。可以看到这个结构非常简单,就是一个指针指向下一块large,一个alloc指向数据。 

    Java代码  收藏代码
    1. struct ngx_pool_large_s {  
    2.     ngx_pool_large_t     *next;  
    3.     void                 *alloc;  
    4. };  



    接下来是ngx_pool_cleanup_s,这个结构用来表示内存池中的数据的清理handler。 

    其中handler表示清理函数。 
    data表示传递给清理函数的数据。 
    next表示下一个清理handler,也就是说当destroy这个pool的时候会遍历清理handler链表,然后调用handler。 

    Java代码  收藏代码
    1. struct ngx_pool_cleanup_s {  
    2.     ngx_pool_cleanup_pt   handler;  
    3.     void                 *data;  
    4.     ngx_pool_cleanup_t   *next;  
    5. }  



    通过ngx_create_temp_buf创建一个buff,然后通过ngx_alloc_chain_link创建一个chain,然后通过cl->buf = rb->buf;将buff链接到chain中. 

    下面就是pool的内存图,摘自老朱同学的nginx internal。 


    ok,接下来我们来通过分析具体的函数,来更好的理解pool的实现。 

    首先来看ngx_create_pool也就是创建一个pool。 

    这里我们要知道虽然我们传递进来的大小是size可是我们真正能使用的数据区大小是要减去ngx_pool_t的大小的。

    Java代码  收藏代码
    1. ///内存池的数据区的最大容量。  
    2. #define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)  
    3.   
    4. ngx_pool_t *  
    5. ngx_create_pool(size_t size, ngx_log_t *log)  
    6. {  
    7.     ngx_pool_t  *p;  
    8. ///可以看到直接分配size大小,也就是说我们只能使用size-sizeof(ngx_poll_t)大小  
    9.     p = ngx_alloc(size, log);  
    10.     if (p == NULL) {  
    11.         return NULL;  
    12.     }  
    13.   
    14. ///开始初始化数据区。  
    15.   
    16. ///由于一开始数据区为空,因此last指向数据区的开始。  
    17.     p->d.last = (u_char *) p + sizeof(ngx_pool_t);  
    18. ///end也就是数据区的结束位置  
    19.     p->d.end = (u_char *) p + size;  
    20.     p->d.next = NULL;  
    21.     p->d.failed = 0;  
    22.   
    23. ///这里才是我们真正能使用的大小。  
    24.     size = size - sizeof(ngx_pool_t);  
    25.   
    26. ///然后设置max。内存池的最大值也就是size和最大容量之间的最小值。  
    27.     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;  
    28.   
    29. ///current表示当前的内存池。  
    30.     p->current = p;  
    31.   
    32. ///其他的域置NULL。  
    33.     p->chain = NULL;  
    34.     p->large = NULL;  
    35.     p->cleanup = NULL;  
    36.     p->log = log;  
    37. ///返回指针。  
    38.     return p;  
    39. }  



    接下来我们来看如何从内存池中分配一块内存来使用。在nginx中有3个函数可以使用,分别是ngx_palloc,ngx_calloc,ngx_pnalloc。这三个函数的区别就是第一个函数分配的内存会对齐。第二个函数用来分配一块清0的内存,第三个函数分配的内存不会对齐。 

    由于这三个函数差不多,因此我们就只分析一个就够了。我们就来看ngx_palloc. 


    Java代码  收藏代码
    1. void *  
    2. ngx_palloc(ngx_pool_t *pool, size_t size)  
    3. {  
    4.     u_char      *m;  
    5.     ngx_pool_t  *p;  
    6.   
    7. ///首先判断当前申请的大小是否超过max,如果超过则说明是大块,此时进入large  
    8.     if (size <= pool->max) {  
    9.   
    10. ///得到当前的内存池指针。  
    11.         p = pool->current;  
    12.   
    13. ///开始遍历内存池,  
    14.         do {  
    15. ///首先对齐last指针。  
    16.             m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);  
    17.   
    18. ///然后得到当前内存池中的可用大小。如果大于请求大小,则直接返回当前的last,也就是数据的指针。  
    19.             if ((size_t) (p->d.end - m) >= size) {  
    20. ///更新last,然后返回前面保存的last。  
    21.                 p->d.last = m + size;  
    22.   
    23.                 return m;  
    24.             }  
    25. ///否则继续遍历  
    26.             p = p->d.next;  
    27.   
    28.         } while (p);  
    29. ///到达这里说明内存池已经满掉,因此我们需要重新分配一个内存池然后链接到当前的data的next上。(紧接着我们会分析这个函数)  
    30.         return ngx_palloc_block(pool, size);  
    31.     }  
    32.   
    33. ///申请大块。  
    34.     return ngx_palloc_large(pool, size);  
    35. }  



    接下来就来看ngx_palloc_block的实现,这个函数主要就是重新分配一块内存池,然后链接到当前内存池的数据区指针。 

    然后要注意这里重新new的这个内存池大小是和它的父内存池一样大的。 

    并且新得内存池只保存了ngx_pool_data_t这个结构,也就是说数据区直接跟在ngx_pool_data_t下面。 

    Java代码  收藏代码
    1. static void *  
    2. ngx_palloc_block(ngx_pool_t *pool, size_t size)  
    3. {  
    4.     u_char      *m;  
    5.     size_t       psize;  
    6.     ngx_pool_t  *p, *new, *current;  
    7.   
    8. ///计算当前的内存池的大小。  
    9.     psize = (size_t) (pool->d.end - (u_char *) pool);  
    10.   
    11. ///再分配一个同样大小的内存池  
    12.     m = ngx_alloc(psize, pool->log);  
    13.     if (m == NULL) {  
    14.         return NULL;  
    15.     }  
    16.   
    17.     new = (ngx_pool_t *) m;  
    18.   
    19. ///接下来和我们create一个内存池做的操作一样。就是更新一些指针  
    20.     new->d.end = m + psize;  
    21.     new->d.next = NULL;  
    22.     new->d.failed = 0;  
    23.   
    24. ///这里要注意了,可以看到last指针是指向ngx_pool_data_t的大小再加上要分配的size大小,也就是现在的内存池只包含了ngx_pool_data_t和数据。  
    25.     m += sizeof(ngx_pool_data_t);  
    26.     m = ngx_align_ptr(m, NGX_ALIGNMENT);  
    27.     new->d.last = m + size;  
    28.   
    29. ///设置current。  
    30.     current = pool->current;  
    31.   
    32. ///这里遍历所有的子内存池,这里主要是通过failed来标记重新分配子内存池的次数,然后找出最后一个大于4的,标记它的下一个子内存池为current。  
    33.     for (p = current; p->d.next; p = p->d.next) {  
    34.         if (p->d.failed++ > 4) {  
    35.             current = p->d.next;  
    36.         }  
    37.     }  
    38.   
    39. ///链接到最后一个内存池后面  
    40.     p->d.next = new;  
    41.   
    42. ///如果current为空,则current就为new。  
    43.     pool->current = current ? current : new;  
    44.   
    45.     return m;  
    46. }  



    这里解释一下为什么这样设置current,这里的主要原因是我们在ngx_palloc中分配内存是从current开始的,而这里也就是设置current为比较新分配的内存。而当failed大于4说明我们至少请求了4次内存分配,都不能满足我们的请求,此时我们就假设老的内存都已经没有空间了,因此我们就从比较新的内存块开始。 


    接下来是ngx_palloc_large,这个函数也是很简单就是malloc一块ngx_poll_large_t,然后链接到主的内存池上。 

    Java代码  收藏代码
    1. static void *  
    2. ngx_palloc_large(ngx_pool_t *pool, size_t size)  
    3. {  
    4.     void              *p;  
    5.     ngx_uint_t         n;  
    6.     ngx_pool_large_t  *large;  
    7.   
    8. ///分配一块内存。  
    9.     p = ngx_alloc(size, pool->log);  
    10.     if (p == NULL) {  
    11.         return NULL;  
    12.     }  
    13.   
    14.     n = 0;  
    15. ///开始遍历large链表,如果有alloc(也就是内存区指针)为空,则直接指针赋值然后返回。一般第一次请求大块内存都会直接进入这里。并且大块内存是可以被我们手动释放的。  
    16.     for (large = pool->large; large; large = large->next) {  
    17.         if (large->alloc == NULL) {  
    18.             large->alloc = p;  
    19.             return p;  
    20.         }  
    21. ///这里不太懂什么意思。  
    22.         if (n++ > 3) {  
    23.             break;  
    24.         }  
    25.     }  
    26.   
    27. ///malloc一块ngx_pool_large_t。  
    28.     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));  
    29.     if (large == NULL) {  
    30.         ngx_free(p);  
    31.         return NULL;  
    32.     }  
    33.   
    34. ///然后链接数据区指针p到large。这里可以看到直接插入到large链表的头的。  
    35.     large->alloc = p;  
    36.     large->next = pool->large;  
    37.     pool->large = large;  
    38.   
    39.     return p;  
    40. }  



    ok,分配看完了,我们来看释放。这里要知道在nginx中,只有大块内存提供了free接口,可以供我们收工释放,而小块内存是没有提供这个接口的。也就是说小块内存只有当整个内存池被desrtoy掉时,才会被释放。 

    这里一些简单的函数就不分析了。 
    比如ngx_pfree(ngx_pool_t *pool, void *p),这个函数就是从pool的large链表中找到p,然后free掉它。 

    ngx_pool_cleanup_t * 
    ngx_pool_cleanup_add(ngx_pool_t *p, size_t size) 

    这个函数也就是添加一个ngx_pool_cleanup_t到当前的pool上,然后返回,我们此时就能通过返回的结构来给对应的handler赋值。 

    而ngx_pool_cleanup_t这个主要是当内存池destroy的时候我们可能需要做一些清理工作,此时我们就能add这些清理handler到pool中,然后当内存池要释放的时候就会自动调用。 

    ok,现在来看pool 被free的实现。 

    这个函数主要是遍历large,遍历current,然后一一释放。 

    Java代码  收藏代码
    1. void  
    2. ngx_destroy_pool(ngx_pool_t *pool)  
    3. {  
    4.     ngx_pool_t          *p, *n;  
    5.     ngx_pool_large_t    *l;  
    6.     ngx_pool_cleanup_t  *c;  
    7.   
    8. ///先做清理工作。  
    9.     for (c = pool->cleanup; c; c = c->next) {  
    10.         if (c->handler) {  
    11.             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,  
    12.                            "run cleanup: %p", c);  
    13.             c->handler(c->data);  
    14.         }  
    15.     }  
    16.   
    17. ///free大块内存  
    18.     for (l = pool->large; l; l = l->next) {  
    19.   
    20.         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);  
    21.   
    22.         if (l->alloc) {  
    23.             ngx_free(l->alloc);  
    24.         }  
    25.     }  
    26.   
    27. ///遍历小块内存池。  
    28.     for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {  
    29. ///直接free掉。  
    30.         ngx_free(p);  
    31.   
    32.         if (n == NULL) {  
    33.             break;  
    34.         }  
    35.     }  
    36. }  



    通过上面我们可以看到在nginx中内存池中的小块数据是从来不释放的,这样就简化了内存池的操作。 

    接下来我们来看buf的实现。 

    buf分为两种类型,一种是file,一种是memory.因此这里会有文件的一些操作域。 

    可以看到buf相对于pool多了一个pos域(file_pos).这里我们要知道我们发送往套接字异或者其他的设备,我们这里会现将数据放到buf中,然后当设备或者套接字准备好了,我们就会从buf中读取,因此这里pos指针就是放到buf中的已经被执行的数据(也就是已经送往套接字)的位置。 

    Java代码  收藏代码
    1. struct ngx_buf_s {  
    2. ///pos表示已经执行的数据的位置。  
    3.     u_char          *pos;  
    4. ///last和上面内存池中last一样,也就是使用的内存的最后一个字节的指针  
    5.     u_char          *last;  
    6. ///文件指针  
    7.     off_t            file_pos;  
    8.     off_t            file_last;  
    9. ///buf的开始指针  
    10.     u_char          *start;         /* start of buffer */  
    11.     u_char          *end;           /* end of buffer */  
    12.   
    13. ///这里表示这个buf从属于那个模块。  
    14.     ngx_buf_tag_t    tag;  
    15.     ngx_file_t      *file;  
    16.     ngx_buf_t       *shadow;  
    17.   
    18. ///一些标记  
    19.     /* the buf's content could be changed */  
    20.     unsigned         temporary:1;  
    21.   
    22. ///在内存中是不能改变的。  
    23.     unsigned         memory:1;  
    24.   
    25. ///是否是mmap的内存  
    26.     unsigned         mmap:1;  
    27.   
    28.     unsigned         recycled:1;  
    29.   
    30. ///是否文件。  
    31.     unsigned         in_file:1;  
    32.     unsigned         flush:1;  
    33.     unsigned         sync:1;  
    34.     unsigned         last_buf:1;  
    35.     unsigned         last_in_chain:1;  
    36.   
    37.     unsigned         last_shadow:1;  
    38.     unsigned         temp_file:1;  
    39.   
    40.     /* STUB */ int   num;  
    41. };  




    ok,接下来我们来看如何创建一个buf.在nginx中一般都是调用ngx_create_temp_buf来创建一个buf。函数很简单,就是从pool中分配内存然后初始化相关域。 

    Java代码  收藏代码
    1. ngx_buf_t *  
    2. ngx_create_temp_buf(ngx_pool_t *pool, size_t size)  
    3. {  
    4.     ngx_buf_t *b;  
    5.   
    6. ///calloc一个buf,可以看到它调用的是calloc,也就是说都会清0.  
    7.     b = ngx_calloc_buf(pool);  
    8.     if (b == NULL) {  
    9.         return NULL;  
    10.     }  
    11.   
    12. ///然后从内存池中分配一块内存。并将这块内存链接到b->start.  
    13.     b->start = ngx_palloc(pool, size);  
    14.     if (b->start == NULL) {  
    15.         return NULL;  
    16.     }  
    17. ///设置相关的域。  
    18.   
    19.     b->pos = b->start;  
    20.     b->last = b->start;  
    21. ///设置打消  
    22.     b->end = b->last + size;  
    23.     b->temporary = 1;  
    24.   
    25.     return b;  
    26. }  



    然后我们来看chain的实现,chain其实也就是多个buf组合而成的。它主要是用来缓存一些未发出去的,或者接收的buf 以及 writev以及readv而存在的。 

    ok我们来看chain的实现,其实它的实现很简单,就是一个单链表。 

    Java代码  收藏代码
    1. struct ngx_chain_s {  
    2. ///buf  
    3.     ngx_buf_t    *buf;  
    4. ///下一个buf的指针。  
    5.     ngx_chain_t  *next;  
    6. };  



    然后来看如何创建一个chain。这里取得一个chain后直接返回给供其他模块使用: 

    Java代码  收藏代码
    1. ngx_chain_t *  
    2. ngx_alloc_chain_link(ngx_pool_t *pool)  
    3. {  
    4.     ngx_chain_t  *cl;  
    5.   
    6. ///取得pool的老的chain  
    7.     cl = pool->chain;  
    8. ///如果chain已经存在,则直接返回这个chain,然后从pool的chain中删除这个chain。  
    9.     if (cl) {  
    10.         pool->chain = cl->next;  
    11.         return cl;  
    12.     }  
    13.   
    14. ///否则从内存池重新new一个chain。这里注意新建的这个chain是链接到pool的chain上的。  
    15.     cl = ngx_palloc(pool, sizeof(ngx_chain_t));  
    16.     if (cl == NULL) {  
    17.         return NULL;  
    18.     }  
    19.   
    20. ///然后返回  
    21.     return cl;  
    22. }  




    接下来就是两个重要的chain,它们其实就是对chain再进行了一次封装。 

    1 ngx_output_chain_ctx_t , 这个chain主要是管理输出buf。 

    2 ngx_chain_writer_ctx_t 这个主要是用在upstream模块。 

    因此我们主要来看ngx_output_chain_ctx_t。 

    ngx_output_chain_ctx_t,它包含了三种类型的chain,分别是in,free以及busy。 

    现在来介绍这几个重要的域: 

    buf : 这个域也就是我们拷贝数据的地方,我们一般输出的话都是从in直接copy相应的size到buf中。 

    in : 这个就是我们保存那些需要发送数据的地方。 

    free : 这个保存了一些空的buf,也就是说如果free存在,我们都会直接从free中取buf到前面的buf域。 

    busy :这个保存了已经发送完毕的buf,也就是每次我们从in中将buf读取完毕后,确定数据已经取完,此时就会将这个chain拷贝到busy中。然后将比较老的busy buf拷贝到free中。 

    output_filter是一个回调函数,用来过滤输出。 

    剩下的就是一些标记域。 

    Java代码  收藏代码
    1. typedef struct {  
    2.     ngx_buf_t                   *buf;  
    3.     ngx_chain_t                 *in;  
    4.     ngx_chain_t                 *free;  
    5.     ngx_chain_t                 *busy;  
    6.   
    7. ///相关的标记,是否使用sendfile,是否使用directio等等。。  
    8.     unsigned                     sendfile:1;  
    9.     unsigned                     directio:1;  
    10. #if (NGX_HAVE_ALIGNED_DIRECTIO)  
    11.     unsigned                     unaligned:1;  
    12. #endif  
    13.     unsigned                     need_in_memory:1;  
    14.     unsigned                     need_in_temp:1;  
    15.   
    16. ///内存池。  
    17.     ngx_pool_t                  *pool;  
    18. ///每次从pool中重新alloc一个buf这个值都会相应加一。  
    19.     ngx_int_t                    allocated;  
    20.     ngx_bufs_t                   bufs;  
    21. ///这个用来标记当前那个模块使用这个chain  
    22.     ngx_buf_tag_t                tag;  
    23.   
    24.     ngx_output_chain_filter_pt   output_filter;  
    25.     void                        *filter_ctx;  
    26. } ngx_output_chain_ctx_t;  




    它对应的主要是ngx_output_chain函数。这个函数主要功能就是拷贝in chain的数据到buf域中。这个函数很复杂,我们这里简要分析一下: 

    Java代码  收藏代码
    1. ngx_int_t  
    2. ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)  
    3. {  
    4.     off_t         bsize;  
    5.     ngx_int_t     rc, last;  
    6.     ngx_chain_t  *cl, *out, **last_out;  
    7.   
    8. ...........................................  
    9.   
    10.     /* add the incoming buf to the chain ctx->in */  
    11.   
    12. ///拷贝in 到ctx的in chain中。  
    13.     if (in) {  
    14.         if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) {  
    15.             return NGX_ERROR;  
    16.         }  
    17.     }  
    18.   
    19.     out = NULL;  
    20.     last_out = &out;  
    21.     last = NGX_NONE;  
    22.   
    23. ///开始循环处理ctx-in chain.这里有两层循环。  
    24.     for ( ;; ) {  
    25.   
    26. ///开始遍历in  
    27.         while (ctx->in) {  
    28.   
    29.   ///计算当前in的buf长度。这个长度也就是还没处理的数据长度。  
    30.   
    31.             bsize = ngx_buf_size(ctx->in->buf);  
    32. ..................................................  
    33. ///如果buf为空,则我们需要给buf分配空间。  
    34.   
    35.             if (ctx->buf == NULL) {  
    36. ///这个函数很简单,主要是处理file buf,如果是file buf则会create一个buf链接到ctx  
    37.                 rc = ngx_output_chain_align_file_buf(ctx, bsize);  
    38.   
    39.                 if (rc == NGX_ERROR) {  
    40.                     return NGX_ERROR;  
    41.                 }  
    42.   
    43. ///如果是memory buf,都会到这里  
    44.                 if (rc != NGX_OK) {  
    45. ///如果free不为空,则我们从free chain中取得buf。  
    46.                     if (ctx->free) {  
    47.   
    48.                         /* get the free buf */  
    49.   
    50.                         cl = ctx->free;  
    51.                         ctx->buf = cl->buf;  
    52.                         ctx->free = cl->next;  
    53.   
    54.                         ngx_free_chain(ctx->pool, cl);  
    55.   
    56.                     } else if (out || ctx->allocated == ctx->bufs.num) {  
    57.   
    58.                         break;  
    59.   
    60.                     }   
    61.   
    62. ///否则我们要重新create一个buf,然后链接到ctx,这里主要buf的大小和in chain的没有处理的数据一样大。  
    63. else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) {  
    64.                         return NGX_ERROR;  
    65.                     }  
    66.                 }  
    67.             }  
    68.   
    69. ///从in chain拷贝数据到buf,并更新相关域。  
    70.             rc = ngx_output_chain_copy_buf(ctx);  
    71.   
    72.             if (rc == NGX_ERROR) {  
    73.                 return rc;  
    74.             }  
    75.   
    76.             if (rc == NGX_AGAIN) {  
    77.                 if (out) {  
    78.                     break;  
    79.                 }  
    80.   
    81.                 return rc;  
    82.             }  
    83.   
    84.     ///如果size为0,说明in chain中的第一个chain的数据已经被拷贝完了,此时删除这个chain。  
    85.   
    86.             if (ngx_buf_size(ctx->in->buf) == 0) {  
    87.                 ctx->in = ctx->in->next;  
    88.             }  
    89.   
    90. ///重新分配一个 chain  
    91.             cl = ngx_alloc_chain_link(ctx->pool);  
    92.             if (cl == NULL) {  
    93.                 return NGX_ERROR;  
    94.             }  
    95.   
    96. ///链接buf到cl  
    97.             cl->buf = ctx->buf;  
    98.             cl->next = NULL;  
    99.             *last_out = cl;  
    100.             last_out = &cl->next;  
    101.             ctx->buf = NULL;  
    102.         }  
    103.   
    104.         if (out == NULL && last != NGX_NONE) {  
    105.   
    106.             if (ctx->in) {  
    107.                 return NGX_AGAIN;  
    108.             }  
    109.   
    110.             return last;  
    111.         }  
    112.   
    113. ///调用回调函数  
    114.         last = ctx->output_filter(ctx->filter_ctx, out);  
    115.   
    116.         if (last == NGX_ERROR || last == NGX_DONE) {  
    117.             return last;  
    118.         }  
    119.   
    120. ///update 相关的chain,主要是将刚才copy完的buf 加入到busy chain,然后从busy chain中取出buf放到free chain中。  
    121.         ngx_chain_update_chains(&ctx->free, &ctx->busy, &out, ctx->tag);  
    122.         last_out = &out;  
    123.     }  
    124. }  



    这里我只是简要的分析了下,详细的还需要接合其他模块来看。 

    最后来看ngx_chain_writer_ctx_t,这个主要用于ustream(由于没看这个模块,因此不理解这里为什么要多出来个writer).大概看了,觉得应该是ustream模块发送的数据量比较大,因此这里通过这个chain来直接调用writev来将数据发送出去。 

    Java代码  收藏代码
    1. typedef struct {  
    2. ///保存了所要输出的chain。  
    3.     ngx_chain_t                 *out;  
    4.   
    5. ///这个保存了这次新加入的所需要输出的chain。  
    6.     ngx_chain_t                **last;  
    7. ///这个表示当前连接  
    8.     ngx_connection_t            *connection;  
    9.     ngx_pool_t                  *pool;  
    10.     off_t                        limit;  
    11. } ngx_chain_writer_ctx_t;  



    这里我们要知道out是会变化的。每次输出后,这个都会指向下一个需要发送的chain。 

    Java代码  收藏代码
      1. ngx_int_t  
      2. ngx_chain_writer(void *data, ngx_chain_t *in)  
      3. {  
      4.     ngx_chain_writer_ctx_t *ctx = data;  
      5.   
      6.     off_t              size;  
      7.     ngx_chain_t       *cl;  
      8.     ngx_connection_t  *c;  
      9.   
      10.     c = ctx->connection;  
      11. ///这里将in中的也就是新加如的chain ,全部复制到last中。也就是它保存了最后的数据。  
      12.     for (size = 0; in; in = in->next) {  
      13. ....................................  
      14.   
      15. ///计算大小。  
      16.         size += ngx_buf_size(in->buf);  
      17.   
      18.   
      19.         cl = ngx_alloc_chain_link(ctx->pool);  
      20.         if (cl == NULL) {  
      21.             return NGX_ERROR;  
      22.         }  
      23.   
      24. ///加入last  
      25.         cl->buf = in->buf;  
      26.         cl->next = NULL;  
      27.         *ctx->last = cl;  
      28.         ctx->last = &cl->next;  
      29.     }  
      30.   
      31.     ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0,  
      32.                    "chain writer in: %p", ctx->out);  
      33.   
      34. ///遍历out chain  
      35.     for (cl = ctx->out; cl; cl = cl->next) {  
      36.   
      37. ///计算所需要输出的大小  
      38.         size += ngx_buf_size(cl->buf);  
      39.     }  
      40.   
      41.     if (size == 0 && !c->buffered) {  
      42.         return NGX_OK;  
      43.     }  
      44.   
      45. ///调用send_chain(一般是writev)来输出out中的数据。  
      46.     ctx->out = c->send_chain(c, ctx->out, ctx->limit);  
      47.   
      48. ........................  
      49.   
      50.     return NGX_AGAIN;  
      51. }  
  • 相关阅读:
    c++中的工具(一):std::pair<class T1, class T2>
    mybatisplus Lambda 表达式
    将查询结果映射成map的注解,其中第一个字段为key,第二个字段为value.
    mybatisplus 添加 /修改 数据 插入公共字段
    mybatisplus扩展BaseMapper的方法(InsertBatchSomeColumn及AlwaysUpdateSomeColumnById)
    根据俩日期(yyyy-MM-dd)格式获取它们之间的年月日组合结果(js实现)
    oralce json字段中根据key提取value的函数(转载)
    记录一下关于oracle还原数据库的问题
    记录一下数据库外连接 的不同
    JAVA项目从运维部署到项目开发(六. Jenkins之静态页面(2))
  • 原文地址:https://www.cnblogs.com/405845829qq/p/4398049.html
Copyright © 2020-2023  润新知