恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Nginx共享内存深度揭秘:annotated_nginx注解版ngx_slab分配器实现细节
首页
资讯中心
/
Nginx共享内存深度揭秘:annotated_nginx注解版ngx_slab分配器实现细节
Nginx共享内存深度揭秘:annotated_nginx注解版ngx_slab分配器实现细节
发布时间:2026/8/26 15:02:11
Nginx共享内存深度揭秘annotated_nginx注解版ngx_slab分配器实现细节【免费下载链接】annotated_nginxAnnotated Nginx Source中文项目地址: https://gitcode.com/gh_mirrors/an/annotated_nginxannotated_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 对象 ≤ 2KNGX_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.c的ngx_init_zone_pool()master 启动时先调用ngx_slab_sizes_init()nginx/src/core/nginx.c按页大小算出两个关键常量ngx_slab_max_size 页大小 / 2通常2K——超过它直接整页分配ngx_slab_exact_size 6464 位系统一个uintptr_t位图恰好能管理一整页。对每个共享 zone 设置sp-min_shift 3最小 8 字节、sp-end区域末尾。创建跨进程互斥锁ngx_shmtx_create()。调用ngx_slab_init()完成布局| pool 结构(200B) | slots 9×24B | stats 9×32B | 页数组 24B/页 | ←对齐4K→ | 真正可用的内存页 |注解版源码算过一笔账管理信息总消耗约 704 字节 每页 24 字节4MB 共享内存的利用率约 99.3%几乎不浪费。四、分配流程ngx_slab_alloc 内部细节对外只有 4 个入口ngx_slab.halloc / 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_freengx_slab_free_locked()nginx/src/core/ngx_slab.c先校验指针必须落在start ~ end区间内防止野指针再按页类型反向操作把对应位图的 bit 清 0若整页全部释放调用ngx_slab_free_pages()归还整页归还时会自动与前后相邻的空闲页合并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 ... zone10m日志出现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),仅供参考