在 nginx 的进程模型下, 类似流量统计, 流量控制, 数据共享, 等需要多个工作进程共同配合完成任务, 共享内存是一个重要的进程通讯的方案. 本文介绍在 nginx 的代码中与共享内存相关的功能, 包括 ngx_shmem 与 ngx_slab 的使用与注意事项, 但不包括 ngx_slab 中实现的内存管理算法.
ngx_shmem 的使用
ngx_shmem.c/h 文件只是对 mmap()/munmap()系统调用或者 shmget()/shmdt()的一个很简单的封装. 实现了 ngx 风格的基础库, 可以申请和释放一段连续的共享内存空间. 一般用于固定长度的共享数据使用, 使用过程中数据长度固定不会伸缩.
- typedef struct {
- u_char *addr;
- size_t size;
- ...
- } ngx_shm_t;
- ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);
- void ngx_shm_free(ngx_shm_t *shm);
在 ngxin 中共享内存的使用流程, 一般是由 master 进程创建, worker 进程通过继承的方式获得内存指针.
关于 ngx_shmem 的使用, 可以参考 ngx_event_module_init()中部分片段, 这部分代码在共享内存中创建了若干个变量, 用于记录各个状态 (accepted/reading/writing...) 的请求数量, 并在 ngx_event_module 中的几个关键事件入口对这几个变量进行加减统计操作. 实现统计所有 worker 进程当前的请求状态.
- shm.size = size;
- ngx_str_set(&shm.name, "nginx_shared_zone");
- shm.log = cycle->log;
- if (ngx_shm_alloc(&shm) != NGX_OK) {
- return NGX_ERROR;
- }
- shared = shm.addr;
- ...
- ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl);
- ngx_stat_handled = (ngx_atomic_t *) (shared + 4 * cl);
- ngx_stat_requests = (ngx_atomic_t *) (shared + 5 * cl);
- ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl);
- ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl);
- ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl);
- ngx_stat_waiting = (ngx_atomic_t *) (shared + 9 * cl);
关于这个功能的更多细节, 可以查看代码中的 NGX_STAT_STUB 宏定义相关代码与 ngx_http_stub_status_module.
ngx_slab 的使用
ngx_shmem 是一层极简的封装, 实现了共享内存的基本功能. 但我们程序中大部分的场景共享数据并不会一个固定大小的结构, 而更多是像 ngx_array,ngx_list,ngx_queue,ngx_rbtree 这类大小可以变化的数据结构.
我们期望能有像 ngx_pool_t 一样可以动态申请释放空间一个内存池. ngx_slab 正是一个这样的结构体, 原理上与系统的 malloc()有相识之处都是通过一系列算法实现对一段段内存片段的申请与释放. 只不过 ngx_slab 操作的对象是基于 ngx_shmem 的共享内存.
先看一下 ngx_slab 的接口
- typedef struct {
- ngx_shmtx_t mutex;
- ...
- void *data; /* 一般存放从 pool 中申请获得的根数据地址(pool 中第一个申请的数据接口) */
- void *addr; /* 使用 ngx_shmem 申请获得的共享内存基地址 */
- } ngx_slab_pool_t;
- void ngx_slab_init(ngx_slab_pool_t *pool);
- void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size);
- void *ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size);
- void *ngx_slab_calloc(ngx_slab_pool_t *pool, size_t size);
- void *ngx_slab_calloc_locked(ngx_slab_pool_t *pool, size_t size);
- void ngx_slab_free(ngx_slab_pool_t *pool, void *p);
- void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);
可以看到接口并不复杂, alloc 与 calloc 的区别在于是否对申请获得的内存段清零,_locked 结尾的接口表示操作的 pool 已经是获取到锁的. 在 ngx_slab_pool_t 的结构体有一个 ngx_shmtx_t 的互斥锁用于同步多进程同时访问 pool 的并发场景. 注意 ngx_slab_alloc()会先获取锁, 然后申请空间, 最后释放锁. 而 ngx_slab_alloc_locked()则直接申请空间, 认为程序已经在其他逻辑中获得锁了.
在 nginx 的开发中使用 ngx_shmem 一般需要遵循以下初始化流程:
模块在配置解析过程中调用 ngx_shared_memory_add()接口, 注册一段共享内存. 提供共享内存大小与内存初始化的回调函数.
框架在 ngx_init_cycle()中使用 ngx_shmem 申请内存, 并初始化 ngx_slab, 然后回调模块注册的初始化函数
模块使用 ngx_slab 的申请 / 是否接口
在这个流程中, 涉及到 ngx_shared_memory_add()接口与对应的 ngx_shm_zone_t 结构体.
- struct ngx_shm_zone_s {
- void *data;
- ngx_shm_t shm;
- ngx_shm_zone_init_pt init;
- void *tag;
- void *sync;
- ngx_uint_t noreuse; /* unsigned noreuse:1; */
- };
- ngx_shm_zone_t *ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name,
- size_t size, void *tag);
其中值得一提的是 noreuse 属性, 这个属性控制了在 nginx 的 reload 过程中是否会重新申请共享内存.
由于关于 ngx_init_cycle()函数较长, 这个流程可以通过查找 /* create shared memory */ 这个注释或者 cycle->shared_memory 这个对象查看相关代码.
关于 ngx_slab 更多细节的使用, 建议可以参考 ngx_http_limit_conn_module, 这是通过共享内存实现连接数限制的模块, 模块复杂度底, 是一个很好的参考范例.
参考资料
深入理解 Nginx(第 2 版) https://book.douban.com/subject/26745255/
ngx_http_limit_conn_module
同时安利一波《深入理解 Nginx》作者 陶辉 在极客时间出版的《Nginx 核心知识 100 讲》, 近期 618 似乎有打折活动, 通过我分享的链接进行购买, 我也将获得部分返现, 感谢支持.
来源: https://www.cnblogs.com/atskyline/p/11029068.html