8.6 master进程是如何工作的

master进程不需要处理网络事件,它不负责业务的执行,只会通过管理worker等子进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。与8.5节类似的是,它会通过检查以下7个标志位来决定ngx_master_process_cycle方法的运行。


sig_atomic_t ngx_reap;

sig_atomic_t ngx_terminate;

sig_atomic_t ngx_quit;

sig_atomic_t ngx_reconfigure;

sig_atomic_t ngx_reopen;

sig_atomic_t ngx_change_binary;

sig_atomic_t ngx_noaccept;


ngx_signal_handler方法会根据接收到的信号设置ngx_reap、ngx_quit、ngx_terminate、ngx_reconfigure、ngx_reopen、ngx_change_binary、ngx_noaccept这些标志位,见表8-4。

8.6 master进程是如何工作的 - 图1

表8-4列出了master工作流程中的7个全局标志位变量。除此之外,还有一个标志位也会用到,它仅仅是在master工作流程中作为标志位使用的,与信号无关。


ngx_uint_t ngx_restart;


在解释master工作流程前,还需要对master进程管理子进程的数据结构有个初步了解。下面定义了ngx_processes全局数组,虽然子进程中也会有ngx_processes数组,但这个数组仅仅是给master进程使用的。下面看一下ngx_processes全局数组的定义,代码如下。


//定义1024个元素的ngx_processes数组,也就是最多只能有1024个子进程

define NGX_MAX_PROCESSES 1024

//当前操作的进程在ngx_processes数组中的下标

ngx_int_t ngx_process_slot;

//ngx_processes数组中有意义的ngx_process_t元素中最大的下标

ngx_int_t ngx_last_process;

//存储所有子进程的数组

ngx_process_t ngx_processes[NGX_MAX_PROCESSES];


master进程中所有子进程相关的状态信息都保存在ngx_processes数组中。再来看一下数组元素的类型ngx_process_t结构体的定义,代码如下。


typedef struct{

//进程ID

ngx_pid_t pid;

//由waitpid系统调用获取到的进程状态

int status;

/这是由socketpair系统调用产生出的用于进程间通信的socket句柄,这一对socket句柄可以互相通信,目前用于master父进程与worker子进程间的通信,详见14.4节/

ngx_socket_t channel[2];

//子进程的循环执行方法,当父进程调用ngx_spawn_process生成子进程时使用

ngx_spawn_proc_pt proc;

/上面的ngx_spawn_proc_pt方法中第2个参数需要传递1个指针,它是可选的。例如,worker子进程就不需要,而cache manage进程就需要ngx_cache_manager_ctx上下文成员。这时,data一般与ngx_spawn_proc_pt方法中第2个参数是等价的/

void*data;

//进程名称。操作系统中显示的进程名称与name相同

char*name;

//标志位,为1时表示在重新生成子进程

unsigned respawn:1;

//标志位,为1时表示正在生成子进程

unsigned just_spawn:1;

//标志位,为1时表示在进行父、子进程分离

unsigned detached:1;

//标志位,为1时表示进程正在退出

unsigned exiting:1;

//标志位,为1时表示进程已经退出

unsigned exited:1;

}ngx_process_t;


master进程怎样启动一个子进程呢?其实很简单,fork系统调用即可以完成。ngx_spawn_process方法封装了fork系统调用,并且会从ngx_processes数组中选择一个还未使用的ngx_process_t元素存储这个子进程的相关信息。如果所有1024个数组元素中已经没有空余的元素,也就是说,子进程个数超过了最大值1024,那么将会返回NGX_INVALID_PID。因此,ngx_processes数组中元素的初始化将在ngx_spawn_process方法中进行。

下面对启动子进程的方法做一个简单说明,它的定义如下。


ngx_pid_t ngx_spawn_process(ngx_cycle_tcycle,ngx_spawn_proc_pt proc,voiddata,char*name,ngx_int_t respawn)


这里的proc函数指针就是子进程中将要执行的工作循环。下面看一下ngx_spawn_proc_pt的定义,代码如下。


typedef void(ngx_spawn_proc_pt)(ngx_cycle_tcycle,void*data);


因此,worker进程的工作循环ngx_worker_process_cycle方法也是依照ngx_spawn_proc_pt来定义的,代码如下。


static void ngx_worker_process_cycle(ngx_cycle_tcycle,voiddata);


cache manage进程或者cache loader进程的工作循环ngx_cache_manager_process_cycle方法也是如此,代码如下。


static void ngx_cache_manager_process_cycle(ngx_cycle_tcycle,voiddata);


那么,ngx_processes数组中这些进程的状态是怎么改变的呢?依靠信号!当每个子进程意外退出时,master父进程会接收到Linux内核发来的CHLD信号,而处理信号的ngx_signal_handler方法这时将会做以下处理:将sig_reap标志位置为1,调用ngx_process_get_status方法修改ngx_processes数组中所有子进程的状态(通过waitpid系统调用得到意外结束的子进程ID,然后遍历ngx_processes数组找到该子进程ID对应的ngx_process_t结构体,将其exited标志位置为1)。那么,一个子进程意外结束后,如何启动新的子进程呢?这可以在图8-8所示的master进程的工作循环中找到答案。

下面简要介绍一下图8-8中列出的流程。实际上,根据以下8个标志位:ngx_reap、ngx_terminate、ngx_quit、ngx_reconfigure、ngx_restart、ngx_reopen、ngx_change_binary、ngx_noaccept,决定执行不同的分支流程,并循环执行(注意,每次一个循环执行完毕后进程会被挂起,直到有新的信号才会激活继续执行)。

8.6 master进程是如何工作的 - 图2

图 8-8 master进程的工作循环

1)如果ngx_reap标志位为0,则继续向下执行第2步;如果ngx_noaccept标志位为1,则表示需要监控所有的子进程,同时调用表8-2中的ngx_reap_children方法来管理子进程。这时,ngx_reap_children方法将会遍历ngx_processes数组,检查每个子进程的状态,对于非正常退出的子进程会重新拉起。最后,ngx_processes方法会返回一个live标志位,如果所有的子进程都已经正常退出,那么live将为0,除此之外,live会为1。

2)当live标志位为0(所有子进程已经退出)、ngx_terminate标志位为1或者ngx_quit标志位为1时,都将调用ngx_master_process_exit方法开始退出master进程,否则继续向下执行第6步。在ngx_master_process_exit方法中,首先会删除存储进程号的pid文件。

3)继续之前的ngx_master_process_exit方法,调用所有模块的exit_master方法。

4)调用ngx_close_listening_sockets方法关闭进程中打开的监听端口。

5)销毁内存池,退出master进程。

6)如果ngx_terminate标志位为1,则向所有子进程发送信号TERM,通知子进程强制退出进程,接下来直接跳到第1步并挂起进程,等待信号激活进程。如果ngx_terminate标志位为0,则继续执行第7步。

7)如果ngx_quit标志位为0,跳到第9步,否则表示需要优雅地退出服务,这时会向所有子进程发送QUIT信号,通知它们退出进程。

8)继续ngx_quit为1的分支流程。关闭所有的监听端口,接下来直接跳到第1步并挂起master进程,等待信号激活进程。

9)如果ngx_reconfigure标志位为0,则跳到第13步检查ngx_restart标志位。如果ngx_reconfigure为1,则表示需要重新读取配置文件。Nginx不会再让原先的worker等子进程再重新读取配置文件,它的策略是重新初始化ngx_cycle_t结构体,用它来读取新的配置文件,再拉起新的worker进程,销毁旧的worker进程。本步中将会调用ngx_init_cycle方法重新初始化ngx_cycle_t结构体。

10)接第9步,调用ngx_start_worker_processes方法再拉起一批worker进程,这些worker进程将使用新ngx_cycle_t结构体。

11)接第10步,调用ngx_start_cache_manager_processes方法,按照缓存模块的加载情况决定是否拉起cache manage或者cache loader进程。在这两个方法调用后,肯定是存在子进程了,这时会把live标志位置为1(第2步中曾用到此标志)。

12)接第11步,向原先的(并非刚刚拉起的)所有子进程发送QUIT信号,要求它们优雅地退出自己的进程。

13)检查ngx_restart标志位,如果为0,则继续第15步,检查ngx_reopen标志位。如果ngx_restart为1,则调用ngx_start_worker_processes方法拉起worker进程,同时将ngx_restart置为0。

14)接13步,调用ngx_start_cache_manager_processes方法根据缓存模块的情况选择是否启动cache manage进程或者cache loader进程,同时将live标志位置为1。

15)检查ngx_reopen标志位,如果为0,则继续第17步,检查ngx_change_binary标志位。如果ngx_reopen为1,则调用ngx_reopen_files方法重新打开所有文件,同时将ngx_reopen标志位置为0。

16)向所有子进程发送USR1信号,要求子进程都得重新打开所有文件。

17)检查ngx_change_binary标志位,如果ngx_change_binary为1,则表示需要平滑升级Nginx,这时将调用ngx_exec_new_binary方法用新的子进程启动新版本的Nginx程序,同时将ngx_change_binary标志位置为0。

18)检查ngx_noaccept标志位,如果ngx_noaccept为0,则继续第1步进行下一个循环;如果ngx_noaccept为1,则向所有的子进程发送QUIT信号,要求它们优雅地关闭服务,同时将ngx_noaccept置为0,并将ngx_noaccepting置为1,表示正在停止接受新的连接。

注意,在以上18个步骤组成的循环中,并不是不停地在循环执行以上步骤,而是会通过sigsuspend调用使master进程休眠,等待master进程收到信号后激活master进程继续由上面的第1步执行循环。