master进程与worker进程是通过socket进行通信的。socket描述符保存在结构体ngx_process_t的channel数组成员中。
//进程相关信息
typedef struct {
ngx_pid_t pid;//进程ID
int status;//由waitpid系统调用获取到的进程状态
//这是由socketpair系统调用产生出的用于进程间通信的socket句柄,这一对socket句柄可以
//互相通信,目前用于master父进程与worker子进程间的通信。
ngx_socket_t channel[2];
//子进程的循环执行方法,当父进程调用ngx_spawn_process生成子进程时使用
ngx_spawn_proc_pt proc;
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;
channel数组保存了两个socket描述符,这一对socket可以互相通信,实现了TCP的全双工。
socket的创建
socket是由socketpair系统调用创建的,下面是ngx_spawn_process函数中的代码片段.
由代码可以看到,创建一对socket后,还有一系列后续操作:
- 将两个socket都设为非阻塞socket,这里是通过ioctl函数设置的(也可以用fcntl函数设置)。设置成非阻塞IO的原因很简单,不能让互相通信的两个进程因为不能立即完成的套接字调用而被投入睡眠。
- 设置基于socket的异步IO。代码中只对其中一个socket设置了异步,并将该socket与相应进程绑定,目的是让这个进程能够接收到SIGIO信号。那么为什么另一个socket没有设置异步呢?
- 设置子进程在调用exec后将socket描述符关闭。
我们知道,父进程中调用fork之前打开的所有描述符在fork之后由子进程分享,而Nginx中本意是为每一对master-worker(一对多)进程间分配一对socket,那么master的原有socket不应该让新的子进程分享,应重新创建并分配。~~此处存疑~~~~
因此,程序中通过fcntl设置FD_CLOEXEC描述符标志禁止描述符跨exec继续保持打开。
if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"socketpair() failed while spawning \"%s\"", name);
return NGX_INVALID_PID;
}
ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"channel %d:%d",
ngx_processes[s].channel[0],
ngx_processes[s].channel[1]);
//使用ioctl函数,将socket设置为非阻塞socket
if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_nonblocking_n " failed while spawning \"%s\"",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}
if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_nonblocking_n " failed while spawning \"%s\"",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}
/*以下代码调用ioctl和fcntl函数设置为基于SOCKET的异步IO,分为两步(无先后):
* 1、通知套接字当I/O操作不会阻塞时发信号。
* 2、建立套接字所有权,这样信号可以被传递到合适的进程。
* */
on = 1;
//通知套接字当I/O操作不会阻塞时发信号
if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"ioctl(FIOASYNC) failed while spawning \"%s\"", name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}
//建立套接字所有权,即被那个进程所拥有,这样信号可以传递到该进程。
//ngx_pid指当前进程ID,设置该进程if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"fcntl(F_SETOWN) failed while spawning \"%s\"", name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}
//当进程调用exec运行的时候,关闭该socket描述符
if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}
if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
name);
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
}
ngx_channel = ngx_processes[s].channel[1];
} else {
ngx_processes[s].channel[0] = -1;
ngx_processes[s].channel[1] = -1;
}