nginxのメモリ管理


先にメモリプールの実現を見にきて、nginxのメモリプールの実現のとても簡単さ.
ここのメモリのいくつかのグラフは朱さんの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;
}