1 定义
ngx_signal_worker_processes 函数 定义在 ./nginx-1.24.0/src/os/unix/ngx_process_cycle.c
staticvoidngx_signal_worker_processes(ngx_cycle_t*cycle,intsigno){ngx_int_ti;ngx_err_terr;ngx_channel_tch;ngx_memzero(&ch,sizeof(ngx_channel_t));#if(NGX_BROKEN_SCM_RIGHTS)ch.command=0;#elseswitch(signo){casengx_signal_value(NGX_SHUTDOWN_SIGNAL):ch.command=NGX_CMD_QUIT;break;casengx_signal_value(NGX_TERMINATE_SIGNAL):ch.command=NGX_CMD_TERMINATE;break;casengx_signal_value(NGX_REOPEN_SIGNAL):ch.command=NGX_CMD_REOPEN;break;default:ch.command=0;}#endifch.fd=-1;for(i=0;i<ngx_last_process;i++){ngx_log_debug7(NGX_LOG_DEBUG_EVENT,cycle->log,0,"child: %i %P e:%d t:%d d:%d r:%d j:%d",i,ngx_processes[i].pid,ngx_processes[i].exiting,ngx_processes[i].exited,ngx_processes[i].detached,ngx_processes[i].respawn,ngx_processes[i].just_spawn);if(ngx_processes[i].detached||ngx_processes[i].pid==-1){continue;}if(ngx_processes[i].just_spawn){ngx_processes[i].just_spawn=0;continue;}if(ngx_processes[i].exiting&&signo==ngx_signal_value(NGX_SHUTDOWN_SIGNAL)){continue;}if(ch.command){if(ngx_write_channel(ngx_processes[i].channel[0],&ch,sizeof(ngx_channel_t),cycle->log)==NGX_OK){if(signo!=ngx_signal_value(NGX_REOPEN_SIGNAL)){ngx_processes[i].exiting=1;}continue;}}ngx_log_debug2(NGX_LOG_DEBUG_CORE,cycle->log,0,"kill (%P, %d)",ngx_processes[i].pid,signo);if(kill(ngx_processes[i].pid,signo)==-1){err=ngx_errno;ngx_log_error(NGX_LOG_ALERT,cycle->log,err,"kill(%P, %d) failed",ngx_processes[i].pid,signo);if(err==NGX_ESRCH){ngx_processes[i].exited=1;ngx_processes[i].exiting=0;ngx_reap=1;}continue;}if(signo!=ngx_signal_value(NGX_REOPEN_SIGNAL)){ngx_processes[i].exiting=1;}}}
ngx_signal_worker_processes 函数是 Nginx 主进程中用于向所有工作进程(及辅助进程)分发信号的函数。 它优先通过进程间通道发送优雅的控制命令(如退出、重新打开日志), 在通道不可用或失败时则使用 `kill` 系统调用直接发送信号, 并根据发送结果更新进程的退出状态。
2 详解
1 函数签名
staticvoidngx_signal_worker_processes(ngx_cycle_t*cycle,intsigno)
返回值 函数不返回任何值
参数1 ngx_cycle_t *cycle 当前运行周期上下文环境
参数2 int signo 表示要发送的信号编号
2 逻辑流程
1 命令准备 1 不支持传递文件描述符,命令置为 0,表示没有有效命令 2 支持传递文件描述符,根据输入信号设置对应的命令 2 遍历所有进程发送命令 1 跳过无需发送命令的进程 2 命令有效,通过通道发送命令 3 命令无效或通道发送命令失败,通过 kill 发送信号
1 命令准备
{ngx_int_ti;ngx_err_terr;ngx_channel_tch;
局部变量声明
ngx_memzero(&ch,sizeof(ngx_channel_t));
将 ch 结构体所有字段清零
若系统不支持通过通道传递文件描述符 (定义了 NGX_BROKEN_SCM_RIGHTS), 则编译以下代码块。
#if(NGX_BROKEN_SCM_RIGHTS)ch.command=0;
直接将通道命令置为 0,表示没有有效命令。 逻辑: 既然系统不支持通道传递描述符, 那么通道命令机制也不可用, 因此强制命令为 0,后续将跳过通道发送,只使用 kill 信号。
支持传递文件描述符
#elseswitch(signo){casengx_signal_value(NGX_SHUTDOWN_SIGNAL):ch.command=NGX_CMD_QUIT;break;casengx_signal_value(NGX_TERMINATE_SIGNAL):ch.command=NGX_CMD_TERMINATE;break;casengx_signal_value(NGX_REOPEN_SIGNAL):ch.command=NGX_CMD_REOPEN;break;default:ch.command=0;}#endif
根据输入信号选择对应的通道命令
优雅关闭信号(通常为 SIGQUIT): 通道命令设为 NGX_CMD_QUIT, 工作进程收到后会优雅关闭。
强制终止信号(通常为 SIGINT 或 SIGTERM): 通道命令设为 NGX_CMD_TERMINATE, 工作进程收到后快速退出。
重新打开日志文件信号(通常为 SIGUSR1): 通道命令设为 NGX_CMD_REOPEN, 工作进程收到后重新打开日志文件。
其他信号: 不设置命令(0 表示无效), 因为其他信号不需要通过通道传递特殊语义。
ch.fd=-1;
将通道消息中的文件描述符字段设为 -1(无效值)。 本次通知不传递任何文件描述符,显式标记为无效,接收方应忽略该字段。
2 遍历所有进程发送命令
for(i=0;i<ngx_last_process;i++){
遍历全局进程数组 ngx_processes。 ngx_last_process 是当前管理的进程总数。 对每一个记录在案的进程尝试发送信号。
ngx_log_debug7(NGX_LOG_DEBUG_EVENT,cycle->log,0,"child: %i %P e:%d t:%d d:%d r:%d j:%d",i,ngx_processes[i].pid,ngx_processes[i].exiting,ngx_processes[i].exited,ngx_processes[i].detached,ngx_processes[i].respawn,ngx_processes[i].just_spawn);
输出当前进程的详细状态到调试日志
if(ngx_processes[i].detached||ngx_processes[i].pid==-1){continue;}
过滤已分离或 PID 无效的进程。 分离的进程已脱离主进程管理; PID 为 -1 表示进程不存在。 这两种情况均无需也无法发送信号,直接跳过。 避免向无效目标发送信号
if(ngx_processes[i].just_spawn){ngx_processes[i].just_spawn=0;continue;}
跳过刚刚生成(fork)的进程,并清除该标志。 刚 fork 出的进程可能还未完成信号处理函数的设置, 立即发送信号可能造成竞态或未定义行为。 跳过一次,下次调用本函数时标志已清除,可以正常发送。
if(ngx_processes[i].exiting&&signo==ngx_signal_value(NGX_SHUTDOWN_SIGNAL)){continue;}
避免对已经在优雅关闭的进程重复发送 SIGQUIT。 如果进程已标记为 exiting(说明已收到退出指令),且本次信号仍是优雅关闭,则无需再次通知。 但若本次是强制终止等信号,则仍会穿透此检查继续处理(因为可能需要强制结束卡住的进程)。 意义:减少不必要的信号发送,同时允许强制信号覆盖正在优雅退出的进程。
if(ch.command){if(ngx_write_channel(ngx_processes[i].channel[0],&ch,sizeof(ngx_channel_t),cycle->log)==NGX_OK){if(signo!=ngx_signal_value(NGX_REOPEN_SIGNAL)){ngx_processes[i].exiting=1;}continue;}}
检查通道命令是否有效(非零)。 如果有效,优先尝试通过进程间通道发送命令, 这比直接 kill 更优雅。
调用 ngx_write_channel 将封装好的命令消息通过 socket 发送给目标进程。 参数: ngx_processes[i].channel[0] 是主进程端与该进程通信的 socket 文件描述符; ch 为消息内容; cycle->log 用于记录错误。 返回值:成功返回 NGX_OK。
若通道发送成功,且信号不是重新打开日志(NGX_REOPEN_SIGNAL), 则将进程状态标记为 exiting = 1(正在退出)。 然后 continue 跳过 kill 操作,处理下一个进程。 重新打开日志并不导致进程退出,因此不设 exiting 标志。 其他信号(关闭、终止)都会令进程最终退出,设置标志以跟踪其退出状态。 意义:精确维护进程生命周期状态,为后续回收子进程提供依据。
ngx_log_debug2(NGX_LOG_DEBUG_CORE,cycle->log,0,"kill (%P, %d)",ngx_processes[i].pid,signo);
记录即将对该 PID 使用 kill 发送信号的调试日志。 当通道命令不可用或发送失败时,会走到这里使用传统信号方式
if(kill(ngx_processes[i].pid,signo)==-1){err=ngx_errno;ngx_log_error(NGX_LOG_ALERT,cycle->log,err,"kill(%P, %d) failed",ngx_processes[i].pid,signo);if(err==NGX_ESRCH){ngx_processes[i].exited=1;ngx_processes[i].exiting=0;ngx_reap=1;}continue;}
调用 kill 系统调用向目标进程发送信号。 直接发送原始信号值 signo。 成功返回 0,失败返回 -1 并设置 errno。 意义:作为通道不可用或失败时的后备方案,保证信号最终能被传递。
若 kill 失败,保存错误码并记录 ALERT 级别日志,包含 PID 和信号编号。
如果错误原因是 ESRCH(进程不存在), 则更新进程状态为已退出 (exited = 1), 清除 exiting 标志,并设置全局 ngx_reap = 1,通知主事件循环进行回收。 逻辑:进程已意外终止,主进程需要尽快获知并清理子进程资源。 意义:自动处理子进程异常退出,避免僵尸进程,保证进程表状态准确。
kill 失败后跳过后续状态设置(因为进程未收到信号), 继续处理下一个进程。
if(signo!=ngx_signal_value(NGX_REOPEN_SIGNAL)){ngx_processes[i].exiting=1;}}}
如果 kill 发送成功,且信号不是重新打开日志, 则同样将进程标记为 exiting = 1。 与通道发送成功时的状态更新逻辑完全一致,保持行为统一。 意义:无论通过哪种方式发送退出信号,都能正确记录进程即将退出的状态。