Maison >Opération et maintenance >Nginx >Comment implémenter le pool de mémoire nginx
La dernière version stable nginx1.20.2.
Afin d'allouer de la mémoire de manière efficace et rapide et de réduire la fragmentation de la mémoire, nginx implémente son propre composant de pool de mémoire de base.
Principaux fichiers d'implémentation 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; } } }
Utilisez le Corps de structure ngx_pool_data_t pour représenter les informations actuelles du pool de mémoire.
last : l'adresse à allouer la prochaine foiscurrent
points à partir du pool de mémoire actuel Commencez à rechercher la mémoire disponible 🎜🎜chain🎜buffer est utilisé, ce qui n'est pas impliqué ici🎜🎜large🎜Lorsque la mémoire requise est supérieure à la taille maximale du pool de mémoire, elle doit être allouée directement via malloc , puis organisé en une liste chaînée🎜🎜cleanup🎜Liste chaînée de rappel pour le travail de nettoyage 🎜🎜log🎜Log handle🎜🎜2.2 Grande chaîne de mémoire🎜🎜Lorsque la mémoire qui doit être allouée est supérieure à la taille maximale du pool de mémoire , le pool de mémoire ne peut pas satisfaire l'allocation, il est donc alloué directement à partir du système et forme ensuite une liste chaînée pour la maintenance. 🎜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 Chaîne de tâches de nettoyage🎜🎜Il existe une liste chaînée de tâches de rappel Lorsque le pool de mémoire est détruit, cette liste chaînée sera parcourue en séquence et les gestionnaires seront rappelés un par un pour effectuer le travail de nettoyage. 🎜
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. Diagramme de structure de la mémoire🎜🎜3.1 Logique🎜🎜🎜🎜3.2 Actual🎜🎜 🎜🎜On peut voir que de nombreux nœuds sont alloués à partir du pool de mémoire, vous pouvez donc vous concentrer sur les données réelles sans vous soucier d'autres détails. 🎜🎜4. Implémentation🎜🎜4.1 Créer un pool de mémoire🎜
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; } } } }
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); } }🎜Comme vous pouvez le voir dans le code, le pool de mémoire maximum ne dépasse pas la taille de la taille de la page🎜🎜🎜🎜4.2 Allouer de l'espace à partir du pool de mémoire🎜🎜La fonction d'allocation est divisée en alignement de la mémoire et désalignement de la mémoire, mais il contrôle uniquement le pool de mémoire alloue de l'espace dans et ne contrôle pas les allocations de mémoire importantes. 🎜🎜(1) Allouer un petit espace 🎜🎜🎜🎜Alignement de la mémoire
ngx_palloc
🎜🎜🎜🎜Désalignement de la mémoire ngx_pnalloc
🎜🎜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); } }🎜Lorsque l'espace alloué est nécessaire Quand il est inférieur au maximum, la petite méthode d'allocation de mémoire (c'est-à-dire l'allocation d'espace à partir du pool de mémoire) sera utilisée. Par rapport à ngx_palloc, ngx_pnalloc n'a que le dernier paramètre de 0 lors de l'appel de ngx_palloc_small. 🎜🎜Commencez à parcourir le pool de mémoire pointé par pool->current, à la recherche d'un espace qui correspond à la taille d'allocation, et s'il est trouvé, renvoyez la première adresse🎜rrreee🎜Lorsque les conditions d'allocation ne peuvent pas être remplies dans le pool de mémoire existant, créez un nouveau pool de mémoire🎜rrreee 🎜Parmi eux, après avoir créé un nouveau pool de mémoire, un autre parcours est effectué pour augmenter le nombre d'échecs de un. Lorsqu'il est supérieur à 4, ce pool de mémoire sera ignoré et la recherche ne démarrera pas à partir de celui-ci. la prochaine fois. 🎜Autrement dit, on considère que vous ne pouvez pas respecter l'allocation plus de 4 fois et que vous ne pourrez pas respecter l'allocation à l'avenir. Vous ne serez plus utilisé. Réduisez le nombre de traversées et accélérez l'efficacité de. allocation réussie🎜🎜(2) Allouer un grand espace🎜rrreee🎜On peut voir que pour éviter d'allouer de l'espace et parcourir la grande chaîne pour trouver des nœuds réutilisables. Cependant, si la liste chaînée est trop grande, elle peut être trop lente, Ainsi, seuls les trois premiers sont recherchés. Si aucun des trois n'est trouvé, ils sont alloués directement (et les nœuds sont également alloués à partir du pool de mémoire. Par conséquent, lors du nettoyage ultérieur, vous n'avez pas besoin de gérer le nœud, il vous suffit de le faire. pour libérer la grande mémoire appliquée elle-même)🎜🎜Alignement de la mémoire🎜rrreee🎜4.3 Enregistrement de la tâche de nettoyage🎜rrreee🎜On peut voir qu'un seul nœud est alloué ici, et aucun gestionnaire ni donnée n'est défini, cela dépend donc du. appelant spécifique pour les paramètres, car le nœud alloué est renvoyé ici. 🎜🎜Par exemple, dans la fonction
ngx_create_temp_file
, 🎜rrreee🎜 génère un fichier temporaire, enregistre le fd et le nom du fichier dans la tâche de nettoyage, et aucun traitement spécial n'est requis si les fichiers suivants ne sont pas utilisés. le pool de mémoire sera unifié lors de sa sortie. Nettoyer. 🎜🎜4.4 Réinitialiser le pool de mémoire🎜🎜🎜🎜Libérer une grande mémoire🎜🎜🎜🎜Réinitialiser la dernière mémoire en mémoire🎜🎜🎜🎜Réinitialiser le nombre d'échecs🎜🎜rrreee🎜Il y a un phénomène ici : 🎜Il y a de l'espace dans le pool de mémoire Lorsqu'il est insuffisant, ngx_palloc_block
sera appelé pour créer un nouveau pool de mémoire, et le dernier pointe vers m += sizeof(ngx_pool_data_t);
, donc le pool de mémoire actuellement nouvellement alloué sera plus grand que La taille disponible du premier pool de mémoire est beaucoup plus grande (max, current, chain, large, cleanup, log) et la taille de ces champs (peut-être pas tant que ça car il doit être aligné, peut-être que ce sera le cas). exactement la même chose après l'alignement), et maintenant il est réinitialisé. Quand, p->d.last = (u_char *) p + sizeof(ngx_pool_t);
la taille disponible de chaque pool de mémoire devient la même encore. 🎜🎜4.5 Détruire le pool de mémoire🎜🎜🎜🎜Tâche de nettoyage de rappel🎜🎜🎜🎜Libérer une grande mémoire🎜🎜🎜🎜Libérer le pool de mémoire lui-même🎜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); } }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!