1 进程内内存池

内存池关系如下图,接收请求后创建内存池,注意分为请求内存池,连接内存池,若开启keepalive,则多个请求是同一个连接内存池。

                                 //ngx_pool_data_t
                                 ++-------+           +--------+
                            +---->|last   |       +---> last   |
 //ngx_pool_s               |    ++-------+       |   +--------+
   +-------------+          |    | end    |       |   | end    |
   |   last      +----+     |    +--------+       |   ++-------+
   +-------------+    |     |    | next   +-------+   ||next   |
+--+   end       |    |     |    +--------+           ++-------+
|  +-------------+    |     |    | failed |           | failed |
|  |   next      +----+-----+    +--------+           +--------+
|  +-------------+    |          |  已使用 |           |  已使用 |
|  |   failed    |    |          |        |           |        |
|  +-------------+    |          +--------+           +--------+
|  |   max       |    |          |  未使用 |           |  未使用 |
|  +-------------+    |          +--------+           +--------+
|  |   current   |    |
|  +-------------+    |
|  |   chain     |    |          //ngx_pool_large_t
|  +-------------+    |          +--------+          +--------+
|  |   large     +----+----------> next   +--------->| next   |
|  +-------------+    |          +--------+          +--------+
|  |   cleanup   |    |         | alloc  |          | alloc  |
|  +-------------+    |          +--------+          +--------+
|  |   log       |    |               |                   |
|  +-------------+    |          +--------+          +--------+
|  |   已使用     +-+--+          |        |          |        |
|  +---------<---+-+             |  内存  |           |  内存  |
|  |             |               |        |          |        |
+-->   未使用     |               +--------+          +--------+
   +-------------+
//代码地址
//src/core/ngx_palloc.h
//src/core/ngx_palloc.c

//内存池
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;
};

//小内存节点
typedef struct {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t            failed; 
} ngx_pool_data_t;

//大内存节点
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc;
};

首先通过ngx_create_pool创建内存池,然后通过ngx_palloc、ngx_pcalloc、ngx_pnalloc分配内存,ngx_palloc是基础api,ngx_pcalloc对内存初始化,ngx_pnalloc为内存对齐 下面我们重点讲解ngx_palloc

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    //如果内存池的最大内存小于size,则分配ngx_pool_large_s大内存
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
    return ngx_palloc_large(pool, size);
}

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);
}
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) {
         //失败次数,比如分配4096内存,已经用了4090,申请7字节,失败了申请新的内存块,直到ngx_uint_t>4(经验值)后,
         //pool->current指向后一个ngx_pool_data_t,当前剩余的6字节不会再被使用
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }
    //将新内存块添加到内存池链表上
    p->d.next = new;
    return m;
}

2 nginx共享内存

2.1 共享内存创建释放

//src/os/unix/ngx_shmem.h
 typedef struct {
    u_char      *addr; //首地址
    size_t       size; //内存卡大小
    ngx_str_t    name; //内存块名称
    ngx_log_t   *log; 
    ngx_uint_t   exists;   /* unsigned  exists:1;  */
} ngx_shm_t;
//创建共享内存
ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);
//释放共享内存
void ngx_shm_free(ngx_shm_t *shm);

2.2 共享内存管理

//src/core/ngx_slab.h
//src/core/ngx_slab.c
typedef struct ngx_slab_page_s  ngx_slab_page_t;

struct ngx_slab_page_s {
    uintptr_t         slab;
    ngx_slab_page_t  *next;
    uintptr_t         prev;
};

//统计信息 详情见下图
typedef struct {
    ngx_uint_t        total;
    ngx_uint_t        used;
    ngx_uint_t        reqs;
    ngx_uint_t        fails;
} ngx_slab_stat_t;

typedef struct {
    ngx_shmtx_sh_t    lock;
    size_t            min_size;  //最小可分配的内存
    size_t            min_shift; //最小可分配内存对应的偏移min_size>>min_shift
    ngx_slab_page_t  *pages; //指向第一页
    ngx_slab_page_t  *last; //指向最后一页
    ngx_slab_page_t   free; //管理空闲页面
    ngx_slab_stat_t  *stats; //slab统计信息
    ngx_uint_t        pfree; //空闲页数
    u_char           *start;
    u_char           *end;
    ngx_shmtx_t       mutex;
    u_char           *log_ctx;
    u_char            zero;
    unsigned          log_nomem:1;
    void             *data;
    void             *addr;
} ngx_slab_pool_t;

统计信息.png

共享内存slab.png

2.2 互斥锁

如果系统支持原子操作,Nginx可通过原子操作实现互斥锁,将共享内存块中原子变量的值改为当前进程pid;如果系统不支持原子操作,Nginx则通过文件锁实现互斥锁

checking for posix_memalign() ... found
checking for memalign() ... found
checking for mmap(MAP_ANON|MAP_SHARED) ... found
checking for mmap("/dev/zero", MAP_SHARED) ... found
checking for System V shared memory ... found
checking for POSIX semaphores ... not found
checking for POSIX semaphores in libpthread ... found

//todo 后面单独补充,本章重点内存
typedef struct {
#if (NGX_HAVE_ATOMIC_OPS)
    ngx_atomic_t  *lock;  //原子变量
#if (NGX_HAVE_POSIX_SEM)
    ngx_atomic_t  *wait; //原子变量
    ngx_uint_t     semaphore; //信号量
    sem_t          sem;
#endif
#else
    //文件锁
    ngx_fd_t       fd;
    u_char        *name;
#endif
    ngx_uint_t     spin; //自旋次数
} ngx_shmtx_t;