Nginx源码分析-内存管理

Nginx源码分析

  • 内存池
    • 内存池结构
    • 与操作系统相关的内存操作函数
    • 申请内存池
    • 申请小块内存
    • 申请大块内存
    • 内存的释放
    • 释放大块内存
    • 销毁内存池
    • 重置内存池
  • 共享内存
  • 参考链接

内存池

Nginx使用内存池管理进程内的内存,可分为两类:小块内存和大块内存。
内存池的引入可有效解决两个问题:
(1) 减少应用程序与OS之间进行频繁内存和释放的系统调用,进而减少程序执行期间在两个空间的切换,提升了程序执行效率;

(2)内存池可依据应用特性组织内存管理方式。能有效减少操作系统的内存碎片

内存池结构

Nginx的内存池採用链表结构,每一个内存池相应3个链表:内存池链表、大块内存链表和需特殊回收的已分配内存链表
主要结构有三个:ngx_pool_s、ngx_pool_data_t、ngx_pool_larges_s。

struct ngx_pool_s {
    ngx_pool_data_t       d;     //指向小块内存
    size_t                max;   //内存池数据块的最大值,用于区分是小块内存还是大块内存
    ngx_pool_t           *current; //当前内存池
    ngx_chain_t          *chain;   //filter 模块在处理从别的 filter 模块或者是 handler 模块传递过来的数据
    ngx_pool_large_t     *large;  //指向大块内存
    ngx_pool_cleanup_t   *cleanup;//内存清理函数
    ngx_log_t            *log;   //日志
};
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;   //下一块大块内存
    void                 *alloc;  // 指向大块内存数据
};
typedef struct {
    u_char               *last; //下一次分配内存的起始位置
    u_char               *end;  //该块内存的结束位置
    ngx_pool_t           *next; // 指向下一块小块内存块
    ngx_uint_t            failed; // 失败次数。分配超过四次,就不再尝试从这块内存中申请内存
} ngx_pool_data_t;

与操作系统相关的内存操作函数

在nginx中,与OS直接相关的内存操作在文件:src\os\unix文件夹下的ngx_alloc.c和ngx_alloc.h中。主要函数有:
(1)void *ngx_alloc(size_t size, ngx_log_t *log);
该函数主要通过malloc函数从OS中申请一块内存;
(2)void *ngx_calloc(size_t size, ngx_log_t *log);
该函数首先通过ngx_alloc从OS中申请一块内存,然后再把所申请内存置零。
(3)void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);
该函数提供一种内存对齐的方式从OS中申请内存,该函数所返回内存块的起始地址都是从对齐大小alignment的整数倍開始。

申请内存池

通过ngx_create_pool 函数创建一个新的内存池。
Nginx源码分析-内存管理_第1张图片

  • 内存池链表中每一个节点初始可使用的存储空间大小是一样的,而且在内存池创建时指定,大块内存管理链表中,每一个分配出去的内存大小不一定一样;
  • 大块内存管理链表中,每块分配应用的内存都大于内存池链表中所管理的内存块大小;
  • 内存池链表的每一个内存池节点中。存储的管理信息(结构体ngx_pool_t和待分配的内存空间连在一起,而且在待分配的内存空间之前),大块内存的管理结构体和该结构体所管理的大内存块不在一个连续内存空间中;
  • 大内存块的管理结构体ngx_pool_large_s所占用的内存是在内存池链表中分配的。
  • 每一个已分配出去的需特殊回收的内存都由一个结构体ngx_pool_cleanup_s来描写叙述,全部需特殊回收的内存被组织成一个链表。链表的表头存放在内存池的第一个存储节点结构体ngx_pool_t的cleanup成员中。
  • 需特殊回收的内存块和其管理结构体ngx_pool_cleanup_s所占用的内存都从内存池中分配。
    Nginx源码分析-内存管理_第2张图片

申请内存池
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;

通过创建内存池函数部分代码可以看到:创建的内存池块包括ngx_pool_t结构以及未分配的空间,该未分配的空间用于小块内存。

申请小块内存

调用函数: static ngx_inline void * ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
1、依次遍历内存池链表中空闲内存块是否充足(p->d.end ->p->d.last),若充足p->d.last+size;并返回p->d.last地址,若不充足,则继续遍历,同时p->d.failed++;
2、若已有链表中都不满足该大小size,则通过ngx_palloc_block函数进行新增内存块,即新的内存池链表节点。

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;
}

通过上面分配内存块可以看到,新分配的内存块的结构依然是ngx_pool_s,但只对ngx_pool_data_t进行了赋值。内存池链表通过ngx_pool_data_t中的next链接起来。
对内存池pool的存储节点链表新扩充一个节点,该函数的扩充算法为:
(1)计算当前内存池的内存池链表的节点大小(在内存池链表中。每一个节点的大小都是一样的。而且节点的管理数据结构和可分配的内存空间是连在一起的)psize。
(2)调用ngx_memalign从操作系统的内存中申请psize大小内存块,作为内存池链表的新增节点;
(3)对新申请存储节点的管理结构体ngx_pool_t的各成员进行初始化。
(4)从新申请存储节点的可分配内存空间中分配出用户申请的内存。
(5)将新申请的存储节点插入到内存池的“内存池链表”的队列尾部,假设当前节点的分配失败次数小于4,则调整内存池的当期可用节点的位置移动到下一个节点;
注意: 当用户申请内存失败(即内存池中新增了存储节点)时。内存池链表汇中。从current节点到链表的最后一个节点的failed值全部+1;

申请大块内存

调用函数: staticvoid * ngx_palloc_large(ngx_pool_t *pool, size_t size)

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;
}

在该函数中,首先通过ngx_alloc从操作系统中申请一块用户申请大小(size參数指定)的内存块,这块内存将被直接返回给申请用户使用,如有必要则在内存池中为该大内存块申请一个小块内存用于存储管理用户所申请大内存块的数据结构ngx_pool_large_t。
Nginx源码分析-内存管理_第3张图片

(1) 在内存池的大块内存链表中。通过结构体ngx_pool_large_t管理每一个大块内存,多个ngx_pool_large_t节点链接起来形成一个大块内存链表;
(2) 在大块内存管理中,假设用户释放了大块内存,则把该大块内存的管理结构体ngx_pool_large_t中的alloc变量设为null。并不会释放该大块内存的管理结构体ngx_pool_large_t。而是留着等待产生新大块内存时复用;
(3) 在申请一个新的大块内存时,首先从头開始遍历由ngx_pool_large_t组成的大块链表。找到某个节点的大块内存已经被释放。则把这个空隙管理节点利用起来。假设从头开始连续找3个节点都没有发现空暇的ngx_pool_large_t节点。就不再找了,而是从当前内存池中新申请一个ngx_pool_large_t,并用它管理为用户新申请的大块内存,然后将这个新申请的ngx_pool_large_t节点插入到大块内存链表的首部!

大块内存的管理结构体ngx_pool_large_t是在当前内存池中所分配,而不一定是在内存池的第一个存储节点中分配

内存的释放

  • 一般从内存池中分配出去的内存不做回收管理(通过ngx_pmemalign、ngx_palloc、ngx_pnalloc、ngx_pcalloc)。当使用完内存池之后,重置整个内存池就可以。让全部内存池的存储节点的可分配区直接初始化为全部可用,这一步仅仅须要调整每一个存储节点last成员就可以。
  • 对于大块内存释放时,直接将其释放给操作系统;
  • 重置内存池时将回收全部的内存池内存,自然也就回收了全部大块内存的管理节点(结构体为ngx_pool_large_t,这些管理节点就是在内存池中进行分配的),并将全部的大块内存全部释放给操作系统;
  • 假设分配须要做特殊回收处理的内存。则需通过接口ngx_pool_cleanup_add来完毕申请,申请出去的每一个内存都通过内存池第一个节点的cleanup成员来管理,全部分配出去的需特殊回收内存以链表方式管理起来;
struct ngx_pool_cleanup_s {
   ngx_pool_cleanup_pt   handler;
   void                 *data;
   ngx_pool_cleanup_t   *next;
};

结构体ngx_pool_cleanup_s用于描写叙述一个从内存池中分配出去的、须要特殊回收的内存块。成员data指向这个须要特殊回收的内存块,Handler在回收data所指向内存块时使用,next指向下一个需特殊回收内存块的管理结构体,这样全部须要特殊回收内存块的管理结构体都被组织成一个链表结构。

释放大块内存

调用函数: ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
该函数并未释放大内存块的管理结构体ngx_pool_large_t

销毁内存池

调用函数:voidngx_destroy_pool(ngx_pool_t *pool);

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;} }
}

重置内存池

调用函数:voidngx_reset_pool(ngx_pool_t *pool);

共享内存

ngx_shm_alloc
将多个进程的虚拟内存映射到同一个物理内存上,实现进程间的通信

参考链接

https://www.cnblogs.com/cynchanpin/p/7359116.html

你可能感兴趣的:(nginx,运维)