ngx_output_chain_get_buf
2026/7/20 0:48:54
网站开发
1 定义ngx_output_chain_get_buf 函数 定义在 src/core/ngx_output_chain.cstaticngx_int_tngx_output_chain_get_buf(ngx_output_chain_ctx_t*ctx,off_tbsize){size_tsize;ngx_buf_t*b,*in;ngx_uint_trecycled;inctx-in-buf;sizectx-bufs.size;recycled1;if(in-last_in_chain){if(bsize(off_t)size){/* * allocate a small temp buf for a small last buf * or its small last part */size(size_t)bsize;recycled0;}elseif(!ctx-directioctx-bufs.num1(bsize(off_t)(sizesize/4))){/* * allocate a temp buf that equals to a last buf, * if there is no directio, the last buf size is lesser * than 1.25 of bufs.size and the temp buf is single */size(size_t)bsize;recycled0;}}bngx_calloc_buf(ctx-pool);if(bNULL){returnNGX_ERROR;}if(ctx-directio){/* * allocate block aligned to a disk sector size to enable * userland buffer direct usage conjunctly with directio */b-startngx_pmemalign(ctx-pool,size,(size_t)ctx-alignment);if(b-startNULL){returnNGX_ERROR;}}else{b-startngx_palloc(ctx-pool,size);if(b-startNULL){returnNGX_ERROR;}}b-posb-start;b-lastb-start;b-endb-lastsize;b-temporary1;b-tagctx-tag;b-recycledrecycled;ctx-bufb;ctx-allocated;returnNGX_OK;}2 目的1 设计意图ngx_output_chain_get_buf是ngx_output_chain输出链引擎的内部缓冲区分配器负责在需要拷贝转发而非零拷贝原样转发时按当前上下文需求智能创建一个新的临时缓冲区ngx_buf_t 数据存储空间。3 详解1 函数签名staticngx_int_tngx_output_chain_get_buf(ngx_output_chain_ctx_t*ctx,off_tbsize)1 返回值ngx_int_t返回值值含义NGX_OK0缓冲区分配成功ctx-buf指向新分配的、已初始化的ngx_buf_tctx-allocated已加 1NGX_ERROR-1分配失败内存池耗尽上层应终止处理并向上返回错误2 函数名ngx_output_chain_get_buf词段含义ngxNginx 源码前缀output_chain属于ngx_output_chain输出链子系统get“获取”表示获取一个可用的缓冲区资源buf获取的对象是缓冲区ngx_buf_t3 参数列表参数类型含义来源约束ctxngx_output_chain_ctx_t *输出链上下文维护缓冲区分配计数、配置参数、DirectIO 标志等调用方ngx_output_chain创建并持有非 NULLctx-in必须有效调用点已保证 bszie 0 且非特殊 bufbsizeoff_t即将需要拷贝的源数据量字节由ngx_buf_size(ctx-in-buf)计算得出ngx_output_chain内循环从ctx-in-buf计算必定 0零长度已在调用前的分支中跳过负长度已返回错误2 逻辑流程ngx_output_chain_get_buf(ctx, bsize) ├─ [1] 上下文初始化 │ └─ 获取源 bufctx-in-buf、标准大小ctx-bufs.size、默认标记 recycled1 ├─ [2] 末尾缓冲区尺寸优化in-last_in_chain 为真时触发 │ ├─ [2.1] 数据量小于标准大小 │ │ └─ bsize bufs.size → 缩小为标准大小标记不可回收recycled0 │ ├─ [2.2] 数据量在 1.0x~1.25x 之间 单缓冲区 非 DirectIO │ │ └─ bsize 1.25*bufs.size 且 num1 且 !directio → 精确分配标记不可回收recycled0 │ └─ [2.3] 数据量 1.25x 或多缓冲区或 DirectIO │ └─ 保持标准大小标记可回收recycled1 ├─ [3] 分配缓冲区控制结构ngx_buf_t │ └─ ngx_calloc_buf → 失败返回 NGX_ERROR ├─ [4] 分配数据存储空间 │ ├─ [4.1] DirectIO 路径 │ │ └─ ngx_pmemalign 对齐分配 → 失败返回 NGX_ERROR │ └─ [4.2] 普通路径 │ └─ ngx_palloc 内存池分配 → 失败返回 NGX_ERROR ├─ [5] 初始化缓冲区元数据 │ └─ poslaststart; endstartsize; temporary1; tagctx-tag; recycledrecycled └─ [6] 注册到上下文 └─ ctx-buf b; ctx-allocated; 返回 NGX_OKsize_tsize;ngx_buf_t*b,*in;ngx_uint_trecycled;局部变量声明1 上下文初始化inctx-in-buf;sizectx-bufs.size;recycled1;处理逻辑in ctx-in-buf获取当前待处理的源缓冲区指针。在调用本函数时ctx-in链已确保非空ngx_output_chain主循环的while (ctx-in)保证且bsize 0零长度和负长度已在前面分支处理size ctx-bufs.size获取标准缓冲区大小。该值来自 Nginx 配置指令output_buffers number size中的size参数——决定了每个临时输出缓冲区的默认大小recycled 1默认标记缓冲区为可回收。可回收意味着该缓冲区在发送完毕后会被ngx_chain_update_chains通过tag匹配回收到ctx-free链表中供后续复用这是 Nginx 缓冲区对象池机制的核心ctx-bufs类型ngx_bufs_t定义在src/core/ngx_buf.h:L65-L68typedefstruct{ngx_int_tnum;/* 允许分配的最大缓冲区数量 */size_tsize;/* 每个缓冲区的标准大小字节 */}ngx_bufs_t;2 末尾缓冲区尺寸优化if(in-last_in_chain){if(bsize(off_t)size){/* * allocate a small temp buf for a small last buf * or its small last part */size(size_t)bsize;recycled0;}elseif(!ctx-directioctx-bufs.num1(bsize(off_t)(sizesize/4))){/* * allocate a temp buf that equals to a last buf, * if there is no directio, the last buf size is lesser * than 1.25 of bufs.size and the temp buf is single */size(size_t)bsize;recycled0;}}进入条件in-last_in_chain 1——当前源缓冲区是整个响应体数据链中的最后一块。last_in_chain是ngx_buf_t结构体的位域字段由上游 content handler 在构造最后一个响应数据缓冲区时置位。它告知输出链引擎这是最后一块数据后面不会再追加。设计意图当知道这是最后一块时有机会避免为少量数据分配一个完整大小的缓冲区从而节省内存。该优化分两个子分支。2.1 数据量小于标准大小if(bsize(off_t)size){size(size_t)bsize;recycled0;进入条件bsize ctx-bufs.size——最后一块数据的实际大小不足一个标准缓冲区。处理逻辑size (size_t) bsize将分配大小缩小为实际数据量。例如配置的output_buffers 1 32k最后一块只有 512 字节则分配 512 字节而非 32KB——省下了约 31.5KB 内存recycled 0标记为不可回收。因为 512 字节的缓冲区在下一轮循环中不可能满足标准 32KB 的再分配需求回收它只会浪费 free 链表上的遍历开销和内存碎片场景典型的 HTTP 响应末尾——如 gzip 压缩后最后一块只有几百字节或者 static 模块发送文件时最后一个 buffer 只包含不到一页的数据。2.2 数据量在 1.0x~1.25x 之间}elseif(!ctx-directioctx-bufs.num1(bsize(off_t)(sizesize/4))){size(size_t)bsize;recycled0;}进入条件三个条件同时满足!ctx-directio非 DirectIO 模式。DirectIO 要求缓冲区按磁盘扇区对齐精确大小的缓冲区无法保证对齐因此不适用此优化ctx-bufs.num 1配置只允许分配 1 个缓冲区即output_buffers 1 size。如果num 1说明系统允许同时持有多个缓冲区先分配一个标准大小的更为合理bsize (off_t) (size size / 4)数据量不超过标准大小的 1.25 倍即bsize 1.25 * size。这里的size size / 4是整数运算实现的 1.25 倍阈值处理逻辑size (size_t) bsize精确分配——分配大小恰好等于数据量recycled 0标记为不可回收设计意图为什么要设 1.25 倍阈值这是一个内存节省 vs 回收复用的权衡。对于 1.01x 到 1.25x 的数据量若分配标准大小如 32KB浪费不多但可以回收复用但 Nginx 选择精确分配 不回收因为在num 1的单缓冲区配置下回收链上只有一个条目回收收益极小而稍微减少一点内存分配更有利对于超过 1.25x 的数据量走 [2.3] 分支使用标准大小多次拷贝为什么num 1是必要条件单缓冲区配置意味着allocated上限为 1——刚分配的这个缓冲区被消费后ctx-allocated回到 0下次 再次调用本函数分配新缓冲区。此时 recycled 的语义意义消失因为只有一个槽位不存在复用池的概念。标记recycled 0防止这个非标大小的缓冲区进入free链造成污染。2.3 末尾大数据或其他情况未进入上述两个子分支时last_in_chain为真但bsize 1.25 * size或num 1或directio为真保持size ctx-bufs.size和recycled 1recycled 1分配标准大小的缓冲区使用完毕后可回收到free链表供后续复用处理方式大数据量将被分多次拷贝到标准大小的缓冲区中ngx_buf_t.last_in_chain字段定义src/core/ngx_buf.hunsignedlast_in_chain:1;该位域位于ngx_buf_t结构体中与pos、last等指针字段同在一个结构体内。当上游模块确定当前缓冲区是响应体数据的最后一块时置 1。它独立于last_buf表示这是整个响应体的最后一块——last_in_chain是ngx_output_chain链内级别的信号而last_buf是 HTTP 层面的信号。3 分配缓冲区控制结构bngx_calloc_buf(ctx-pool);if(bNULL){returnNGX_ERROR;}处理逻辑分配并零初始化一个ngx_buf_t控制结构。ngx_calloc_buf宏定义src/core/ngx_buf.h#definengx_calloc_buf(pool)ngx_pcalloc(pool,sizeof(ngx_buf_t))展开为ngx_pcalloc(ctx-pool, sizeof(ngx_buf_t))——从 Nginx 内存池中分配一个ngx_buf_t大小的内存块并将所有位域清零。4 分配数据存储空间if(ctx-directio){/* * allocate block aligned to a disk sector size to enable * userland buffer direct usage conjunctly with directio */b-startngx_pmemalign(ctx-pool,size,(size_t)ctx-alignment);if(b-startNULL){returnNGX_ERROR;}}else{b-startngx_palloc(ctx-pool,size);if(b-startNULL){returnNGX_ERROR;}}ctx-directio是ngx_output_chain_ctx_s的位域字段由ngx_output_chain_align_file_buf在检测到 DirectIO 文件时置 1。4.1 DirectIO 路径b-startngx_pmemalign(ctx-pool,size,(size_t)ctx-alignment);进入条件ctx-directio 1。处理逻辑通过ngx_pmemalign分配一块按ctx-alignment磁盘扇区大小通常 512 字节对齐的内存。为什么 DirectIO 需要对齐DirectIOO_DIRECT绕过操作系统的页缓存直接从磁盘 DMA 传输数据到用户态缓冲区。这一机制要求用户态缓冲区地址按磁盘扇区边界对齐否则内核必须分配一块中间对齐缓冲区做中转拷贝——这恰恰是 DirectIO 想要避免的。ngx_pmemalign确保用户态缓冲区满足对齐要求使 DirectIO DMA 可以直接写入用户态缓冲区实现真正的零拷贝。4.2 普通路径b-startngx_palloc(ctx-pool,size);进入条件ctx-directio 0。处理逻辑通过ngx_palloc从 Nginx 内存池中分配size字节——不需要对齐约束。5 初始化缓冲区元数据b-posb-start;b-lastb-start;b-endb-lastsize;b-temporary1;b-tagctx-tag;b-recycledrecycled;字段初始化逐行解释字段赋值含义b-pos b-start读指针指向缓冲区起始位置初始时无可读数据pos与start同位置。数据被ngx_output_chain_copy_buf拷贝进来后dst-last后移此时last - pos即为已填充数据量b-last b-start写指针指向缓冲区起始位置初始时无已填充数据。ngx_output_chain_copy_buf填充数据后dst-last sizeb-end b-last size缓冲区尾部边界end - start size即分配的总容量。end - pos表示剩余可写入空间b-temporary 1标记为临时缓冲区告知下游该缓冲区数据可被修改区别于memory:1只读内存和mmap:1映射内存对应的内存将在请求结束时随内存池一起释放b-tag ctx-tag打上上下文标签tag用于ngx_chain_update_chains中的身份识别——只回收 tag 匹配的缓冲区避免跨请求/跨阶段的缓冲区误回收。ctx-tag由调用方在创建上下文时设置通常为模块指针b-recycled recycled回收标记recycled 1的缓冲区在发送完毕后会进入ctx-free链表供复用recycled 0的缓冲区在发送完毕后不会被回收到 free 链因为大小不标准不能复用6 注册到上下文ctx-bufb;ctx-allocated;returnNGX_OK;处理逻辑ctx-buf b将新分配的缓冲区注册到上下文中。ctx-buf是ngx_output_chain主循环中 拷贝步骤使用的工作缓冲区—ngx_output_chain_copy_buf从ctx-buf读取目标指针将源数据拷贝进去ctx-allocated递增已分配缓冲区计数。该计数在ctx-allocated ctx-bufs.num检查中用于限制同时存在的缓冲区数量达到上限后break退出内循环先发送已累积的数据释放空间返回NGX_OK告知上层调用者缓冲区已成功分配并初始化可以进入下一步ngx_output_chain_copy_buf进行数据拷贝。