大纲 前言 本文将剖析 Nginx 内存池的源码,并介绍内存池的底层设计和工作原理,最后基于 C++ 移植 Nginx 内存池的核心源码。
C++ 常见的池
在 C++ 中有五大池,包括内存池、连接池、协程池、线程池、进程池。
内存管理概述 内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效、快速地分配内存,并且在适当的时候释放和回收内存资源。
源码学习目标 在剖析 Nginx 内存管理的源码之前,先思考以下几个问题:
(1) Nginx 为什么要进行内存管理? (2) Nginx 如何进行内存管理? (3) Nginx 的内存管理解决了哪些问题? Nginx 内存池 内存池的源码剖析 这里剖析的 Nginx 版本是 1.12.2
,Nginx 内存池的核心源码主要位于 ngx_palloc.h
和 ngx_palloc.c
源文件中。
重要类型和变量的定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1) #define NGX_DEFAULT_POOL_SIZE (16 * 1024) #define NGX_POOL_ALIGNMENT 16 #define NGX_MIN_POOL_SIZE ngx_align ((sizeof (ngx_pool_t) + 2 * sizeof (ngx_pool_large_t)), NGX_POOL_ALIGNMENT) #define NGX_ALIGNMENT sizeof (unsigned long)
1 2 3 4 5 6 7 8 9 10 11 typedef struct ngx_pool_s ngx_pool_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 2 3 4 5 6 7 8 typedef struct ngx_pool_s ngx_pool_t ;typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed; } ngx_pool_data_t ;
1 2 3 4 5 6 typedef struct ngx_pool_large_s ngx_pool_large_t ;struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc; };
清理操作的类型定义,包括一个清理回调函数、传给回调函数的数据和下一个清理操作的地址 1 2 3 4 5 6 7 8 9 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; };
重要的内存池函数接口 1 2 3 4 5 ngx_pool_t *ngx_create_pool (size_t size, ngx_log_t *log) ; void ngx_destroy_pool (ngx_pool_t *pool) ; void ngx_reset_pool (ngx_pool_t *pool) ;
内存池管理核心函数 1 2 3 4 5 6 7 static ngx_inline void * ngx_palloc_small (ngx_pool_t *pool, size_t size, ngx_uint_t align) static void * ngx_palloc_block (ngx_pool_t *pool, size_t size) static void * ngx_palloc_large (ngx_pool_t *pool, size_t size) ngx_int_t ngx_pfree (ngx_pool_t *pool, void *p)
内存池的底层设计 Nginx 中所有请求都单独对应一个内存池,在这个请求的过程中,所有涉及到内存分配的地方,都到该请求相关的内存池中处理,而中间不会去释放内存,内存池的生命周期与请求一样,请求完毕则直接回收内存。这样的好处在于:统一分配和统一释放,降低了内存泄露问题的出现。Nginx 的内存池设计分为两个部分:
大块内存:超过 max
大小(默认 4095 字节)的内存分配,走大块内存分配,这部分内存管理由 ngx_pool_large_t
结构体负责。 小块内存:在 ngx_pool_t
链表中遍历符合要求的 ngx_pool_t
结构体,找到符合要求大小的 pool
直接返回,否则就申请新的小块内存 pool
。 特别注意
小块内存在分配后不会被单独释放,而是在整个内存池销毁(ngx_destroy_pool()
)时,通过 free()
一次性释放。 大块内存会在整个内存池重置(ngx_reset_pool()
)或者整个内存池销毁(ngx_destroy_pool()
)时被 free()
释放,但还可以手动调用 ngx_pfree()
单独释放。 小块内存的重用机制:Nginx 也有机制尝试在链表中查找可用内存块(并非永远只分配不回收),但它不做碎片整理或回收,只是简单地继续分配新的小块内存。 内存池结构设计 Nginx 采用内存池的结构设计来管理内存,而内存池是由若干固定大小的内存块组成的单向链表(如下图所示)。从图中可以看出来,内存池的头结点维护着内存池的总体信息,从头结点开始,可以访问内存池的小块内存(单向链表,由 ngx_pool_data_t
结构特维护),大块内存(单向链表,由 ngx_pool_large_t
结构特维护),以及抽象内存数据(单向链表,由 ngx_pool_chain_t
结构体维护)。
ngx_pool_data_t ngx_pool_data_t
结构体负责存储每个 ngx_pool_t
结构体的元数据:
1 2 3 4 5 6 7 8 typedef struct ngx_pool_s ngx_pool_t ;typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed; } ngx_pool_data_t ;
提示
failed
成员的引入是为了避免某个 pool
虽然还有可用的内存空间,但是由于内存空间很小了,导致经常性的分配内存空间失败,当累计失败的次数达到某个阈值时,下一次再次查找内存就直接跳过这个 pool
,直接去寻找内存池链表中的下一个 pool
。在 ngx_pool_s
结构体中,current
指针会随着 failed
的增加而发生改变,如果 current
指向的内存池的 failed
达到了 4
的话,current
就会指向下一个内存池。
ngx_pool_large_t ngx_pool_large_t
结构体用于存储大内存块,这一块就比较简单粗暴了,直接调用 malloc()
分配一块大内存来使用,多个大内存块之间也是以链表形式来组织数据。正常情况下,这些大块内存会在整个内存池销毁时统一释放,比如在请求处理完毕后、连接断开后、Worker 进程(子进程)退出后会释放大块内存。
1 2 3 4 5 6 typedef struct ngx_pool_large_s ngx_pool_large_t ;struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc; };
提示
大块内存的分配请求不会直接在内存池上分配内存来满足,而是直接向操作系统申请一大块内存(底层直接调用 malloc()
分配内存),然后将这块内存挂到内存池头部的 large
指针下。内存池的作用在于解决小块内存池的频繁申请问题,对于这种大块内存,是可以忍受直接申请的。为什么大块内存分配后是挂在链表头部而不是尾部呢?根据程序局部性原理,最近分配的内存一般经常使用,挂在头部可以提高空闲内存块的查找效率。
ngx_pool_t ngx_pool_t
结构体用于表示一个内存池,内存池的内部以链表形式来组织数据。如下图:
1 2 3 4 5 6 7 8 9 10 11 typedef struct ngx_pool_s ngx_pool_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
结构体的 next
成员。 current
指针,用于表示当前该内存池在使用的 pool
指针。除了内存池链表的头结点之外,内存池链表其他节点的该指针无效。之所以需要这个指针,就是前面提到的,在某个内存池多次失效的情况下,下一次直接跳过该内存池查找内存空间,current
指针保存当前在内存池链表的哪一个内存池上面查找内存空间。large
指针,指向 ngx_pool_large_t
结构体,用于管理大块内存。内存池的工作原理 内存池的创建 经过测试,Nginx 会为每个 Http 连接(Connection)创建大小为 256
字节的 pool
,为每个请求(Request)创建大小为 4096
的 pool
。 当客户端使用长连接向服务器请求资源时,Nginx 处理完 Request 后会释放 Request 对应的 pool
,但不会立即释放 Connection 对应的 pool
,而是等连接超时后再释放。 在一次简单的会话中(比如请求首页),从连接建立到连接关闭,至少有约 30 次的内存分配(调用 ngx_palloc()
)。 内存分配机制 小块内存的分配
对于小块内存的分配,Nginx 会首先尝试在当前小块内存池中查找是否存在足够的空闲内存空间。 如果当前小块内存池的可用内存不足以满足需求,Nginx 会尝试遍历该小块内存池的链表(即多个小块内存池),寻找可用的内存空间。 如果仍然找不到可用的内存空间,则会创建一个新的小块内存块,并将其添加到该小块内存池链表的尾部。 小块内存的分配是按顺序进行的,不会进行回收,因此分配效率很高,适合频繁分配和释放内存的场景(比如 Web 服务器处理大量短连接)。 大块内存的分配
对于大块内存的分配,当分配的内存大小超过小块内存分配的阈值(一般是 4095
字节,根据平台决定),Nginx 会直接调用 malloc()
申请一块大内存,并将该大块内存封装成一个 ngx_pool_large_t
节点,挂载到内存池的大块内存链表的头部。 这类内存块比较大、分配成本比较高,通常用于缓存大数据或模块中临时需要的较大资源。 由于小块内存使用的是顺序分配模型,而大块内存是从系统申请分配的 ,因此两者的管理方式不同,但最终都会在内存池销毁时(比如处理完 HTTP 请求、HTTP 连接关闭等)统一释放,确保资源不会泄漏。 内存分配流程 Nginx 从内存池分配内存的流程如下图所示:
内存池的销毁 一个 Web Server 通常会持续不断地接收 Connection 和 Request,因此 Nginx 将内存池划分为不同的层级,包括进程级内存池、Connection 级内存池以及 Request 级内存池。具体来说,当创建一个 Worker 进程时,系统会为该 Worker 分配一个独立的内存池;当有新的 Connection 到来时,又会在该 Worker 的内存池基础上为该 Connection 分配一个新的内存池;当该 Connection 上接收到一个新的 Request 时,再在 Connection 的内存池中为该 Request 创建一个新的内存池。
这种分层次的内存池管理方式,使得在处理完一个 Request 后,可以一次性释放该 Request 的整个内存池;当 Connection 关闭时,可以释放该 Connection 对应的内存池;而当 Worker 进程退出时,其对应的内存池也会整体释放。通过这种机制,确保了内存既有分配,也能及时回收,避免了内存泄漏的问题。
从内存的分配与释放策略可以看出,Nginx 内存池的核心作用在于将多个小块内存的分配操作集中处理,并在适当的时机统一释放,从而避免频繁的小内存申请,降低内存碎片的产生,提升了系统的内存管理效率和性能。
内存池的源码测试 这里将介绍如何编写代码测试 Nginx 内存池的 ngx_destroy_pool()
接口,并编译运行自定义的测代码。
编译 Nginx 源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 wget https://nginx.org/download/nginx-1.12.2.tar.gz tar -xvf nginx-1.12.2.tar.gz cd nginx-1.12.2./configure make -j4
编写测试代码 在 Nginx 的源码目录下(比如 nginx-1.12.2
),创建 C 源文件 ngx_testpool.c
,其测试代码的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <ngx_config.h> #include <nginx.h> #include <ngx_core.h> #include <ngx_palloc.h> #include <stdio.h> #include <stdlib.h> #include <string.h> void ngx_log_error_core (ngx_uint_t level, ngx_log_t *log , ngx_err_t err, const char *fmt, ...) {} typedef struct Data stData ;struct Data { char *ptr; FILE *pfile; }; void cleanFunc1 (char *p) { printf ("free ptr mem!\n" ); free (p); } void cleanFunc2 (FILE *pf) { printf ("close file!\n" ); fclose (pf); } void main () { ngx_pool_t *pool = ngx_create_pool (512 , NULL ); if (pool == NULL ) { printf ("ngx_create_pool fail...\n" ); return ; } void *p1 = ngx_palloc (pool, 128 ); if (p1 == NULL ) { printf ("ngx_palloc 128 bytes fail...\n" ); return ; } stData *p2 = ngx_palloc (pool, 512 ); if (p2 == NULL ) { printf ("ngx_palloc 512 bytes fail...\n" ); return ; } p2->ptr = malloc (12 ); strcpy (p2->ptr, "hello world" ); p2->pfile = fopen ("data.txt" , "w" ); ngx_pool_cleanup_t *c1 = ngx_pool_cleanup_add (pool, sizeof (char *)); c1->handler = cleanFunc1; c1->data = p2->ptr; ngx_pool_cleanup_t *c2 = ngx_pool_cleanup_add (pool, sizeof (FILE *)); c2->handler = cleanFunc2; c2->data = p2->pfile; ngx_destroy_pool (pool); return ; }
运行测试代码 1 2 3 4 5 6 7 8 9 10 11 cd nginx-1.12.2gcc -c -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules -o ngx_testpool.o ngx_testpool.c gcc -o ngx_testpool ngx_testpool.o objs/src/core/ngx_palloc.o objs/src/os/unix/ngx_alloc.o ./ngx_testpool
1 2 close file! free ptr mem!
内存池的源码移植 这里将介绍如何基于 C++ 移植 Nginx 内存池的核心源码,其中移植的代码主要位于 Nginx 的 ngx_palloc.h
和 ngx_palloc.c
源文件中。
提示
这里移植的 Nginx 版本是 1.12.2
,完整的案例代码可以从 这里 下载得到。
核心代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 #pragma once #include <string.h> #define ngx_align (d, a) (((d) + (a - 1)) & ~(a - 1)) #define ngx_align_ptr (p, a) (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1)) #define ngx_memzero (buf, n) (void) memset (buf, 0, n) using u_char = unsigned char ;using ngx_uint_t = unsigned int ;struct ngx_pool_s ;typedef void (*ngx_pool_cleanup_pt) (void *data) ;struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void *data; ngx_pool_cleanup_s *next; }; struct ngx_pool_large_s { ngx_pool_large_s *next; void *alloc; }; struct ngx_pool_data_t { u_char *last; u_char *end; ngx_pool_s *next; ngx_uint_t failed; }; struct ngx_pool_s { ngx_pool_data_t d; size_t max; ngx_pool_s *current; ngx_pool_large_s *large; ngx_pool_cleanup_s *cleanup; }; const int NGX_PAGESIZE = 4096 ;const int NGX_MAX_ALLOC_FROM_POOL = NGX_PAGESIZE - 1 ;const int NGX_DEFAULT_POOL_SIZE = 16 * 1024 ;const int NGX_POOL_ALIGNMENT = 16 ;const int NGX_MIN_POOL_SIZE = ngx_align ((sizeof (ngx_pool_s) + 2 * sizeof (ngx_pool_large_s)), NGX_POOL_ALIGNMENT);const int NGX_ALIGNMENT = sizeof (unsigned long );class ngx_mem_pool {public : ngx_mem_pool (int size = NGX_DEFAULT_POOL_SIZE); ~ngx_mem_pool (); ngx_mem_pool (const ngx_mem_pool &pool) = delete ; ngx_mem_pool &operator =(const ngx_mem_pool &) = delete ; public : void *ngx_palloc (size_t size) ; void *ngx_pnalloc (size_t size) ; void *ngx_pcalloc (size_t size) ; void ngx_pfree (void *p) ; void ngx_reset_pool () ; void ngx_destroy_pool () ; ngx_pool_cleanup_s *ngx_pool_cleanup_add (size_t size) ; private : void *ngx_create_pool (size_t size) ; void *ngx_palloc_small (size_t size, ngx_uint_t align) ; void *ngx_palloc_large (size_t size) ; void *ngx_palloc_block (size_t size) ; private : ngx_pool_s *_pool; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 #include <iostream> #include <stdexcept> #include "ngx_mem_pool.h" ngx_mem_pool::ngx_mem_pool (int size) { this ->ngx_create_pool (size); if (_pool == nullptr ) { throw std::runtime_error ("create memory pool fail..." ); } } ngx_mem_pool::~ngx_mem_pool () { if (_pool != nullptr ) { this ->ngx_destroy_pool (); } } void *ngx_mem_pool::ngx_create_pool (size_t size) { if (size < NGX_MIN_POOL_SIZE) { std::cout << "create memory pool fail, pool size too small" << std::endl; return nullptr ; } _pool = (ngx_pool_s *) malloc (size); if (_pool == nullptr ) { return nullptr ; } _pool->d.last = (u_char *) _pool + sizeof (ngx_pool_s); _pool->d.end = (u_char *) _pool + size; _pool->d.next = nullptr ; _pool->d.failed = 0 ; size = size - sizeof (ngx_pool_s); _pool->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; _pool->current = _pool; _pool->large = nullptr ; _pool->cleanup = nullptr ; return _pool; } void *ngx_mem_pool::ngx_palloc (size_t size) { if (size <= this ->_pool->max) { return ngx_palloc_small (size, 1 ); } return ngx_palloc_large (size); } void *ngx_mem_pool::ngx_pnalloc (size_t size) { if (size <= this ->_pool->max) { return ngx_palloc_small (size, 0 ); } return ngx_palloc_large (size); } void *ngx_mem_pool::ngx_pcalloc (size_t size) { void *p; p = ngx_palloc (size); if (p) { ngx_memzero (p, size); } return p; } void *ngx_mem_pool::ngx_palloc_small (size_t size, ngx_uint_t align) { u_char *m; ngx_pool_s *p; p = this ->_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 (size); } void *ngx_mem_pool::ngx_palloc_large (size_t size) { void *p; ngx_uint_t n; ngx_pool_large_s *large; p = malloc (size); if (p == nullptr ) { return nullptr ; } n = 0 ; for (large = this ->_pool->large; large; large = large->next) { if (large->alloc == nullptr ) { large->alloc = p; return p; } if (n++ > 3 ) { break ; } } large = (ngx_pool_large_s *) ngx_palloc_small (sizeof (ngx_pool_large_s), 1 ); if (large == nullptr ) { free (p); return nullptr ; } large->alloc = p; large->next = this ->_pool->large; this ->_pool->large = large; return p; } void *ngx_mem_pool::ngx_palloc_block (size_t size) { u_char *m; size_t psize; ngx_pool_s *p, *_new; psize = (size_t ) (this ->_pool->d.end - (u_char *) this ->_pool); m = (u_char *) malloc (psize); if (m == nullptr ) { return nullptr ; } _new = (ngx_pool_s *) m; _new->d.end = m + psize; _new->d.next = nullptr ; _new->d.failed = 0 ; m += sizeof (ngx_pool_data_t ); m = ngx_align_ptr (m, NGX_ALIGNMENT); _new->d.last = m + size; for (p = this ->_pool->current; p->d.next; p = p->d.next) { if (p->d.failed++ > 4 ) { this ->_pool->current = p->d.next; } } p->d.next = _new; return m; } void ngx_mem_pool::ngx_pfree (void *p) { ngx_pool_large_s *l; for (l = this ->_pool->large; l; l = l->next) { if (p == l->alloc) { free (l->alloc); l->alloc = nullptr ; return ; } } } void ngx_mem_pool::ngx_reset_pool () { ngx_pool_s *p; ngx_pool_large_s *l; for (l = this ->_pool->large; l; l = l->next) { if (l->alloc) { free (l->alloc); } } p = this ->_pool; p->d.last = (u_char *) p + sizeof (ngx_pool_s); p->d.failed = 0 ; for (p = p->d.next; p; p = p->d.next) { p->d.last = (u_char *) p + sizeof (ngx_pool_data_t ); p->d.failed = 0 ; } this ->_pool->current = this ->_pool; this ->_pool->large = nullptr ; } void ngx_mem_pool::ngx_destroy_pool () { ngx_pool_s *p, *n; ngx_pool_large_s *l; ngx_pool_cleanup_s *c; for (c = this ->_pool->cleanup; c; c = c->next) { if (c->handler) { c->handler (c->data); } } for (l = this ->_pool->large; l; l = l->next) { if (l->alloc) { free (l->alloc); } } for (p = this ->_pool, n = this ->_pool->d.next; ; p = n, n = n->d.next) { free (p); if (n == nullptr ) { break ; } } } ngx_pool_cleanup_s *ngx_mem_pool::ngx_pool_cleanup_add (size_t size) { ngx_pool_cleanup_s *c; c = (ngx_pool_cleanup_s *) ngx_palloc (sizeof (ngx_pool_cleanup_s)); if (c == nullptr ) { return nullptr ; } if (size) { c->data = ngx_palloc (size); if (c->data == nullptr ) { return nullptr ; } } else { c->data = nullptr ; } c->handler = nullptr ; c->next = this ->_pool->cleanup; this ->_pool->cleanup = c; return c; }
测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <iostream> #include <memory> #include <string.h> #include "ngx_mem_pool.h" using namespace std;struct Data { char *ptr; FILE *pfile; }; void cleanFunc1 (void *arg) { char *p = (char *) arg; cout << "free ptr memory!" << endl; free (p); } void cleanFunc2 (void *arg) { FILE *p = (FILE *) arg; cout << "close file!" << endl; fclose (p); } int main () { unique_ptr<ngx_mem_pool> pool (new ngx_mem_pool (256 )) ; void *p1 = pool->ngx_palloc (128 ); if (p1 == nullptr ) { cout << "ngx_palloc 128 bytes fail..." << endl; return -1 ; } Data *p2 = (Data *) pool->ngx_palloc (512 ); if (p2 == nullptr ) { cout << "ngx_palloc 512 bytes fail..." << endl; return -1 ; } p2->ptr = (char *) malloc (12 ); if (p2->ptr == nullptr ) { cout << "malloc 12 bytes fail..." << endl; return -1 ; } strcpy (p2->ptr, "hello world" ); p2->pfile = fopen ("data.txt" , "w" ); ngx_pool_cleanup_s *c1 = pool->ngx_pool_cleanup_add (sizeof (char *)); c1->handler = cleanFunc1; c1->data = p2->ptr; ngx_pool_cleanup_s *c2 = pool->ngx_pool_cleanup_add (sizeof (FILE *)); c2->handler = cleanFunc2; c2->data = p2->pfile; return 0 ; }
1 2 close file! free ptr memory!
FAQ Nginx 为什么要进行内存管理?
Nginx 如何进行内存管理?
Nginx 首先将内存池进行分级管理:包括进程级、连接级和请求级三个层次;随后再根据内存使用情况的不同,将内存池细分为三类:小块内存、大块内存以及自定义资源内存。
进程级内存池在通过 Fork()
创建 Worker 子进程时完成初始化。由于 Fork()
会复制父进程的数据段和堆栈段,因此每个子进程拥有独立的内存空间。根据 Web Server 的运行特点,当客户端建立连接时,会在函数 void ngx_event_accept (ngx_event_t *ev)
中创建连接级的内存池;当客户端发起请求时,会在函数 void ngx_http_init_connection (ngx_connection_t *c)
中创建请求级的内存池。
在处理 HTTP 请求的过程中,所有与该请求相关的内存分配操作都在对应连接的内存池中完成。根据实际需要的内存大小及资源类型,Nginx 会采用不同的分配策略:对于较小的内存块,采用顺序分配以提高效率;对于较大的内存块或特殊资源,则使用独立分配方式以便于管理。
当请求处理完成后,请求级的内存池会被整体释放;当连接超时或者断开时,连接级的内存池随之释放;当进程退出时,系统会释放进程占用的全部内存池资源。通过这种分级且分类型的内存管理机制,Nginx 有效提升了内存分配效率,降低了内存碎片率,同时确保了资源的可控释放和系统的高性能运行。
Nginx 的内存管理解决了哪些问题?
简化了内存操作 :程序员不必担心何时释放内存,当连接释放时,就回收该连接对应的内存池。避免了内存碎片 :从外部内存碎片来看,采用一次性申请一个内存页,避免了外部内存碎片;从内部内存碎片来看,对大小内存申请分别管理,提高了内存利用率,避免了内部内存碎片。避免了内存泄露 :在同一内存池上进行内存申请和回收,当连接关闭后,不存在没有被回收的内存,即可以避免内存泄漏问题。提高了内存访问效率 :充分利用程序局部性原理,结合内存对齐和内存分页机制,有效提高了 CPU 访存的 Cache 命中率。参考资料