Heim >Betrieb und Instandhaltung >Nginx >So implementieren Sie den Nginx-Speicherpool
Die neueste stabile Version nginx1.20.2.
Um Speicher effizient und schnell zuzuweisen und die Speicherfragmentierung zu reduzieren, implementiert Nginx eine eigene grundlegende Speicherpoolkomponente.
Hauptimplementierungsdateien ngx_palloc.h, ngx_palloc.c
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; };
内存池中第一个成员是一个结构体:
使用ngx_pool_data_t结构体来表示当前内存池信息。
last :下次开始分配的地址
end: 内存池的结束地址
next: 内存池链表,将多个内存池连接起来
max
整个内存池的最大大小
current
指向从当前内存池开始查找可用内存
chain
buffer使用的,这里不涉及
large
当需要的内存大于内存池最大大小时,需要通过malloc直接分配,然后形成链表进行组织
cleanup
清理工作的回调链表
log
日志句柄
当需要分配的内存比内存池的最大大小都大时,内存池无法满足分配,所以直接从系统中分配,然后构成一个链表进行维护。
typedef struct ngx_pool_large_s ngx_pool_large_t; struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc; };
有一个回调任务的链表,当内存池销毁时,将依次遍历此链表,逐一回调handler进行清理工作。
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; }
从代码中可以看到,内存池最大不超过pagesize的大小
分配函数分了内存对齐和内存不对齐,但这只控制了内存池中分配空间,不控制大内存分配。
(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相比只是调用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; }
其中,创建好新的内存池后,又做了一次遍历,将failed计数加一,当大于4时,将跳过此内存池,下次就不从它开始查找。
即认为超过4次你都不能满足分配,以后都不能满足分配,不再用你了,减少遍历个数,加快成功分配效率
(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; }
可以看出,为了避免分配空间,遍历large链查找可重用的节点,但是如果链表过大又可能太慢,所以只查找前三个,如果三个都没有找到,则直接分配(而且节点也是从内存池中分配的,所以后续清理时,不需要管节点,只需要释放申请的大内存本身)
内存对齐
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; }
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; }
可以看出,这里只是分配了一个节点,并没有设置handler以及data数据,所以还得看具体的调用方进行设置,因为这里返回了分配的节点。
比如在函数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以及文件名注册到清理任务中,后续文件不使用了则不需要特殊处理,内存内存池释放时将统一清理。
释放大内存
重置内存中last
重置failed计数
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
创建一个新的内存池,而last指向的是m += sizeof(ngx_pool_data_t);
, 因此当前新分配的内存池将比第一个内存池可用大小多了(max,current,chain,large,cleanup,log)这几个字段大小(可能没有那么多,因为要对齐,可能对齐后就完全一样了),而现在重置时,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; } } }
Verwenden Sie die Der Strukturkörper ngx_pool_data_t stellt die aktuellen Speicherpoolinformationen dar.
last: die Adresse, die beim nächsten Mal zugewiesen werden sollaktuelle
Punkte Beginnen Sie mit der Suche nach verfügbarem Speicher aus dem aktuellen Speicherpool. 🎜🎜Kette🎜Puffer wird verwendet, was hier nicht in Frage kommt🎜🎜groß🎜Wenn der erforderliche Speicher größer als die maximale Größe des Speicherpools ist, muss er direkt über malloc zugewiesen werden und dann in einer verknüpften Liste organisiert , kann der Speicherpool die Zuweisung nicht erfüllen, daher wird er direkt vom System zugewiesen und bildet dann eine verknüpfte Liste zur Wartung. 🎜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; }🎜2.3 Bereinigungsaufgabenkette🎜🎜Es gibt eine verknüpfte Liste von Rückrufaufgaben. Wenn der Speicherpool zerstört wird, wird diese verknüpfte Liste der Reihe nach durchlaufen und die Handler werden nacheinander zurückgerufen, um Reinigungsarbeiten durchzuführen. 🎜
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; }🎜3. Speicherstrukturdiagramm🎜🎜3.1 Logik🎜🎜🎜🎜3.2 Actual🎜🎜 🎜🎜Es ist ersichtlich, dass viele Knoten aus dem Speicherpool zugewiesen werden, sodass Sie sich auf die tatsächlichen Daten konzentrieren können, ohne sich um andere Details kümmern zu müssen. 🎜4. Implementierung /upload/article/ 000/887/227/168430118922665.png" alt="So implementieren Sie den Nginx-Speicherpool" />🎜🎜4.2 Speicherplatz aus dem Speicherpool zuweisen🎜🎜Die Zuordnungsfunktion ist in Speicherausrichtung und Speicherfehlausrichtung unterteilt. Es steuert jedoch nur den Speicherpool, der Speicherplatz zuweist, und steuert nicht die Zuweisung großer Speicher. 🎜🎜(1) Wenigen Speicherplatz zuweisen 🎜🎜🎜🎜Speicherausrichtung
ngx_palloc
🎜🎜🎜🎜Speicherfehlausrichtung ngx_pnalloc
🎜🎜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; } } } }🎜Wenn zugewiesener Speicherplatz benötigt wird. Wann Wenn es kleiner als max ist, wird die kleine Speicherzuweisungsmethode (dh die Zuweisung von Speicherplatz aus dem Speicherpool) verwendet. Im Vergleich zu ngx_palloc hat ngx_pnalloc beim Aufruf von ngx_palloc_small nur den letzten Parameter 0. 🎜🎜Beginnen Sie mit dem Durchlaufen des Speicherpools, auf den pool->current zeigt, suchen Sie nach einem Speicherplatz, der der Zuordnungsgröße entspricht, und geben Sie, wenn er gefunden wird, die erste Adresse zurück. 🎜
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); } }🎜Wenn die Zuordnungsbedingungen im vorhandenen Speicherpool nicht erfüllt werden können, erstellen Sie ein neuer Speicherpool🎜
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); } }🎜Unter diesen wird nach dem Erstellen eines neuen Speicherpools ein weiterer Durchlauf durchgeführt, um die Anzahl der Fehler um eins zu erhöhen. Wenn er größer als 4 ist, wird dieser Speicherpool übersprungen und die Suche wird nicht von dort aus gestartet Nächstes Mal. 🎜Das heißt, es wird davon ausgegangen, dass Sie die Zuweisung nicht mehr als viermal erfüllen können und dass Sie die Zuweisung in Zukunft nicht mehr erfüllen können. Reduzieren Sie die Anzahl der Durchläufe und beschleunigen Sie die Effizienz erfolgreiche Zuweisung🎜🎜(2) Weisen Sie großen Speicherplatz zu🎜rrreee🎜Es ist ersichtlich, dass die verknüpfte Liste möglicherweise zu langsam ist, um dies zu vermeiden. Weisen Sie Speicherplatz zu und durchqueren Sie die große Kette, um wiederverwendbare Knoten zu finden. Daher werden nur die ersten drei durchsucht. Wenn keiner der drei gefunden wird, werden sie direkt zugewiesen (und die Knoten werden auch aus dem Speicherpool zugewiesen). Daher müssen Sie den Knoten bei der anschließenden Bereinigung nicht verwalten, sondern nur um den angewendeten großen Speicher selbst freizugeben)🎜🎜Speicherausrichtung🎜rrreee🎜4.3 Registrieren der Bereinigungsaufgabe🎜rrreee🎜Es ist ersichtlich, dass hier nur ein Knoten zugewiesen ist und kein Handler und keine Daten festgelegt sind, es hängt also von der ab spezifischer Aufrufer für Einstellungen, da hier der zugeordnete Knoten zurückgegeben wird. 🎜🎜Zum Beispiel generiert 🎜rrreee🎜 in der Funktion
ngx_create_temp_file
eine temporäre Datei, registriert den fd und den Dateinamen in der Bereinigungsaufgabe und es ist keine spezielle Verarbeitung erforderlich, wenn nachfolgende Dateien nicht verwendet werden Der Speicherpool wird vereinheitlicht, wenn er freigegeben wird. 🎜🎜4.4 Speicherpool zurücksetzen. Großer Speicher freigeben Wenn dies nicht ausreicht, wird ngx_palloc_block
aufgerufen, um einen neuen Speicherpool zu erstellen, und zeigt zuletzt auf m += sizeof(ngx_pool_data_t);
, also den aktuellen neu zugewiesenen Speicherpool wird größer sein als Die verfügbare Größe des ersten Speicherpools ist viel größer (maximal, aktuell, Kette, groß, Bereinigung, Protokoll) und die Größe dieser Felder (vielleicht nicht so groß, weil sie ausgerichtet werden müssen, vielleicht wird es so sein). genau das Gleiche nach der Ausrichtung), und jetzt wird es zurückgesetzt. Wenn p->d.last = (u_char *) p + sizeof(ngx_pool_t);
die verfügbare Größe jedes Speicherpools gleich wird wieder. 🎜🎜4.5 Speicherpool zerstören🎜🎜🎜🎜Aufgabe zur Rückrufbereinigung🎜🎜🎜🎜Großen Speicher freigeben🎜🎜🎜🎜Speicherpool selbst freigeben🎜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); } }
Das obige ist der detaillierte Inhalt vonSo implementieren Sie den Nginx-Speicherpool. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!