06Nginx源码分析之缓冲区结构(ngx_buf.c)

概述:
缓冲区ngx_buf_t是nginx处理大数据的关键数据结构,它既应用于内存数据也应用于磁盘数据。

一 缓冲区结构结构定义

1. 缓冲区内存块的数据结构 ngx_buf_t

typedef void *            ngx_buf_tag_t;
typedef struct ngx_buf_s  ngx_buf_t;

/**
 * Nginx缓冲区
 */
struct ngx_buf_s {
    u_char          *pos;           /* 待处理数据的开始标记  */
    u_char          *last;          /* 待处理数据的结尾标记 */
    off_t            file_pos;		/* 处理文件时,待处理的文件开始标记  */
    off_t            file_last;		/* 处理文件时,待处理的文件结尾标记  */
 
    u_char          *start;         /* 缓冲区开始的指针地址 */
    u_char          *end;           /* 缓冲区结尾的指针地址 */
    ngx_buf_tag_t    tag;			/* 缓冲区标记地址,是一个void类型的指针。 */
    ngx_file_t      *file;			/* 引用的文件 */
    ngx_buf_t       *shadow;
 
 
    /* the buf's content could be changed */
 
    unsigned         temporary:1;	 /* 标志位,为1时,内存可修改 */
 
    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;   	/* 标志位,为1时,内存只读 */
 
    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;		/* 标志位,为1时,mmap映射过来的内存,不可修改 */
 
    unsigned         recycled:1;	/* 标志位,为1时,可回收 */
    unsigned         in_file:1;		/* 标志位,为1时,表示处理的是文件 */
    unsigned         flush:1;		/* 标志位,为1时,表示需要进行flush操作 */
    unsigned         sync:1;		/* 标志位,为1时,表示可以进行同步操作,容易引起堵塞 */
    unsigned         last_buf:1;	/* 标志位,为1时,表示为缓冲区链表ngx_chain_t上的最后一块待处理缓冲区 */
    unsigned         last_in_chain:1;/* 标志位,为1时,表示为缓冲区链表ngx_chain_t上的最后一块缓冲区 */
 
    unsigned         last_shadow:1;	/* 标志位,为1时,表示是否是最后一个影子缓冲区 */
    unsigned         temp_file:1;	/* 标志位,为1时,表示当前缓冲区是否属于临时文件 */
 
    /* STUB */ int   num;
};
  • 1)从上面这个数据结构中,可以看到ngx_buf_t结构,即可以处理内存,也可以处理文件。
  • 2)每个buf都记录了开始和结束点以及未处理的开始和结束点,因为缓冲区的内存申请了之后,是可以被复用的。
  • 3)tag是一个void*的标志位,在下面的ngx_chain_update_chains函数可以看到,可以通过传入的tag来决定回收空闲的buf交给内存池的chain还是交给free这个空闲链表。
  • 4)内存包含三个标志位,分别是temporary,memory,mmap。
  • 5)所有缓冲区需要的数据结构以及缓冲区的buf内存块都会被分配到内存池上面。

2. 缓冲区链表结构 ngx_chain_t
注意:这里的ngx_chain_t和ngx_buf_t我有时也会称之为缓冲区链表,有时称ngx_chain_t为缓冲区的管理链表,ngx_buf_t仍为缓冲区链表,下面会给出它们之间的关系图,大家理解就行了。

typedef struct ngx_chain_s       ngx_chain_t;
struct ngx_chain_s {
    ngx_buf_t    *buf;		/* 缓冲区buf */
    ngx_chain_t  *next;	
};
  • 1)ngx_chain_t是我们内存池的一个成员,也就是说,内存池通过成员ngx_chain_t链表来管理ngx_buf_t缓冲区。
  • 2)通过链表的方式实现buf有一个非常大的好处:如果一次需要缓冲区的内存很大,那么并不需要分配一块完整的内存,只需要将缓冲区串起来就可以了。

二 数据结构图

这个图对于我们理解下面的函数非常重要,它列出了ngx_chain_t的类型,以及ngx_chain_t与ngx_buf_t的关系。
在这里插入图片描述

  • 1)先看上图中间的ngx_chain_t,ngx_chain_t包含busy类型和free类型。但是需要注意,内存池成员的ngx_chain_t链表只保存着空闲的buf,被使用着的buf不会放上去。
  • 2)Nginx可以在自定义的业务层面管理繁忙busy和空闲free的缓冲区链表结构。通过后边的函数,可以对缓冲区的链表结构和buf结构进行管理,当我们需要被回收使用时(并不会释放内存),则会放到Nginx内存池的pool->chain链表上。
  • 3)然后每一个ngx_chain_t都有一个ngx_buf_t结构,ngx_buf_t结构里面的start和end是真正的可使用内存。
  • 4)所以,我们可以认为,内存池上的ngx_chain_t是一个保存空闲buf的链表;而busy和free是用户自定义的ngx_chain_t链表,当我们有需要时可以将这两个自定义的链表中有空闲的ngx_chain_t放在内存池的ngx_chain_t上,以供其它地方使用。
  • 5)缓冲区是Nginx用的非常多的一种数据结构,主要用于接收和输出HTTP的数据信息。所以对Nginx的缓冲区的数据结构深入理解非常有必要。

三 具体函数实现

1. 创建一个缓冲区buf ngx_create_temp_buf
该函数一般作为buf的底层函数被调用。

/**
 * 参数1:内存池。
 * 参数2:开辟缓存buf的大小。
 * 作用:创建一个缓冲区,需要传入pool和buf的大小。
*/

ngx_buf_t *
ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
{
    ngx_buf_t *b;

	/*
		#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))
		开辟一个ngx_buf_t结构体。
	*/
    b = ngx_calloc_buf(pool);
    if (b == NULL) {
        return NULL;
    }
	
	/* 为真正保存数据即ngx_buf_t里的start开辟内存。*/
    b->start = ngx_palloc(pool, size);
    if (b->start == NULL) {
        return NULL;
    }

    /*
     * set by ngx_calloc_buf():
     *
     *     b->file_pos = 0;
     *     b->file_last = 0;
     *     b->file = NULL;
     *     b->shadow = NULL;
     *     b->tag = 0;
     *     and flags
     */

    b->pos = b->start;		/* 待处理数据的标记指针 */
    b->last = b->start;		/* 待处理数据的结尾标记指针,因为未保存数据,故last=start */
    b->end = b->last + size;/* 缓冲区结尾地址 */
    b->temporary = 1;		/* 标志位,为1时,内存可修改 */

    return b;
}

2. 创建一个缓冲区的链表结构 ngx_alloc_chain_link
单独创建缓冲区ngx_buf_t是没法形成回收和管理机制的。所以需要创建ngx_chain_t缓冲区链表结构,用来管理整个缓冲区。

/**
 * 参数1:内存池。
 * 作用:创建一个缓冲区的链表结构。
 * 步骤:1. 优先从内存池的chain中获取;2. 没有则新开辟。
*/

ngx_chain_t *
ngx_alloc_chain_link(ngx_pool_t *pool)
{
	ngx_chain_t *cl;
	
	/*
	 * 1. 首先从内存池中去取ngx_chain_t,被清空的ngx_chain_t结构都会放在pool->chain 缓冲链上
	 */
	cl = pool->chain;
	if (cl) {/* 如果内存池上存在空闲的ngx_chain_t即cl!=NULL,那么取完后直接返回 */
		pool->chain = cl->next;//取空闲的ngx_chain_t,相当于减1
		return cl;
	}
	
	/* 2. 如果取不到,则从内存池pool上分配一个数据结构  */
	cl = ngx_palloc(pool, sizeof(ngx_chain_t));
	if (cl == NULL) {
		return NULL;
	}
 
	return cl;
}

3. 批量创建多个缓冲区buf ngx_create_chain_of_bufs
上面的ngx_alloc_chain_link是创建一个buf,当我们需要的缓冲区非常大的时候就显得效率低下。所以需要提供批量创建多个buf,并且用链表串起来,用于缓冲区的数据管理。

/**
 * 参数1:内存池。
 * 参数2:一个用于创建多少个buf的结构体。
 * typedef struct {
    ngx_int_t    num;
    size_t       size;
} ngx_bufs_t;

 * 作用:批量创建多个buf,并且用链表串起来。
 * 注意:批量创建时,不会再从内存池获取空闲的ngx_chain_t里的buf,而是直接新开辟多个buf。
*/

ngx_chain_t *
ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs)
{
	u_char *p;	//真正的数据内存首地址
	ngx_int_t i;
	ngx_buf_t *b;
	ngx_chain_t *chain, *cl, **ll;//chain为链表的首地址,cl用于临时保存,ll用于操作整个链表的移动
 
	/* 
		1. 在内存池pool上分配bufs->num个 buf缓冲区 ,每个大小为bufs->size,
		   该内存是真正buf里保存数据的内存,即一会分配给start,end
	*/
	p = ngx_palloc(pool, bufs->num * bufs->size);
	if (p == NULL) {
		return NULL;
	}
 
 	// 2. 意思代表将chain作为链表首个元素即首地址,用于返回
	ll = &chain;
 
	/* 3. 循环创建ngx_chain_t,或者说循环创建buf,并且将ngx_buf_t挂载到ngx_chain_t链表上*/
	for (i = 0; i < bufs->num; i++) {
 
		b = ngx_calloc_buf(pool);
		if (b == NULL) {
			return NULL;
		}
 
		/*
		 * set by ngx_calloc_buf():
		 *
		 *     b->file_pos = 0;
		 *     b->file_last = 0;
		 *     b->file = NULL;
		 *     b->shadow = NULL;
		 *     b->tag = 0;
		 *     and flags
		 *
		 */
 
		b->pos = p;//给真正保存数据的内存赋值
		b->last = p;
		b->temporary = 1;
 
		b->start = p;
		p += bufs->size; //p往前增,以便为下一个内存赋值
		b->end = p;//注意,p已经自增bufs->size,不需再次自增
 
		/* 分配一个ngx_chain_t */
		cl = ngx_alloc_chain_link(pool);
		if (cl == NULL) {
			return NULL;
		}
 
		/* 将buf,都挂载到ngx_chain_t链表上,最终返回ngx_chain_t链表 */
		cl->buf = b;//buf挂上ngx_chain_t
		*ll = cl;//ngx_chain_t挂上ngx_chain_t链表
		ll = &cl->next;//ll往下移,这里新建的ngx_chain_t的next为NULL
	}
 
	*ll = NULL;
 
	/* 返回链表首地址,最终得到一个分配了bufs->num的缓冲区链表  */
	return chain;
}

4. 拷贝缓冲区链表 ngx_chain_add_copy
将其它缓冲区链表放到已有缓冲区链表结构的尾部。

/**
 * 参数1:内存池。
 * 参数2:已有缓冲区的链表。
 * 参数3:待拷贝进参数2的链表。

 * 作用:将其它缓冲区链表放到已有缓冲区链表结构的尾部。
 * 
 * 注意:copy后,两个链表共用一份内存,因为拷贝时只是简
 * 单使用cl->buf = in->buf赋值,而并未重新malloc。
*/
 
ngx_int_t ngx_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain,
		ngx_chain_t *in)
{
	ngx_chain_t *cl, **ll;
 
	ll = chain; //ll用于操作chain
 
	/* 找到缓冲区链表结尾部分,最后ll指向NULL */
	for (cl = *chain; cl; cl = cl->next) {
		ll = &cl->next;
	}
 
	/*  遍历in链表,将in链表上的buf拷贝给chain链表,
		但是注意,这个拷贝是两个链表共用一份buf,并且in这个链表结构体未释放(单单指in这个结
		构体而不指里面的buf),所以这个in链表一般可以自定义回收或者最后由内存池回收
	*/
	while (in) {
		cl = ngx_alloc_chain_link(pool);
		if (cl == NULL) {
			return NGX_ERROR;
		}
 
		cl->buf = in->buf; //in上的buf拷贝到cl上面
		*ll = cl; //放到chain链表上
		ll = &cl->next; //链表往下走
		in = in->next; //遍历,直到NULL
	}
 
	*ll = NULL;
 
	return NGX_OK;
}

5. 获取一个空闲的buf链表结构 ngx_chain_get_free_buf

/**
 * 参数1:内存池。
 * 参数2:传入的空闲链表。

 * 作用:往空闲的free链表上,获取一个未使用的buf链表。
 * 
*/

ngx_chain_t *
ngx_chain_get_free_buf(ngx_pool_t *p, ngx_chain_t **free)
{
	ngx_chain_t *cl;
 
	/* 1. 空闲列表中有数据,则直接返回 */
	if (*free) {
		cl = *free;
		*free = cl->next;//空闲链表减1
		cl->next = NULL;
		return cl;
	}
 
	/* 2. 否则分配一个新的buf,但是这里需要注意一点:buf里的start内存需要自己申请 */
	cl = ngx_alloc_chain_link(p);
	if (cl == NULL) {
		return NULL;
	}
 
	cl->buf = ngx_calloc_buf(p);
	if (cl->buf == NULL) {
		return NULL;
	}
 
	cl->next = NULL;
 
	return cl;
}

6. 释放缓冲区链表 ngx_free_chain和ngx_chain_update_chains

/**
 * 参数1:内存池。
 * 参数2:传入的空闲链表。
 * 参数3:传入的繁忙链表。
 * 参数4:一个链表,默认是繁忙链表,但里面可能包含空闲和不空闲的ngx_chain_t,所以需要更新。
 * 参数5:void*的标志位。可以使用其将相同的ngx_chain_t放在free链表,而不相等的空闲buf放在内存池的chain上。

 * 作用:更新繁忙的busy链表(可以加上一个额外链表out),以找出空闲的buf放在free或者内存池的chain链表上。
 * 
 * 注:
 * #define ngx_buf_size(b)                                                      \
    (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos):                         \
                            (b->file_last - b->file_pos))
 * 
 * #define ngx_buf_in_memory(b)        (b->temporary || b->memory || b->mmap)
*/

void
ngx_chain_update_chains(ngx_pool_t *p, ngx_chain_t **free, ngx_chain_t **busy,
    ngx_chain_t **out, ngx_buf_tag_t tag)
{
    ngx_chain_t  *cl;

	/*
		1.
		如果繁忙链表为空,则直接将out链表当中busy链表;
		若繁忙链表不为空,则在尾部加上out链表
	*/
    if (*out) {
        if (*busy == NULL) {
            *busy = *out;

        } else {
            for (cl = *busy; cl->next; cl = cl->next) { /* void */ }//寻找busy的最后一个元素,即NULL之前的元素

            cl->next = *out;//合并
        }

        *out = NULL;
    }

	/*
		2.
		循环找出busy链表的空闲buf。其中:
		1)若busy首个元素的buf不为空闲,则直接跳出。
		2)若busy首个元素为空闲,且tag不与参数tag相等,则将空闲的buf放进内存池chain链表。
		3)若busy首个元素为空闲,且tag与参数tag相等,则将空闲的buf放进free链表。
		至此,更新内存池的chain,busy,free三个链表完毕。
	*/
    while (*busy) {
        cl = *busy;
		
		/* 
			看上面注释,当buf是内存时,那么取值(off_t) (b->last - b->pos)
			而last-pos不等于0即非空闲时,程序直接跳出。
			所以只有首个元素为空闲时才会往下走
		*/
        if (ngx_buf_size(cl->buf) != 0) {
            break;
        }

		//若标志位不相等,则将空闲的buf放在内存池的chain链表上。
        if (cl->buf->tag != tag) {
            *busy = cl->next;
            ngx_free_chain(p, cl);
            continue;
        }

        cl->buf->pos = cl->buf->start;//更新在free链表的空闲buf,需要重置pos和last为未使用,以便在free获取的buf可以直接使用
        cl->buf->last = cl->buf->start;

        *busy = cl->next;//busy继续往后遍历,相当于加1
        cl->next = *free;//将cl与freel链表连接
        *free = cl;//更新freel链表的头结点为cl
    }
}

上面可以看出:

  • 1)ngx_free_chain:直接交还给Nginx内存池的pool->chain空闲buf链表。
  • 2)ngx_chain_update_chains:可以交还给自定义的空闲链表上。

四 总结

  • 1)Nginx的缓冲区并不难,对着数据结构图看就行,对于具体二次开发Nginx的,仍需要再用心去理解本篇文章,然后调用代码,配合调试开发即可。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值