Nginx内存管理源码剖析注解

4 篇文章 0 订阅

Nginx内存池总览

在这里插入图片描述

内存池中变量类型定义

/*
 * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
 * On Windows NT it decreases a number of locked pages in a kernel.
 */
// 内存池中小内存和大内存分配的分界点:一个页的大小4k
#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)


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


typedef struct ngx_pool_large_s  ngx_pool_large_t;

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;

// 内存池类型
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_create_pool

// 创建内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p; // 内存池的头部信息,记录内存池中可用内存的信息

	// 对size进行16字节的内存对齐之后开辟对应大小的空间(不同平台调用函数不同)
    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; // 指向内存池的末尾
    p->d.next = NULL;
    p->d.failed = 0;

    size = size - sizeof(ngx_pool_t); // 内存池中实际能够使用的内存大小
    // 使用小内存块进行内存分配时,内存池中能够分配的内存块的最大值,最多是一个页面大小
    // 也可以理解为是内存池分配内存时,使用小块内存分配和大块内存分配的分界线
    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;
}

内存池分配空间:ngx_palloc

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
	// 如果需要分配的内存小于等于pool->max,那么就使用小内存分配
	// 否则就使用大内存块分配。注意:pool->max最大也不能超过一个页面大小(4K)
    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; // 从pool->current指向的大内存块进行分配内存

    do {
        m = p->d.last;

        if (align) {
			// 对可用空间的起始地址进行内存对齐
			// 这样可以减少CPU进行IO的次数,提高CPU访问数据的效率
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

		// 判断当前大内存块中的可用内存是否能够分配size大小的内存
		// 如果可以,就直接分配出去。否则就寻找下一个大内存块中的可用内存
        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
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);

	// 分配内存对齐之后的大内存块
	// 注意:分配的大内存块只有内存块的头信息(ngx_pool_data_t),其余的部分都没有
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); 
    if (m == NULL) {
        return NULL;
    }

	// new成为新大内存的头信息
    new = (ngx_pool_t *) m;

    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

	// m在跳过内存块的头部信息之后,进行内存对齐。最后就可以被分配出去使用了
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

	// 遍历内存块链表,并设置pool->current
    for (p = pool->current; p->d.next; p = p->d.next) {
		// 如果内存块分配内存失败的次数超过4次,那么下一次分配内存的时候
		// 就会默认该大内存块所剩的可用内存太小,不适合进行内存分配了
		// 之后分配内存的时候就会直接跳过该大内存块,使用后面的大内存块
		// 进行内存分配
        if (p->d.failed++ > 4) {
            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); // malloc大块内存
    if (p == NULL) {
        return NULL;
    }

    n = 0;

	// 遍历大块内存块的链表,将其中节点中alloc变量指向NULL的节点指向刚创建的大块内存
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }
		// 为了减少遍历节点,所以只会从pool->large向后找三个节点
		// 如果这三个节点的alloc都不为NULL的话,就不会在找了,而是创建新的large节点
        if (n++ > 3) {
            break;
        }
    }

	// 使用小块内存分配器分配large节点的头部信息
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

	// 将节点的alloc指针指向分配的大块内存
	// 并将头部信息节点头插到large链表中
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}


重置内存池:ngx_reset_pool

// 内存池中大块内存和小块内存的内存重置
void
ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;

	// 回收所有的大块内存
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

	// 重置所有小块内存的可用空间
    for (p = pool; p; p = p->d.next) {
		// fixbug: 除了第一个内存池,其余的内存池头部信息只有ngx_pool_data_t
		// 		   不需要跳过ngx_pool_t这么多
        p->d.last = (u_char *) p + sizeof(ngx_pool_t); 
        p->d.failed = 0; // 重置内存池分配内存失败次数
    }
	
	/*
	// 完美的重置小块内存池的方式
	for (p = pool; p; p = p->d.next) {
		if (p == pool) { // 处理第一个内存池
			p->d.last = (u_char *) p + sizeof(ngx_pool_t);
		} else { // 处理第二个及其之后的内存池
			p->d.last = (u_char *) p + sizeof(ngx_pool_data_t);
		}
		p->d.failed = 0;
	}
	*/

	// 重置内存池元信息
    pool->current = pool; // 分配内存的第一个内存池重置为pool
    pool->chain = NULL; 
    pool->large = NULL; // 重置大块内存链表
}


void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
	// 如果需要分配的内存小于等于pool->max,那么就使用小内存分配
	// 否则就使用大内存块分配。注意:pool->max最大也不能超过一个页面大小(4K)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}

释放大块内存:ngx_pfree

// 释放内存池中起始地址为p的大块内存
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
		// 在大块内存块的链表中找到以p为起始位置的节点
		// 释放p指向大块内存块,并将其头部信息节点的alloc置为NULL
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
            ngx_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

为什么Nginx只提供了大块内存的释放,而不提供小块内存的释放?

  1. 从小块内存的分配方式来看,小块内存无法被直接回收。因为需要被释放的小块内存的前后可能都在被使用中,所以不能直接更新last指针(可用内存空间的起始地址)或者直接释放该内存。
  2. 从Nginx的应用场景来看,Nginx是一个HTTP服务器,并且是一个基于短连接的服务器。在客户端和服务端的一次request和response之后,连接就会自动断开。即使是HTTP1.1中提供的keep-alive也会有时间限制,并不是会一直占用这个连接。当连接断开的时候,Nginx就会调用ngx_reset_pool重置整个内存池,这个时候小块内存和大块内存就都会被释放。这个内存池也可以给下一个连接进行分配内存。正是因为Nginx是一个HTTP服务器,而不是一个一直需要为一个用户提供服务的服务器,因此可以不用释放小块内存的资源。

销毁内存池:ngx_destroy_pool

// 销毁内存池
void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

	// 执行用户注册的cleanup回调,将内存块指向的资源释放掉
    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);
        }
    }

#if (NGX_DEBUG)

    /*
     * we could allocate the pool->log from this pool
     * so we cannot use this log while free()ing the pool
     */

    for (l = pool->large; l; l = l->next) {
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                       "free: %p, unused: %uz", p, p->d.end - p->d.last);

        if (n == NULL) {
            break;
        }
    }

#endif

	// 释放掉所有大块内存块
    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;
        }
    }
	// 注意:由于大块内存的头信息和cleanup节点的头信息都在小块内存池中
	//       所以小块内存池一定要最后才能被销毁
}

注册内存回收函数:ngx_pool_cleanup_add

如果直接释放掉内存的话,那么内存块中指向的资源就资源泄漏了。因此用户需要将资源释放函数注册到内存池中,这样在销毁内存池的时候,就会依次将内存指向的资源,大块内存,小块内存全部释放,从而不会造成内存资源泄漏的情况。

// 注册cleanup回调函数,用于释放内存块指向的资源
// 相当于是内存块对象的析构函数了
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;

	// 创建cleanup节点
    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

	// 给cleanup节点中的data开辟空间
    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }

    c->handler = NULL; // 清理回调函数handler置空
	
	// 将ngx_pool_cleanup_t节点头插到cleanup链表中
    c->next = p->cleanup;
    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hyzhang_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值