ngx_process_cycle.c源文件主要是定义了nginx中各进程的主循环函数。主要是包括:
-
ngx_master_process_cycle()主循环
-
ngx_single_process_cycle()主循环
-
ngx_worker_process_cycle()主循环
-
ngx_cache_manager_process_cycle()主循环
(注:cache manager和cache loader都用ngx_cache_manager_process_cycle()函数作为其主循环函数)
1. 相关函数声明
如下主要是一些静态函数的声明,我们通过注释的方式简要介绍各函数的功能:
2. 相关变量定义
前面ngx_process
到ngx_noaccept
间的这些变量我们都在头文件中讲述过,这里不再赘述。这里只讲述一下剩余的几个变量:
-
ngx_noaccepting: 本变量是与ngx_noaccept
变量搭配一起使用的。因为在master优雅的停止子进程的时候,需要一定的时间,这时会用到此变量表示正在停止接收
这一状态.
-
ngx_restart: 本变量是与ngx_noaccept
变量搭配一起使用的。因为在热升级的时候,新创建的进程的父进程可能已经不是本master进程了,则此时要重启原来的子进程。
-
master_process: master进程的名称
-
ngx_cache_manager_ctx: 缓存管理器上下文
-
ngx_cache_loader_ctx: 缓存加载器上下文
-
ngx_exit_cycle: 此静态变量主要是为了保存相应信息,使得在进程退出时,信号处理器仍可以用该静态变量
-
ngx_exit_log: 同上
-
ngx_exit_log_file: 同上
2. 函数ngx_master_process_cycle()
这里会分成master进程初始化
以及master进程主循环
两个部分来进行讲解。
2.1 master进程初始化
1) master进程信号屏蔽
关于sigprocmask()
函数请先参看: ngx_process_cycle源码分析-附录
这里主要是屏蔽一下10个信号:
-
SIGCHLD
-
SIGALRM
-
SIGIO
-
SIGINT
-
NGX_RECONFIGURE_SIGNAL
-
NGX_REOPEN_SIGNAL
-
NGX_NOACCEPT_SIGNAL
-
NGX_TERMINATE_SIGNAL
-
NGX_SHUTDOWN_SIGNAL
-
NGX_CHANGEBIN_SIGNAL
可以看到,与ngx_process.c中signals数组中的信号一致,只是少了SIGSYS
与SIGPIPE
。这是因为这两个信号的处理函数都是SIG_IGN
, sigsuspend()并不会受到所忽略的信号的影响。
2) 设置master进程的标题
这里首先分配一定的空间用于存放title,然后将nginx process
字符串与nginx启动参数拼接,最后调用ngx_setproctitle()函数设置进程title。最后大概设置成如下样式:
nginx: master process /usr/local/nginx/nginx
3) 启动worker进程
关于ngx_start_worker_processes()函数我们会在下面进行讲解。这里只注意到一个参数NGX_PROCESS_RESPAWN
,表示此进程退出后需要重启。
4) 启动cache manager进程
关于ngx_start_cache_manager_processes()函数我们会在下面进行讲解。这里注意到传入的值为0。
2.2 master主循环
在讲解主循环之前,请先参看附录ngx_process_cycle源码分析-附录中关于setitimer()的讲解。
下面我们分几个小的部分来进行讲解:
1) nginx进程terminate结束
这里首先涉及到一个延时退出的问题:首先在第一次收到SIGINT信号时,将delay设置为50,sigio设置为worker进程数+cache进程数,然后通过channel的方式告知子进程退出。如果长时间没有退出,则定时器时间就会超时,产生SIGALRM信号,这时会再发送相应的信号告诉子进程退出。如果在1秒钟的时间内子进程都没有退出,则发送SIGKILL信号暴力退出。
这里有两个变量delay
与sigio
控制nginx的terminate退出。前一个主要控制超时,后一个主要控制信号。如果在terminate过程中收到指定数量的信号,则继续发送NGX_TERMINATE_SIGNAL信号指示进程退出,同时重置定时器;否则等待delay控制的定时器超时,然后再继续发送退出信号指示进程退出。
在sigsuspend()上面设置超时定时器的部分关键代码段是不会被打断的,因此可以确保定时器设置成功,进入超时倒计时阶段。并且用sigio变量作为定时器超时过程中的一个额外的辅助变量,相互协作共同控制nginx进程的退出。
2) 回收退出的子进程
这里我们看到sigsuspend()接收到信号,调用对应的信号处理函数返回后首先会调用ngx_time_update()函数更新相应的时间。然后检测收到的信号,如果该信号是进程退出信号,则调用ngx_reap_children()回收相应的资源;如果该信号是terminate或quit信号,则调用ngx_master_process_exit()函数执行退出主循环。
3) 执行ngx_quit退出操作
nginx_quit是优雅退出,因此这里首先向对应的子进程(worker进程、缓存管理器进程)发送shutdown信号,然后依次关闭对应的监听socket。
这里注意到,当执行terminate和quit退出时,末尾执行continue语句,并不会再对如ngx_reconfigure、ngx_restart等做出反应。
4) 执行ngx_reconfigure
ngx_reconfigure会重新加载配置文件。如果是平滑升级时,执行重新加载配置文件的操作,则ngx_new_binary条件为真,此时会新创建worker进程及cache manager进程,关于这一点请参看ngx_process_cycle源码分析-附录中相关章节。
如果是普通的重新加载配置文件操作,则首先读取配置文件,然后创建新的worker进程、cache manager进程,最后向原来旧的子进程发送shutdown信号优雅的退出。
5) 执行ngx_restart
ngx_restart是配合ngx_noaccept
使用的,ngx_noaccept会优雅的停止worker、cache manager进程,而如果此时又正好碰到平滑升级失败,则通过ngx_restart重新创建worker进程及cache manager进程。
6) 执行ngx_reopen
ngx_reopen()重新回滚日志。首先调用ngx_reopen_files()重新打开文件,然后向对应的子进程发送reopen信号。
7) 执行ngx_change_binary
这里执行平滑升级。首先创建出一个子进程,然后再执行exec函数族用新的nginx二进制文件替换当前子进程。
8) 执行ngx_noaccept
发送shutdown信号通知子进程优雅的退出。但是master进程不会退出,这一点是与ngx_quit相区别的,也不会向ngx_quit那样关闭监听socket。
2. 函数ngx_single_process_cycle()
nginx只有在配置文件中master_process设置为off时,才会以单进程运行。这里首先设置相应的环境变量,并初始化对应的模块,然后进入主循环。
单进程nginx与master-worker方式运行的nginx,在主循环的处理上还是有很大的不同。在master-worker方式运行的主循环中,master只需要用sigsuspend()接收相应的信号,然后处理。而单进程nginx,它是通过在主循环中调用:
ngx_process_events_and_timers(cycle);
不断的接收和处理相关的事件来维持运行的。当ngx_terminate、ngx_quit、ngx_reopen等信号发生时,信号就会中断相应的事件处理模型(select、poll、epoll等)的系统调用,从而执行相应的信号处理函数,并在主循环中执行相应信号引起的其他操作:
1) 执行ngx_terminate/ngx_quit
这里退出相应的模块,然后调用ngx_master_process_exit()退出主循环。
2) 执行ngx_reconfigure
此种情况下,没有相应的平滑升级等方面需要考虑,直接调用ngx_init_cycle()重新初始化配置即可。
3) 执行ngx_reopen
这里直接重新打开文件实现日志回滚。
3. 函数ngx_start_worker_processes()
这里循环调用ngx_spawn_process()产生n个worker子进程。并向ngx_processes表中的其他子进程(worker子进程以及cache manager子进程)传送文件描述符,用于进程之间的通信。这里有两点需要注意的地方:
1) 参数i的作用
这里传递参数i,主要是为了标识当前所产生的进程是属于第几个worker子进程,然后将该子进程与CPU进行绑定,即设置该进程的CPU亲和性。
2) 子进程间的通信
子进程之间的通信都是通过往channel[0]中写数据,然后从channel[1]中读取数据。
4. 函数ngx_start_cache_manager_processes()
这里首先检查是否有相应的管理器与加载器,如果有的话则创建对应的子进程。关于cache manager我们后边会进行更详细的探讨。
5. 函数ngx_pass_open_channel()
这里主要是通过master进程,向其各个子进程通过ngx_write_channel()传递文件描述符。
6. 函数ngx_signal_worker_processes()
本函数是向worker子进程、cache manager子进程发送信号。首先在发送前准备好相应的命令:
#if (NGX_BROKEN_SCM_RIGHTS)
#else
#endif
这里并未定义NGX_BROKEN_SCM_RIGHTS
,因此执行的是else分支。接下来循环向各个子进程发送相关命令:首先用ngx_write_channel()向子进程发送信息,如果发送失败则用kill()直接向对应的子进程发送信号。
7. 函数ngx_reap_children()
本函数回收所有已经退出子进程。如果ngx_processes进程表中仍有未退出的子进程,则返回1;否则返回0。接着执行循环关闭与该子进程相关的channel:
8. 函数ngx_master_process_exit()
在master退出时,执行步骤如下:
-
删除nginx.pid文件
-
调用各个模块的exit_master()函数
-
关闭监听socket
-
销毁pool
注意:在销毁pool之前会先把ngx_cycle->log相关的数据保存到一个静态的数据结构ngx_exit_cycle中,这是因为在ngx_cycle->pool
销毁之后,有可能仍然会调用到日志打印相关的操作。
[参看]:
-
nginx中cache的设计和实现(一)
-
Nginx缓存机制详解之一缓存管理进程
-
cache loader process进程分析
-
绑定Nginx到gdb
-
Nginx核心进程模型
-
event 模块(二) ——事件驱动核心
-
Nginx平滑升级源码分析
-
nginx文件结构