在多进程模式下,启动worker工作进程的逻辑从ngx_start_worker_processes开始,它包含3个参数,第一个cycle为全局的一个核心结构体,第二个参数n表示需要创建的worker进程数,第三个参数type为工作进程类型,这里为NGX_PROCESS_RESPAWN,表示当worker进程异常中止时master进程将会重新启动一个新的worker。
#file:ngx_process_cycle.c
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;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");
ngx_memzero(&ch, sizeof(ngx_channel_t));
ch.command = NGX_CMD_OPEN_CHANNEL;
//n进程数
for (i = 0; i < n; i++) {
ngx_spawn_process(cycle, ngx_worker_process_cycle, (void *) (intptr_t) i, "worker process", type);
ch.pid = ngx_processes[ngx_process_slot].pid;//新创建的进程id
ch.slot = ngx_process_slot; //在ngx_spawn_process中被赋值
ch.fd = ngx_processes[ngx_process_slot].channel[0];
ngx_pass_open_channel(cycle, &ch);
}
}
在讨论master与worker进程间的通信时会要涉及到一个重要的数据结构ngx_channel_t,也叫频道,它是使用本地套接字实现。利用下面的socketpair方法,就可以创建父子进程间使用的套接字。
int socketpair(int d, int type, int protocol, int sv[2]);
当socketpair执行成功时,sv[2]这两个套接字有下列关系:向sv[0套接字写入数据,将可以从sv[1]套接字中读取数据;同样,向sv[1]套接字写入数据,也可以从sv[0]中读取到写入的数据。在父子进程间应用时,在父进程中调用socketpair创建这样一组套接字,接着调用fork创建出子进程。然后在父进程中关闭sv[1]套接字,在子进程中关闭sv[0]套接字,这样父进程可以用sv[0]和子进程的sv[1]自由地进行双向通信。for loop中会调用ngx_spawn_process,该函数主要就是调用socketpair创建流式套接字组,然后调用fork创建出子进程,并在子进程中运行进程函数ngx_worker_process_cycle。每创建一个新的worker,都需要向其它worker同步新建worker的信息,这个由ngx_pass_open_channel来完成,其第2个参数为指向ngx_channel_t的指针,ngx_channel_t结构如下:
typedef struct {
ngx_uint_t command;//传递的TCP消息中的命令
ngx_pid_t pid;//进程id
ngx_int_t; //ngx_processes进程数组中的序号
ngx_fd_t fd;//通信的套接字句柄
}ngx_channel_t;
这个简单的结构便用来同步master进程与worker进程间的状态。每创建一个新的进程,频道中pid成员将赋值为新进程的id,slot赋值为新进程在ngx_processes进程数组中的序号,fd为socketpair创建的进程组中的sv[0],这个进程数组的信息被保存在ngx_process_t结构体的channel成员中,ngx_process_t是描述进程的结构。
static void
ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
{
ngx_int_t i;
for (i = 0; i < ngx_last_process; i++) {
if (i == ngx_process_slot
|| ngx_processes[i].pid == -1
|| ngx_processes[i].channel[0] == -1)
{
continue;
}
ngx_log_debug6(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"pass channel s:%d pid:%P fd:%d to s:%i pid:%P fd:%d",
ch->slot, ch->pid, ch->fd,
i, ngx_processes[i].pid,
ngx_processes[i].channel[0]);
ngx_write_channel(ngx_processes[i].channel[0],
ch, sizeof(ngx_channel_t), cycle->log);
}
}
在ngx_pass_open_channel函数中,for循环每次获取一个非新建的有效worker进程的索引,并通过ngx_write_channel函数向该worker传递新建进程信息。其中ngx_write_channel的第一个参数为与该worker进行通信的流式套接字数组中的channel[0],worker进程在channel1]上会收到传递的信息。这里有个疑问是,worker进程如何得知channel[1]上有消息到达,这是因为在worker的进程函数中,已经将channel[1]上的读事件添加到了事件驱动中,并绑定读事件回调函数为ngx_channel_handler。
static void
ngx_channel_handler(ngx_event_t *ev)
{
ngx_int_t n;
ngx_channel_t ch;
ngx_connection_t *c;
if (ev->timedout) {
ev->timedout = 0;
return;
}
c = ev->data;
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel handler");
for ( ;; ) {
n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log); //从fd中读信息
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel: %i", n);
if (n == NGX_ERROR) {
if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
ngx_del_conn(c, 0);
}
ngx_close_connection(c);
return;
}
if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
if (ngx_add_event(ev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return;
}
}
if (n == NGX_AGAIN) {
return; //返回继续等待事件
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
"channel command: %d", ch.command);
switch (ch.command) {
case NGX_CMD_QUIT:
ngx_quit = 1;
break;
case NGX_CMD_TERMINATE:
ngx_terminate = 1;
break;
case NGX_CMD_REOPEN:
ngx_reopen = 1;
break;
case NGX_CMD_OPEN_CHANNEL:
ngx_log_debug3(NGX_LOG_DEBUG_CORE, ev->log, 0,
"get channel s:%i pid:%P fd:%d",
ch.slot, ch.pid, ch.fd);
ngx_processes[ch.slot].pid = ch.pid;
ngx_processes[ch.slot].channel[0] = ch.fd;
break;
case NGX_CMD_CLOSE_CHANNEL:
ngx_log_debug4(NGX_LOG_DEBUG_CORE, ev->log, 0,
"close channel s:%i pid:%P our:%P fd:%d",
ch.slot, ch.pid, ngx_processes[ch.slot].pid,
ngx_processes[ch.slot].channel[0]);
if (close(ngx_processes[ch.slot].channel[0]) == -1) {
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
"close() channel failed");
}
ngx_processes[ch.slot].channel[0] = -1;
break;
}
}
}