Ngix内存管理

Ngix内存管理

宏定义和结构体
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)	//分配最大的内存 4096 (一页内存)

#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)		//默认内存池大小 16k 

#define NGX_POOL_ALIGNMENT       16					//内存对齐的基准 16
#define NGX_MIN_POOL_SIZE       					//内存池最小的大小
		ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),            \
              NGX_POOL_ALIGNMENT)
//内存对齐函数
#define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))
https://blog.csdn.net/weixin_45768137/article/details/127266001?spm=1001.2014.3001.5501
//上述函数讲解
//内存块的头部信息
//U_char*  == char *
typedef struct {
    u_char               *last;	//指向内存块可用的首地址(去掉头部信息)
    u_char               *end;	//指向内存块的末尾地址
    ngx_pool_t           *next;	//指向下一内存块的指针
    ngx_uint_t            failed;
} ngx_pool_data_t;

//	typedef struct ngx_pool_s            ngx_pool_t;
struct ngx_pool_s {
    ngx_pool_data_t       d;
    size_t                max;		//内存块可使用的大小,最大为 NGX_MAX_ALLOC_FROM_POOL 4095
    ngx_pool_t           *current;	//指向内存块的首地址
    ngx_chain_t          *chain;
    ngx_pool_large_t     *large;	//大块内存的入口指针
    ngx_pool_cleanup_t   *cleanup;	//内存块链接指针
    ngx_log_t            *log;		//日志指针
};
//current --- log 只在首个内存块中存在

//下述为大块内存头部信息
typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;		//链表节点指针
    void                 *alloc;	//指向大块内存的指针
};

//下述为外部内存头部信息
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;		//链表节点指针
};

相关函数

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);	//创建内存池
void ngx_destroy_pool(ngx_pool_t *pool);					//内存销毁
void ngx_reset_pool(ngx_pool_t *pool);						//重置内存池

void *ngx_palloc(ngx_pool_t *pool, size_t size);			//分配内存
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);			//有内存对齐的分配内存
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);			//分配内存,	
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);	//向系统申请内存
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);				//释放大块内存


内存池初始化函数

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

	//如果未指定平台,则调用malloc()进行分配内存块
	p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    
	if (p == NULL) {
	    return NULL;
	}
	//last指向申请内存除头部信息外的首地址,也就是能使用的内存的首地址
	p->d.last = (u_char *) p + sizeof(ngx_pool_t);
	
	//end指向申请的内存块的末尾地址
	p->d.end = (u_char *) p + size;
	
	p->d.next = NULL;
	p->d.failed = 0;
	
	//内存池总体大小最大为 NGX_MAX_ALLOC_FROM_POOL  (4095)
	size = size - sizeof(ngx_pool_t);
	p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
	
	//current指向刚刚申请的内存块的地址,后续会有变动
	p->current = p;
	p->chain = NULL;
    //大块内存头部信息指针先置为null
	p->large = NULL;
    //外部资源处理链表指针先置为null
	p->cleanup = NULL;
	p->log = log;
	
	return p;
}

创建内存块函数,并且初始化内存卡中的一些头部信息和指针指向.

posix_memalign(&p, alignment, size);针对不同平台的定义

{
    #if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)
		void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);
    #else
		#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)
	#endif
}


#if (NGX_HAVE_POSIX_MEMALIGN)

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
    void  *p;
    int    err;
	//返回 size 字节的动态内存,并且该内存的地址是 alignment 的倍数,由p指向这块内存
    err = posix_memalign(&p, alignment, size);
	
    if (err) {
        ngx_log_error(NGX_LOG_EMERG, log, err,
                      "posix_memalign(%uz, %uz) failed", alignment, size);
        p = NULL;
    }    
    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
                   "posix_memalign: %p:%uz @%uz", p, size, alignment);    
    return p;
}

#elif (NGX_HAVE_MEMALIGN)

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
    void  *p;
	//返回一个是size大小并且地址是 alignment 的倍数的内存块
    p = memalign(alignment, size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "memalign(%uz, %uz) failed", alignment, size);
    }
    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
                   "memalign: %p:%uz @%uz", p, size, alignment);
    return p;
}

#endif

分配内存函数

//不做内存对齐
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);
}

//内存对齐版本
void * ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 0);
    }
#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;	//char * 类型
    ngx_pool_t  *p;

    p = pool->current;		//p指向当前内存块的首地址,分配内存都从current指向的内存块进行分配
    
    do {
        m = p->d.last;		//m指向当前内存块的可用首地址
    
    	//如果 align > 0 需要内存对齐
        if (align) {
    		/*
    			将 m 对齐到 NGX_ALIGNMENT 整数倍的地址
    			NGX_ALIGNMENT:
    			#define NGX_ALIGNMENT   sizeof(unsigned long)    // platform word 
    			#define NGX_ALIGNMENT  _MAX_ALIGNMENT
    		*/
            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);	//当p指向nullptr时跳出循环
    //当前所有内存块都不满足分配 size 个字节,进入下述函数,开辟新内存块
    return ngx_palloc_block(pool, size);
}


static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

	//获取传入的 pool 内存块的整体大小(创建内存池时传入的size大小),pool就是我们一开始创建的第一个内存块
    psize = (size_t) (pool->d.end - (u_char *) pool);

	//分配一个和 pool 大小相同的内存块
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }

	//new指向新分配的内存块首地址
    new = (ngx_pool_t *) m;

	//end指向新内存块的末尾地址
    new->d.end = m + psize;

    new->d.next = NULL;
    new->d.failed = 0;

	/*
		m指向可用内存块的首地址,并且后续返回给客端
		注意!!
		ngx_pool_data_t 这是头部信息的结构体
		ngx_pool_t 这是包括 current --- log 这些变量的结构体
		说明除了第一个内存块有 current --- log 这些变量,其他的内存块都没有这些变量
	*/
    m += sizeof(ngx_pool_data_t);
	
	//让m内存地址对齐 16 的整数倍
    m = ngx_align_ptr(m, NGX_ALIGNMENT);

	//新内存块的last指向后续可用内存的首地址,m是要返回给客端的
    new->d.last = m + size;


	//每次分配失败后对所有的内存块(除了新开辟的)的failed进行++,当failed为4,表示这个内存块
	//已经无法进行分配内存了,则调整current指针的位置到下一个内存块,下次就直接从current的指向开始分配
	//在ngx_palloc_small()函数中我们可以知道每次分配内存都是从current指针指向的内存块进行分配内存
	//所以到这里我们可以知道ngix内存池是由一个个小块内存块进行串联起来的
	//问题:  当 current移动之后,记录在第一个内存块中的信息 如: log large chain 指针这些变量会跟着一起移动吗? 如果不移动后续怎么操作
    //答:后续所有传入的pool 都是第一个开辟的 pool 只是 pool 里面的current指向改变了
    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_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;
    /*
    	typedef struct ngx_pool_large_s  ngx_pool_large_t;
    	
		struct ngx_pool_large_s {
   			ngx_pool_large_t     *next;
   	 		void                 *alloc;
		};
		
		在Ngix中的小块内存是不会被释放掉的,所以被分配的 大块内存的头部是一直都存在的.
		下述代码就是重复利用大块内存的头部信息,
		判断前三个large指针的指向是否为空,如果为空,则让它指向新分配的大块内存,
		如果前三个large都不为空,则直接创建一个新的大块内存头部指向分配的大块内存
    */
    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) {
    	//如果小块内存分配失败,则free()掉申请的大块内存并且返回null
        ngx_free(p);
        return NULL;
    }
    //大块内存头部信息的 alloc 指针指向大块内存的首地址
    large->alloc = p;
    
    //使用头插法把大块内存的头部进串起来
    large->next = pool->large;
    pool->large = large;
    
    return p;

}

通过large指针维护了一个大块内存头部信息的链表(这个头部信息中保存了大块内存的地址).每次重新开辟大块内存时,会判断前三个头部信息的alloc指针是否为空,如果为空则把新开辟的大块内存直接用alloc指向.否则重新分配一个头部信息来管理新开辟的大块内存

内存重置函数 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);
	    }
	}
	//此处的内存重置,会导致除了第一块内存块的后续内存块中一部分的内存无法使用
	//因为后续内存块的last指针多偏移了 sizeof(ngx_poot_t) - sizeof(ngx_pool_data_t) 字节
	/*
	for (p = pool; p; p = p->d.next) {
	    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
	    p->d.failed = 0;
	}
	*/
	//针对第一块内存块进行重置
	p = pool;
	p->d.last = (u_char *) p + sizeof(ngx_pool_t);
	p->d.failed = 0;
	
	//针对后续内存块进行重置
	for (p = p->d.next; p; p = p->d.next) {
	    p->d.last = (u_char *) p + sizeof(ngx_pool_data_t);
	    p->d.failed = 0;
	}
	
	//调整当前current指针
	pool->current = pool;
	pool->chain = NULL;
	pool->large = NULL;
	/*
		小块内存不释放的问题:
			ngix中没有提供任何小块内存的释放函数,并且从小块内存的分配方式来看(偏移指针),它也无法进行
			小块内存的释放.
		为什么不设置一个小块内存的分配:
			nginx本质是一个http服务器,当客户发起一个request请求,nginx处理完成之后,就会释放掉这个客端所占用的
			资源(断开链接),此时就可以调用 ngx_reset_pool()来重置内存池了
	
			即使是 keep-alive 链接在时间内没有再次发送请求到服务器,也会执行上述流程
	
	*/

}

通过large指针维护了一个大块内存头部信息的链表(这个头部信息中保存了大块内存的地址),释放的时候遍历这个链表并且依次free(large->alloc)即可

小块内存只需要重置last指针的位置和对第一块内存做特殊处理.

内存池销毁函数 ngx_destroy_pool()

//外部内存:指分配的内存中有指针指向了其他地方的内存
/*
	struct data{
		int *p 		//外部内存指针
		FILE *fd 	//外部文件指针
	}
*/
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;	//外部内存的头部信息

	//进行分配外部内存的头部信息,并且使用小块内存进行分配
	c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
	if (c == NULL) {
	    return NULL;
	}
	//如果回调函数有参数传入,那么分配给data内存(作为函数的传入参数)
	if (size) {
	    c->data = ngx_palloc(p, size);
	    if (c->data == NULL) {
	        return NULL;
	    }
	
	} else {
	    c->data = NULL;
	}
	
	//先把外部内存的析构函数置为null
	c->handler = NULL;
	
	//头插法把外部内存的头部信息串联起来(非空头链表)
	c->next = p->cleanup;
	
	p->cleanup = c;
	
	ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
	
	return c;

}

//释放内存池函数
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);
			//传入需要的参数,如果没有则传输null  handler(void *data)  
            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指针来维护了一个外部内存释放的链表,在销毁内存池的时候都需要遍历这个链表,并且把每个节点的handler调用一遍,然后再依次释放大块内存和小块内存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值