- static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
- static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
- static ngx_int_t
- ngx_http_gzip_filter_init(ngx_conf_t *cf)
- {
- ngx_http_next_header_filter = ngx_http_top_header_filter;
- ngx_http_top_header_filter = ngx_http_gzip_header_filter;
- ngx_http_next_body_filter = ngx_http_top_body_filter;
- ngx_http_top_body_filter = ngx_http_gzip_body_filter;
- return NGX_OK;
- }
static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_gzip_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_gzip_body_filter; return NGX_OK; }
这里nginx处理filter将所有的过滤器做成一个类似链表的东东,每次声明一个ngx_http_next_header_filter以及ngx_http_next_body_filter来保存当前的最前面的filter,然后再将自己的filter处理函数赋值给ngx_http_top_header_filter以及ngx_http_top_body_filter ,这样也就是说最后面初始化的filter反而是最早处理。
而在模块本身的filter处理函数中会调用ngx_http_next_header_filter,也就是当前filter插入前的那个最top上的filter处理函数。
然后我们来看nginx如何启动filter的调用。
先来看head_filter的调用:
- ngx_int_t
- ngx_http_send_header(ngx_http_request_t *r)
- {
- if (r->err_status) {
- r->headers_out.status = r->err_status;
- r->headers_out.status_line.len = 0;
- }
- return ngx_http_top_header_filter(r);
- }
ngx_int_t ngx_http_send_header(ngx_http_request_t *r) { if (r->err_status) { r->headers_out.status = r->err_status; r->headers_out.status_line.len = 0; } return ngx_http_top_header_filter(r); }
可以看到当发送header的时候就是调用ngx_http_top_header_filter,nginx这里把status这些也作为一个filter模块来处理的。当启动ngx_http_top_header_filter之后所有的filter处理函数就会象链表一样被一个个的调用。
然后是body filter的调用,这个和header的类似,因此就不解释了。
- ngx_int_t
- ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
- {
- ngx_int_t rc;
- ngx_connection_t *c;
- c = r->connection;
- ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
- "http output filter \"%V?%V\"", &r->uri, &r->args);
- //启动body filter。
- rc = ngx_http_top_body_filter(r, in);
- ..............................................................
- return rc;
- }
ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_connection_t *c; c = r->connection; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http output filter \"%V?%V\"", &r->uri, &r->args); //启动body filter。 rc = ngx_http_top_body_filter(r, in); .............................................................. return rc; }
这里还有一个问题,那就是最后一个ngx_http_top_header_filter和ngx_http_top_body_filter是什么呢?也就是第一个被插入的filter。
先来看filter被初始化的地方。这里filter的初始化是在ngx_http_block函数中:
- for (m = 0; ngx_modules[m]; m++) {
- if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
- continue;
- }
- module = ngx_modules[m]->ctx;
- //如果存在postconfiguratio则调用初始化。
- if (module->postconfiguration) {
- if (module->postconfiguration(cf) != NGX_OK) {
- return NGX_CONF_ERROR;
- }
- }
- }
for (m = 0; ngx_modules[m]; m++) { if (ngx_modules[m]->type != NGX_HTTP_MODULE) { continue; } module = ngx_modules[m]->ctx; //如果存在postconfiguratio则调用初始化。 if (module->postconfiguration) { if (module->postconfiguration(cf) != NGX_OK) { return NGX_CONF_ERROR; } } }
代码很简单就是遍历ngx_modules然后调用初始化函数,而我们这里要找第一个filter,也就是ngx_modules中的第一个bodyfilter和header filter。
来看objs/ngx_modules.c中的ngx_module的定义:
- ngx_module_t *ngx_modules[] = {
- ..............................................................
- &ngx_http_write_filter_module,
- &ngx_http_header_filter_module,
- ........................................................................
- NULL
- };
ngx_module_t *ngx_modules[] = { .............................................................. &ngx_http_write_filter_module, &ngx_http_header_filter_module, ........................................................................ NULL };
可以看到ngx_http_write_filter_module和ngx_http_header_filter_module分别是body filter和header filter的第一个初始化模块,也就是filter链中的最后一个模块。
接下来我们就来详细分析这两个模块,首先是ngx_http_write_filter_module模块。
这个模块的功能起始很简单,就是遍历chain,然后输出所有的数据,如果有设置flush的话刷新chain。
这里要注意ngx_http_request_t中有一个out的chain,这个chain保存的是上一次还没有被发完的buf,这样每次我们接收到新的chain的话,就需要将新的chain连接到老的out chain上,然后再发出去。
来看代码。
- ngx_int_t
- ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
第一个是request请求,第二个参数是输入的chain。
先来看初始化部分:
- off_t size, sent, nsent, limit;
- ngx_uint_t last, flush;
- ngx_msec_t delay;
- ngx_chain_t *cl, *ln, **ll, *chain;
- ngx_connection_t *c;
- ngx_http_core_loc_conf_t *clcf;
- //得到当前所属的连接
- c = r->connection;
- if (c->error) {
- return NGX_ERROR;
- }
- size = 0;
- flush = 0;
- last = 0;
- //得到上次没有发送完毕的chain
- ll = &r->out;
off_t size, sent, nsent, limit; ngx_uint_t last, flush; ngx_msec_t delay; ngx_chain_t *cl, *ln, **ll, *chain; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; //得到当前所属的连接 c = r->connection; if (c->error) { return NGX_ERROR; } size = 0; flush = 0; last = 0; //得到上次没有发送完毕的chain ll = &r->out;
然后接下来这部分是校验并统计out chain,也就是上次没有完成的chain buf。
- for (cl = r->out; cl; cl = cl->next) {
- ll = &cl->next;
- #if 1
- //如果有0长度的buf则返回错误。
- if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
- ......................................................................
- ngx_debug_point();
- return NGX_ERROR;
- }
- #endif
- //得到buf的大小
- size += ngx_buf_size(cl->buf);
- //看当传输完毕后是否要刷新buf。
- if (cl->buf->flush || cl->buf->recycled) {
- flush = 1;
- }
- //看是否是最后一个buf
- if (cl->buf->last_buf) {
- last = 1;
- }
- }
for (cl = r->out; cl; cl = cl->next) { ll = &cl->next; #if 1 //如果有0长度的buf则返回错误。 if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { ...................................................................... ngx_debug_point(); return NGX_ERROR; } #endif //得到buf的大小 size += ngx_buf_size(cl->buf); //看当传输完毕后是否要刷新buf。 if (cl->buf->flush || cl->buf->recycled) { flush = 1; } //看是否是最后一个buf if (cl->buf->last_buf) { last = 1; } }
接下来这部分是用来链接新的chain到上面的out chain后面:
- for (ln = in; ln; ln = ln->next) {
- //
- cl = ngx_alloc_chain_link(r->pool);
- if (cl == NULL) {
- return NGX_ERROR;
- }
- cl->buf = ln->buf;
- //前面的代码我们知道ll已经指向out chain的最后一个位置了,因此这里就是将新的chain链接到out chain的后面。
- *ll = cl;
- ll = &cl->next;
- #if 1
- //校验buf
- if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
- ngx_debug_point();
- return NGX_ERROR;
- }
- #endif
- //计算大小
- size += ngx_buf_size(cl->buf);
- //判断是否需要flush
- if (cl->buf->flush || cl->buf->recycled) {
- flush = 1;
- }
- //判断是否是最后一个buf
- if (cl->buf->last_buf) {
- last = 1;
- }
- }
for (ln = in; ln; ln = ln->next) { // cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = ln->buf; //前面的代码我们知道ll已经指向out chain的最后一个位置了,因此这里就是将新的chain链接到out chain的后面。 *ll = cl; ll = &cl->next; #if 1 //校验buf if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { ngx_debug_point(); return NGX_ERROR; } #endif //计算大小 size += ngx_buf_size(cl->buf); //判断是否需要flush if (cl->buf->flush || cl->buf->recycled) { flush = 1; } //判断是否是最后一个buf if (cl->buf->last_buf) { last = 1; } }
然后接下来的这段代码主要是对进行发送前buf的一些标记的处理。
在看代码之前先来解释下几个比较重要的标记。
第一个是ngx_http_core_module的conf的一个标记postpone_output(conf里面可以配置的),这个表示延迟输出的阀,也就是说将要发送的字节数如果小于这个的话,并且还有另外几个条件的话(下面会解释),就会直接返回不发送当前的chain。
第二个是c->write->delayed,这个表示当前的连接的写必须要被delay了,也就是说现在不能发送了(原因下面会解释),得等另外的地方取消了delayed才能发送,此时我们修改连接的buffered的标记,然后返回NGX_AGAIN.
第三个是c->buffered,因为有时buf并没有发完,因此我们有时就会设置buffed标记,而我们可能会在多个filter模块中被buffered,因此下面就是buffered的类型。
- //这个并没有用到
- #define NGX_HTTP_LOWLEVEL_BUFFERED 0xf0
- //主要是这个,这个表示在最终的write filter中被buffered
- #define NGX_HTTP_WRITE_BUFFERED 0x10
- //判断是否有被设置
- #define NGX_LOWLEVEL_BUFFERED 0x0f
- //下面几个filter中被buffered
- #define NGX_HTTP_GZIP_BUFFERED 0x20
- #define NGX_HTTP_SSI_BUFFERED 0x01
- #define NGX_HTTP_SUB_BUFFERED 0x02
- #define NGX_HTTP_COPY_BUFFERED 0x04
//这个并没有用到 #define NGX_HTTP_LOWLEVEL_BUFFERED 0xf0 //主要是这个,这个表示在最终的write filter中被buffered #define NGX_HTTP_WRITE_BUFFERED 0x10 //判断是否有被设置 #define NGX_LOWLEVEL_BUFFERED 0x0f //下面几个filter中被buffered #define NGX_HTTP_GZIP_BUFFERED 0x20 #define NGX_HTTP_SSI_BUFFERED 0x01 #define NGX_HTTP_SUB_BUFFERED 0x02 #define NGX_HTTP_COPY_BUFFERED 0x04
然后我们来看第二个的意思,这个表示当前的chain已经被buffered了,
第四个是r->limit_rate,这个表示当前的request的发送限制速率,这个也是在nginx.conf中配置的,而一般就是通过这个值来设置c->write->delayed的。也就是说如果发送速率大于这个limit了的话,就设置delayed,然后这边的request就会延迟发送,下面我们的代码会看到nginx如何处理。
- clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
- //也就是说将要发送的字节数小于postpone_output并且不是最后一个buf,并且不需要刷新chain的话,就直接返回。
- if (!last && !flush && in && size < (off_t) clcf->postpone_output) {
- return NGX_OK;
- }
- ///如果设置了write的delayed,则设置标记。
- if (c->write->delayed) {
- c->buffered |= NGX_HTTP_WRITE_BUFFERED;
- return NGX_AGAIN;
- }
- //如果size为0,并且没有设置buffered标记,则进入清理工作。
- if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED)) {
- //如果是最后一个buf,则清理buffered标记然后清理out chain
- if (last) {
- r->out = NULL;
- c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
- return NGX_OK;
- }
- //如果有设置flush的话,则会强行传输当前buf之前的所有buf,因此这里就需要清理out chain。
- if (flush) {
- do {
- r->out = r->out->next;
- } while (r->out);
- //清理buf 标记
- c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
- return NGX_OK;
- }
- ngx_log_error(NGX_LOG_ALERT, c->log, 0,
- "the http output chain is empty");
- ngx_debug_point();
- return NGX_ERROR;
- }
- //如果有发送速率限制。
- if (r->limit_rate) {
- //计算是否有超过速率限制
- limit = r->limit_rate * (ngx_time() - r->start_sec + 1)
- - (c->sent - clcf->limit_rate_after);
- //如果有
- if (limit <= 0) {
- //设置delayed标记
- c->write->delayed = 1;
- //设置定时器
- ngx_add_timer(c->write,
- (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));
- //设置buffered。
- c->buffered |= NGX_HTTP_WRITE_BUFFERED;
- return NGX_AGAIN;
- }
- } else if (clcf->sendfile_max_chunk) {
- //sendfile所用到的limit。
- limit = clcf->sendfile_max_chunk;
- } else {
- limit = 0;
- }
- sent = c->sent;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); //也就是说将要发送的字节数小于postpone_output并且不是最后一个buf,并且不需要刷新chain的话,就直接返回。 if (!last && !flush && in && size < (off_t) clcf->postpone_output) { return NGX_OK; } ///如果设置了write的delayed,则设置标记。 if (c->write->delayed) { c->buffered |= NGX_HTTP_WRITE_BUFFERED; return NGX_AGAIN; } //如果size为0,并且没有设置buffered标记,则进入清理工作。 if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED)) { //如果是最后一个buf,则清理buffered标记然后清理out chain if (last) { r->out = NULL; c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; return NGX_OK; } //如果有设置flush的话,则会强行传输当前buf之前的所有buf,因此这里就需要清理out chain。 if (flush) { do { r->out = r->out->next; } while (r->out); //清理buf 标记 c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; return NGX_OK; } ngx_log_error(NGX_LOG_ALERT, c->log, 0, "the http output chain is empty"); ngx_debug_point(); return NGX_ERROR; } //如果有发送速率限制。 if (r->limit_rate) { //计算是否有超过速率限制 limit = r->limit_rate * (ngx_time() - r->start_sec + 1) - (c->sent - clcf->limit_rate_after); //如果有 if (limit <= 0) { //设置delayed标记 c->write->delayed = 1; //设置定时器 ngx_add_timer(c->write, (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1)); //设置buffered。 c->buffered |= NGX_HTTP_WRITE_BUFFERED; return NGX_AGAIN; } } else if (clcf->sendfile_max_chunk) { //sendfile所用到的limit。 limit = clcf->sendfile_max_chunk; } else { limit = 0; } sent = c->sent;
然后接下来这段就是发送buf,以及发送完的处理部分。这里要注意send_chain返回值为还没有发送完的chain,这个函数我后面的blog会详细的分析的。
- //调用发送函数。
- chain = c->send_chain(c, r->out, limit);
- ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
- "http write filter %p", chain);
- if (chain == NGX_CHAIN_ERROR) {
- c->error = 1;
- return NGX_ERROR;
- }
- //控制imit_rate,这个值一般是在nginx.conf中配置的。
- if (r->limit_rate) {
- nsent = c->sent;
- if (clcf->limit_rate_after) {
- sent -= clcf->limit_rate_after;
- if (sent < 0) {
- sent = 0;
- }
- nsent -= clcf->limit_rate_after;
- if (nsent < 0) {
- nsent = 0;
- }
- }
- delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate + 1);
- if (delay > 0) {
- c->write->delayed = 1;
- ngx_add_timer(c->write, delay);
- }
- } else if (c->write->ready
- && clcf->sendfile_max_chunk
- && (size_t) (c->sent - sent)
- >= clcf->sendfile_max_chunk - 2 * ngx_pagesize)
- {
- c->write->delayed = 1;
- ngx_add_timer(c->write, 1);
- }
- //开始遍历上一次还没有传输完毕的chain,如果这次没有传完的里面还有的话,就跳出循环,否则free这个chain
- for (cl = r->out; cl && cl != chain; /* void */) {
- ln = cl;
- cl = cl->next;
- ngx_free_chain(r->pool, ln);
- }
- ///out chain赋值
- r->out = chain;
- //如果chain存在,则设置buffered并且返回again。
- if (chain) {
- c->buffered |= NGX_HTTP_WRITE_BUFFERED;
- return NGX_AGAIN;
- }
- //否则清理buffered
- c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
- //如果有其他的filter buffered并且postponed被设置了,则我们返回again,也就是还有buf要处理。
- if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
- return NGX_AGAIN;
- }
- //否则返回ok
- return NGX_OK;
//调用发送函数。 chain = c->send_chain(c, r->out, limit); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http write filter %p", chain); if (chain == NGX_CHAIN_ERROR) { c->error = 1; return NGX_ERROR; } //控制imit_rate,这个值一般是在nginx.conf中配置的。 if (r->limit_rate) { nsent = c->sent; if (clcf->limit_rate_after) { sent -= clcf->limit_rate_after; if (sent < 0) { sent = 0; } nsent -= clcf->limit_rate_after; if (nsent < 0) { nsent = 0; } } delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate + 1); if (delay > 0) { c->write->delayed = 1; ngx_add_timer(c->write, delay); } } else if (c->write->ready && clcf->sendfile_max_chunk && (size_t) (c->sent - sent) >= clcf->sendfile_max_chunk - 2 * ngx_pagesize) { c->write->delayed = 1; ngx_add_timer(c->write, 1); } //开始遍历上一次还没有传输完毕的chain,如果这次没有传完的里面还有的话,就跳出循环,否则free这个chain for (cl = r->out; cl && cl != chain; /* void */) { ln = cl; cl = cl->next; ngx_free_chain(r->pool, ln); } ///out chain赋值 r->out = chain; //如果chain存在,则设置buffered并且返回again。 if (chain) { c->buffered |= NGX_HTTP_WRITE_BUFFERED; return NGX_AGAIN; } //否则清理buffered c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; //如果有其他的filter buffered并且postponed被设置了,则我们返回again,也就是还有buf要处理。 if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) { return NGX_AGAIN; } //否则返回ok return NGX_OK;
然后我们来看ngx_http_header_filter_module模块,这个模块的处理函数是ngx_http_header_filter。这个函数最终还是会调用ngx_http_write_filter来将head输出。
这个函数主要就是处理http的头域,然后设置对应的reponse值,最终输出。
这里header filter比较简单,这里没有什么复杂的东西,主要就是设置一些status。然后拷贝,最后通过ngx_http_write_filter进行发送
1.为什么需要内存池
为什么需要内存池?
a. 在大量的小块内存的申请和释放的时候,能更快地进行内存分配(对比malloc和free)
b.减少内存碎片,防止内存泄露。
2.内存池的原理
内存池的原理非常简单,用申请一块较大的内存来代替N多的小内存块,当有需要malloc一块
比较小的内存是,直接拿这块大的内存中的地址来用即可。
当然,这样处理的缺点也是很明显的,申请一块大的内存必然会导致内存空间的浪费,但是
比起频繁地malloc和free,这样做的代价是非常小的,这是典型的以空间换时间。
一个典型的内存池如下图所示:
图一:一个典型的内存池。
首先定义这样一个结构体:
typedef struct MemoryBlock { char *Data ; //数据 std::size_t DataSize ; //总的大小 std::size_t UsedSize ; //已经用了的大小 MemoryBlock*Next ; } MemoryBlock;
一个内存池就是这样一连串的内存块组成。当需要用到内存的时候,调用此内存池定义好的接口
GetMemory(),而需要删除的时候FreeMemory()。
而GetMemory和FreeMemory干了什么呢?GetMemory只是简单返回内存池中可用空间的地址。
而FreeMemory干了两件事情:一: 改变UsedSize 的值,二:重新初始化这一内存区域。
3.nginx中的内存池
3.1 nginx内存池的结构表示
首先我们看一下nginx内存池的定义:
struct ngx_pool_s { ngx_pool_data_t d;//表示数据区域 size_t max;//内存池能容纳数据的大小 ngx_pool_t * current;//当前内存池块(nginx中的内存池是又一连串的内存池链表组成的) ngx_chain_t* chain;//主要为了将内存池连接起来 ngx_pool_large_t* large;//大块的数据 ngx_pool_cleanup_t* cleanup;//清理函数 ngx_log_t* log;//写log };
nginx中的内存池和普通的有比较大的不同。nginx中的内存池是由N个内存池链表
组成的,当一个内存池满了以后,就会从下一个内存池中提取空间来使用。
对于ngx_pool_data_t的定义非常简单:
typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed; } ngx_pool_data_t;
其中last表示当前数据区域的已经使用的数据的结尾。
end表示当前内存池的结尾。
next表示下一个内存池,前面已经说过,再nignx中,当一个内存池空间
不足的时候,它不会扩大其空间,而是再新建一个内存池,组成一个内存池链表。
failed标志申请内存的时候失败的次数。
在理解了这个结构体后面的就非常简单了。
current 表示当前的内存池。
chain表示内存池链表。
large表示大块的数据。
对于ngx_pool_large_t定义如下:
struct ngx_pool_large_s { ngx_pool_large_t* next; void* alloc; };
此结构体的定义也是非常简单的。一个内存地址的指针已经指向下一个地址的指针。
这里再解释下为什么需要有large数据块。当一个申请的内存空间大小比内存池的大小还要大的时候,
malloc一块大的空间,再内存池用保留这个地址的指针。
Cleanup保持存着内存池被销毁的时候的清理函数。
typedef void (*ngx_pool_cleanup_pt)(void *data);struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void* data; ngx_pool_cleanup_t* next; };
ngx_pool_cleanup_pt 是一个函数指针的典型用法,
在这个结果中保存这需要清理的数据指针以及相应的清理函数, 让内存池销毁
或其他需要清理内存池的时候,可以调用此结构体中的handler。
下面是我画的一张nginx的内存池的结构图。
3.2 nginx内存池源代码分析
要大体了解一个内存池,只需要了解其池子的创建,内存的分配以及池子的销毁即可。下面就分析下
ngx_pool_t的这个3个方面。注:其中有些代码可能与ngx_pool中的源代码有所差异,但是整体意思
绝对是一样的,本人的修改,只是为了更好的分析,比如 我就把所有写log的过程都去掉了。
3.2.1 ngx_create_pool
创建一个内存池
ngx_pool_t* ngx_create_poo(size_t size) { ngx_pool_t* p; p = (ngx_pool_t*)malloc(size); if (!p){ 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;//最大数据 //我现在还是是一个单一的内存池 p->current = p; p->chain = NULL; //只有在需要的时候才分配大的内存区域 p->large = NULL; p->cleanup = NULL; return p; }
nginx内存池的创建非常简单,申请一开size大小的内存,把它分配给 ngx_poo_t。
3.2.2 ngx_palloc
从内存池中分配内存.
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 = p->d->last; if ((size_t)(p->d->end - m) >= size) { p->d->last = m + size;//用掉了当然要改变*last了 return m; } p = p->d->next; } while (p); return ngx_palloc_block(pool, size); //所有的内存池都已经满了,我要再增加一个 } //申请的内存超过了内存池的大小,所以用 return ngx_palloc_large(pool, size); }
这个函数从内存池用拿出内存,如果当前内存池已满,到下一个内存池,如果所有的内存池已满,
增加一个新的内存池,如果申请的内存超过了内存池的最大值,从*large中分配
3.3.3 ngx_destroy_pool
内存池的销毁
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) { c->handler(c->data); } } //释放大块的内存 for (l = pool->large; l; l = l->next) { if (l->alloc) { free(l->alloc); } } //小块的内存,真正意义上的内存池 for (p = pool, n = pool->d->next; /* void */; p = n, n = n->d->next) { free(p); //如果当前内存池为空,之后的毕为空 if (n == NULL) { break; } } }
销毁一个内存池其实就是干了三件事, 调用清理韩式, 释放大块的内存,释放内存池,需要注意的
一点是在nginx中, 小块内存除了在内存池被销毁的时候都是不能被释放的。
3.3.4 ngx_palloc_block
前面说过,在nginx中,当内存池满了以后,会增加一个新的内存池。这个动作就是靠ngx_palloc_block
函数实现的。
static void* ngx_palloc_block(ngx_pool_t* pool, size_t size) { u_char *m; size_t psize; ngx_pool_t *p, *pnew, *current; psize = (size_t) (pool->d->end - (u_char *) pool); m = (u_char*)malloc(psize); if (!m){ return NULL; } //一个新的内存池 pnew = (ngx_pool_t*) m; pnew->d->end = m +psize; pnew->d->next = NULL; pnew->d->failed = 0; //是不是和ngx_palloc很相似啊 m += sizeof(ngx_pool_data_t); pnew->d->last = m + size; current = pool->current; //遍历到内存池链表的末尾 for (p = current; p->d->next; p = p->d->next) { if (p->d->failed++ > 4) {//为什么4?推测是个经验值 current = p->d->next; } } p->d->next = pnew; pool->current = current ? current : pnew; return m; }
这个函数就是申请了一块内存区域,变为一个内存池,然后把它连接到原来内存池的末尾。
3.3.5 ngx_palloc_large 和ngx_pfree
在nginx中,小块内存除了在内存池销毁之外是不能释放的,但是大块内存却可以,这两个
函数就是用来控制大块内存的申请和释放, 代码也非常简单,调用malloc申请内存,连接到
ngx_pool_large_t中 和 调用free释放内存。这里就不贴上代码了。
4. 小结
不知不觉,写了快一个下午的时间了,真快啊。
nginx的内存池的代码也先介绍到这里,其实nginx内存池功能强大,所以代码也比较复杂,
这里只
1踩
nginx中的output chain的处理(一)
文章分类:C++编程一般来说,我们最终都会调用这个函数来发送最终的数据,因此我们来着重分析这个函数,这里主要就是对buf的一些参数的理解。
来看函数原型:
- ngx_chain_t *
- ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
ngx_chain_t * ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
第一个参数是当前的连接,第二个参数是所需要发送的chain,第三个参数是所能发送的最大值。
然后来看这里的几个重要的变量:
- send 表示将要发送的buf已经已经发送的大小。
- sent表示已经发送的buf的大小
- prev_send 表示上一次发送的大小,也就是已经发送的buf的大小。
- fprev 和prev-send类似,只不过是file类型的。
- complete表示是否buf被完全发送了,也就是sent是否等于send - prev_send.
- header表示需要是用writev来发送的buf。也就是only in memory的buf。
- struct iovec *iov, headers[NGX_HEADERS] 这个主要是用于sendfile和writev的参数,这里注意上面header数组保存的就是iovec。
send 表示将要发送的buf已经已经发送的大小。 sent表示已经发送的buf的大小 prev_send 表示上一次发送的大小,也就是已经发送的buf的大小。 fprev 和prev-send类似,只不过是file类型的。 complete表示是否buf被完全发送了,也就是sent是否等于send - prev_send. header表示需要是用writev来发送的buf。也就是only in memory的buf。 struct iovec *iov, headers[NGX_HEADERS] 这个主要是用于sendfile和writev的参数,这里注意上面header数组保存的就是iovec。
然后我们来看初始化
- wev = c->write;
- if (!wev->ready) {
- return in;
- }
- if (limit == 0 || limit > (off_t) (NGX_SENDFILE_LIMIT - ngx_pagesize)) {
- limit = NGX_SENDFILE_LIMIT - ngx_pagesize;
- }
- send = 0;
- //设置header,也就是in memory的数组
- header.elts = headers;
- header.size = sizeof(struct iovec);
- header.nalloc = NGX_HEADERS;
- header.pool = c->pool;
wev = c->write; if (!wev->ready) { return in; } if (limit == 0 || limit > (off_t) (NGX_SENDFILE_LIMIT - ngx_pagesize)) { limit = NGX_SENDFILE_LIMIT - ngx_pagesize; } send = 0; //设置header,也就是in memory的数组 header.elts = headers; header.size = sizeof(struct iovec); header.nalloc = NGX_HEADERS; header.pool = c->pool;
这里nginx的处理核心思想就是合并内存连续并相邻的buf(不管是in memory还是in file)
下面这段代码就是处理in memory的部分,然后将buf放入对应的iovec数组。
- //开始遍历
- for (cl = in;
- cl && header.nelts < IOV_MAX && send < limit;
- cl = cl->next)
- {
- if (ngx_buf_special(cl->buf)) {
- continue;
- }
- //如果不止是在buf中,这是因为有时in file的文件我们可能需要内存中也有拷贝,所以如果一个buf同时in memoey和in file的话,nginx会认为它是in file的来处理。
- if (!ngx_buf_in_memory_only(cl->buf)) {
- break;
- }
- //得到buf的大小
- size = cl->buf->last - cl->buf->pos;
- //大于limit的话修改为size
- if (send + size > limit) {
- size = limit - send;
- }
- //如果prev等于pos,则说明当前的buf的数据和前一个buf的数据是连续的。
- if (prev == cl->buf->pos) {
- iov->iov_len += (size_t) size;
- } else {
- //否则说明是不同的buf,因此add一个iovc。
- iov = ngx_array_push(&header);
- if (iov == NULL) {
- return NGX_CHAIN_ERROR;
- }
- iov->iov_base = (void *) cl->buf->pos;
- iov->iov_len = (size_t) size;
- }
- //这里可以看到prev保存了当前buf的结尾。
- prev = cl->buf->pos + (size_t) size;
- //更新发送的大小
- send += size;
- }
//开始遍历 for (cl = in; cl && header.nelts < IOV_MAX && send < limit; cl = cl->next) { if (ngx_buf_special(cl->buf)) { continue; } //如果不止是在buf中,这是因为有时in file的文件我们可能需要内存中也有拷贝,所以如果一个buf同时in memoey和in file的话,nginx会认为它是in file的来处理。 if (!ngx_buf_in_memory_only(cl->buf)) { break; } //得到buf的大小 size = cl->buf->last - cl->buf->pos; //大于limit的话修改为size if (send + size > limit) { size = limit - send; } //如果prev等于pos,则说明当前的buf的数据和前一个buf的数据是连续的。 if (prev == cl->buf->pos) { iov->iov_len += (size_t) size; } else { //否则说明是不同的buf,因此add一个iovc。 iov = ngx_array_push(&header); if (iov == NULL) { return NGX_CHAIN_ERROR; } iov->iov_base = (void *) cl->buf->pos; iov->iov_len = (size_t) size; } //这里可以看到prev保存了当前buf的结尾。 prev = cl->buf->pos + (size_t) size; //更新发送的大小 send += size; }
然后是in file的处理这里比较核心的一个判断就是fprev == cl->buf->file_pos,和上面的in memory类似,fprev保存的就是上一次处理的buf的尾部。这里如果这两个相等,那就说明当前的两个buf是连续的(文件连续).
ok.来看代码。
- //可以看到如果header的大小不为0则说明前面有需要发送的buf,因此我们就跳过in file处理
- if (header.nelts == 0 && cl && cl->buf->in_file && send < limit) {
- //得到file
- file = cl->buf;
- //开始合并。
- do {
- //得到大小
- size = cl->buf->file_last - cl->buf->file_pos;
- //如果太大则进行对齐处理。
- if (send + size > limit) {
- size = limit - send;
- aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
- & ~((off_t) ngx_pagesize - 1);
- if (aligned <= cl->buf->file_last) {
- size = aligned - cl->buf->file_pos;
- }
- }
- //设置file_size.
- file_size += (size_t) size;
- //设置需要发送的大小
- send += size;
- //和上面的in memory处理一样就是保存这次的last
- fprev = cl->buf->file_pos + size;
- cl = cl->next;
- } while (cl
- && cl->buf->in_file
- && send < limit
- && file->file->fd == cl->buf->file->fd
- && fprev == cl->buf->file_pos);
- }
//可以看到如果header的大小不为0则说明前面有需要发送的buf,因此我们就跳过in file处理 if (header.nelts == 0 && cl && cl->buf->in_file && send < limit) { //得到file file = cl->buf; //开始合并。 do { //得到大小 size = cl->buf->file_last - cl->buf->file_pos; //如果太大则进行对齐处理。 if (send + size > limit) { size = limit - send; aligned = (cl->buf->file_pos + size + ngx_pagesize - 1) & ~((off_t) ngx_pagesize - 1); if (aligned <= cl->buf->file_last) { size = aligned - cl->buf->file_pos; } } //设置file_size. file_size += (size_t) size; //设置需要发送的大小 send += size; //和上面的in memory处理一样就是保存这次的last fprev = cl->buf->file_pos + size; cl = cl->next; } while (cl && cl->buf->in_file && send < limit && file->file->fd == cl->buf->file->fd && fprev == cl->buf->file_pos); }
然后就是发送部分,这里in file使用sendfile,in memory使用writev.这里处理比较简单,就是发送然后判断发送的大小
- if (file) {
- #if 1
- if (file_size == 0) {
- ngx_debug_point();
- return NGX_CHAIN_ERROR;
- }
- #endif
- #if (NGX_HAVE_SENDFILE64)
- offset = file->file_pos;
- #else
- offset = (int32_t) file->file_pos;
- #endif
- //发送数据
- rc = sendfile(c->fd, file->file->fd, &offset, file_size);
- ......................................................
- //得到发送的字节数
- sent = rc > 0 ? rc : 0;
- } else {
- rc = writev(c->fd, header.elts, header.nelts);
- .......................................................................
- sent = rc > 0 ? rc : 0;
- }
if (file) { #if 1 if (file_size == 0) { ngx_debug_point(); return NGX_CHAIN_ERROR; } #endif #if (NGX_HAVE_SENDFILE64) offset = file->file_pos; #else offset = (int32_t) file->file_pos; #endif //发送数据 rc = sendfile(c->fd, file->file->fd, &offset, file_size); ...................................................... //得到发送的字节数 sent = rc > 0 ? rc : 0; } else { rc = writev(c->fd, header.elts, header.nelts); ....................................................................... sent = rc > 0 ? rc : 0; }
接下来这部分就是更新标记的部分,主要是buf的标记。
这里要注意一个地方,那就是ngx_buf_size部分,这个宏很简单就是判断buf是不是在memory中,如果是的话,就用pos和last计算,否则认为是在file中。
可是这里就有个问题了,如果一个buf本来是在file中的,我们由于某种原因,在内存中也有一份拷贝,可是我们并没有修改内存中的副本,于是如果我们还需要切割这个buf,这个时候,如果last和pos也就是buf对应的指针没有设置正确的话,这里就会出现问题了。
这里我觉得应该还有个标记,那就是如果内存中的副本我只是只读的话,发送的时候不应该算它在memory中。
- //如果send - prev_send == sent则说明该发送的都发完了。
- if (send - prev_send == sent) {
- complete = 1;
- }
- //更新congnect的sent域。
- c->sent += sent;
- //开始重新遍历chain,这里是为了防止没有发送完全的情况,此时我们就需要切割buf了。
- for (cl = in; cl; cl = cl->next) {
- if (ngx_buf_special(cl->buf)) {
- continue;
- }
- if (sent == 0) {
- break;
- }
- //得到buf size
- size = ngx_buf_size(cl->buf);
- //如果大于当前的size,则说明这个buf的数据已经被完全发送完毕了。,因此更新它的域。
- if (sent >= size){
- //更新sent域
- sent -= size;
- //如果在内存则更新pos
- if (ngx_buf_in_memory(cl->buf)) {
- cl->buf->pos = cl->buf->last;
- }
- //如果在file
- if (cl->buf->in_file) {
- cl->buf->file_pos = cl->buf->file_last;
- }
- continue;
- }
- //到这里说明当前的buf只有一部分被发送出去了,因此这里我们只需要修改指针。以便于下次发送。
- if (ngx_buf_in_memory(cl->buf)) {
- cl->buf->pos += (size_t) sent;
- }
- //同上。
- if (cl->buf->in_file) {
- cl->buf->file_pos += sent;
- }
- break;
- }
//如果send - prev_send == sent则说明该发送的都发完了。 if (send - prev_send == sent) { complete = 1; } //更新congnect的sent域。 c->sent += sent; //开始重新遍历chain,这里是为了防止没有发送完全的情况,此时我们就需要切割buf了。 for (cl = in; cl; cl = cl->next) { if (ngx_buf_special(cl->buf)) { continue; } if (sent == 0) { break; } //得到buf size size = ngx_buf_size(cl->buf); //如果大于当前的size,则说明这个buf的数据已经被完全发送完毕了。,因此更新它的域。 if (sent >= size){ //更新sent域 sent -= size; //如果在内存则更新pos if (ngx_buf_in_memory(cl->buf)) { cl->buf->pos = cl->buf->last; } //如果在file if (cl->buf->in_file) { cl->buf->file_pos = cl->buf->file_last; } continue; } //到这里说明当前的buf只有一部分被发送出去了,因此这里我们只需要修改指针。以便于下次发送。 if (ngx_buf_in_memory(cl->buf)) { cl->buf->pos += (size_t) sent; } //同上。 if (cl->buf->in_file) { cl->buf->file_pos += sent; } break; }
最后一部分就是一些是否退出循环的操作。这里要注意,nginx中如果发送未完全的话,将会直接返回的,返回的就是没有发送完毕的chain,它的buf也已经被更新。这是因为nginx是单线程的,不能有任何意义的空跑和阻塞,因此当complete为0,nginx就认为是系统负载过大,此时直接返回,然后处理其他的事情,等待和下次的chain一起发送。
- if (eintr) {
- continue;
- }
- //如果未完成,则返回。
- if (!complete) {
- wev->ready = 0;
- return cl;
- }
- if (send >= limit || cl == NULL) {
- return cl;
- }
- //更新in,也就是开始处理下一个chain
- in = cl;
- 是列出了内存池的大体流程,还有很到一部分代码未列出来
- static ngx_int_t
- ngx_http_copy_filter_init(ngx_conf_t *cf)
- {
- ngx_http_next_filter = ngx_http_top_body_filter;
- ngx_http_top_body_filter = ngx_http_copy_filter;
- return NGX_OK;
- }
static ngx_int_t ngx_http_copy_filter_init(ngx_conf_t *cf) { ngx_http_next_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_copy_filter; return NGX_OK; }
可以看到,它只有body filter,而没有header filter,也就是说只有body filter才会使用这个filter。
然后这个模块对应也有一个命令,那就是output_buffers,这个命令保存值在它的conf的bufs中:
- typedef struct {
- ngx_bufs_t bufs;
- } ngx_http_copy_filter_conf_t;
typedef struct { ngx_bufs_t bufs; } ngx_http_copy_filter_conf_t;
这里要知道在nginx的配置文件中所有的bufs的格式都是一样,个数+每个的大小。这个值我们接下来分析filter代码的时候会再次看到。
然后来看对应的merge方法,来看这个bufs的默认值是多少。
- static char *
- ngx_http_copy_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
- {
- ngx_http_copy_filter_conf_t *prev = parent;
- ngx_http_copy_filter_conf_t *conf = child;
- //默认是1个buf,大小为32768字节
- ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 1, 32768);
- return NULL;
- }
static char * ngx_http_copy_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_copy_filter_conf_t *prev = parent; ngx_http_copy_filter_conf_t *conf = child; //默认是1个buf,大小为32768字节 ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 1, 32768); return NULL; }
由于copy filter没有header filter,因此它的context的初始化也是放在body filter中的,而它的ctx就是ngx_output_chain_ctx_t,为什么名字是output_chain呢,这是因为copy filter的主要逻辑的处理都是放在ngx_output_chain中的,这个模块我们可以看到它是保存在core目录下的,而不是属于http目录的。
接下来我们就来看这个context的结构。
- typedef struct {
- //保存临时的buf
- ngx_buf_t *buf;
- //保存了将要发送的chain
- ngx_chain_t *in;
- //保存了已经发送完毕的chain,以便于重复利用
- ngx_chain_t *free;
- //保存了还未发送的chain
- ngx_chain_t *busy;
- //sendfile标记
- unsigned sendfile:1;
- //directio标记
- unsigned directio:1;
- #if (NGX_HAVE_ALIGNED_DIRECTIO)
- unsigned unaligned:1;
- #endif
- //是否需要在内存中保存一份(使用sendfile的话,内存中没有文件的拷贝的,而我们有时需要处理文件,此时就需要设置这个标记)
- unsigned need_in_memory:1;
- //是否存在的buf复制一份,这里不管是存在在内存还是文件,后面会看到这两个标记的区别。
- unsigned need_in_temp:1;
- ngx_pool_t *pool;
- //已经allocated的大小
- ngx_int_t allocated;
- //对应的bufs的大小,这个值就是我们loc conf中设置的bufs
- ngx_bufs_t bufs;
- //表示现在处于那个模块(因为upstream也会调用output_chain)
- ngx_buf_tag_t tag;
- //这个值一般是ngx_http_next_filter,也就是继续调用filter链
- ngx_output_chain_filter_pt output_filter;
- //当前filter的上下文,这里也是由于upstream也会调用output_chain
- void *filter_ctx;
- } ngx_output_chain_ctx_t;
typedef struct { //保存临时的buf ngx_buf_t *buf; //保存了将要发送的chain ngx_chain_t *in; //保存了已经发送完毕的chain,以便于重复利用 ngx_chain_t *free; //保存了还未发送的chain ngx_chain_t *busy; //sendfile标记 unsigned sendfile:1; //directio标记 unsigned directio:1; #if (NGX_HAVE_ALIGNED_DIRECTIO) unsigned unaligned:1; #endif //是否需要在内存中保存一份(使用sendfile的话,内存中没有文件的拷贝的,而我们有时需要处理文件,此时就需要设置这个标记) unsigned need_in_memory:1; //是否存在的buf复制一份,这里不管是存在在内存还是文件,后面会看到这两个标记的区别。 unsigned need_in_temp:1; ngx_pool_t *pool; //已经allocated的大小 ngx_int_t allocated; //对应的bufs的大小,这个值就是我们loc conf中设置的bufs ngx_bufs_t bufs; //表示现在处于那个模块(因为upstream也会调用output_chain) ngx_buf_tag_t tag; //这个值一般是ngx_http_next_filter,也就是继续调用filter链 ngx_output_chain_filter_pt output_filter; //当前filter的上下文,这里也是由于upstream也会调用output_chain void *filter_ctx; } ngx_output_chain_ctx_t;
接下来我们来看具体函数的实现,就能更好的理解context中的这些域的意思。
来看copy_filter的body filter。
- static ngx_int_t
- ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)
- {
- ngx_int_t rc;
- ngx_connection_t *c;
- ngx_output_chain_ctx_t *ctx;
- ngx_http_copy_filter_conf_t *conf;
- c = r->connection;
- //获取ctx
- ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module);
- //如果为空,则说明需要初始化ctx
- if (ctx == NULL) {
- conf = ngx_http_get_module_loc_conf(r, ngx_http_copy_filter_module);
- ctx = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t));
- if (ctx == NULL) {
- return NGX_ERROR;
- }
- ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module);
- //设置对应的域
- ctx->sendfile = c->sendfile;
- //可以看到如果我们给request设置filter_need_in_memory的话,ctx的这个域就会被设置
- ctx->need_in_memory = r->main_filter_need_in_memory
- || r->filter_need_in_memory;
- //和上面类似
- ctx->need_in_temp = r->filter_need_temporary;
- ctx->pool = r->pool;
- ctx->bufs = conf->bufs;
- ctx->tag = (ngx_buf_tag_t) &ngx_http_copy_filter_module;
- //可以看到output_filter就是body filter的next
- ctx->output_filter = (ngx_output_chain_filter_pt) ngx_http_next_filter;
- //此时filter ctx为当前的请求
- ctx->filter_ctx = r;
- r->request_output = 1;
- }
- //最关键的函数,下面会详细分析。
- rc = ngx_output_chain(ctx, in);
- ......................................................
- return rc;
- }
static ngx_int_t ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_connection_t *c; ngx_output_chain_ctx_t *ctx; ngx_http_copy_filter_conf_t *conf; c = r->connection; //获取ctx ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module); //如果为空,则说明需要初始化ctx if (ctx == NULL) { conf = ngx_http_get_module_loc_conf(r, ngx_http_copy_filter_module); ctx = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module); //设置对应的域 ctx->sendfile = c->sendfile; //可以看到如果我们给request设置filter_need_in_memory的话,ctx的这个域就会被设置 ctx->need_in_memory = r->main_filter_need_in_memory || r->filter_need_in_memory; //和上面类似 ctx->need_in_temp = r->filter_need_temporary; ctx->pool = r->pool; ctx->bufs = conf->bufs; ctx->tag = (ngx_buf_tag_t) &ngx_http_copy_filter_module; //可以看到output_filter就是body filter的next ctx->output_filter = (ngx_output_chain_filter_pt) ngx_http_next_filter; //此时filter ctx为当前的请求 ctx->filter_ctx = r; r->request_output = 1; } //最关键的函数,下面会详细分析。 rc = ngx_output_chain(ctx, in); ...................................................... return rc; }
然后就是ngx_output_chain这个函数了,这里nginx filter的主要逻辑都在这个函数里面。下面就是这个函数的原型。
- ngx_int_t
- ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)
ngx_int_t ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)
然后我们来分段看它的代码,下面这段代码可以说是一个short path,也就是说我们能直接确定所有的in chain都不需要复制的时候,我们就可以直接调用output_filter来交给剩下的filter去处理。
- if (ctx->in == NULL && ctx->busy == NULL) {
- //下面的注释解释的很详细
- /*
- * the short path for the case when the ctx->in and ctx->busy chains
- * are empty, the incoming chain is empty too or has the single buf
- * that does not require the copy
- */
- if (in == NULL) {
- return ctx->output_filter(ctx->filter_ctx, in);
- }
- //这里说明只有一个chain,并且它的buf不需要复制
- if (in->next == NULL
- #if (NGX_SENDFILE_LIMIT)
- && !(in->buf->in_file && in->buf->file_last > NGX_SENDFILE_LIMIT)
- #endif
- && ngx_output_chain_as_is(ctx, in->buf))
- {
- return ctx->output_filter(ctx->filter_ctx, in);
- }
- }
if (ctx->in == NULL && ctx->busy == NULL) { //下面的注释解释的很详细 /* * the short path for the case when the ctx->in and ctx->busy chains * are empty, the incoming chain is empty too or has the single buf * that does not require the copy */ if (in == NULL) { return ctx->output_filter(ctx->filter_ctx, in); } //这里说明只有一个chain,并且它的buf不需要复制 if (in->next == NULL #if (NGX_SENDFILE_LIMIT) && !(in->buf->in_file && in->buf->file_last > NGX_SENDFILE_LIMIT) #endif && ngx_output_chain_as_is(ctx, in->buf)) { return ctx->output_filter(ctx->filter_ctx, in); } }
上面我们看到了一个函数 ngx_output_chain_as_is,这个函数很关键,下面还会再次被调用,这个函数主要用来判断是否需要复制buf。返回1,表示不需要拷贝,否则为需要拷贝
- static ngx_inline ngx_int_t
- ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf)
- {
- ngx_uint_t sendfile;
- //是否为specialbuf,是的话返回1,也就是不用拷贝
- if (ngx_buf_special(buf)) {
- return 1;
- }
- //如果buf在文件中,并且使用了directio的话,需要拷贝buf
- if (buf->in_file && buf->file->directio) {
- return 0;
- }
- //sendfile标记
- sendfile = ctx->sendfile;
- #if (NGX_SENDFILE_LIMIT)
- //如果pos大于sendfile的限制,设置标记为0
- if (buf->in_file && buf->file_pos >= NGX_SENDFILE_LIMIT) {
- sendfile = 0;
- }
- #endif
- if (!sendfile) {
- //此时如果buf不在内存中,则我们就需要复制到内存一份。
- if (!ngx_buf_in_memory(buf)) {
- return 0;
- }
- //否则设置in_file为0.
- buf->in_file = 0;
- }
- //如果需要内存中有一份拷贝,而并不在内存中,此时返回0,表示需要拷贝
- if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) {
- return 0;
- }
- //如果需要内存中有拷贝,并且存在于内存中或者mmap中,则返回0.
- if (ctx->need_in_temp && (buf->memory || buf->mmap)) {
- return 0;
- }
- return 1;
- }
static ngx_inline ngx_int_t ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) { ngx_uint_t sendfile; //是否为specialbuf,是的话返回1,也就是不用拷贝 if (ngx_buf_special(buf)) { return 1; } //如果buf在文件中,并且使用了directio的话,需要拷贝buf if (buf->in_file && buf->file->directio) { return 0; } //sendfile标记 sendfile = ctx->sendfile; #if (NGX_SENDFILE_LIMIT) //如果pos大于sendfile的限制,设置标记为0 if (buf->in_file && buf->file_pos >= NGX_SENDFILE_LIMIT) { sendfile = 0; } #endif if (!sendfile) { //此时如果buf不在内存中,则我们就需要复制到内存一份。 if (!ngx_buf_in_memory(buf)) { return 0; } //否则设置in_file为0. buf->in_file = 0; } //如果需要内存中有一份拷贝,而并不在内存中,此时返回0,表示需要拷贝 if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) { return 0; } //如果需要内存中有拷贝,并且存在于内存中或者mmap中,则返回0. if (ctx->need_in_temp && (buf->memory || buf->mmap)) { return 0; } return 1; }
上面有两个标记要注意,一个是need_in_memory ,这个主要是用于当我们使用sendfile的时候,nginx并不会将请求文件拷贝到内存中,而有时我们需要操作文件的内容,此时我们就需要设置这个标记(设置方法前面初始化有介绍).然后我们在body filter就能操作内容了。
第二个是need_in_temp,这个主要是用于把本来就存在于内存中的buf复制一份拷贝出来,这里有用到的模块有charset,也就是编解码 filter.
然后接下来这段是复制in chain到ctx->in的结尾.它是通过调用ngx_output_chain_add_copy来进行add copy的,这个函数比较简单,这里就不分析了,不过只有一个要注意的,那就是如果buf是存在于文件中,并且file_pos超过了sendfile limit,此时就会切割buf为两个buf,然后保存在两个chain中,最终连接起来.
- if (in) {
- //复制到ctx->in中.
- if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) {
- return NGX_ERROR;
- }
- }
if (in) { //复制到ctx->in中. if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) { return NGX_ERROR; } }
然后就是主要的逻辑处理阶段。这里nginx做的非常巧妙也非常复杂,首先是chain的重用,然后是buf的重用。
先来看chain的重用。关键的几个结构以及域,ctx的free,busy以及ctx->pool的chain域。
其中每次发送没有发完的chain就放到busy中,而已经发送完毕的就放到free中,而最后会调用 ngx_free_chain来将free的chain放入到pool->chain中,而在ngx_alloc_chain_link中,如果pool->chain中存在chain的话,就不用malloc了,而是直接返回pool->chain,我们来看相关的代码。
- //链接cl到pool->chain中
- #define ngx_free_chain(pool, cl) \
- cl->next = pool->chain; \
- pool->chain = cl
- ngx_chain_t *
- ngx_alloc_chain_link(ngx_pool_t *pool)
- {
- ngx_chain_t *cl;
- cl = pool->chain;
- //如果cl存在,则直接返回cl
- if (cl) {
- pool->chain = cl->next;
- return cl;
- }
- //否则才会malloc chain
- cl = ngx_palloc(pool, sizeof(ngx_chain_t));
- if (cl == NULL) {
- return NULL;
- }
- return cl;
- }
//链接cl到pool->chain中 #define ngx_free_chain(pool, cl) \ cl->next = pool->chain; \ pool->chain = cl ngx_chain_t * ngx_alloc_chain_link(ngx_pool_t *pool) { ngx_chain_t *cl; cl = pool->chain; //如果cl存在,则直接返回cl if (cl) { pool->chain = cl->next; return cl; } //否则才会malloc chain cl = ngx_palloc(pool, sizeof(ngx_chain_t)); if (cl == NULL) { return NULL; } return cl; }
然后是buf的重用,严格意义上来说buf的重用是从free中的chain中取得的,当free中的buf被重用,则这个buf对应的chain就会被链接到ctx->pool中,从而这个chain就会被重用.
也就是说buf的重用是第一被考虑的,只有当这个chain的buf确定不需要被重用(或者说已经被重用)的时候,chain才会被链接到ctx->pool中被重用。
还有一个就是ctx的allocated域,这个域表示了当前的上下文中已经分配了多少个buf,blog一开始我们有提到有个output_buffer命令用来设置output的buf大小以及buf的个数。而allocated如果比output_buffer大的话,我们就需要先发送完已经存在的buf,然后才能再次重新分配buf。
来看代码,上面所说的重用以及buf的控制,代码里面都可以看的比较清晰。这里代码我们分段来看,下面这段主要是拷贝buf前所做的一些工作,比如判断是否拷贝,以及给buf分贝内存等。
- //out为我们最终需要传输的chain,也就是交给剩下的filter处理的chain
- out = NULL;
- //last_out为out的最后一个chain
- last_out = &out;
- last = NGX_NONE;
- for ( ;; ) {
- //开始遍历chain
- while (ctx->in) {
- //取得当前chain的buf大小
- bsize = ngx_buf_size(ctx->in->buf);
- //跳过bsize为0的buf
- if (bsize == 0 && !ngx_buf_special(ctx->in->buf)) {
- ngx_debug_point();
- ctx->in = ctx->in->next;
- continue;
- }
- //判断是否需要复制buf
- if (ngx_output_chain_as_is(ctx, ctx->in->buf)) {
- /* move the chain link to the output chain */
- //如果不需要复制,则直接链接chain到out,然后继续循环
- cl = ctx->in;
- ctx->in = cl->next;
- *last_out = cl;
- last_out = &cl->next;
- cl->next = NULL;
- continue;
- }
- //到达这里,说明我们需要拷贝buf,这里buf最终都会被拷贝进ctx->buf中,因此这里先判断ctx->buf是否为空
- if (ctx->buf == NULL) {
- //如果为空,则取得buf,这里要注意,一般来说如果没有开启directio的话,这个函数都会返回NGX_DECLINED的(具体实现可以去看这个函数的代码)。
- rc = ngx_output_chain_align_file_buf(ctx, bsize);
- if (rc == NGX_ERROR) {
- return NGX_ERROR;
- }
- //大部分情况下,都会落入这个分支
- if (rc != NGX_OK) {
- //准备分配buf,首先在free中寻找可以重用的buf
- if (ctx->free) {
- /* get the free buf */
- //得到free buf
- cl = ctx->free;
- ctx->buf = cl->buf;
- ctx->free = cl->next;
- //将要重用的chain链接到ctx->poll中,以便于chain的重用.
- ngx_free_chain(ctx->pool, cl);
- } else if (out || ctx->allocated == ctx->bufs.num) {
- //如果已经等于buf的个数限制,则跳出循环,发送已经存在的buf.这里可以看到如果out存在的话,nginx会跳出循环,然后发送out,等发送完会再次处理,这里很好的体现了nginx的流式处理
- break;
- } else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) {
- //这个函数也比较关键,它用来取得buf.我们接下来会详细看这个函数
- return NGX_ERROR;
- }
- }
- }
- ............................................................
- }
//out为我们最终需要传输的chain,也就是交给剩下的filter处理的chain out = NULL; //last_out为out的最后一个chain last_out = &out; last = NGX_NONE; for ( ;; ) { //开始遍历chain while (ctx->in) { //取得当前chain的buf大小 bsize = ngx_buf_size(ctx->in->buf); //跳过bsize为0的buf if (bsize == 0 && !ngx_buf_special(ctx->in->buf)) { ngx_debug_point(); ctx->in = ctx->in->next; continue; } //判断是否需要复制buf if (ngx_output_chain_as_is(ctx, ctx->in->buf)) { /* move the chain link to the output chain */ //如果不需要复制,则直接链接chain到out,然后继续循环 cl = ctx->in; ctx->in = cl->next; *last_out = cl; last_out = &cl->next; cl->next = NULL; continue; } //到达这里,说明我们需要拷贝buf,这里buf最终都会被拷贝进ctx->buf中,因此这里先判断ctx->buf是否为空 if (ctx->buf == NULL) { //如果为空,则取得buf,这里要注意,一般来说如果没有开启directio的话,这个函数都会返回NGX_DECLINED的(具体实现可以去看这个函数的代码)。 rc = ngx_output_chain_align_file_buf(ctx, bsize); if (rc == NGX_ERROR) { return NGX_ERROR; } //大部分情况下,都会落入这个分支 if (rc != NGX_OK) { //准备分配buf,首先在free中寻找可以重用的buf if (ctx->free) { /* get the free buf */ //得到free buf cl = ctx->free; ctx->buf = cl->buf; ctx->free = cl->next; //将要重用的chain链接到ctx->poll中,以便于chain的重用. ngx_free_chain(ctx->pool, cl); } else if (out || ctx->allocated == ctx->bufs.num) { //如果已经等于buf的个数限制,则跳出循环,发送已经存在的buf.这里可以看到如果out存在的话,nginx会跳出循环,然后发送out,等发送完会再次处理,这里很好的体现了nginx的流式处理 break; } else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) { //这个函数也比较关键,它用来取得buf.我们接下来会详细看这个函数 return NGX_ERROR; } } } ............................................................ }
上面的代码分析的时候有个很关键的函数,那就是ngx_output_chain_get_buf,这个函数是当没有可重用的buf的时候,用来分配buf的。
这里只有一个要注意的,那就是如果当前的buf是位于最后一个chain的话,会有特殊处理。这里特殊处理有两个地方,一个是buf的recycled域,一个是将要分配的buf的大小。
先来说recycled域,这个域表示我们当前的buf是需要被回收的。而我们知道nginx一般情况下(比如非last buf)是会缓存一部分buf,然后再发送的(默认是1460字节),而设置了recycled的话,我们就不会让它缓存buf,也就是尽量发送出去,然后以供我们回收使用。
因此如果是最后一个buf的话,一般来说我们是不需要设置recycled域的,否则的话,需要设置recycled域。因为不是最后一个buf的话,我们可能还会需要重用一些buf,而buf只有被发送出去的话,我们才能重用。
然后就是size的大小。这里会有两个大小,一个是我们需要复制的buf的大小,一个是nginx.conf中设置的size。如果不是最后一个buf,则我们只需要分配我们设置的buf的size大小就行了。如果是最后一个buf,则就处理不太一样,下面的代码会看到。
- static ngx_int_t
- ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, off_t bsize)
- {
- size_t size;
- ngx_buf_t *b, *in;
- ngx_uint_t recycled;
- in = ctx->in->buf;
- //可以看到这里分配的buf,每个的大小都是我们在nginx.conf中设置的size
- size = ctx->bufs.size;
- //默认有设置recycled域.
- recycled = 1;
- //如果当前的buf是属于最后一个chain的时候。这里我们要特殊处理。
- if (in->last_in_chain) {
- //这边注释很详细,我就不解释了.
- if (bsize < (off_t) size) {
- /*
- * allocate a small temp buf for a small last buf
- * or its small last part
- */
- size = (size_t) bsize;
- recycled = 0;
- } else if (!ctx->directio
- && ctx->bufs.num == 1
- && (bsize < (off_t) (size + size / 4)))
- {
- /*
- * allocate a temp buf that equals to a last buf,
- * if there is no directio, the last buf size is lesser
- * than 1.25 of bufs.size and the temp buf is single
- */
- size = (size_t) bsize;
- recycled = 0;
- }
- }
- //开始分配buf内存.
- b = ngx_calloc_buf(ctx->pool);
- if (b == NULL) {
- return NGX_ERROR;
- }
- if (ctx->directio) {
- //directio需要对齐
- b->start = ngx_pmemalign(ctx->pool, size, NGX_DIRECTIO_BLOCK);
- if (b->start == NULL) {
- return NGX_ERROR;
- }
- } else {
- //大部分情况会走到这里.
- b->start = ngx_palloc(ctx->pool, size);
- if (b->start == NULL) {
- return NGX_ERROR;
- }
- }
- b->pos = b->start;
- b->last = b->start;
- b->end = b->last + size;
- //设置temporary.
- b->temporary = 1;
- b->tag = ctx->tag;
- b->recycled = recycled;
- ctx->buf = b;
- //更新allocated,可以看到每分配一个就加1.
- ctx->allocated++;
- return NGX_OK;
- }
static ngx_int_t ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, off_t bsize) { size_t size; ngx_buf_t *b, *in; ngx_uint_t recycled; in = ctx->in->buf; //可以看到这里分配的buf,每个的大小都是我们在nginx.conf中设置的size size = ctx->bufs.size; //默认有设置recycled域. recycled = 1; //如果当前的buf是属于最后一个chain的时候。这里我们要特殊处理。 if (in->last_in_chain) { //这边注释很详细,我就不解释了. if (bsize < (off_t) size) { /* * allocate a small temp buf for a small last buf * or its small last part */ size = (size_t) bsize; recycled = 0; } else if (!ctx->directio && ctx->bufs.num == 1 && (bsize < (off_t) (size + size / 4))) { /* * allocate a temp buf that equals to a last buf, * if there is no directio, the last buf size is lesser * than 1.25 of bufs.size and the temp buf is single */ size = (size_t) bsize; recycled = 0; } } //开始分配buf内存. b = ngx_calloc_buf(ctx->pool); if (b == NULL) { return NGX_ERROR; } if (ctx->directio) { //directio需要对齐 b->start = ngx_pmemalign(ctx->pool, size, NGX_DIRECTIO_BLOCK); if (b->start == NULL) { return NGX_ERROR; } } else { //大部分情况会走到这里. b->start = ngx_palloc(ctx->pool, size); if (b->start == NULL) { return NGX_ERROR; } } b->pos = b->start; b->last = b->start; b->end = b->last + size; //设置temporary. b->temporary = 1; b->tag = ctx->tag; b->recycled = recycled; ctx->buf = b; //更新allocated,可以看到每分配一个就加1. ctx->allocated++; return NGX_OK; }
然后接下来这部分就是复制buf,然后调用filter链进行发送。
- //复制buf.
- rc = ngx_output_chain_copy_buf(ctx);
- if (rc == NGX_ERROR) {
- return rc;
- }
- //如果返回AGAIn,一般来说不会返回这个值的.
- if (rc == NGX_AGAIN) {
- if (out) {
- break;
- }
- return rc;
- }
- /* delete the completed buf from the ctx->in chain */
- //如果ctx->in中处理完毕的buf则删除当前的buf
- if (ngx_buf_size(ctx->in->buf) == 0) {
- ctx->in = ctx->in->next;
- }
- cl = ngx_alloc_chain_link(ctx->pool);
- if (cl == NULL) {
- return NGX_ERROR;
- }
- //链接chain到out.
- 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;
- }
- //调用filter链
- last = ctx->output_filter(ctx->filter_ctx, out);
- if (last == NGX_ERROR || last == NGX_DONE) {
- return last;
- }
- //update chain,这里主要是将处理完毕的chain放入到free,没有处理完毕的放到busy中.
- ngx_chain_update_chains(&ctx->free, &ctx->busy, &out, ctx->tag);
- last_out = &out;
//复制buf. rc = ngx_output_chain_copy_buf(ctx); if (rc == NGX_ERROR) { return rc; } //如果返回AGAIn,一般来说不会返回这个值的. if (rc == NGX_AGAIN) { if (out) { break; } return rc; } /* delete the completed buf from the ctx->in chain */ //如果ctx->in中处理完毕的buf则删除当前的buf if (ngx_buf_size(ctx->in->buf) == 0) { ctx->in = ctx->in->next; } cl = ngx_alloc_chain_link(ctx->pool); if (cl == NULL) { return NGX_ERROR; } //链接chain到out. 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; } //调用filter链 last = ctx->output_filter(ctx->filter_ctx, out); if (last == NGX_ERROR || last == NGX_DONE) { return last; } //update chain,这里主要是将处理完毕的chain放入到free,没有处理完毕的放到busy中. ngx_chain_update_chains(&ctx->free, &ctx->busy, &out, ctx->tag); last_out = &out;
ngx_chain_update_chains这个函数我以前的blog有分析过,想了解的,可以看我前面的blog