ホームページ  >  記事  >  運用・保守  >  nginxメモリプールの実装方法

nginxメモリプールの実装方法

WBOY
WBOY転載
2023-05-17 13:26:271310ブラウズ

1. はじめに

最新の安定バージョン nginx1.20.2。
メモリを効率的かつ迅速に割り当て、メモリの断片化を軽減するために、nginx は独自の基本的なメモリ プール コンポーネントを実装しています。
主な実装ファイルngx_palloc.h、ngx_palloc.c

2.データ構造

2.1 メモリプールの主な構造

typedef struct {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t            failed;
} ngx_pool_data_t;

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;
};

最初のメモリ プール 1 つのメンバーは構造体です。
ngx_pool_data_t 構造体を使用して、現在のメモリ プール情報を表します。
last: 次回割り当てられるアドレス
end: メモリプールの終了アドレス
next: 複数のメモリプールを接続したメモリプールリンクリスト

max
全体メモリ プールの最大サイズ

current
は、使用可能なメモリ

chain
buffer を見つけるために現在のメモリ プールから開始することを指します。これには

は関係しません。 large
when 必要なメモリがメモリ プールの最大サイズよりも大きい場合は、malloc を通じてメモリを直接割り当ててからリンク リストに編成する必要があります

cleanup
クリーンアップ用のコールバック リンク リストwork

log
ログ ハンドル

2.2 大規模なメモリ チェーン

割り当てられるメモリがメモリ プールの最大サイズより大きい場合、メモリ プールは割り当てを満たしているため、システムから直接割り当てられ、メンテナンスのためにリンク リストが形成されます。

typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc;
};

2.3 クリーンアップ タスク チェーン

コールバック タスクのリンク リストがあります。メモリ プールが破棄されると、このリンク リストが順番に走査され、ハンドラーが次のいずれかにコールバックされます。掃除するために1つ。

typedef void (*ngx_pool_cleanup_pt)(void *data);

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data;
    ngx_pool_cleanup_t   *next;
};

3. メモリ構造図

3.1 ロジック

nginxメモリプールの実装方法

##3.2 実際の

nginxメモリプールの実装方法

#多くのノードがメモリ プールから割り当てられていることがわかります。そのため、他の詳細を気にせずに実際のデータに集中できます。

4. 実装

4.1 メモリ プールの作成

/*
 * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
 * On Windows NT it decreases a number of locked pages in a kernel.
 */
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    if (p == NULL) {
        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 < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}

コードからわかるように、最大​​メモリ プールは pagesize のサイズを超えません

nginxメモリプールの実装方法

4.2 メモリ プールからの領域の割り当て

割り当て機能はメモリ アライメントとメモリの不整合に分かれていますが、これはメモリ プール内の領域の割り当てを制御するだけであり、大規模なメモリ割り当てを制御しません。

(1) 小さなスペースの割り当て

  • メモリのアライメント

    ngx_palloc

  • メモリのアライメントのずれ

    ngx_pnalloc

  • void *
    ngx_palloc(ngx_pool_t *pool, size_t size)
    {
    #if !(NGX_DEBUG_PALLOC)
        if (size <= pool->max) {
            return ngx_palloc_small(pool, size, 1);
        }
    #endif
    
        return ngx_palloc_large(pool, size);
    }
割り当てる必要があるスペースが max 未満の場合、小規模メモリ割り当て方法 (つまり、メモリ プールからスペースを割り当てる) が使用されます。 ngx_pnalloc は、ngx_palloc_small を呼び出すときの最後のパラメータが 0 です。

pool->current が指すメモリ プールの走査を開始し、割り当てサイズを満たす領域を探し、見つかった場合は最初のアドレスを返します。

static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;

    p = pool->current;

    do {
        m = p->d.last;

        if (align) {
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

        if ((size_t) (p->d.end - m) >= size) {
            p->d.last = m + size;

            return m;
        }

        p = p->d.next;

    } while (p);

    return ngx_palloc_block(pool, size);
}

割り当て条件が満たせない場合既存のメモリ プールで満たす場合は、新しいメモリ プールを作成します

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    psize = (size_t) (pool->d.end - (u_char *) pool);

    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }

    new = (ngx_pool_t *) m;

    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    p->d.next = new;

    return m;
}

このうち、新しいメモリ プールを作成した後、再度トラバーサルが実行され、失敗回数が 1 つ増加します。 , このメモリ プールはスキップされ、失敗した回数も次回からスキップされます。そこから検索を開始しないでください。

4回を超えると割り当てを満たせないとみなされ、今後も割り当てを満たせないため使用されなくなります。トラバース回数を減らし、成功の効率を向上させます。 allocation

(2) 大きなスペースの割り当て

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;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {
            break;
        }
    }

    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

スペースの割り当てを避けるために、再利用可能なノードを見つけるために大きなチェーンが走査されていることがわかります。サイズが大きいと遅すぎる可能性があるため、最初の 3 つだけが検索されます。3 つが見つからない場合は、直接割り当てられます (さらに、ノードはメモリ プールからも割り当てられるため、後続のクリーンアップ中に、ノードを管理するには、要求された大きなメモリ自体を解放するだけで済みます)

メモリ アライメント

void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
    void              *p;
    ngx_pool_large_t  *large;

    p = ngx_memalign(alignment, size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

4.3 レジスタ クリーンアップ タスク

ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;

    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }

    c->handler = NULL;
    c->next = p->cleanup;

    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}

ノードが 1 つだけであることがわかります。ここでは が割り当てられており、ハンドラーとデータ データが設定されていないため、割り当てられたノードがここで返されるため、設定は特定の呼び出し元に依存します。

たとえば、関数

ngx_create_temp_file

ngx_int_t
ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path, ngx_pool_t *pool,
    ngx_uint_t persistent, ngx_uint_t clean, ngx_uint_t access)
{
    ...

    cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
    if (cln == NULL) {
        return NGX_ERROR;
    }

       ...
        file->fd = ngx_open_tempfile(file->name.data, persistent, access);
				...
        if (file->fd != NGX_INVALID_FILE) {

            cln->handler = clean ? ngx_pool_delete_file : ngx_pool_cleanup_file;
            clnf = cln->data;

            clnf->fd = file->fd;
            clnf->name = file->name.data;
            clnf->log = pool->log;

            return NGX_OK;
        }
			...
}

では、一時ファイルが生成され、fd とファイル名がクリーンアップ タスクに登録されます。後続のファイルが使用されない場合、いいえメモリプールは解放時に一律にクリーンアップされます。

4.4 メモリ プールのリセット

  • 大規模メモリの解放

  • メモリ内の最後のメモリをリセット

  • リセット失敗数

  • void
    ngx_reset_pool(ngx_pool_t *pool)
    {
        ngx_pool_t        *p;
        ngx_pool_large_t  *l;
    
        for (l = pool->large; l; l = l->next) {
            if (l->alloc) {
                ngx_free(l->alloc);
            }
        }
    
        for (p = pool; p; p = p->d.next) {
            p->d.last = (u_char *) p + sizeof(ngx_pool_t);
            p->d.failed = 0;
        }
    
        pool->current = pool;
        pool->chain = NULL;
        pool->large = NULL;
    }
ここで現象が発生します:

メモリ プールに十分なスペースがない場合、
ngx_palloc_block が呼び出されます。新しいメモリ プールを作成し、最後に m = sizeof(ngx_pool_data_t); をポイントするため、現在新しく割り当てられたメモリ プールは、最初のメモリ プールの利用可能なサイズ (max,current,chain) よりも大きくなります。 、大、クリーンアップ、ログ)これらのフィールドのサイズ(調整する必要があるため、おそらくそれほど多くはありませんが、調整後はまったく同じになる可能性があります)、そしてリセット時に、p->d。 last = (u_char *) p sizeof(ngx_pool_t);各メモリプールの利用可能なサイズは同じになります。

4.5 メモリ プールの破棄

  • コールバック クリーンアップ タスク

  • 大容量メモリの解放

  • メモリプール自体を解放する

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);
        }
    }


    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}

4.6 大内存释放

通过遍历找到要释放的节点,将内存释放,并且将alloc设置成NULL,则有了节点重用的情况。

ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
            ngx_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

4.7 分配并清空数据

void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;

    p = ngx_palloc(pool, size);
    if (p) {
        ngx_memzero(p, size);
    }

    return p;
}

正常分配的空间中都是垃圾数据,所以当前函数在分配空间后,将分配的空间清零。

4.8 回调文件清理

(1) 手动关闭指定fd

遍历清理任务,找到ngx_pool_cleanup_file的handler,如果是要关闭的fd,则回调

void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
    ngx_pool_cleanup_t       *c;
    ngx_pool_cleanup_file_t  *cf;

    for (c = p->cleanup; c; c = c->next) {
        if (c->handler == ngx_pool_cleanup_file) {

            cf = c->data;

            if (cf->fd == fd) {
                c->handler(cf);
                c->handler = NULL;
                return;
            }
        }
    }
}

(2) 关闭fd

void
ngx_pool_cleanup_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
                   c->fd);

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

(3) 删除文件并关闭fd

void
ngx_pool_delete_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_err_t  err;

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
                   c->fd, c->name);

    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
        err = ngx_errno;

        if (err != NGX_ENOENT) {
            ngx_log_error(NGX_LOG_CRIT, c->log, err,
                          ngx_delete_file_n " \"%s\" failed", c->name);
        }
    }

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

以上がnginxメモリプールの実装方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。