ngx_chain_update_chains
1 定义ngx_chain_update_chains 函数 定义在 src/core/ngx_buf.cvoidngx_chain_update_chains(ngx_pool_t*p,ngx_chain_t**free,ngx_chain_t**busy,ngx_chain_t**out,ngx_buf_tag_ttag){ngx_chain_t*cl;if(*out){if(*busyNULL){*busy*out;}else{for(cl*busy;cl-next;clcl-next){/* void */}cl-next*out;}*outNULL;}while(*busy){cl*busy;if(cl-buf-tag!tag){*busycl-next;ngx_free_chain(p,cl);continue;}if(ngx_buf_size(cl-buf)!0){break;}cl-buf-poscl-buf-start;cl-buf-lastcl-buf-start;*busycl-next;cl-next*free;*freecl;}}2 目的1 设计意图ngx_chain_update_chains的核心职责是维护ngx_output_chain_ctx_t中三条链表free、busy、out的状态转移实现ngx_chain_t链节的对象池复用和已清空缓冲区的复位回收。该函数位于 Nginx 的buf/chain 基础设施层src/core/ngx_buf.c是ngx_output_chain输出链框架的核心配套工具。它不直接参与数据的发送与接收而是在每次output_filter调用完成后被调用充当三条链表之间的调度员。3 详解1 函数签名voidngx_chain_update_chains(ngx_pool_t*p,ngx_chain_t**free,ngx_chain_t**busy,ngx_chain_t**out,ngx_buf_tag_ttag)1 返回值void返回值含义void函数无返回值所有结果通过修改外部链表头指针*free、*busy、*out来体现与大多数 Nginx 函数不同该函数不产生错误——它不分配内存、不发起 I/O唯一的副作用是链表指针的重新编排。2 函数名ngx_chain_update_chains词段含义ngx_Nginx 标准前缀chain操作对象为ngx_chain_t链表update更新而非创建或销毁——在三链表之间转移节点维护对象池状态chains复数强调该函数同时操作free、busy、out三条链表的联动更新3 参数列表参数类型含义来源约束pngx_pool_t *内存池用于ngx_free_chain归还链节到pool-chain调用者传入通常为r-pool或ctx-pool不可为 NULLfreengx_chain_t **指向空闲链表头指针的二级指针被回收的链节通过头插法放入此链ctx-free不可为 NULLbusyngx_chain_t **指向忙链表头指针的二级指针存放已交网络层但未完全消费的缓冲区ctx-busy不可为 NULLoutngx_chain_t **指向输出链表头指针的二级指针存放上一轮构造并已交output_filter处理的数据链out调用者局部变量不可为 NULL函数返回后*out必为 NULLtagngx_buf_tag_t模块标识用于区分不同模块分配的缓冲区实际上就是void *调用者传入通常为(ngx_buf_tag_t) ngx_http_xxx_module应设为非 NULL 的唯一值若为 NULL 则所有 tagNULL 的缓冲区均视为匹配2 逻辑流程ngx_chain_update_chains(p, free, busy, out, tag) ├─ [1] 输出链表迁移*out 非空 │ ├─ [1.1] 忙链表为空 │ │ └─ *busy *out → *out NULL → 进入 [2] │ └─ [1.2] 忙链表非空 │ └─ 遍历 busy 找尾部 → 追加 *out 到尾部 → *out NULL → 进入 [2] └─ [2] 忙链回收扫描遍历 *busy 头部节点 ├─ [2.1] tag 不匹配 │ └─ 从 busy 摘下 → ngx_free_chain 归还链节到 pool → 继续下一节点 ├─ [2.2] 缓冲区仍有数据ngx_buf_size ! 0 │ └─ break停止扫描此节点及之后所有节点留在 busy 中等待网络层消费 └─ [2.3] 缓冲区已清空ngx_buf_size 0且 tag 匹配 └─ 复位 bufposstart, laststart→ 从 busy 摘下 → 头插法放入 free → 继续下一节点{ngx_chain_t*cl;局部变量声明1 输出链表迁移将 *out 追加到 *busy 尾部if(*out){if(*busyNULL){*busy*out;}else{for(cl*busy;cl-next;clcl-next){/* void */}cl-next*out;}*outNULL;}进入条件*out ! NULL。即上一轮output_filter调用之前调用者构造了一条非空的输出链经output_filter处理后需要将链的所有权移交给忙链表。*out的来源以典型调用方ngx_output_chain为例src/core/ngx_output_chain.clastctx-output_filter(ctx-filter_ctx,out);if(lastNGX_ERROR||lastNGX_DONE){returnlast;}ngx_chain_update_chains(ctx-pool,ctx-free,ctx-busy,out,ctx-tag);last_outout;out是主循环中逐缓冲区累积的输出链。output_filter拿到这条链后可能只消费了头部几个缓冲区就返回如 socket 发送缓冲区已满也可能全部消费完毕。无论消费了多少剩余的链都需要由*busy持有等待网络层后续继续发送。整体语义这一段 if 是所有权转移。将*out指向的整条后继链整体移交给*busy然后将*out置 NULL——调用方不再持有对这条链的引用防止外部误操作。1.1忙链表为空*busy*out;进入条件*busy NULL忙链表中没有任何节点。处理逻辑直接将*out赋给*busyO(1) 操作。不需要遍历——因为没有已存在的尾部需要定位。典型场景ngx_output_chain首次被调用或上一轮所有忙链节点都已被回收或消费完毕。1.2忙链表非空for(cl*busy;cl-next;clcl-next){/* void */}cl-next*out;进入条件*busy ! NULL忙链表中还有未被完全消费的旧数据。处理逻辑遍历*busy找到尾部节点cl-next NULL。循环体为空——纯粹定位尾部是一个 O(n) 操作。将*out链接到尾部cl-next。这保证忙链表保持FIFO 顺序先产生的数据在头部后产生的在尾部。为什么追加到尾部而非头部网络层按顺序发送数据——先加入忙链的缓冲区先被 socket 发送。如果将*out插入头部新数据会抢在旧数据之前被发送破坏 HTTP 响应的顺序性。追加到尾部保证了发送顺序与数据产生的顺序一致。2 忙链回收扫描while(*busy){cl*busy;if(cl-buf-tag!tag){*busycl-next;ngx_free_chain(p,cl);continue;}if(ngx_buf_size(cl-buf)!0){break;}cl-buf-poscl-buf-start;cl-buf-lastcl-buf-start;*busycl-next;cl-next*free;*freecl;}进入条件无条件执行。即使*busy NULLwhile (*busy)直接跳过函数返回——这是正常路径表示忙链表为空本轮无回收任务。整体语义从忙链表头部开始逐个检查每个节点决定三种去向之一去向触发条件动作归还到pool-chaintag 不匹配摘除 →ngx_free_chain→ 继续留在 busy 中有数据未发完break停止整个扫描回收到*free已清空且 tag 匹配复位缓冲区 → 摘除 → 头插到 free → 继续为什么从头部开始扫描忙链表是 FIFO 顺序头部是最早加入、最早被发送的缓冲区因此最有可能已被完全消费。从头部开始扫描能最大化回收率。*busy指针的更新每个分支在摘下头节点后通过*busy cl-next将忙链表头前进到下一个节点同时通过外部二级指针busy将这一变更写回到调用者——因此循环的每一轮都可能改变调用者看到的*busy值。2.1tag 不匹配——归还异主链节if(cl-buf-tag!tag){*busycl-next;ngx_free_chain(p,cl);continue;}ngx_buf_tag_t就是void *。在 Nginx 中每个模块在分配ngx_buf_t时将buf-tag设为其ngx_module_t结构体的地址如(ngx_buf_tag_t) ngx_http_gzip_filter_module。ngx_module_t是全局唯一的静态变量其地址天然唯一适合做模块标识。进入条件cl-buf-tag ! tag。当前忙链表头节点的缓冲区不属于调用本函数的模块。处理逻辑*busy cl-next从忙链表头部摘下该节点。ngx_free_chain(p, cl)将ngx_chain_t链节归还到内存池的全局空闲链表。continue继续处理忙链表的下一个头节点此时*busy已前进到下一个节点。设计意图——模块隔离tag 不匹配意味着忙链表中混入了其他模块的缓冲区。如果不做 tag 检查就将这些异主缓冲区回收到本模块的*free链会导致其他模块的缓冲区被劫持原模块后续执行时会访问到已被改写的内存本模块的空闲链被不属于自己的缓冲区污染后续ngx_chain_get_free_buf返回的类型和大小不可预期。因此异主链节直接归还到全局pool-chain而非本模块的*free是最安全的选择。2.2缓冲区仍有数据——停止扫描if(ngx_buf_size(cl-buf)!0){break;}进入条件cl-buf-tag tag通过上一检查且ngx_buf_size(cl-buf) ! 0缓冲区中还有未发送的数据。处理逻辑break——遇到第一个仍有数据的缓冲区后立即终止整个 while 循环。这是本函数最关键的性能决策忙链表保持 FIFO 顺序头部最先加入最先被发送如果头部节点还有数据未发完说明网络层尚未消费完毕由于发送是顺序的头部之后的所有节点不可能已被完全消费——检查它们是纯浪费典型时序时间线 T1: ngx_output_chain 产生 buf_A, buf_B, buf_C → output_filter → busy[A,B,C] T2: socket 只发送了 buf_A 的 2000/4096 字节 → *busy 扫描到此停止 T3: socket 发完 buf_A 剩余 2096 字节 → 下一轮 ngx_chain_update_chains 回收 buf_A T4: socket 开始发 buf_B ...安全视角如果此处不break而是continue被部分消费的缓冲区在ngx_buf_size ! 0时不会被复位回收因为进不了分支 [2.3]但会浪费 CPU 遍历后续不可能已清空的节点。2.3缓冲区已清空——复位并回收cl-buf-poscl-buf-start;cl-buf-lastcl-buf-start;*busycl-next;cl-next*free;*freecl;进入条件cl-buf-tag tag且ngx_buf_size(cl-buf) 0——缓冲区数据已被网络层完全消费链节和缓冲区均可安全复用。处理逻辑逐行cl-buf-pos cl-buf-start;— 将读/发送指针复位到缓冲区起始位置。cl-buf-last cl-buf-start;— 将有效数据尾指针复位到起始位置。复位后pos last startngx_buf_size 0缓冲区处于已分配内存但无有效数据的干净状态可被下一轮ngx_output_chain通过ngx_chain_get_free_buf取出后写入新数据。*busy cl-next;— 从忙链表头部摘下本节点。cl-next *free;— 将本节点的next指向当前空闲链表的头部可能为 NULL。*free cl;— 将本节点设置为空闲链表的新头部。采用LIFO 头插法O(1) 插入。LIFO 的意义在于缓存局部性最近回收的缓冲区数据可能仍在 CPU 缓存中下一次分配时优先命中缓存。循环继续处理忙链表的下一个头节点已在第 3 步更新。设计意图——对象池模式零 malloc/free这是 Nginx 高性能内存管理的核心设计之一。ngx_chain_t链节和ngx_buf_t缓冲区通过三链表循环在请求生命周期内从不进入系统的malloc/free具体生命周期ngx_output_chain通过ngx_chain_get_free_buf(p, free)从*free取出一个链节O(1)将新数据写入其buf。装满数据的链节被放入out链经output_filter交给网络层。output_filter返回后ngx_chain_update_chains将*out移入*busy分支 [1]。网络层逐步发送数据完成后ngx_buf_size归零。下一轮ngx_chain_update_chains回收该链节本分支将其放回*free。全过程零次malloc/free仅在请求池销毁时统一释放所有内存。这避免了频繁分配释放导致的内存碎片和系统调用开销是 Nginx 能在 C10K 场景下保持高性能的关键机制之一。边界条件——*free初始为 NULL首次调用时*free为 NULL空闲链尚未建立。执行cl-next *free即cl-next NULL和*free cl后空闲链从单节点开始构建。后续每次回收不断在头部累积节点。ngx_chain_get_free_buf的分配逻辑是先尝试从*free取若为空则通过ngx_alloc_chain_link(p)从池中分配新链节——两者对调用者透明。

相关新闻

AM263P CPDMA与CPPI 3.0协议实战:DMA配置与网络数据高效传输

AM263P CPDMA与CPPI 3.0协议实战:DMA配置与网络数据高效传输

1. 项目概述与核心价值在嵌入式网络设备开发中,尤其是面对AM263P这类高性能处理器,如何高效、稳定地处理海量的以太网数据包,是决定系统性能上限的关键。CPU如果深陷于每个字节的搬移工作,那再强的算力也会被I/O瓶颈所吞噬。这时&…

2026/7/21 2:38:16阅读更多 →
基于Git与对象存储的AI安全分析结果自动归档与版本控制实践

基于Git与对象存储的AI安全分析结果自动归档与版本控制实践

1. 项目概述:为什么我们需要为SecGPT-14B分析结果建立备份策略?如果你正在使用OpenClaw来驱动SecGPT-14B进行安全分析、代码审计或者威胁情报挖掘,那么你肯定已经体会到了它带来的效率提升。无论是自动化的漏洞扫描报告,还是对复杂…

2026/7/21 2:38:16阅读更多 →
奥沙利文与希金斯第80次对决:斯诺克传奇的技术解析

奥沙利文与希金斯第80次对决:斯诺克传奇的技术解析

1. 斯诺克传奇对决:奥沙利文与希金斯的第80次巅峰之战当"火箭"罗尼奥沙利文与"巫师"约翰希金斯再次在斯诺克球台相遇,这已经不仅仅是场比赛,而是现代斯诺克运动最珍贵的活历史。两位47岁的传奇选手将在2023年英国锦标赛上…

2026/7/21 2:38:16阅读更多 →
Nette Finder完全指南:如何用直观API快速查找文件与目录

Nette Finder完全指南:如何用直观API快速查找文件与目录

Nette Finder完全指南:如何用直观API快速查找文件与目录 【免费下载链接】finder [DISCONTINUED] 🔍 Finder: find files and directories with an intuitive API. 项目地址: https://gitcode.com/gh_mirrors/finder8/finder 想要在PHP项目中快速…

2026/7/21 14:16:54阅读更多 →
全面解锁Mac微信新体验:WeChatExtension-ForMac功能详解与使用指南

全面解锁Mac微信新体验:WeChatExtension-ForMac功能详解与使用指南

全面解锁Mac微信新体验:WeChatExtension-ForMac功能详解与使用指南 WeChatExtension-ForMac是一款专为Mac用户打造的微信功能拓展插件,能够显著提升微信使用体验,带来防撤回、多开登录、消息转发等实用功能。本文将详细介绍这款插件的核心特…

2026/7/21 14:16:54阅读更多 →
OpenCode环境变量终极配置指南:从零到精通的完整教程

OpenCode环境变量终极配置指南:从零到精通的完整教程

OpenCode环境变量终极配置指南:从零到精通的完整教程 【免费下载链接】opencode The open source coding agent. 项目地址: https://gitcode.com/GitHub_Trending/openc/opencode 想要充分发挥OpenCode作为AI编程助手的强大能力?环境变量配置就是…

2026/7/21 14:16:54阅读更多 →
Spring Boot与MyBatis整合开发实践指南

Spring Boot与MyBatis整合开发实践指南

1. 为什么Spring Boot与MyBatis是黄金组合 在Java企业级开发领域,Spring Boot和MyBatis的组合已经成为事实上的标准配置。这种组合之所以流行,是因为它完美地结合了Spring Boot的约定优于配置理念和MyBatis的SQL灵活性。 Spring Boot 3.5作为当前主流版…

2026/7/21 14:16:54阅读更多 →
终极解决方案:如何让Mac微信群聊管理变得简单高效

终极解决方案:如何让Mac微信群聊管理变得简单高效

终极解决方案:如何让Mac微信群聊管理变得简单高效 Mac微信虽然便捷,但群聊管理功能一直是用户痛点。WeChatExtension-ForMac作为一款专为Mac微信设计的功能拓展插件,通过简单安装即可解锁高效群管理能力,让你轻松应对多群消息、成…

2026/7/21 14:16:54阅读更多 →
【Autosar从入门到精通到进阶实战篇】68 DCM诊断通信:从“黑盒”到“可对话”

【Autosar从入门到精通到进阶实战篇】68 DCM诊断通信:从“黑盒”到“可对话”

68 DCM诊断通信:从“黑盒”到“可对话” 开篇故事:一场深夜的“ECU会诊” 凌晨两点,我被客户的电话吵醒:“新批次的ECU在产线上刷写总是失败,但同样的固件在实验室里跑得好好的!”电话那头,测试工程师的声音透着焦虑。 赶到现场,故障现象让人头疼:诊断仪发送了0x10…

2026/7/21 14:14:54阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/21 0:51:49阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 0:51:49阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/21 0:51:49阅读更多 →
Windows+macOS 通用 OpenClaw 部署流程,内置依赖一键启动智能桌面助手

Windows+macOS 通用 OpenClaw 部署流程,内置依赖一键启动智能桌面助手

📌教程适配:OpenClaw v2.7.9 | 兼容 Windows10/11、macOS 双系统 📖前言 当下各类本地 AI 工具层出不穷,多数产品仅能完成文字问答交互,很难直接操控电脑执行实际操作。OpenClaw,业内常称小龙虾 AI&#…

2026/7/21 0:01:46阅读更多 →
Codex 接入后 Bug 反增?复盘从个人演示到团队协作的“流程陷阱”

Codex 接入后 Bug 反增?复盘从个人演示到团队协作的“流程陷阱”

聊《一次Codex项目复盘,问题最后出在流程而不是模型》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要先把这篇文章的目标说清楚:看完之后,你应该能判断这件事值不值得做&…

2026/7/21 0:01:46阅读更多 →
手把手搓一个五子棋游戏,零代码也能当“游戏开发者”

手把手搓一个五子棋游戏,零代码也能当“游戏开发者”

大家好,还是我。前几期带大家做了心情日记本和可视化大屏,后台有朋友留言:“能不能教点好玩的?我想做游戏,但一行代码都不会。”行,这期就安排。今天的目标:从零做一个五子棋游戏。 带AI对战、三…

2026/7/21 0:03:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/20 22:51:39阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/20 18:51:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/20 18:51:18阅读更多 →