Nginx:Buffer缓冲区设计

Posted 看,未来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx:Buffer缓冲区设计相关的知识,希望对你有一定的参考价值。

刚那个长得像deque的链表需要找它的应用场景,缓冲区就不用我多说了吧,前前后后也看了好几个缓冲区的实现,谁的好,就拿来用咯。


缓冲区 Buffer

此前研究了 muduo 的缓冲区设计,有些心得,再来看看 nginx 的缓冲区设计。

typedef struct ngx_buf_s  ngx_buf_t;

struct ngx_buf_s {
    u_char          *pos;				//数据处理起始地址
    u_char          *last;			//数据处理结束地址
		//以上这俩,不是缓冲区的起止,只是需要被处理的数据的起止,谁用谁自己定义
	
    off_t            file_pos;
    off_t            file_last;
    //上面这俩是处理文件用的,其他和前面那俩一样

    u_char          *start;         /* start of buffer */
    u_char          *end;           /* end of buffer */
		//这俩才是缓冲区的起止位置

    ngx_buf_tag_t    tag;		//当前缓冲区的类型
    ngx_file_t      *file;	//引用的文件
    ngx_buf_t       *shadow;//影子缓冲区,不怎么用,用的时候再说


    /* the buf's content could be changed */
    unsigned         temporary:1;

    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;

    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;

    unsigned         recycled:1;	//可回收
    unsigned         in_file:1;		//处理的是文件
    unsigned         flush:1;		//需要刷新
    unsigned         sync:1;		//有些框架代码在sync为1时可能会有阻塞的方式进行I/O操作,它的意义视使用它的Nginx模块而定
    unsigned         last_buf:1;	//因为 ngx_buf_t可以由 ngx_chain_t链表串联起来。last_buf为1时,表示当前是最后一块待处理的缓冲区
    unsigned         last_in_chain:1;	//是否是 ngx_chain_t中的最后一块缓冲区
    unsigned         last_shadow:1;
    unsigned         temp_file:1;

    /* STUB */ int   num;
};

好长哈,而且不知道为什么要来个 typedef,是历史遗留原因?

这是个公用的缓冲区吗?嗯,是从内存池中去申请空间的。

看来需要去完整的看一下 nginx 内存池的设计了,那个会比较重要些吧。


ngx_chain_t

ngx_chain_t是与ngx_buf_t配合使用的链表数据结构。

typedef struct ngx_chain_s       ngx_chain_t;		//又来
struct ngx_chain_s {
    ngx_buf_t    *buf;	//指向当前的ngx_buf_t缓冲区
    ngx_chain_t  *next;	//来指向下一个ngx_chain_t。
    //如果这是最后一个 ngx_chain_t,则需要把next置为NULL。
};

buf指向当前的ngx_buf_t缓冲区,next则用来指向下一个ngx_chain_t。如果这是最后一个 ngx_chain_t,则需要把next置为NULL。 在向用户发送HTTP包体时,就要传入ngx_chain_t链表对象,注意,如果是最后一个 ngx_chain_t,那么必须将next置为NULL,否则永远不会发送成功,而且这个请求将一直不会 结束(Nginx框架的要求)。

(包头存在链表里面)


方法配置

申请临时缓冲区

//申请临时缓冲区
ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
{
    ngx_buf_t *b;

    b = ngx_calloc_buf(pool);
    if (b == NULL) {
        return NULL;
    }

    b->start = ngx_palloc(pool, size);
    if (b->start == NULL) {
        return NULL;
    }

	//目前还是空的,所以这样配备
    b->pos = b->start;
    b->last = b->start;
    b->end = b->last + size;
    b->temporary = 1;		//设置为临时缓冲区

    return b;
}

临时的哈,用完还得还的哈。


分配 chain 节点

ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool)
{
    ngx_chain_t  *cl;

    cl = pool->chain;

	    /* Nginx为了提升效率,会把已经使用过ngx_chain_t保存到ngx_pool_t中以便下次使用 */
    if (cl) {
        pool->chain = cl->next;
        return cl;
    }

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

    return cl;
}

分配一个 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;

    p = ngx_palloc(pool, bufs->num * bufs->size);
    if (p == NULL) {
        return NULL;
    }

    ll = &chain;

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

        b = ngx_calloc_buf(pool);
        if (b == NULL) {
            return NULL;
        }

        b->pos = p;
        b->last = p;
        b->temporary = 1;

        b->start = p;
        p += bufs->size;
        b->end = p;

        cl = ngx_alloc_chain_link(pool);
        if (cl == NULL) {
            return NULL;
        }

        cl->buf = b;
        *ll = cl;
        ll = &cl->next;
    }

    *ll = NULL;

    return chain;
}

合并buf链表

合并buf链表,将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;

    for (cl = *chain; cl; cl = cl->next) {
        ll = &cl->next;
    }

    while (in) {
        cl = ngx_alloc_chain_link(pool);
        if (cl == NULL) {
            return NGX_ERROR;
        }

        cl->buf = in->buf;
        //这也要秀一下?
        *ll = cl;
        ll = &cl->next;
        in = in->next;
    }

    *ll = NULL;	//这是为何?

    return NGX_OK;
}

从free chain链中获取一个空闲buf

ngx_chain_t *ngx_chain_get_free_buf(ngx_pool_t *p, ngx_chain_t **free)	//@param free 待查找chain链
{
    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);
    if (cl->buf == NULL) {
        return NULL;
    }

    cl->next = NULL;

    return cl;
}

回收链表空间

更新chain链表 释放内存 将busy中空闲节点回到free链表中或者内存池中。

/**
 * 更新chain链表 释放内存 将busy中空闲节点回到free链表中或者内存池中
 * @param p 内存池
 * @param free 空闲链
 * @param busy 正在使用链
 * @param out  将out中分发到 free或者busy中
 * @param tag  标志 分发原则 一般是函数指针
 */

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;
        }

        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;

        *busy = cl->next;
        cl->next = *free;
        *free = cl;
    }
}

你悟到了吗?

这么大块空间,收收发发的不累吗?既然都被申请过了,那就不要收回来了,就放外面吧,下次要用直接拿去。


关于文件操作的我就先不说了哈。。

歇会儿去看内存池去了哈哈

以上是关于Nginx:Buffer缓冲区设计的主要内容,如果未能解决你的问题,请参考以下文章

具有多个缓冲区的片段着色器颜色错误

Buffer lab——20145326蔡馨熠

nginx缓冲区,跳转,超时

nginx 返回数据被截断

20179209《Linux内核原理与分析》第十二周作

WebGL中Stencil Buffer的运用以及ThreeJS的实现