Nginx共享内存
共享内存是 Linux 下提供的最基本的进程间通信方法,它通过 mmap
或者 shmget
系统调用在内存中创建了一块连续的线性地址空间,而通过 munmap
或者 shmdt
系统调用可以释放这块内存。使用共享内存的好处是当多个进程使用同一块共享内存时,在任何一个进程修改了共享内存中的内容后,其他进程通过访问这段共享内存都能够得到修改后的内容。
Nginx 各进程间共享数据的主要方式就是使用共享内存(在使用共享内存时,Nginx 一般在 master 进程创建,在 master 进程 fork 出子进程后,所有的进程开始使用这块内存中的数据)。Nginx 为了兼容性,提供了三种共享内存的实现方式。
mmap 文档
shmget 文档
Nginx 共享内存特点
- 实际上
mmap
和shmget
都支持多个无亲缘关系的进程间通信,mmap
通过映射到文件来实现,shmget
则是通过映射key
的方式,可以通过ipcs -m
来查看当前分配的共享内存情况。但是对于 Nginx 来说没有必要,因为worker
和master
是具有亲缘关系的进程,因此使用匿名共享内存的方式更为合适 - 使用匿名共享内存的好处是,如果程序中没有主动释放这部分内存或因为异常情况导致没有释放内存,当进程结束后内存将由操作系统回收
shmget
与mmap
有个不同之处就是通过key
映射的内存如果不释放则不会由操作系统回收,需要通过ipcs
命令手动删除
Nginx 共享内存结构
addr
指向共享内存的起始地址- 共享内存的大小为
size
字节 - 共享内存的名称用
name
表示 - 记录日志的
log
成员 exists
是表示共享内存是否已经分配过的标志,为 1 时表示已经存在
typedef struct {
u_char *addr;
size_t size;
ngx_str_t name;
ngx_log_t *log;
ngx_uint_t exists; /* unsigned exists:1; */
} ngx_shm_t;
Nginx 共享内存的创建和删除
mmap
- 分配使用
mmap
系统调用,设置MAP_ANON
创建匿名内存,这个标志表示不使用文件映射方式,这时fd
和offset
参数就没有意义,mmap
分配的匿名内存只能在具有亲缘关系的进程之间共享,且如果内存不释放,会由操作系统回收 - 释放内存使用
munmap
系统调用 - 如果不支持
MAP_ANON
标志,则通过通过类似磁盘映射的方式,如果是一般的文件需要在文件打开的时候操作共享内存,如果关闭了文件描述符再操作则会引发Bus error
,但是如果映射到/dev/zero
则与匿名内存行为相同,它是一个特殊的设备文件,不会存储任何实际的数据,而是在需要时生成零值。当将其映射到内存中时,实际上并不是从文件中读取数据,而是在内存中分配了一块新的区域,并将其初始化为零值。 - 由于
/dev/zero
不是一个普通文件,而是是一个用于生成零值的特殊设备文件,所以即使关闭了与之关联的文件描述符,内存映射区域仍然存在,并且可以继续访问和使用#if (NGX_HAVE_MAP_ANON) ngx_int_t ngx_shm_alloc(ngx_shm_t *shm) { shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); if (shm->addr == MAP_FAILED) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size); return NGX_ERROR; } return NGX_OK; } void ngx_shm_free(ngx_shm_t *shm) { if (munmap((void *) shm->addr, shm->size) == -1) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "munmap(%p, %uz) failed", shm->addr, shm->size); } } #elif (NGX_HAVE_MAP_DEVZERO) ngx_int_t ngx_shm_alloc(ngx_shm_t *shm) { ngx_fd_t fd; fd = open("/dev/zero", O_RDWR); if (fd == -1) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "open(\"/dev/zero\") failed"); return NGX_ERROR; } shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (shm->addr == MAP_FAILED) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "mmap(/dev/zero, MAP_SHARED, %uz) failed", shm->size); } if (close(fd) == -1) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "close(\"/dev/zero\") failed"); } return (shm->addr == MAP_FAILED) ? NGX_ERROR : NGX_OK; } void ngx_shm_free(ngx_shm_t *shm) { if (munmap((void *) shm->addr, shm->size) == -1) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "munmap(%p, %uz) failed", shm->addr, shm->size); } }
shmget
- Nginx 中使用
shmget
的 IPC_PRIVATE 选项来创建匿名内存,使用这种方式创建,可以提前调用shctl
来删除得到的id
,同mmap
关闭文件描述符类似。在调用shmctl
函数时使用 IPC_RMID 命令会从内核中删除共享内存段的数据结构,但并不会影响当前已经连接到该共享内存段的进程。换句话说,它不会导致对共享内存段的映射立即无效。这是因为共享内存的机制允许多个进程将同一块内存映射到它们的地址空间中。因此,尽管调用了shmctl
函数删除了共享内存段的标识符,但在当前进程中,仍然可以继续访问和使用共享内存段,直到它被分离或进程终止 - 释放共享内存使用
shmdt
系统调用,注意调用shmdt
只是分离了共享内存,只有当所有使用它的进程都detach
了这块内存才会真正释放,类似引用计数#elif (NGX_HAVE_SYSVSHM) #include <sys/ipc.h> #include <sys/shm.h> ngx_int_t ngx_shm_alloc(ngx_shm_t *shm) { int id; id = shmget(IPC_PRIVATE, shm->size, (SHM_R|SHM_W|IPC_CREAT)); if (id == -1) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmget(%uz) failed", shm->size); return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_CORE, shm->log, 0, "shmget id: %d", id); shm->addr = shmat(id, NULL, 0); if (shm->addr == (void *) -1) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmat() failed"); } if (shmctl(id, IPC_RMID, NULL) == -1) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmctl(IPC_RMID) failed"); } return (shm->addr == (void *) -1) ? NGX_ERROR : NGX_OK; } void ngx_shm_free(ngx_shm_t *shm) { if (shmdt(shm->addr) == -1) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmdt(%p) failed", shm->addr); } } #endif
Nginx 共享内存使用
- 在 Nginx 中共享内存主要用于
accept_mutex
和统计连接相关的信息,如总连接数 - 在 master 进程初始化时,会为这些变量分配响应的内存,供 worker 进程工作时使用
- 以下代码段来自
ngx_event_module_init
事件模块初始化函数,当使用ngx_http_stub_status_module
模块时,还支持统计更多状态的连接数/* cl should be equal to or greater than cache line size */ cl = 128; size = cl /* ngx_accept_mutex */ + cl /* ngx_connection_counter */ + cl; /* ngx_temp_number */ #if (NGX_STAT_STUB) size += cl /* ngx_stat_accepted */ + cl /* ngx_stat_handled */ + cl /* ngx_stat_requests */ + cl /* ngx_stat_active */ + cl /* ngx_stat_reading */ + cl /* ngx_stat_writing */ + cl; /* ngx_stat_waiting */ #endif 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_accept_mutex_ptr = (ngx_atomic_t *) shared; ngx_accept_mutex.spin = (ngx_uint_t) -1; if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared, cycle->lock_file.data) != NGX_OK) { return NGX_ERROR; } ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl); (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "counter: %p, %uA", ngx_connection_counter, *ngx_connection_counter); ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl); tp = ngx_timeofday(); ngx_random_number = (tp->msec << 16) + ngx_pid; #if (NGX_STAT_STUB) 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); #endif
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.