news 2026/8/26 15:01:57

Nginx共享内存深度揭秘:annotated_nginx注解版ngx_slab分配器实现细节

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nginx共享内存深度揭秘:annotated_nginx注解版ngx_slab分配器实现细节

Nginx共享内存深度揭秘:annotated_nginx注解版ngx_slab分配器实现细节

【免费下载链接】annotated_nginxAnnotated Nginx Source(中文)项目地址: https://gitcode.com/gh_mirrors/an/annotated_nginx

annotated_nginx 是 Nginx 源码的中文注解版本,逐行为核心代码添加了通俗易懂的中文注释。本文基于该注解版源码,带你看懂Nginx 共享内存的核心——ngx_slab分配器的实现细节:它如何把一块 mmap 出来的内存切分、复用、加锁管理。

想要对照源码阅读?获取注解版源码:

git clone https://gitcode.com/gh_mirrors/an/annotated_nginx

一、为什么 Nginx 需要共享内存

Nginx 是多进程模型:master 进程负责管理配置和监听,worker 进程负责处理连接,各进程地址空间相互隔离。凡是"跨进程必须一致"的状态,都不能放在某个 worker 的私有内存里:

功能为什么需要共享内存
limit_req/limit_conn所有 worker 必须看到同一份限流计数
upstream 被动健康检查后端挂掉要被所有 worker 知晓
open_file_cache文件元数据跨 worker 复用
stub_status活跃连接数需要全局汇总

图:Nginx 进程模型与 ngx_cycle_t 结构关系。共享内存 zone 就挂在 ngx_cycle_t 的 conf_ctx 里,由 master 创建后传给所有 worker

而普通业务内存走的是进程私有的内存池ngx_poolnginx/src/core/ngx_palloc.c),不可跨进程共享。共享内存的管理者,就是本文主角——slab 分配器。

二、ngx_slab 的三大核心结构

源码位置:nginx/src/core/ngx_slab.h(注解版,逐行有中文注释)。

1. ngx_slab_pool_t:共享内存池的"总管家"

它是共享内存区域的起始内容,64 位系统上约 200 字节,关键字段:

字段含义
lock/mutex跨进程互斥锁(自旋 + 信号量)
min_shift/min_size最小分配单位,2^3 = 8字节
pages页管理数组,一个元素管一个 4K 页
free空闲整页链表的头节点(哨兵)
pfree空闲页数量
start/end真正可用内存的起止地址

2. ngx_slab_page_t:每个 4K 页的管理信息

仅 3 个字段、24 字节,却靠指针低 2 位复用承载了 4 种页状态:

页类型适用场景
NGX_SLAB_PAGE0整页分配(>2K 的大对象)
NGX_SLAB_BIG164B < 对象 ≤ 2K
NGX_SLAB_EXACT2对象恰好 ≤ 64B,一个位图指针刚好管满一页
NGX_SLAB_SMALL3对象 < 64B,借用页内头部空间做位图

3. slots 数组:9 条"规格链表"

共享内存头部紧跟一个 9 元素的数组(ngx_slab_init中初始化),分别管理8 / 16 / 32 / … / 1024 / 2048 字节规格的半满页。分配时按"最接近的 2 的幂"选槽位,这就是 ngx_slab 的best-fit(最佳适配)策略。

💡 注解版里一句话概括:分成 8/16/32…2K/4K 的多个 slot,找最合适的分配。

三、4MB 共享内存如何被初始化

入口在nginx/src/core/ngx_cycle.cngx_init_zone_pool()

  1. master 启动时先调用ngx_slab_sizes_init()nginx/src/core/nginx.c),按页大小算出两个关键常量:
    • ngx_slab_max_size = 页大小 / 2,通常2K——超过它直接整页分配;
    • ngx_slab_exact_size = 64(64 位系统),一个uintptr_t位图恰好能管理一整页。
  2. 对每个共享 zone 设置sp->min_shift = 3(最小 8 字节)、sp->end(区域末尾)。
  3. 创建跨进程互斥锁ngx_shmtx_create()
  4. 调用ngx_slab_init()完成布局:
| pool 结构(200B) | slots 9×24B | stats 9×32B | 页数组 24B/页 | ←对齐4K→ | 真正可用的内存页 |

注解版源码算过一笔账:管理信息总消耗约 704 字节 + 每页 24 字节,4MB 共享内存的利用率约 99.3%,几乎不浪费。

四、分配流程:ngx_slab_alloc 内部细节

对外只有 4 个入口(ngx_slab.h):alloc / calloc / free及对应的_locked免锁版本。核心逻辑在ngx_slab_alloc_locked(),决策路径非常清晰:

分配 size ├─ size > 2K ───────────► ngx_slab_alloc_pages() 在空闲页链表找连续页,直接整页切走 ├─ 8B < size ≤ 2K ──────► 找到对应 slot 的半满页链表 │ ├─ < 64B (SMALL):在页内位图找空位 │ ├─ ≤ 64B (EXACT):用 page->slab 本身当 64 位位图 │ └─ > 64B (BIG):用 slab 高 32 位当位图 └─ slot 链表为空 ────────► 申请一个全新的 4K 空闲页,按规格初始化后挂入链表

几个注解版点出的精妙之处:

  • 指针低 2 位复用ngx_slab_page_prev()宏先& ~3还原真实指针,ngx_slab_page_type()& 3判断页类型,一个字段省出一个字段的空间;
  • 位图即状态:SMALL 页直接拿页内存的开头当位图,EXACT 页 64 位位图恰好管 64 个 64B 块(64×64 = 4096),无一浪费;
  • 页满了自动摘链:某 slot 链表上的页被分满后,会从链表摘下并打上对应类型标记,释放时再挂回去。

五、释放与页合并:ngx_slab_free

ngx_slab_free_locked()nginx/src/core/ngx_slab.c)先校验指针必须落在start ~ end区间内(防止野指针),再按页类型反向操作:

  1. 把对应位图的 bit 清 0;
  2. 若整页全部释放,调用ngx_slab_free_pages()归还整页;
  3. 归还时会自动与前后相邻的空闲页合并(1.7.x 起支持),有效对抗碎片化——这是 slab 分配器能长期稳定运行的关键。

分配侧对应的是ngx_slab_alloc_pages():从空闲链表找到足够长的连续空闲段,多页时切分并把剩余部分挂回链表,page->slab高位打NGX_SLAB_PAGE_START标记 + 低位记录页数。

六、跨进程并发控制:ngx_shmtx

所有 alloc/free 都必须持有pool->mutex。它不是 pthread 锁,而是nginx/src/core/ngx_shmtx.h定义的自旋 + 信号量混合锁:

  • 先做 CAS 原子写(用 PID 做锁值,保证只有持锁者能解锁,避免"锁误删");
  • 抢不到锁就自旋若干轮;
  • 仍失败则通过 POSIX 信号量睡眠,由解锁者唤醒——比纯自旋更省 CPU。

这也是为什么 alloc/free 各提供_locked免锁版本:模块可以一次性持锁做多次分配,减少锁开销。

七、给运维和开发者的实用建议

场景建议
zone 大小怎么选估算:条目数 × 每条大小,再乘 1.5~2 的碎片余量,如limit_req_zone ... zone=10m
日志出现ngx_slab_alloc() failed: no memoryzone 太小或碎片化,优先调大 zone,其次检查模块是否泄漏不释放
高频分配热路径_locked版本批量操作,减少锁竞争

小结

ngx_slab 分配器用极小的管理开销(约 0.7%)实现了跨进程、加锁、best-fit、自动合并的完整内存池:pool管全局、page管 4K 单元、slots管 8B~2K 九种规格、位图管页内小块。配合 annotated_nginx 中逐行中文注释的nginx/src/core/ngx_slab.c,即使是新手也能一步步跟完分配与释放的全过程。

【免费下载链接】annotated_nginxAnnotated Nginx Source(中文)项目地址: https://gitcode.com/gh_mirrors/an/annotated_nginx

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/26 14:59:14

yuzu 模拟器使用教程:从零到第一次跑通的完整设置

yuzu 模拟器使用教程&#xff1a;从零到第一次跑通的完整设置 【免费下载链接】yuzu 任天堂 Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu 6 英寸小屏、3 小时续航、出门还得带底座——想认真玩 Switch 游戏的人&#xff0c;迟早会把这套东西搬…

作者头像 李华
网站建设 2026/8/26 14:58:32

3 步解除 PDF 复制打印限制:PDFPatcher 单文件与批量处理完整指南

3 步解除 PDF 复制打印限制&#xff1a;PDFPatcher 单文件与批量处理完整指南 【免费下载链接】PDFPatcher PDF补丁丁——PDF工具箱&#xff0c;可以编辑书签、剪裁旋转页面、解除限制、提取或合并文档&#xff0c;探查文档结构&#xff0c;提取图片、转成图片等等 项目地址:…

作者头像 李华
网站建设 2026/8/26 14:57:55

RevokeMsgPatcher 使用指南:四步完成微信QQ防撤回补丁

RevokeMsgPatcher 使用指南&#xff1a;四步完成微信QQ防撤回补丁 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁&#xff08;我已经看到了&#xff0c;撤回也没用了&#xff09; 项目地址: https://gitcode.co…

作者头像 李华
网站建设 2026/8/26 14:56:23

有自研算法的 GEO 服务商,价格会不会更高?先看成本结构再判断

有自研算法的 GEO 服务商不一定报价更高&#xff0c;但它的成本结构通常和“代发内容、堆外链、套模板”的服务不同。判断价格是否合理&#xff0c;不能只看有没有“自研算法”四个字&#xff0c;而要看算法具体解决了什么问题&#xff1a;是否能持续监测 AI 搜索可见度、是否能…

作者头像 李华
网站建设 2026/8/26 14:50:57

Python + AI Agent:让你办公效率暴增10倍的8个“狠”招(附案例)

你还在用 写死板的自动化脚本吗&#xff1f;比如&#xff1a;“如果有邮件&#xff0c;就下载附件”。这叫自动化。可是到了二零二六年, 我们所要玩的是人工智能智能体, 没错, 就是那个AI Agent。说像这样, 查看一下邮件究竟是谁发送过来的, 要是发送者是老板, 那就即刻去进行重…

作者头像 李华