Nginx 源码学习Nginx 的缓冲区

Posted 看,未来

tags:

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

缓冲区结构设计

网络缓冲区的重要性不言而喻,就如高并发下的消息队列一样。

redis的系列里面没有写缓冲区,muduo 的那个缓冲区设计的就挺不错。

缓冲区内存块的数据结构 ngx_buf_t:

typedef struct ngx_buf_s  ngx_buf_t;
/*
 * nginx缓冲区
 */
struct ngx_buf_s 
    u_char          *pos;           /* 待处理数据的开始标记  */
    u_char          *last;          /* 待处理数据的结尾标记 */
    //以上这俩,不是缓冲区的起止,只是需要被处理的数据的起止,谁用谁自己定义
    
    off_t            file_pos;		/* 处理文件时,待处理的文件开始标记  */
    off_t            file_last;		/* 处理文件时,待处理的文件结尾标记  */
	//上面这俩是处理文件用的,其他和前面那俩一样
  
    u_char          *start;         /* 缓冲区开始的指针地址 */
    u_char          *end;           /* 缓冲区结尾的指针地址 */
    //这俩才是缓冲区的起止位置
  
    ngx_buf_tag_t    tag;			/* 缓冲区标记地址,是一个void类型的指针。 */
    ngx_file_t      *file;			/* 引用的文件 */
    ngx_buf_t       *shadow;
 
 
    /* the buf's content could be changed */
 
    unsigned         temporary:1;	 /* 标志位,为1时,内存可修改 */
 
    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;   	/* 标志位,为1时,内存只读 */
 
    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;		/* 标志位,为1时,mmap映射过来的内存,不可修改 */
 
    unsigned         recycled:1;	/* 标志位,为1时,可回收 */
    unsigned         in_file:1;		/* 标志位,为1时,表示处理的是文件 */
    unsigned         flush:1;		/* 标志位,为1时,表示需要进行flush操作 */
    unsigned         sync:1;		/* 标志位,为1时,表示可以进行同步操作,容易引起堵塞 */
    unsigned         last_buf:1;	/* 标志位,为1时,表示为缓冲区链表ngx_chain_t上的最后一块待处理缓冲区 */
    unsigned         last_in_chain:1;/* 标志位,为1时,表示为缓冲区链表ngx_chain_t上的最后一块缓冲区 */
 
    unsigned         last_shadow:1;	/* 标志位,为1时,表示是否是最后一个影子缓冲区 */
    unsigned         temp_file:1;	/* 标志位,为1时,表示当前缓冲区是否属于临时文件 */
 
    /* STUB */ int   num;
;

从上面这个数据结构中,可以看到ngx_buf_t结构:
1、既可以处理内存,也可以处理文件。
2、Nginx使用了位域的方法,节省存储空间。
3、每个buf都记录了开始和结束点以及未处理的开始和结束点,因为缓冲区的内存申请了之后,是可以被复用的。


缓冲区链表结构 ngx_chain_t:

typedef struct ngx_chain_s       ngx_chain_t;
/**
 * 缓冲区链表结构,放在pool内存池上面
 */
struct ngx_chain_s 
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
;

1、是否还记得内存池结构中,有一个数据结构pool->chain就是保存空闲的缓冲区链表的。
2、Nginx的缓冲区ngx_buf_t,通过ngx_chain_t链表结构进行关联和管理。
3、通过链表的方式实现buf有一个非常大的好处:如果一次需要缓冲区的内存很大,那么并不需要分配一块完整的内存,只需要将缓冲区串起来就可以了。

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

所有缓冲区需要的数据结构以及缓冲区的buf内存块都会被分配到内存池上面。

缓冲区设计图

1、Nginx的缓冲区数据结构主要包含链表数据结构ngx_chain_t和buf数据结构ngx_buf_t
2、Nginx可以在自定义的业务层面管理繁忙busy和空闲free的缓冲区链表结构。通过后边的函数,可以对缓冲区的链表结构和buf结构进行管理。
3、如果缓冲区链表需要被回收,则会放到Nginx内存池的pool->chain链表上。


创建一个缓冲区

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;

	/* 在内存池pool上分配bufs->num个 buf缓冲区 ,每个大小为bufs->size */
    p = ngx_palloc(pool, bufs->num * bufs->size);
    if (p == NULL) 
        return NULL;
    

    ll = &chain;

	/* 循环创建BUF,并且将ngx_buf_t挂载到ngx_chain_t链表上,并且返回链表*/
    for (i = 0; i < bufs->num; i++) 
    	/* 最终调用的是内存池pool,开辟一段内存用作缓冲区,主要放置ngx_buf_t结构体 */
        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;
        

		/* 将buf,都挂载到ngx_chain_t链表上,最终返回ngx_chain_t链表 */
        cl->buf = b;
        *ll = cl;
        ll = &cl->next;
    

    *ll = NULL;

	/* 最终得到一个分配了bufs->num的缓冲区链表  */
    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;

	/* 找到缓冲区链表结尾部分,cl->next== NULL;cl = *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; //in上的buf拷贝到cl上面
		*ll = cl; //并且放到chain链表上
		ll = &cl->next; //链表往下走
		in = in->next; //遍历,直到NULL
    

    *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 源码学习Nginx 的缓冲区的主要内容,如果未能解决你的问题,请参考以下文章

nginx源码学习

[学习笔记]在Linux中使用源码编译的方式安装Nginx

nginx源码学习

nginx源码学习

nginx源码学习资源

Nginx 源码学习Nginx 中的 “deque“