ngx_buf_t数据结构

本文详细梳理了nginx源码中ngx_buf_t数据结构的相关内容,包括结构体定义、宏定义和函数声明。同时介绍了用于辅助理解源码的graphviz画图工具及其用法,提供了相关教程链接,帮助读者更好地理解和学习nginx源码。

      导语:通过网上阅读查阅,尽量把nginx源码这一部分整理完善,让以后想要学习nginx的同学也可以快速上手。这一节涉及src/core/ngx_buf.h|c的数据结构,其次,看一些大牛文章时候 get 了一个好的画图工具- graphviz 和一个拍照app-扫描全能王,可以帮助理清源码的思路。

      

一、结构体、相关宏定义、函数声明梳理        

       分析采用nginx-1.6.2源码。在src/core/ngx_buf.h|c中的ngx_buf_t 结构体还是很重要,看源码的过程中遇到几次,确实有必要好好理理。ngx_buf_t 包含于ngx_chain_t 结构体中。

// 这个就是链表的形式,还是把ngx_buf_t串起来,
// 主要作用就是接受nginx服务器的http请求包头、包体、以及响应客户端的应答包头、包体
// 都会放在chain链表缓冲区
struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};

接着是ngx_buf_t结构体,ngx_buf_t 就是ngx_chain_t链表每个节点的实际数据,缓冲区ngx_buf_t是nginx处理大数据的关键数据结构,它既可以应用于内存也可以用于磁盘数据注释。里面的参数设置,考虑的情况真多,而且最后标志位都只占1位,可以说对内存利用锱铢必较,值得借鉴学习。



typedef void *            ngx_buf_tag_t;

typedef struct ngx_buf_s  ngx_buf_t;

// ngx_buf_t 就是ngx_chain_t链表每个节点的实际数据,
// 缓冲区ngx_buf_t是nginx处理大数据的关键数据结构,它既可以应用于内存也可以用于磁盘数据
struct ngx_buf_s {
    // pos指向的是这段数据在内存中的开始位置
    u_char          *pos;
    // last指向的是这段数据在内存中的结束位置
    u_char          *last;
    // file_pos指向这段数据的开始位置在文件中的偏移量
    off_t            file_pos;
    // file_last指向这段数据的结束位置在文件中的偏移量
    off_t            file_last;

    // 如果ngx_buf_t 缓冲区用于内存,那么start指向这段内存的起始地址
    u_char          *start;         /* start of buffer */
    // 如果ngx_buf_t 缓冲区用于内存,那么end指向这段内存的结束地址
    u_char          *end;           /* end of buffer */

    // 实际上就是一个void *类型的指针。使用者可以关联任何对象上去
    // 由哪个模块使用就执行哪个模块的ngx_module_t结构
    ngx_buf_tag_t    tag;
    // 当buf所包含的内容在文件中时,file字段指向对应的文件对象上去
    ngx_file_t      *file;

    // 当这个buf完整copy了另外一个buf的所有字段的时候,那么这个两个buf指向的实际上是同一块内存,或者是
    // 同一个文件的同一部分,此时这两个buf的shadow字段指向对方的。释放的时候特别注意。
    ngx_buf_t       *shadow;


    // 为1表示该buf所包含的内容是一个用户创建的内存块中,并且可以被在filter处理的过程中进行变更,
    // 不会造成问题
    unsigned         temporary:1;

    /*the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     *
     * 为1表示该buf所包含的内容是在内存中,但是这些内容却不能被进行处理的filter进行变更
     */
    unsigned         memory:1;

    /* the buf's content is mmap()ed and must not be changed
     * 为1表示该buf所包含的内容是在内存中,是通过mmap使用内存映射从文件中映射到内存中的,
     * 这些内容不能进行变更
     */
    unsigned         mmap:1;
    // 可以回收,也就是这个buf是可以释放的
    unsigned         recycled:1;
    // 为1时表示该buf所包含的内容是在文件中
    unsigned         in_file:1;
    // 遇到有flush字段被设置为1的buf的chain,则该chain的数据即便不是最后结束的数据,也会输出 ??
    unsigned         flush:1;
    /* 对于操作这块缓冲区是否使用同步方式,需要谨慎考虑,这可能会阻塞nginx进程,nginx中所有的操作
     * 几乎都是异步的,这是它支持高并发的关键。
     */
    unsigned         sync:1;
    // 数据被多个chain传递给过滤器,此字段为1表明这是最后一个buf
    unsigned         last_buf:1;
    // 在当前的chain里面,此buf是最后一个。
    unsigned         last_in_chain:1;

    // 在创建一个buf的shadow的时候,通常将新创建的一个buf的last_shadow置为1
    unsigned         last_shadow:1;
    // 由于内存使用的限制,有时候一些buf的内容需要被写到磁盘上的临时文件中去,这个时候就设置次标志
    unsigned         temp_file:1;

    // 这个参数还没有弄清楚 ????
    /* STUB */ int   num;
};

来一张手绘图清晰明了,这个就是用扫描全能王app拍的,我画的太丑了,能理解清除意思就可以


 ngx_bufs_t结构体

// ngx_bufs_t起到了一个管理作用,用于说明当前使用的bufs的数量和每个buf的存储空间
// 在ngx_create_chain_of_bufs可以看到怎么使用
typedef struct {
    ngx_int_t    num;
    size_t       size;
} ngx_bufs_t;

 宏定义和部分函数声明解释,一些函数涉及body filter(包体过滤)http的东西先不管,主要说数据结构。nginx代码看起来就很舒服,很多数据类型,宏定义,哪怕一个很简单的操作都要用nginx的标准封装,也是值得学习的地方

#define NGX_CHAIN_ERROR     (ngx_chain_t *) NGX_ERROR

// 返回这个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))

// 对于创建temporary字段为1的buf(内容可以被后续的filter模块进行修改),直接用ngx_create_temp_buf个函数创建
// 其中pool分配该buf和buf使用的内存,size是该buf使用的内存大小
ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);
ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs);

#define ngx_alloc_buf(pool)  ngx_palloc(pool, sizeof(ngx_buf_t))
#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))

// 该函数创建一个ngx_chain_t对象,并返回指向对象的指针,失败返回NULL
ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);
// 该函数释放一个ngx_chain_t的对象,如果要释放整个chain,则迭代此链表
// 对ngx_chain_t的释放,并不是真正的释放,而是把这个对象挂在pool对象一个叫chain的
// 字段对应的chain上,以供下次可以快速取到
#define ngx_free_chain(pool, cl)                                             \
    cl->next = pool->chain;                                                  \
    pool->chain = cl

二、函数的解释 , 函数在/src/core/ngx_buf.c文件中。里面的函数都比较简单,说的彻底一点,还是指针指来指去

#include <ngx_config.h>
#include <ngx_core.h>

// 用ngx_create_temp_buf函数在内存池上创建一个临时缓冲区
ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
{
    ngx_buf_t *b;

    b = ngx_calloc_buf(pool);  // 从内存池pool中创建一个ngx_buf_t的大小的内存,在ngx_buf.h中宏定义了
    if (b == NULL) {
        return NULL;
    }

    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
     */
   // 起始位置pos和start
    b->pos = b->start;
    b->last = b->start;
    b->end = b->last + size; // end为缓冲区末尾
    b->temporary = 1;     // 临时缓冲区,内存可以被修改

    return b;
}

// 创建缓冲区链表,如果有直接拿,么有从pool请求一段空间
ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool)
{
    ngx_chain_t  *cl;

    cl = pool->chain;

    if (cl) {
        //因为c1将会被使用,所以指向c1的下一个
        pool->chain = cl->next;
        return cl;
    }

    cl = ngx_palloc(pool, sizeof(ngx_chain_t));
    if (cl == NULL) {
        return NULL;
    }

    return cl;
}

// ngx_create_temp_buf只是创建了单个缓冲区空间。如果要创建一片连续的缓冲区,
// 就由链表统一管理,则需要使用ngx_create_chain_of_bus函数
// 从内存池pool中创建一个bufs->num长度的buf->size大小的链表,返回ngx_chain_t链表头节点
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;

    p = ngx_palloc(pool, bufs->num * bufs->size); // 上面描述了
    if (p == NULL) {
        return NULL;
    }

    // 这里用一个二级指针保存第一个节点指针地址,同样也保存了后面节点指针地址,这么做
    // 少了一次循环内的判断,否则我们需要判断出第一次循环,并给chain赋值
    ll = &chain;

    for (i = 0; i < bufs->num; i++) {

        b = ngx_calloc_buf(pool); // 在内存池中创建ngx_buf_t对象,在ngx_buf.h头文件宏定义了
        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
         *
         */

        // 初始化每个buf缓冲区的起始位置
        b->pos = p;
        b->last = p;
        b->temporary = 1;

        b->start = p;
        // 此时p会指向下一个缓冲区
        p += bufs->size;
        b->end = p;

        // 创建一个ngx_chain_t节点
        cl = ngx_alloc_chain_link(pool);
        if (cl == NULL) {
            return NULL;
        }

        // 将ngx_chain_t与buf关联起来
        cl->buf = b;
        // ll保存的上一个节点指针的指针,赋值相当于给上一个节点next指针赋值
        *ll = cl;
        // 取本节点next指针地址,用于下次循环使用
        ll = &cl->next;
    }

    *ll = NULL;

    // 返回ngx_chain_t链表的头节点
    return chain;
}


// 将其他缓冲区拷贝到已有缓冲区末尾,也就是in链表插入到chain链表末尾
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;

    // 找到chain链表的结尾,然后给ll
    for (cl = *chain; cl; cl = cl->next) {
        ll = &cl->next;
    }

    // 遍历in,并且创建分配chain的基本节点,并将其buf指向in的部分
    while (in) {
        cl = ngx_alloc_chain_link(pool);
        if (cl == NULL) {
            return NGX_ERROR;
        }

        cl->buf = in->buf;  // 开始复制
        *ll = cl;           // 将next指向c1
        ll = &cl->next;     // 将ll指针移向c1的next
        in = in->next;      // in向下移动
    }

    *ll = NULL;  // 最后一个节点指向NULL

    return NGX_OK;
}


// 获取一个可用的ngx_chain_t节点,上面需要有一个可用的ngx_buf_t
ngx_chain_t *ngx_chain_get_free_buf(ngx_pool_t *p, ngx_chain_t **free)
{
    ngx_chain_t  *cl;

    // 若有空闲链表中有节点,直接返回
    if (*free) {
        cl = *free;
        *free = cl->next; // 不太明白为什么再移动指针
        cl->next = NULL;
        return cl;
    }

    // 程序移动到这里表示free是个空指针,需要申请空间
    cl = ngx_alloc_chain_link(p);
    if (cl == NULL) {
        return NULL;
    }

    cl->buf = ngx_calloc_buf(p);  // 申请新的buf空间
    if (cl->buf == NULL) {
        return NULL;
    }

    cl->next = NULL;

    return cl;
}


// ngx_chain_update_chains函数会将busy链表中的空闲节点回收到free链表中
// 将out链表插入到busy链表尾部,同时将合并后的链表从头开始的所有没有使用的节点,插入到空闲链表
// 合并链表后,out为NULL
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;

    if (*busy == NULL) {
        *busy = *out;

    } else {
        for (cl = *busy; cl->next; cl = cl->next) { /* void */ }

        cl->next = *out;
    }

    *out = NULL;

    while (*busy) {
        cl = *busy;

        // 这个节点内存占有,不满足条件,直接退出
        if (ngx_buf_size(cl->buf) != 0) {
            break;
        }

       //  缓冲区类型不为void *,直接释放,在循环开始
        if (cl->buf->tag != tag) {
            *busy = cl->next;
            ngx_free_chain(p, cl);
            continue;
        }

        // 重置缓冲区所有空间都可用
        cl->buf->pos = cl->buf->start;
        cl->buf->last = cl->buf->start;

        // 将该空闲区加入到free链表表头
        *busy = cl->next;
        cl->next = *free;
        *free = cl;
    }
}

三、graphviz画图工具

       graphviz采用dot语言,就感觉像描述一样,随便画出来的图就是这个样子了

      

     

看源码梳理函数完全可以用这个,我贴出来一些链接

   1.windows下Graphviz安装及入门教程      http://m.blog.csdn.net/article/details?id=49472949

   2.利用Graphviz 画结构图

      http://www.cnblogs.com/sld666666/archive/2010/06/25/1765510.html

   3.使用Graphviz绘制流程图和关系图

      http://www.tuicool.com/articles/qeqeuyb


 如果有什么问题,欢迎交流
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值