根据Nginx(0.7.67版本)的代码,对Nginx基本的进程创建,进程主体以及事件处理进行了简要的分析。
基本上,父进程(即主进程)一开始会初始化及读取配置,并加载各模块的功能,然后fork()出N个子进程(即工作进程),具有相同的工作逻辑和功能。父进程负责监听信号(如HUP,QUIT等),通过socket pair把信号传递给子进程(子进程间一般不通信)。子进程通过事件来处理父进程传递的信号。因为每个子进程都共享服务监听端口(如http 80),当用户发送请求时,会触发子进程的事件调用函数。因此在accept()请求的时候,需要用到mutex,保证只有一个工作进程接受并处理请求。
Nginx主进程的入口是跟一般的程序一样的main()函数,它的代码是这样的:
int ngx_cdecl main(int argc, char *const *argv) { /*...*/ //nginx 启动的时候会尝试从环境变量中读取前次执行时候的监听套接口的id, //并会创建对应数量的ngx_listening_t结构变量(存于 cycle->listening数组中), //然后调用这个接口通过getsockname,getsockopt等系统调用把原来套接口的属性信息和 //设置参数读取出来去设置那些新创建的ngx_listening_t结构变量,这样就继承了前次执行 //时候的监听套接口了,这个接口是在 ngx_init_cycle之前调用的 if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) { return 1; } /*...*/ //ngx_modules数组在objs/ngx_modules.c定义 ngx_max_module = 0; for (i = 0; ngx_modules[i]; i++) { ngx_modules[i]->index = ngx_max_module++; //将每个模块编号 } //ngx_init_cycle()函数,这是个比较重要的函数,被main, ngx_master_process_cycle //和ngx_single_process_cycle 调用, 其中后两者是在reconfigure的时候被调用的。 //它主要工作是,初始化cycle是基于旧有的cycle进行的; 会继承old cycle的很多属性, //比如log等, 但是同时会对很多资源重新分配, 比如pool, shared mem, file handler, //bind/listen socket等,同时清除旧有的cycle的资源 //读取配置文件也是在这里完成的 cycle = ngx_init_cycle(&init_cycle); if (ngx_process == NGX_PROCESS_SINGLE) { ngx_single_process_cycle(cycle); } else { //一般采用多进程处理请求 ngx_master_process_cycle(cycle); } }
//ngx_start_worker_processes()函数,这个函数按指定数目n,以ngx_worker_process_cycle()函 //数为参数调用ngx_spawn_process()创建work进程并初始化相关资源和属性; //执行子进程的执行函数ngx_worker_process_cycle;向之前已经创建的所有worker广播当前创建的 //worker进程的信息;每个进程打开一个通道(ngx_pass_open_channel()) //n是worker process的数目 //type 即创建新进程式的方式,如NGX_PROCESS_RESPAWN, NGX_PROCESS_JUST_RESPAWN static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type) { ngx_int_t i; ngx_channel_t ch; ch.command = NGX_CMD_OPEN_CHANNEL; for (i = 0; i < n; i++) { //从config读取CPU的分配 cpu_affinity = ngx_get_cpu_affinity(i); //ngx_spawn_process中设置ngx_process_slot为被分配到子进程的 //空闲slot ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL, "worker process", type); ch.pid = ngx_processes[ngx_process_slot].pid; ch.slot = ngx_process_slot; ch.fd = ngx_processes[ngx_process_slot].channel[0]; //ngx_pass_open_channel 把这个worker的channel[0]和进程id //在进程表中的偏移slot广播(ngx_write_channel())给所有其他已经 //创建的worker。 //这样,创建完所有进程之后,每个worker就获得了所有其他worker的 //channel[0]了 ngx_pass_open_channel(cycle, &ch); } }
ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn) { /*...*/ //如果类型为NGX_PROCESS_DETACHED,则说明是热代码替换(热代码替换也是通过这 //个函数进行处理的),因此不需要新建socketpair。 if (respawn != NGX_PROCESS_DETACHED) { //建立socketpair //创建socketpair用于进程间通信,master进程为每个worker创建一对 //socket, master进程空间打开所有socketpair的channel[0], //channel[1]两端句柄。 //当创建一个worker的时候,这个worker会继承master当前已经创建并 //打开的所有socketpair。 //这个worker初始化的时候(调用ngx_worker_process_init)会关闭 //本进程对应socketpair的channel[0]和其他worker对应的 //channel[1],保持打开本进程对应socketpair的channel[1]和其他 //worker对应的channel[0],并监听本进程对应socketpair的 //channel[1]的可读事件。这样,每个worker就拥有了其他worker的 //channel[0],可以sendmsg(channel[0], ...)向其他worker发送消息 /*...*/ } /*...*/ //创建子进程 pid = fork(); switch (pid) { case -1: ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "fork() failed while spawning "%s"", name); ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; //在子进程里,执行传递进来的子进程的函数 case 0: ngx_pid = ngx_getpid(); //调用ngx_worker_process_cycle() //注意:这个函数中定义了子进程处理事件的循环,即子进程不会 //执行这个函数之后的语句 proc(cycle, data); break; default: break; } }
//worker进程的主体 static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) { //设置一些环境变量 //调用所有模块的init_process钩子函数 //将其他进程的channel[1]关闭,自己的除外 //子进程继承了父进程的ngx_processes数组,但子进程只监听自己的channel[1] //将自己的channel[0]关闭 //因为自己的channel[0]是给其他子进程,用来发送消息的sendmsg //调用ngx_add_channel_event()函数,给ngx_channel注册一个读事件处理函数。 ngx_worker_process_init(cycle, 1); //主循环,处理事件 for ( ;; ) { /*...*/ //调用epoll,周期性监听事件 //先接收连接(并不处理事件),以及处理进程间信号(如有) //处理accept queue和event queue里面的事件 ngx_process_events_and_timers(cycle); /*...*/ } /*...*/ }