Nginx源码分析_01_内存管理

排、内存池概述
内存池代码见src/core/ngx_palloc.c,内存池的结构体见src/core/ngx_palloc.h;

Nginx接受到请求之后,会创建内存池或在已有内存池中进行内存的开辟,把内存分配给请求;

内存可分为大块内存和小块内存,大块内存可以被手动释放,小块内存不需要,在内存池销毁时,大块和小块内存会被依次释放;

内存池中开辟的内存全部都是小块内存,而大块内存指的是由其中一个指针指向的结点里的一个指针指向的内存 (doge)

连、主要涉及的结构体

一、ngx_pool_s(常用内部的ngx_pool_t称呼)
该结构体为Nginx的内存池

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 d; 用于存放内存池的数据,是一个结构体;
size_t max; 为内存池的大小;
ngx_pool_t *current; 指向该内存池,同时在别处很多地方都用它来指代内存池而不用ngx_pool_s;
ngx_pool_large_t *large; 为大块内存的管理节点,也是一个结构体;
ngx_log_t *log; 为日志;

二、ngx_pool_large_s(也作ngx_pool_large_t)

该结构体是大块内存的管理结点;

typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    
    
    ngx_pool_large_t     *next;
    void                 *alloc;
};

next 指针用于指向下一个大块内存的管理结点,也就是说管理结点是用链表链起来的;
alloc 用于指向真正的大块内存,一个管理结点只能指向一块大块内存;

三、ngx_pool_data_t

用于存放内存池的一些数据

typedef struct {
    
    
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t            failed;
} ngx_pool_data_t;

last和end表示小块内存从哪到哪;
next指向下一个内存池;
failed表示失败次数;

营、内存的开辟

一、 ngx_create_pool
该函数负责创建内存池,并返回内存池类型ngx_pool_t的指针;
为了方便阅读,直接在源码上进行批注;
加上双斜线会让批注变灰,不易查看,所以不加;

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

	设置last指针,指向(u_char *)强转内存池初始大小后的位置,也就是小块内存的起始位置;
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);

	end指向内存池的末尾;
    p->d.end = (u_char *) p + size;

	next指向空;
    p->d.next = NULL;

	失败次数设置为0;
    p->d.failed = 0;

	计算该内存池的内存块可以分配的大小,也就是减去了那些控制信息后可以分配的小块内存大小;
    size = size - sizeof(ngx_pool_t);

	内存池能够分配的最大内存是NGX_MAX_ALLOC_FROM_POOL,如果可分配的内存大于这个值,要取定这个值
    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;
}

由此可以给出初始化后的内存池图
在这里插入图片描述

本文参考《Nginx底层设计与源码分析》,但在此处作者的言词的确较为晦涩,易引起歧义;

二、ngx_palloc
该函数负责内存块的申请;

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

如果要申请的内存块大于这个内存池的小块内存,则申请大块内存,反之则申请小块内存;

三、ngx_palloc_small
该函数用于申请小块内存;

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 {
    
    

		获取last指针,也就是内存池中还可以分配的内存的起始位置;
        m = p->d.last;

		内存对齐;
        if (align) {
    
    
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

		如果这个内存池中剩余的内存足够,那就把last指针往下移;
        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_create_pool创建内存池,后面的内存池就是对它进行复制粘贴;

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;

	这个pool是现存内存池的第一个!
	让前面的内存池分配失败次数依次加一;
	如果某个内存池内存分配次数超过4,则不再对该内存池进行操作;
	
    for (p = pool->current; p->d.next; p = p->d.next) {
    
    
        if (p->d.failed++ > 4) {
    
    
        
        这句代码看起来似乎不太合理,但是我们知道靠前的内存池会先加到4,而且失败次数是依次递减的!
        所以如果要执行这句代码,pool->current和p->d.next永远都是指向的相邻的两个内存池!
            pool->current = p->d.next;
        }
    }

    p->d.next = new;

    return m;
}

五、ngx_palloc_large
该函数用于申请大块内存;

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

		管理结点链表的长度最多为3if (n++ > 3) {
    
    
            break;
        }
    }

	大块内存管理结点的内存是从内存池中的小块内存里获取的;
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
    
    
    	如果large为空,释放大块内存,返回空;
        ngx_free(p);
        return NULL;
    }

	如果不为空,large的alloc指针指向大块内存;
    large->alloc = p;

	把该管理结点插到管理链表的头部
    large->next = pool->large;
    pool->large = large;

    return p;
}

在这里插入图片描述

团、释放内存
大块内存可以手动释放,小块内存不需要手动释放,在释放内存池时会统一释放所有的大块小块内存;

旅、共享内存
留个坑,以后再补

猜你喜欢

转载自blog.csdn.net/dgytjhe/article/details/121436140