- 浏览: 1403644 次
- 性别:
- 来自: 火星
文章分类
最新评论
-
aidd:
内核处理time_wait状态详解 -
ahtest:
赞一下~~
一个简单的ruby Metaprogram的例子 -
itiProCareer:
简直胡说八道,误人子弟啊。。。。谁告诉你 Ruby 1.9 ...
ruby中的类变量与类实例变量 -
dear531:
还得补充一句,惊群了之后,数据打印显示,只有一个子线程继续接受 ...
linux已经不存在惊群现象 -
dear531:
我用select试验了,用的ubuntu12.10,内核3.5 ...
linux已经不存在惊群现象
先来看内存池的实现,nginx的内存池实现的非常简单。
这里内存池的一些图表可以看老朱同学的slides :
http://blog.zhuzhaoyuan.com/2009/09/nginx-internals-slides-video/
当内存池初始化的时候(下面会分析到)ngx_poll_s只相当于内存池的一个头,保存了当前内存池的一些必要信息而已。
当从内存池存取数据的时候,nginx是分为两种类型来处理得,一种是小块数据,它是直接从内存池中取得数据,另一方面,当为大块数据时,它是直接malloc一块数据(也就是从内存池外部分配数据),然后保存这个指针到内存池。可以看到很多内存池,比如py的内存池实现,也基本是这个思想。这里的细节,我们将会在下面分析内存池相关函数的时候详细分析。
这里还有一个要注意就是这些子内存池和父内存池是不一样的,我们后面分析函数的时候会详细介绍。
大小块数据得分割线是你创建内存池时传递进来的size和页大小之间的最小值。
下面就是内存池的结构:
然后我们一个个来看上面的链表。首先是数据区的指针ngx_pool_data_t。
这个结构很简单,它就是包含了我们所需要操作这个内存池的数据的一些指针。
其中last表示当前的数据区的已经使用的数据的结尾。
end表示当前的内存池的结尾。也就是说end-last就是内存池未使用的大小。
我们要知道当我们从一个内存池请求一块内存时,如果此时内存池已经满掉,这是一般都是扩大内存池,而nginx中不是这么做的,它会直接再分配一个内存池,然后链接到data的next指针上。也就是说在nginx中,其实每个内存池都会包含一些子内存池。因此我们请求内存的时候都会遍历这些子内存池。
failed域主要是为了标记我们请求内存(小块内存)由于内存池空间不够,我们需要重新分配一个子内存池的次数。 下面分析函数的时候会再会看到这个域。
ngx_chain_t这里就先不介绍了,我们现在只需要知道它是与buf相关的。
然后是ngx_pool_large_s,它表示了大块的内存。可以看到这个结构非常简单,就是一个指针指向下一块large,一个alloc指向数据。
接下来是ngx_pool_cleanup_s,这个结构用来表示内存池中的数据的清理handler。
其中handler表示清理函数。
data表示传递给清理函数的数据。
next表示下一个清理handler,也就是说当destroy这个pool的时候会遍历清理handler链表,然后调用handler。
通过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的大小的。
接下来我们来看如何从内存池中分配一块内存来使用。在nginx中有3个函数可以使用,分别是ngx_palloc,ngx_calloc,ngx_pnalloc。这三个函数的区别就是第一个函数分配的内存会对齐。第二个函数用来分配一块清0的内存,第三个函数分配的内存不会对齐。
由于这三个函数差不多,因此我们就只分析一个就够了。我们就来看ngx_palloc.
接下来就来看ngx_palloc_block的实现,这个函数主要就是重新分配一块内存池,然后链接到当前内存池的数据区指针。
然后要注意这里重新new的这个内存池大小是和它的父内存池一样大的。
并且新得内存池只保存了ngx_pool_data_t这个结构,也就是说数据区直接跟在ngx_pool_data_t下面。
这里解释一下为什么这样设置current,这里的主要原因是我们在ngx_palloc中分配内存是从current开始的,而这里也就是设置current为比较新分配的内存。而当failed大于4说明我们至少请求了4次内存分配,都不能满足我们的请求,此时我们就假设老的内存都已经没有空间了,因此我们就从比较新的内存块开始。
接下来是ngx_palloc_large,这个函数也是很简单就是malloc一块ngx_poll_large_t,然后链接到主的内存池上。
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,然后一一释放。
通过上面我们可以看到在nginx中内存池中的小块数据是从来不释放的,这样就简化了内存池的操作。
接下来我们来看buf的实现。
buf分为两种类型,一种是file,一种是memory.因此这里会有文件的一些操作域。
可以看到buf相对于pool多了一个pos域(file_pos).这里我们要知道我们发送往套接字异或者其他的设备,我们这里会现将数据放到buf中,然后当设备或者套接字准备好了,我们就会从buf中读取,因此这里pos指针就是放到buf中的已经被执行的数据(也就是已经送往套接字)的位置。
ok,接下来我们来看如何创建一个buf.在nginx中一般都是调用ngx_create_temp_buf来创建一个buf。函数很简单,就是从pool中分配内存然后初始化相关域。
然后我们来看chain的实现,chain其实也就是多个buf组合而成的。它主要是用来缓存一些未发出去的,或者接收的buf 以及 writev以及readv而存在的。
ok我们来看chain的实现,其实它的实现很简单,就是一个单链表。
然后来看如何创建一个chain。这里取得一个chain后直接返回给供其他模块使用:
接下来就是两个重要的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是一个回调函数,用来过滤输出。
剩下的就是一些标记域。
它对应的主要是ngx_output_chain函数。这个函数主要功能就是拷贝in chain的数据到buf域中。这个函数很复杂,我们这里简要分析一下:
这里我只是简要的分析了下,详细的还需要接合其他模块来看。
最后来看ngx_chain_writer_ctx_t,这个主要用于ustream(由于没看这个模块,因此不理解这里为什么要多出来个writer).大概看了,觉得应该是ustream模块发送的数据量比较大,因此这里通过这个chain来直接调用writev来将数据发送出去。
这里我们要知道out是会变化的。每次输出后,这个都会指向下一个需要发送的chain。
0.7.65, ,你也在研究nginx的源码?
这里内存池的一些图表可以看老朱同学的slides :
http://blog.zhuzhaoyuan.com/2009/09/nginx-internals-slides-video/
当内存池初始化的时候(下面会分析到)ngx_poll_s只相当于内存池的一个头,保存了当前内存池的一些必要信息而已。
当从内存池存取数据的时候,nginx是分为两种类型来处理得,一种是小块数据,它是直接从内存池中取得数据,另一方面,当为大块数据时,它是直接malloc一块数据(也就是从内存池外部分配数据),然后保存这个指针到内存池。可以看到很多内存池,比如py的内存池实现,也基本是这个思想。这里的细节,我们将会在下面分析内存池相关函数的时候详细分析。
这里还有一个要注意就是这些子内存池和父内存池是不一样的,我们后面分析函数的时候会详细介绍。
大小块数据得分割线是你创建内存池时传递进来的size和页大小之间的最小值。
下面就是内存池的结构:
struct ngx_pool_s { ///数据区的指针 ngx_pool_data_t d; ///其实也就是内存池所能容纳的最大值。 size_t max; ///指向当前的内存池的头。 ngx_pool_t *current; ///这个主要是为了讲所有的内存池都链接起来。(他会创建多个内存池的) ngx_chain_t *chain; ///这个链表表示大的数据块 ngx_pool_large_t *large; ///这个就是清理函数链表 ngx_pool_cleanup_t *cleanup; ngx_log_t *log; };
然后我们一个个来看上面的链表。首先是数据区的指针ngx_pool_data_t。
这个结构很简单,它就是包含了我们所需要操作这个内存池的数据的一些指针。
其中last表示当前的数据区的已经使用的数据的结尾。
end表示当前的内存池的结尾。也就是说end-last就是内存池未使用的大小。
我们要知道当我们从一个内存池请求一块内存时,如果此时内存池已经满掉,这是一般都是扩大内存池,而nginx中不是这么做的,它会直接再分配一个内存池,然后链接到data的next指针上。也就是说在nginx中,其实每个内存池都会包含一些子内存池。因此我们请求内存的时候都会遍历这些子内存池。
failed域主要是为了标记我们请求内存(小块内存)由于内存池空间不够,我们需要重新分配一个子内存池的次数。 下面分析函数的时候会再会看到这个域。
typedef struct { u_char *last; u_char *end; //指向下一块内存池 ngx_pool_t *next; ///失败标记 ngx_uint_t failed; } ngx_pool_data_t;
ngx_chain_t这里就先不介绍了,我们现在只需要知道它是与buf相关的。
然后是ngx_pool_large_s,它表示了大块的内存。可以看到这个结构非常简单,就是一个指针指向下一块large,一个alloc指向数据。
struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc; };
接下来是ngx_pool_cleanup_s,这个结构用来表示内存池中的数据的清理handler。
其中handler表示清理函数。
data表示传递给清理函数的数据。
next表示下一个清理handler,也就是说当destroy这个pool的时候会遍历清理handler链表,然后调用handler。
struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void *data; ngx_pool_cleanup_t *next; }
通过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的大小的。
///内存池的数据区的最大容量。 #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1) ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log) { ngx_pool_t *p; ///可以看到直接分配size大小,也就是说我们只能使用size-sizeof(ngx_poll_t)大小 p = ngx_alloc(size, log); if (p == NULL) { return NULL; } ///开始初始化数据区。 ///由于一开始数据区为空,因此last指向数据区的开始。 p->d.last = (u_char *) p + sizeof(ngx_pool_t); ///end也就是数据区的结束位置 p->d.end = (u_char *) p + size; p->d.next = NULL; p->d.failed = 0; ///这里才是我们真正能使用的大小。 size = size - sizeof(ngx_pool_t); ///然后设置max。内存池的最大值也就是size和最大容量之间的最小值。 p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; ///current表示当前的内存池。 p->current = p; ///其他的域置NULL。 p->chain = NULL; p->large = NULL; p->cleanup = NULL; p->log = log; ///返回指针。 return p; }
接下来我们来看如何从内存池中分配一块内存来使用。在nginx中有3个函数可以使用,分别是ngx_palloc,ngx_calloc,ngx_pnalloc。这三个函数的区别就是第一个函数分配的内存会对齐。第二个函数用来分配一块清0的内存,第三个函数分配的内存不会对齐。
由于这三个函数差不多,因此我们就只分析一个就够了。我们就来看ngx_palloc.
void * ngx_palloc(ngx_pool_t *pool, size_t size) { u_char *m; ngx_pool_t *p; ///首先判断当前申请的大小是否超过max,如果超过则说明是大块,此时进入large if (size <= pool->max) { ///得到当前的内存池指针。 p = pool->current; ///开始遍历内存池, do { ///首先对齐last指针。 m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); ///然后得到当前内存池中的可用大小。如果大于请求大小,则直接返回当前的last,也就是数据的指针。 if ((size_t) (p->d.end - m) >= size) { ///更新last,然后返回前面保存的last。 p->d.last = m + size; return m; } ///否则继续遍历 p = p->d.next; } while (p); ///到达这里说明内存池已经满掉,因此我们需要重新分配一个内存池然后链接到当前的data的next上。(紧接着我们会分析这个函数) return ngx_palloc_block(pool, size); } ///申请大块。 return ngx_palloc_large(pool, size); }
接下来就来看ngx_palloc_block的实现,这个函数主要就是重新分配一块内存池,然后链接到当前内存池的数据区指针。
然后要注意这里重新new的这个内存池大小是和它的父内存池一样大的。
并且新得内存池只保存了ngx_pool_data_t这个结构,也就是说数据区直接跟在ngx_pool_data_t下面。
static void * ngx_palloc_block(ngx_pool_t *pool, size_t size) { u_char *m; size_t psize; ngx_pool_t *p, *new, *current; ///计算当前的内存池的大小。 psize = (size_t) (pool->d.end - (u_char *) pool); ///再分配一个同样大小的内存池 m = ngx_alloc(psize, pool->log); if (m == NULL) { return NULL; } new = (ngx_pool_t *) m; ///接下来和我们create一个内存池做的操作一样。就是更新一些指针 new->d.end = m + psize; new->d.next = NULL; new->d.failed = 0; ///这里要注意了,可以看到last指针是指向ngx_pool_data_t的大小再加上要分配的size大小,也就是现在的内存池只包含了ngx_pool_data_t和数据。 m += sizeof(ngx_pool_data_t); m = ngx_align_ptr(m, NGX_ALIGNMENT); new->d.last = m + size; ///设置current。 current = pool->current; ///这里遍历所有的子内存池,这里主要是通过failed来标记重新分配子内存池的次数,然后找出最后一个大于4的,标记它的下一个子内存池为current。 for (p = current; p->d.next; p = p->d.next) { if (p->d.failed++ > 4) { current = p->d.next; } } ///链接到最后一个内存池后面 p->d.next = new; ///如果current为空,则current就为new。 pool->current = current ? current : new; return m; }
这里解释一下为什么这样设置current,这里的主要原因是我们在ngx_palloc中分配内存是从current开始的,而这里也就是设置current为比较新分配的内存。而当failed大于4说明我们至少请求了4次内存分配,都不能满足我们的请求,此时我们就假设老的内存都已经没有空间了,因此我们就从比较新的内存块开始。
接下来是ngx_palloc_large,这个函数也是很简单就是malloc一块ngx_poll_large_t,然后链接到主的内存池上。
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); if (p == NULL) { return NULL; } n = 0; ///开始遍历large链表,如果有alloc(也就是内存区指针)为空,则直接指针赋值然后返回。一般第一次请求大块内存都会直接进入这里。并且大块内存是可以被我们手动释放的。 for (large = pool->large; large; large = large->next) { if (large->alloc == NULL) { large->alloc = p; return p; } ///这里不太懂什么意思。 if (n++ > 3) { break; } } ///malloc一块ngx_pool_large_t。 large = ngx_palloc(pool, sizeof(ngx_pool_large_t)); if (large == NULL) { ngx_free(p); return NULL; } ///然后链接数据区指针p到large。这里可以看到直接插入到large链表的头的。 large->alloc = p; large->next = pool->large; pool->large = large; return p; }
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,然后一一释放。
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); } } ///free大块内存 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) { ///直接free掉。 ngx_free(p); if (n == NULL) { break; } } }
通过上面我们可以看到在nginx中内存池中的小块数据是从来不释放的,这样就简化了内存池的操作。
接下来我们来看buf的实现。
buf分为两种类型,一种是file,一种是memory.因此这里会有文件的一些操作域。
可以看到buf相对于pool多了一个pos域(file_pos).这里我们要知道我们发送往套接字异或者其他的设备,我们这里会现将数据放到buf中,然后当设备或者套接字准备好了,我们就会从buf中读取,因此这里pos指针就是放到buf中的已经被执行的数据(也就是已经送往套接字)的位置。
struct ngx_buf_s { ///pos表示已经执行的数据的位置。 u_char *pos; ///last和上面内存池中last一样,也就是使用的内存的最后一个字节的指针 u_char *last; ///文件指针 off_t file_pos; off_t file_last; ///buf的开始指针 u_char *start; /* start of buffer */ u_char *end; /* end of buffer */ ///这里表示这个buf从属于那个模块。 ngx_buf_tag_t tag; ngx_file_t *file; ngx_buf_t *shadow; ///一些标记 /* the buf's content could be changed */ unsigned temporary:1; ///在内存中是不能改变的。 unsigned memory:1; ///是否是mmap的内存 unsigned mmap:1; unsigned recycled:1; ///是否文件。 unsigned in_file:1; unsigned flush:1; unsigned sync:1; unsigned last_buf:1; unsigned last_in_chain:1; unsigned last_shadow:1; unsigned temp_file:1; /* STUB */ int num; };
ok,接下来我们来看如何创建一个buf.在nginx中一般都是调用ngx_create_temp_buf来创建一个buf。函数很简单,就是从pool中分配内存然后初始化相关域。
ngx_buf_t * ngx_create_temp_buf(ngx_pool_t *pool, size_t size) { ngx_buf_t *b; ///calloc一个buf,可以看到它调用的是calloc,也就是说都会清0. b = ngx_calloc_buf(pool); if (b == NULL) { return NULL; } ///然后从内存池中分配一块内存。并将这块内存链接到b->start. b->start = ngx_palloc(pool, size); if (b->start == NULL) { return NULL; } ///设置相关的域。 b->pos = b->start; b->last = b->start; ///设置打消 b->end = b->last + size; b->temporary = 1; return b; }
然后我们来看chain的实现,chain其实也就是多个buf组合而成的。它主要是用来缓存一些未发出去的,或者接收的buf 以及 writev以及readv而存在的。
ok我们来看chain的实现,其实它的实现很简单,就是一个单链表。
struct ngx_chain_s { ///buf ngx_buf_t *buf; ///下一个buf的指针。 ngx_chain_t *next; };
然后来看如何创建一个chain。这里取得一个chain后直接返回给供其他模块使用:
ngx_chain_t * ngx_alloc_chain_link(ngx_pool_t *pool) { ngx_chain_t *cl; ///取得pool的老的chain cl = pool->chain; ///如果chain已经存在,则直接返回这个chain,然后从pool的chain中删除这个chain。 if (cl) { pool->chain = cl->next; return cl; } ///否则从内存池重新new一个chain。这里注意新建的这个chain是链接到pool的chain上的。 cl = ngx_palloc(pool, sizeof(ngx_chain_t)); if (cl == NULL) { return NULL; } ///然后返回 return cl; }
接下来就是两个重要的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是一个回调函数,用来过滤输出。
剩下的就是一些标记域。
typedef struct { ngx_buf_t *buf; ngx_chain_t *in; ngx_chain_t *free; ngx_chain_t *busy; ///相关的标记,是否使用sendfile,是否使用directio等等。。 unsigned sendfile:1; unsigned directio:1; #if (NGX_HAVE_ALIGNED_DIRECTIO) unsigned unaligned:1; #endif unsigned need_in_memory:1; unsigned need_in_temp:1; ///内存池。 ngx_pool_t *pool; ///每次从pool中重新alloc一个buf这个值都会相应加一。 ngx_int_t allocated; ngx_bufs_t bufs; ///这个用来标记当前那个模块使用这个chain ngx_buf_tag_t tag; ngx_output_chain_filter_pt output_filter; void *filter_ctx; } ngx_output_chain_ctx_t;
它对应的主要是ngx_output_chain函数。这个函数主要功能就是拷贝in chain的数据到buf域中。这个函数很复杂,我们这里简要分析一下:
ngx_int_t ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in) { off_t bsize; ngx_int_t rc, last; ngx_chain_t *cl, *out, **last_out; ........................................... /* add the incoming buf to the chain ctx->in */ ///拷贝in 到ctx的in chain中。 if (in) { if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) { return NGX_ERROR; } } out = NULL; last_out = &out; last = NGX_NONE; ///开始循环处理ctx-in chain.这里有两层循环。 for ( ;; ) { ///开始遍历in while (ctx->in) { ///计算当前in的buf长度。这个长度也就是还没处理的数据长度。 bsize = ngx_buf_size(ctx->in->buf); .................................................. ///如果buf为空,则我们需要给buf分配空间。 if (ctx->buf == NULL) { ///这个函数很简单,主要是处理file buf,如果是file buf则会create一个buf链接到ctx rc = ngx_output_chain_align_file_buf(ctx, bsize); if (rc == NGX_ERROR) { return NGX_ERROR; } ///如果是memory buf,都会到这里 if (rc != NGX_OK) { ///如果free不为空,则我们从free chain中取得buf。 if (ctx->free) { /* get the free buf */ cl = ctx->free; ctx->buf = cl->buf; ctx->free = cl->next; ngx_free_chain(ctx->pool, cl); } else if (out || ctx->allocated == ctx->bufs.num) { break; } ///否则我们要重新create一个buf,然后链接到ctx,这里主要buf的大小和in chain的没有处理的数据一样大。 else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) { return NGX_ERROR; } } } ///从in chain拷贝数据到buf,并更新相关域。 rc = ngx_output_chain_copy_buf(ctx); if (rc == NGX_ERROR) { return rc; } if (rc == NGX_AGAIN) { if (out) { break; } return rc; } ///如果size为0,说明in chain中的第一个chain的数据已经被拷贝完了,此时删除这个chain。 if (ngx_buf_size(ctx->in->buf) == 0) { ctx->in = ctx->in->next; } ///重新分配一个 chain cl = ngx_alloc_chain_link(ctx->pool); if (cl == NULL) { return NGX_ERROR; } ///链接buf到cl cl->buf = ctx->buf; cl->next = NULL; *last_out = cl; last_out = &cl->next; ctx->buf = NULL; } if (out == NULL && last != NGX_NONE) { if (ctx->in) { return NGX_AGAIN; } return last; } ///调用回调函数 last = ctx->output_filter(ctx->filter_ctx, out); if (last == NGX_ERROR || last == NGX_DONE) { return last; } ///update 相关的chain,主要是将刚才copy完的buf 加入到busy chain,然后从busy chain中取出buf放到free chain中。 ngx_chain_update_chains(&ctx->free, &ctx->busy, &out, ctx->tag); last_out = &out; } }
这里我只是简要的分析了下,详细的还需要接合其他模块来看。
最后来看ngx_chain_writer_ctx_t,这个主要用于ustream(由于没看这个模块,因此不理解这里为什么要多出来个writer).大概看了,觉得应该是ustream模块发送的数据量比较大,因此这里通过这个chain来直接调用writev来将数据发送出去。
typedef struct { ///保存了所要输出的chain。 ngx_chain_t *out; ///这个保存了这次新加入的所需要输出的chain。 ngx_chain_t **last; ///这个表示当前连接 ngx_connection_t *connection; ngx_pool_t *pool; off_t limit; } ngx_chain_writer_ctx_t;
这里我们要知道out是会变化的。每次输出后,这个都会指向下一个需要发送的chain。
ngx_int_t ngx_chain_writer(void *data, ngx_chain_t *in) { ngx_chain_writer_ctx_t *ctx = data; off_t size; ngx_chain_t *cl; ngx_connection_t *c; c = ctx->connection; ///这里将in中的也就是新加如的chain ,全部复制到last中。也就是它保存了最后的数据。 for (size = 0; in; in = in->next) { .................................... ///计算大小。 size += ngx_buf_size(in->buf); cl = ngx_alloc_chain_link(ctx->pool); if (cl == NULL) { return NGX_ERROR; } ///加入last cl->buf = in->buf; cl->next = NULL; *ctx->last = cl; ctx->last = &cl->next; } ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0, "chain writer in: %p", ctx->out); ///遍历out chain for (cl = ctx->out; cl; cl = cl->next) { ///计算所需要输出的大小 size += ngx_buf_size(cl->buf); } if (size == 0 && !c->buffered) { return NGX_OK; } ///调用send_chain(一般是writev)来输出out中的数据。 ctx->out = c->send_chain(c, ctx->out, ctx->limit); ........................ return NGX_AGAIN; }
评论
2 楼
simohayha
2010-06-21
cocobear 写道
你分析的nginx代码是哪个版本的呢?
0.7.65, ,你也在研究nginx的源码?
1 楼
cocobear
2010-06-21
你分析的nginx代码是哪个版本的呢?
发表评论
-
nginx中sub_request的处理
2010-06-30 18:17 11872首先来看subrequest的处理。 什么是subreque ... -
nginx中handler的处理(二)
2010-05-30 18:33 12448这次我们来看各个phase ... -
nginx中handler的处理(一)
2010-05-20 01:09 13194nginx中的处理一个http的请求分为了8个phase,分别 ... -
nginx中的output chain的处理(二)
2010-05-09 13:45 10212接着上次的分析继续, ... -
nginx中锁的设计以及惊群的处理
2010-05-03 02:13 19434nginx中使用的锁是自己来实现的,这里锁的实现分为两种情况, ... -
nginx中的output chain的处理(一)
2010-04-24 00:59 7191这里我们详细来看ngx_linux_sendfile_chai ... -
nginx的filter的处理
2010-04-13 00:38 11784随笔拿一个nginx的filter模块来看,gzip模块,来看 ... -
nginx中request请求的解析
2010-04-04 01:15 12013ngx_http_init_request 中初始化event ... -
linux已经不存在惊群现象
2010-01-02 22:10 16043惊群也就是指多个进程 ... -
nginx的进程模型
2009-09-13 21:11 20364nginx采用的也是大部分http服务器的做法,就是maste ...
相关推荐
在IT行业中,内存管理是优化程序性能的关键因素之一。特别是在高并发环境中,频繁的内存分配和释放可能导致大量的开销。为了应对这一挑战,Nginx引入了一种名为“内存池”的内存管理机制。本文将深入探讨C++中Nginx...
### Nginx内存模型详解 #### 一、引言 Nginx作为一款高性能的Web服务器及反向代理服务器,在其设计与实现中充分考虑到了性能优化的重要性。尤其是在内存管理方面,Nginx采取了一种非常高效的方式,既保证了程序...
**Nginx内存池源码移植**... 移植Nginx内存池源码是一项技术性较强的工作,需要深入理解内存管理和Nginx内存池的工作原理。通过合理的移植和优化,可以大幅提升项目的内存管理效率,为系统的稳定性和性能打下坚实基础。
总结,Nginx 1.12.2中的Palloc优化是Nginx内存管理的一次重大改进,它通过预分配策略提升了内存分配的效率,减少了内存碎片,从而增强了Nginx在处理高并发请求时的表现。对于依赖Nginx作为基础架构的企业来说,理解...
**Nginx内存池原理与实现** Nginx作为一个高性能的HTTP和反向代理服务器,其内存管理机制是其高效运行的关键之一。其中,内存池(Memory Pool)是一种特殊的内存分配方式,它优化了内存的分配和释放过程,减少了...
内存池是一种内存管理技术,常用于高性能服务器如Nginx中,以提高内存分配和释放的效率。在C/C++编程中,传统的内存管理方式(如`malloc`和`free`)频繁地申请和释放内存可能导致碎片化,降低系统性能。而内存池通过...
nginx的slab的内存管理方式,这种方式的内存管理在nginx中,主要是与nginx 的共享内存协同使用的。nginx的slab管理与linux的slab管理相同的地方在于均是利用了内存 的缓存与对齐机制,slab内存管理中一些设计相当...
Nginx内存池的核心概念包括内存池结构体、内存块和子池。内存池结构体通常包含一个或多个内存块,每个内存块则由一系列连续的小块组成。当需要分配内存时,内存池会从已有的内存块中寻找合适大小的空闲块,如果找不...
内存池是一种优化内存分配策略的技术,它...通过内存池技术,Nginx能够提供高性能的服务,同时降低了内存管理和系统调用的开销。了解并熟练掌握Nginx的内存池机制,对于优化Nginx的配置和提升其运行效率具有重要意义。
Nginx UI 是一个全新的 Nginx 网络管理界面,旨在简化 Nginx 服务器的管理和配置。它提供实时服务器统计数据、ChatGPT 助手、一键部署、Let's Encrypt 证书的自动续签以及用户友好的网站配置编辑工具。此外,Nginx ...
标题中的"C++面向对象实现nginx内存池,带测试代码.zip"揭示了本次讨论的主要内容,即使用C++的面向对象编程技术实现一个针对Nginx的内存池,并提供了相关的测试代码。内存池是一种优化内存分配的技术,它预先申请一...
在实际部署中,还需要考虑其他因素,如健康检查(health checks)以确保Nginx只将请求转发给正常运行的Tomcat实例,SSL/TLS加密以保护数据安全,以及日志管理、错误处理和监控等运维方面的需求。 总的来说,"nginx...
3. **启动与管理**:使用 `nginx` 命令启动或停止服务,`systemd` 或其他服务管理工具可以用来控制 Nginx 的启动、停止和重启。 4. **配置**:编辑 `/etc/nginx/nginx.conf` 文件,根据需求设置服务器块、反向代理...
1. **性能优化**:新版本通常会进行性能上的改进,包括更快的响应速度、更高的并发处理能力以及更低的内存占用。 2. **安全增强**:定期的安全更新可确保抵御最新的网络威胁,修复已知漏洞,提高系统的安全性。 3. *...
Nginx是一个高性能的Web服务器和反向代理服务器,以其高并发、低内存消耗和稳定性能而闻名。在Ubuntu 18.04上安装Nginx通常通过官方软件仓库进行,但在此情况下,我们无法直接使用`apt-get`命令。因此,我们需要手动...
在IT行业中,内存管理是系统优化的关键环节,尤其是在高性能服务器软件如Nginx中。Nginx使用了一种称为“内存池”的特殊内存分配策略,名为`ngx_palloc`,以提高内存分配和释放的效率,减少碎片并优化性能。本文将...
Nginx是一个高性能的Web服务器和反向代理服务器,它以其高效的并发处理能力、低内存占用和稳定性而闻名。在1.23.2版本中,Nginx继续提供了优化和改进,以满足不断变化的互联网需求。这个版本可能是对之前版本的bug...
- **低内存消耗**:在 3 万个并发连接下,10 个 Nginx 进程仅消耗约 150MB 内存(每个进程平均消耗 15MB)。 - **简单的配置文件**:Nginx 的配置文件简洁明了,易于理解和维护。 - **成本效益**:Nginx 是一款开源...