nginxのメモリ管理
先にメモリプールの実現を見にきて、nginxのメモリプールの実現のとても簡単さ.
ここのメモリのいくつかのグラフは朱さんのsladeを見ることができます.
http://blog.zhuzhaoyuan.com/2009/09/nginx-internals-slides-video/
メモリが初期化されると(以下分析します)ngx_poll_sはメモリの頭にしか相当しません.現在のメモリプールの必要な情報を保存しています.
メモリからデータをアクセスする場合、nginxは二つのタイプに分けて処理されます.一つは小さいデータで、直接にメモリからデータを取得します.一方、大きなデータの場合は、直接にMallocのデータ(つまりメモリプールの外の部分からデータを配合します)を保存します.多くのメモリプールが見られます.例えば、pyのメモリプールが実現しても、基本的にはこの考えです.ここの詳細については、下のメモリプール関連関数を分析する際に詳しく分析します.
これらのサブメモリは父メモリとは違っていますので、関数を分析する時に詳しく紹介します.
ブロックサイズデータの分割線は、メモリを作成するときに渡されるsizeとページサイズの最小値です.
下はメモリの構造です.
この構造は簡単で、このメモリを操作する必要があるデータのいくつかの指針を含んでいます.
ここで、lastは、現在のデータ領域の使用済みデータの最後を表す.
endは現在のメモリプールの最後を表します.つまりend-lastとは、メモリが使われていないサイズのことです.
私たちはメモリから一つのメモリを要求した時、メモリが満杯になったら、これは一般的にメモリを拡大しますが、nginxではないです.直接にメモリプールを割り当てて、dataのnextポインタにリンクします.つまり、nginxの中には、各メモリプールにいくつかのサブメモリが含まれています.だから私たちはメモリを要求する時、これらのサブメモリを巡回します.
failedドメインは主にメモリ(小さいサイズのメモリ)を要求します.メモリ容量が足りないので、サブメモリの回数を再割り当てしたいです.関数を分析する時はまたこのドメインを見ます.
そしてngx_pool_largesは、大きなメモリを表しています.この構造は非常に単純であることが分かります.つまり、一つのポインタは次のブロックのlargeを指し、一つのallocはデータを指しています.
ここで、ハンドルはクリーン機能を表しています.
dataはクリーンアップ関数に渡すデータを表します.
nextは次の掃除handlerを表しています.つまりdestroyというpoolの時にhandlerチェーンを巡回して掃除して、handlerを呼び出します.
以下はpoolのメモリマップで、朱さんのinx internalから抜粋します.
ok,次に,具体的な関数を解析することによって,poolの実現をより良く理解した.
まず、ngx_を見にきますクリアードpoolとはつまりpoolを作成することです.
ここでは、私たちが送ってきたサイズはsizeですが、本当に使えるデータエリアの大きさはngx(u)を差し引いています.pool_tのサイズです
この三つの関数は同じですから、一つだけ分析すればいいです.私たちはグクスクスを見に来ましたpallo.
そしてここで改めてnewのこのメモリの大きさは父のメモリと同じです.
そして新しいメモリはngx_だけ保存しました.pool_data_tこの構造は、つまりデータエリアが直接ngx_に付いているということです.pool_data_t以下です
次はngx_ですパロコ.largeというのは、この関数も簡単です.mallocの一つのngx(u)です.poll_largetをメインメモリにリンクします.
ここのいくつかの簡単な関数は分析しません.
例えばngx_p free(gxupoolmut*pool、void*p)は、この関数は、poolのlargeチェーンからpを見つけてfreeを落とします.
ngx_pool_cleanupt*
ngx_pool_cleanupadd(gxuplool mut*p,size size)
この関数は、ngx(u)を追加します.pool_cleanuptは現在のpoolに行って、戻ってきます.私たちはこの時に戻る構造によって対応するhandlerに値を割り当てることができます.
そしてngx_pool_cleanuptこれは主にメモリプールdestroyの時に掃除をしなければならないかもしれません.この時、私達はhandlerをOolに掃除して、メモリプールをリリースする時に自動的に呼び出します.
ok、今見に来てpoolはfreeの実現に遭います.
この関数は主にlargeを巡回して、currentを巡回して、それから1つずつ釈放します.
これからbufの実現を見に来ます.
bufは二つのタイプに分けられています.一つはfileで、一つはmemoryです.ここにはファイルの操作領域があります.
bufがpoolに対して一つのposドメインが多くなりました.ここで私たちはソケットの違いや他のデバイスに送信します.ここでデータをbufに入れます.設備やソケットの準備ができたら、私たちはbufから読みます.ここでposポインタはbufに入れた既に実行されているデータです.の位置です
ok私達はchainの実現を見にきましたが、実はその実現は簡単で、シングルチェーン表です.
1 ngx_out put_chain_ctx_t,このchainは主に出力bufを管理しています.
2 ngx_chain_writer.ctx_tこれは主にアップストリームモジュールに使われます.
ですから、私たちは主にngx_を見に来ました.out put_chain_ctx_tです
ngx_out put_chain_ctx_tは、in、free、及びbusyの3種類のchainを含む.
これらの重要な領域を紹介します.
buf:このドメインはつまり私達がデータをコピーしたところです.私達が普通出力するのはin直接copy対応のsizeからbufまでです.
in:これが私たちがデータを送るところです.
free:これはいくつかの空いているbufを保存しました.つまりfreeが存在すれば、私達は直接freeからbufを前のbuf領域に取ります.
busy:これは発送済みのbufを保存しています.つまり、私達はinからbufを読み取り終わった後、データがすでに取り終わったと確定します.この時、このchainをbusyにコピーします.そして古いbusy bufをfreeにコピーします.
out put_filterは出力をフィルタするためのコールバック関数です.
あとはマーキングエリアがあります.
最後にグクスクスを見に来ましたchain_writer.ctx_t,これは主にustream(このモジュールを見ていないので、ここでなぜ多くのwriterが出るのか分かりません.大体見ましたが、ustreamモジュールから送られるべきデータ量が大きいと思いますので、ここではこのchainを通じて直接writevを呼び出してデータを送ります.
ここのメモリのいくつかのグラフは朱さんのsladeを見ることができます.
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;
};
そして私たちは上のチェーンを見に来ました.まずはデータエリアのポインターです.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_ここではまず紹介しません.今はbufと関連があることを知るだけです.そしてngx_pool_largesは、大きなメモリを表しています.この構造は非常に単純であることが分かります.つまり、一つのポインタは次のブロックのlargeを指し、一つのallocはデータを指しています.
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};
次はngx_ですpool_cleanupこの構造はメモリ内のデータのクリーンアップを表すものです.ここで、ハンドルはクリーン機能を表しています.
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_を通るクリアードtemp_bufでbuffを作成し、ngx_を通じてalloc_chain_linkはchainを作成し、cl->buf=rb->bufを通過する.buffをchainにリンクする.以下はpoolのメモリマップで、朱さんのinx internalから抜粋します.
ok,次に,具体的な関数を解析することによって,poolの実現をより良く理解した.
まず、ngx_を見にきますクリアードpoolとはつまりpoolを作成することです.
ここでは、私たちが送ってきたサイズはsizeですが、本当に使えるデータエリアの大きさはngx(u)を差し引いています.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_です.パラドックスcaloc、ngx_pnallo.この3つの関数の違いは、最初の関数で割り当てられたメモリが揃うということです.第二の関数を使用して、ブロック0のメモリをクリアします.第三の関数で割り当てられたメモリは配置されません.この三つの関数は同じですから、一つだけ分析すればいいです.私たちはグクスクスを見に来ましたpallo.
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);
}
続いてグクスクスクスを見に来ます.パロコ.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_ですパロコ.largeというのは、この関数も簡単です.mallocの一つのngx(u)です.poll_largetをメインメモリにリンクします.
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;
}
私たちはリリースを見に来ました.ここではnginxの中で、大きなメモリだけがfreeインターフェースを提供しています.私たちは仕事を終えて解放されますが、小さいメモリはこのインターフェースを提供していません.つまり、小さいメモリはメモリ全体がdesrtoyに落とされた時だけ解放されます.ここのいくつかの簡単な関数は分析しません.
例えばngx_p free(gxupoolmut*pool、void*p)は、この関数は、poolのlargeチェーンからpを見つけてfreeを落とします.
ngx_pool_cleanupt*
ngx_pool_cleanupadd(gxuplool mut*p,size size)
この関数は、ngx(u)を追加します.pool_cleanuptは現在のpoolに行って、戻ってきます.私たちはこの時に戻る構造によって対応するhandlerに値を割り当てることができます.
そしてngx_pool_cleanuptこれは主にメモリプールdestroyの時に掃除をしなければならないかもしれません.この時、私達はhandlerをOolに掃除して、メモリプールをリリースする時に自動的に呼び出します.
ok、今見に来てpoolはfreeの実現に遭います.
この関数は主にlargeを巡回して、currentを巡回して、それから1つずつ釈放します.
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;
}
}
}
上記を通して、私達はinxの中のメモリの池の中の小さい塊のデータがこれまで釈放したのではないことを見ることができて、このようにメモリの操作を簡略化しました.これからbufの実現を見に来ます.
bufは二つのタイプに分けられています.一つはfileで、一つはmemoryです.ここにはファイルの操作領域があります.
bufがpoolに対して一つの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_を呼び出します.クリアードtemp_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を組み合わせたものです.これは主に未送信のものや受信したもの、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_out put_chain_ctx_t,このchainは主に出力bufを管理しています.
2 ngx_chain_writer.ctx_tこれは主にアップストリームモジュールに使われます.
ですから、私たちは主にngx_を見に来ました.out put_chain_ctx_tです
ngx_out put_chain_ctx_tは、in、free、及びbusyの3種類のchainを含む.
これらの重要な領域を紹介します.
buf:このドメインはつまり私達がデータをコピーしたところです.私達が普通出力するのはin直接copy対応のsizeからbufまでです.
in:これが私たちがデータを送るところです.
free:これはいくつかの空いているbufを保存しました.つまりfreeが存在すれば、私達は直接freeからbufを前のbuf領域に取ります.
busy:これは発送済みのbufを保存しています.つまり、私達はinからbufを読み取り終わった後、データがすでに取り終わったと確定します.この時、このchainをbusyにコピーします.そして古いbusy bufをfreeにコピーします.
out put_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_に対応しています.out put_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;
}
}
ここでは簡単に分析しただけです.詳細には他のモジュールとの接合も必要です.最後にグクスクスを見に来ました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;
}