最新の安定バージョン nginx1.20.2。
メモリを効率的かつ迅速に割り当て、メモリの断片化を軽減するために、nginx は独自の基本的なメモリ プール コンポーネントを実装しています。
主な実装ファイルngx_palloc.h、ngx_palloc.c
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
ログ ハンドル
割り当てられるメモリがメモリ プールの最大サイズより大きい場合、メモリ プールは割り当てを満たしているため、システムから直接割り当てられ、メンテナンスのためにリンク リストが形成されます。
typedef struct ngx_pool_large_s ngx_pool_large_t; struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc; };
コールバック タスクのリンク リストがあります。メモリ プールが破棄されると、このリンク リストが順番に走査され、ハンドラーが次のいずれかにコールバックされます。掃除するために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; };
/* * 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; }
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
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; }
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);各メモリプールの利用可能なサイズは同じになります。
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; } } }
通过遍历找到要释放的节点,将内存释放,并且将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; }
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; }
正常分配的空间中都是垃圾数据,所以当前函数在分配空间后,将分配的空间清零。
(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 サイトの他の関連記事を参照してください。