chain和buf是nginx过滤模块涉及到的结构体,而pool则是管理内存分配的一个结构。在日常的过滤模块中,这两类结构使用非常频繁,所以nginx采用类似freelist重复利用的原则,将使用完毕的chain或者buf结构体,放置到一个固定的空闲链表里,以待下次使用。
比如,在通用内存池结构体中,pool->chain变量里面就保存着释放的chain。而一般的buf结构体,没有模块间公用的空闲链表池,都是保存在各模块的缓存空闲链表池里面。对于buf结构体,还有一种busy链表,表示该链表中的buf都处于输出状态,如果buf输出完毕,这些buf就可以释放并重复利用了。
再比如,nginx的过滤模块在处理从别的filter模块或者是handler模块传递过来的数据(实际上就是需要发送给客户端的http response)。这个传递过来的数据是以一个链表的形式(ngx_chain_t)。而且数据可能被分多次传递过来。也就是多次调用filter的处理函数,以不同的ngx_chain_t。如果chain最后一个节点的next值没有被设为NULL,数据就无法完成接收。
接下来我们就看看chain和buf的结构体,最后再研究一下pool。
第一点:ngx_buf_t
ngx_buf_t是Nginx处理大数据的关键数据结构,是一种抽象的数据结构,它代表某种具体的数据,既应用于内存数据(缓冲区)也应用于磁盘数据(文件)或者一些元数据(指示链表读取者对链表的处理方式)。
下面我们来看看它的定义:
typedef struct ngx_buf_s ngx_buf_t;
typedef void * ngx_buf_tag_t;
struct ngx_buf_s {
u_char *pos;
/* 本次内存数据处理的起始位置,因为一个buf可能被多次处理。
* if (ngx_buf_in_memory(in->buf)) {
in->buf->pos += (size_t) sent;
}
*/
u_char *last;
/* pos和last之间即为nginx本次想要处理的区域。
* if (ngx_buf_in_memory(in->buf)) {
in->buf->pos = in->buf->last;
}
*/
off_t file_pos;
off_t file_last;
/* 处理文件时,概念与内存相同,表示相对于文件头的偏移量。
* size = cl->buf->file_last - cl->buf->file_pos;
*/
u_char *start; /* start of buffer */
u_char *end; /* end of buffer */
/* 当buf所指向的数据在内存里的时候,这一整块内存包含的内容可能被包含在多个buf中,
* 比如在某段数据中间插入了其他的数据,这一块数据就需要被拆分开。
* 那么这些buf中的start和end都指向这一块内存的开始地址和结束地址。
* 而pos和last指向本buf所实际包含的数据的开始和结尾。
*/
ngx_buf_tag_t tag;
/* 见上定义,表示当前缓冲区的类型,例如由哪个模块使用就指向这个模块ngx_module_t变量的地址。
* buf->tag = (ngx_buf_tag_t) &ngx_http_gzip_filter_module;
*/
ngx_file_t *file;
/* 指向文件的引用。之后介绍这个结构体。*/
ngx_buf_t *shadow;
/* 当前缓冲区的影子缓冲区,该成员很少用到。当缓冲区转发上游服务器的响应时才使用了shadow成员,
* 这是因为nginx太节约内存了,分配一块内存并使用ngx_buf_t表示接收到的上游服务器响应后,
* 在向下游客户端转发时可能会把这块内存存储到文件中,也可能直接向下游发送,此时nginx绝对不会
* 重新复制一份内存用于新的目的,而是再次建立一个ngx_buf_t结构体指向原内存,这样多个ngx_buf_t
* 结构体指向了同一份内存,它们之间的关系就通过shadow成员来引用,一般不建议使用。
* buf = cl->buf->shadow;
*/
unsigned temporary:1;
/* 表示buf包含的内容在内存缓冲区中,在被处理过程中可以修改,不会造成问题。
* if (b->pos == dst) {
b->sync = 1;
b->temporary = 0;
}
*/
unsigned memory:1;
/* 与temporary相反,表示不可以被修改 */
unsigned mmap:1;
/* 为1时表示该buf是通过mmap使用内存映射从文件中映射到内存中的,不可以被修改 */
unsigned recycled:1;
/* 可以回收的。也就是这个buf是可以被释放的。这个字段通常是配合shadow字段一起使用的,
对于使用ngx_create_temp_buf 函数创建的buf,并且是另外一个buf的shadow,
那么可以使用这个字段来标示这个buf是可以被释放的。*/
unsigned in_file:1;
/* 为1时表示该buf所包含的内容是在文件中。*/
unsigned flush:1;
/* 为1时表示需要执行flush操作,遇到有flush字段被设置为1的的buf的chain,
则该chain的数据即便不是最后结束的数据(last_buf被设置,标志所有要输出的内容都完了),
也会进行输出,不会受postpone_output配置的限制,但是会受到发送速率等其他条件的限制。*/
unsigned sync:1;
/* 标志位,对于操作这块缓冲区时是否使用同步方式,需谨慎考虑,这可能会阻塞nginx进程,
nginx中所有操作几乎都是异步的,这是它支持高并发的关键。
有些框架代码在sync为1时可能会有阻塞的方式进行I/O操作,它的意义视使用它的nginx模块而定。*/
unsigned last_buf:1;
/* 数据被以多个chain传递给了过滤器,此字段为1表明这是最后一个buf。
* if (in->buf->last_buf) last = 1;
*/
unsigned last_in_chain:1;
/* 在当前的chain里面,此buf是最后一个。特别要注意的是last_in_chain的buf不一定是last_buf,
但是last_buf的buf一定是last_in_chain的。这是因为数据会被以多个chain传递给某个filter模块。
* if (in->last_in_chain) {
if (bsize < (off_t) size) {
size = (size_t) bsize;
recycled = 0;
}
// else if ...
}
*/
unsigned last_shadow:1;
/* 在创建一个buf的shadow的时候,通常将新创建的一个buf的last_shadow置为1,表示为最后一个shadow。
* if (cl->buf->last_shadow) {
if (ngx_event_pipe_add_free_buf(p, cl->buf->shadow) != NGX_OK) {
return NGX_ABORT;
}
cl->buf->last_shadow = 0;
}
*/
unsigned temp_file:1;
/* 由于内存使用限制,有时候一些buf需要被写到磁盘上的临时文件中去,那么就设置此标志表示为临时文件。*/
/* STUB */ int num;
};
对于此对象的创建,可以直接在某个ngx_pool_t上分配,然后根据需要,给对应的字段赋值。也可以使用定义好的2个宏,这两个宏使用类似函数:
#define ngx_alloc_buf(pool) ngx_palloc(pool, sizeof(ngx_buf_t))
#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))
对于创建temporary字段为1的buf(就是其内容可以被后续的filter模块进行修改),可以直接使用函数ngx_create_temp_buf进行创建:
ngx_buf_t *
ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
{
ngx_buf_t *b = ngx_calloc_buf(pool);
if (b == NULL) return NULL;
// 从pool中分配出size大小的内存给buf
b->start = ngx_palloc(pool, size); // 指向内存开始的地方
if (b->start == NULL) return NULL;
// set by ngx_calloc_buf():
// b->file_pos = b->file_last = b->tag = 0;
// b->file = b->shadow = NULL;
// and flags
b->pos = b->start; // 指向内存块的起始位置便于写入
b->last = b->start;
b->end = b->last + size; // 指向内存结束的地方
b->temporary = 1;
return b;
}
为了配合对ngx_buf_t的使用,nginx定义了以下的宏方便操作:
// buf是否在内存里
#define ngx_buf_in_memory(b) (b->temporary || b->memory || b->mmap)
// buf里面的内容是否仅仅在内存里,并且没有在文件里
#define ngx_buf_in_memory_only(b) (ngx_buf_in_memory(b) && !b->in_file)
// buf是否是一个特殊的buf,只含有特殊的标志和没有包含真正的数据
#define ngx_buf_special(b) \
((b->flush || b->last_buf || b->sync) \
&& !ngx_buf_in_memory(b) && !b->in_file)
// buf是否是一个只包含sync标志而不包含真正数据的特殊buf
#define ngx_buf_sync_only(b) \
(b->sync \
&& !ngx_buf_in_memory(b) && !b->in_file && !b->flush && !b->last_buf)
// 返回该buf所含数据的大小,不管这个数据是在文件里还是在内存里。
#define ngx_buf_size(b) \
(ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos): \
(b->file_last - b->file_pos))
第二点:ngx_chain_t
ngx_chain_t:
typedef struct ngx_chain_s ngx_chain_t;
struct ngx_chain_s {
ngx_buf_t *buf; // 指向当前的buf缓冲区
ngx_chain_t *next; // 如果这是最后一个ngx_chain_t,需要把next置为NULL
};
创建缓冲区链表的函数:
ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool)
{
ngx_chain_t *cl;
cl = pool->chain;
if (cl)
{
pool->chain = cl->next;
return cl;
}
cl = ngx_palloc(pool, sizeof(ngx_chain_t));
if (cl == NULL)
{
return NULL;
}
return cl;
}
该宏释放一个ngx_chain_t类型的对象:
#define ngx_free_chain(pool, cl) \
cl->next = pool->chain; \
pool->chain = cl
如果要释放整个chain,则迭代此链表,对每个节点使用此宏即可。注意:对ngx_chaint_t类型的释放,并不是真的释放了内存,而仅仅是把这个对象挂在了这个pool对象的一个叫做chain的字段对应的chain上,以供下次从这个pool上分配ngx_chain_t类型对象的时候,快速的从这个pool->chain上取下链首元素就返回了,当然,如果这个链是空的,才会真的在这个pool上使用ngx_palloc函数进行分配。