nginx 平滑重载时共享内存区域通过“旧进程读、新进程写”的机制实现同步更新,无需手动干预。具体做法是:nginx 主进程(master)在收到信号后 fork 出新的工作进程(worker),新旧 worker 进程挂载到同一块由 mmap 创建的共享内存段。旧进程继续处理完现有连接并退出,新进程直接复用该内存空间,因此配置变更或状态数据在重载瞬间对新老进程均可见。

共享内存的创建与挂载原理是什么
共享内存的同步依赖于 Linux 内核的 mmap 映射机制和 Nginx 的配置解析流程。当 Nginx 启动时,master 进程会调用 mmap 分配一块物理内存,并将其映射到所有 worker 进程的地址空间中。这块内存通常用于存储计数器、缓存数据或会话信息。在重载过程中,master 进程重新解析配置文件,如果配置中定义了新的共享内存区域,它会尝试申请新的内存;如果名称相同且大小一致,它可能复用原有的映射关系或通知子进程重新映射。关键在于,只要内存地址不变,新旧进程看到的都是同一块物理内存,从而实现数据的无缝衔接。

#include <sys>
#include <sys>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#define SHM_NAME "/nginx_shm_sync"
#define SHM_SIZE 4096
int main() {
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
perror("shm_open");
return 1;
}
if (ftruncate(fd, SHM_SIZE) == -1) {
perror("ftruncate");
return 1;
}
void *ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
// 模拟写入数据
*(int*)ptr = 100;
printf("Main process wrote: %d\n", *(int*)ptr);
// 这里可以 fork 子进程模拟 Nginx worker
pid_t pid = fork();
if (pid == 0) {
// 子进程读取数据
printf("Child process read: %d\n", *(int*)ptr);
munmap(ptr, SHM_SIZE);
close(fd);
exit(0);
}
wait(NULL);
munmap(ptr, SHM_SIZE);
close(fd);
shm_unlink(SHM_NAME);
return 0;
}
</stdio.h></unistd.h></fcntl.h></sys></sys>
上述代码演示了 POSIX 共享内存的基本用法。在实际 Nginx 源码中,类似逻辑被封装在 ngx_shm_alloc 函数中。当 master 进程初始化时,它会遍历配置中的所有 shared_memory 块,并为每个块分配内存。这些内存块通过唯一的名称(如 zone_name)进行标识。在重载时,新的 worker 进程启动后会再次调用相同的分配逻辑,由于名称相同,它们会映射到相同的物理内存页上,从而保证数据的一致性。
重载期间如何避免读写冲突
为了避免在重载期间出现读写冲突,Nginx 采用了严格的进程生命周期管理和锁机制。在平滑重载(Reload)过程中,master 进程不会立即杀死旧的 worker 进程,而是先启动新的 worker 进程。新的 worker 进程接管新的连接请求,而旧的 worker 进程继续处理已经建立的连接,直到所有连接关闭后才优雅退出。这种“双活”状态确保了在任何时刻,只有一组进程在写入共享内存中的特定部分,或者通过原子操作来更新计数器。此外,对于复杂的共享数据结构,Nginx 内部实现了细粒度的自旋锁(Spinlock),确保同一时间只有一个进程能修改关键数据。

ngx_atomic_cmp_set











