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

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函数进行分配。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章