第3章 Nginx内存管理

内存管理是各个WEB服务器都相继实现了的独立功能,作为一个满足高性能的WEB服务器,面对各种请求和应答处理流程,必然涉及到内存以及连接的分配与管理,如果完全采用标准的malloc/free函数接口实现内存管理,频繁的调用必然引起性能的低效。Nginx也不例外,采用了短小精干的方式,实现了其特有的内存管理方式。通过这部分的分析学习,希望我们也能达到融会贯通的目的,不仅能深入理解Nginx的内存管理机制,在实际应用中也能学到其独特的机制,并加以运用。对于开发Nginx模块来说,就更要熟悉其内存管理的相关接口。

内存相关的操作主要在文件 os/unix/ngx_alloc.{h,c} 和core/ngx_palloc.{h,c} 中实现。先分析内存管理的几个主要数据结构:

14 typedef struct ngx_pool_s        ngx_pool_t;
 
48 typedef struct {
 49    u_char               *last;
 50    u_char               *end;
 51    ngx_pool_t           *next;
 52    ngx_uint_t            failed;
 53 } ngx_pool_data_t;
Last:当前内存分配结束位置,即下一段可分配内存的起始位置;
End:内存池的结束位置;
Next:链接到下一个内存池
Failded:记录内存分配不能满足需求的失败次数;
ngx_pool_data_t;结构用来维护内存池的数据块,供用户分配之用。
 54
 55
 56 struct ngx_pool_s {
 57    ngx_pool_data_t       d;
 58    size_t                max;
 59    ngx_pool_t           *current;
 60    ngx_chain_t          *chain;
 61    ngx_pool_large_t     *large;
 62    ngx_pool_cleanup_t   *cleanup;
 63    ngx_log_t            *log;
 64 };
d:数据块
max:数据块大小,小块内存的最大值
current:指向本内存池
chain:这里可以挂一个链结构
large:指向大块内存分配,nginx中,大块内存分配直接采用标准系统接口malloc
cleanup:析构函数,挂载内存释放时需要清理资源的一些必要操作;
log:内存分配相关的日志记录
再看看大块数据分配的结构体:
42 struct ngx_pool_large_s {
 43    ngx_pool_large_t     *next;
 44    void                 *alloc;
 45 };

这个组织比较简单就是一个链表。

下图展示了内存的管理组织结构:


怎么判断nginx是否有缓存_c


接下来,我们深入分析内存管理的主要函数。

(1)ngx_create_pool(size_t size, ngx_log_t *log)
size:分配内存的大小
log:用于日志记录
下面是该函数的具体实现
15 ngx_pool_t *
 16ngx_create_pool(size_t size, ngx_log_t *log)
 17 {
 18    ngx_pool_t  *p;
 19
 20     p= ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
 21    if (p == NULL) {
 22        return NULL;
 23     }
 24
 25    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
 26    p->d.end = (u_char *) p + size;
 27    p->d.next = NULL;
 28    p->d.failed = 0;
 29
 30    size = size - sizeof(ngx_pool_t);
 31    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size :NGX_MAX_ALLOC_FROM_POOL;
 32
 33    p->current = p;
 34    p->chain = NULL;
 35    p->large = NULL;
 36    p->cleanup = NULL;
 37    p->log = log;
 38
 39    return p;
 40 }
第20行ngx_memalign()函数执行内存分配,该函数的实现在src/os/unix/ngx_alloc.c文件中(假定NGX_HAVE_POSIX_MEMALIGN被定义):
50 void *
 51ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
 52 {
 53    void  *p;
 54    int    err;
 55
 56    err = posix_memalign(&p, alignment, size);
该函数分配以alignment为对齐的size字节的内存大小,其中p指向分配的内存块。
 57
 58    if (err) {
 59        ngx_log_error(NGX_LOG_EMERG, log, err,
 60                       "posix_memalign(%uz, %uz)failed", alignment, size);
 61        p = NULL;
 62     }
 63
 64    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
 65                    "posix_memalign:%p:%uz @%uz", p, size, alignment);
 66
 67    return p;
 68 }
从这个函数的实现体,我们可以看到p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h文件中:
23 #define NGX_POOL_ALIGNMENT       16

因此,nginx的内存池分配,是以16字节为边界对齐的。

函数的25—40行,是按照图4的所示的结构进行组织进行初始化,具体实现读者可以对照图4来加以理解。

 

怎么判断nginx是否有缓存_内存管理_02

(2)void ngx_destroy_pool(ngx_pool_t *pool)
内存池的销毁函数,pool指向需要销毁的内存池。
43 void
 44ngx_destroy_pool(ngx_pool_t *pool)
 45 {
 46    ngx_pool_t          *p, *n;
 47    ngx_pool_large_t    *l;
 48    ngx_pool_cleanup_t  *c;
 49
 50    for (c = pool->cleanup; c; c = c->next) {
 51        if (c->handler) {
 52            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
 53                            "run cleanup:%p", c);
 54            c->handler(c->data);
 55        }
 56     }
前面讲到,cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等,清理函数是一个handler的函数指针挂载。因此,在这部分,对内存池中的析构函数遍历调用。
 57
 58    for (l = pool->large; l; l = l->next) {
 59
 60        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free:%p", l->alloc);
 61
 62        if (l->alloc) {
 63            ngx_free(l->alloc);
 64        }
 65     }
这一部分用于清理大块内存,ngx_free实际上就是标准的free函数,即大内存块就是通过malloc和free操作进行管理的。
 66
 67#if (NGX_DEBUG)
 68
 69    /*
 70     * we could allocate the pool->log from this pool
 71     * so we cannot use this log while free()ing the pool
 72     */
 73
 74    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next){
 75        ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
 76                        "free: %p, unused:%uz", p, p->d.end - p->d.last);
 77
 78        if (n == NULL) {
 79            break;
 80        }
 81     }
 82
只有debug模式才会执行这个片段的代码,主要是log记录,用以跟踪函数销毁时日志记录。
 83#endif
84
 85    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next){
 86        ngx_free(p);
 87
 88        if (n == NULL) {
 89            break;
 90        }
 91     }
 92 }

该片段彻底销毁内存池本身。