概述:
缓冲区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的,仍需要再用心去理解本篇文章,然后调用代码,配合调试开发即可。