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