您的位置:首页 > 理论基础 > 数据结构算法

nginx源码初读(2)--让烦恼从数据结构开始(ngx_buf/ngx_chain)

2016-02-19 19:00 375 查看
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包含的内容在内存缓冲区中
c97a
,在被处理过程中可以修改,不会造成问题。
* 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函数进行分配。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: