nginx源码学习(五)进程间的通信

来源:互联网 发布:中国金融战略2020知乎 编辑:程序博客网 时间:2024/04/29 11:17

nginx启动worker进程的函数主体还是比较简单的

static voidngx_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;    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;        ch.slot = ngx_process_slot;        ch.fd = ngx_processes[ngx_process_slot].channel[0];        ngx_pass_open_channel(cycle, &ch);    }}

for循环启动配置的worker进程个数,下面主要分析两个函数:
ngx_spawn_process和ngx_pass_open_channel;

ngx_pid_tngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,    char *name, ngx_int_t respawn){    u_long     on;    ngx_pid_t  pid;    ngx_int_t  s;    if (respawn >= 0) { //进程启动时respawn = -3,不走这里        s = respawn;    } else {        //找到一个空的槽记录子进程信息        for (s = 0; s < ngx_last_process; s++) {             if (ngx_processes[s].pid == -1) {                break;            }        }        //超过最大进程数会报错 unix:1024        if (s == NGX_MAX_PROCESSES) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,                          "no more than %d processes can be spawned",                          NGX_MAX_PROCESSES);            return NGX_INVALID_PID;        }    }    if (respawn != NGX_PROCESS_DETACHED) {        //这里主要是创建本地父子进程通信的pair,因为在Solaris 9不支持AF_LOCAL ,所以用了兼容的AF_UNIX,其实也是本地通信,ngx_processes[s]是一个结构体,存储进程信息,channel[2]属性主要存储socket对象,其中channel[0]为父进程的socket,channel[1]为子进程的socket。        /* Solaris 9 still has no AF_LOCAL           在该函数进行fork()之前,先调用了socketpair()创建一对socket描述符,存放在变量ngx_processes[s].chanel内,(其中s标志在ngx_processes数组内第一个可用元素的下标,比如最开始产生第一个工作进程时,可用元素的下标s0),而在fork() 之后,由于子进程继承了父进程的资源,那么父子进程都拥有一对socket描述符,而nginx将channel[0]给父进程使用, channel[1]给子进程使用,这样分别错开使用不同的描述符,即可实现父子进程之间的双向通信.     除此之外,对于各个子进程也能进行双向通信.父子进程的通信channel设定是自然而然的事情,而子进程之间的通信channel设定就涉及到进程之间文件描述符(socket描述符也属于文件描述符)的传递,因为虽然后生成的子进程通过继承的channel[0]能够往前生成的子进程发送信息,但前生成的子进程无法获知后生成子进程的channel[0]而不能发送信息,所以后生成的子进程必须利用已知的前生成子进程的channel[0]进行主动告知。    在子进程的初始话函数ngx_worker_process_init()里,会把ngx_channel(channel[1])加入到读事件监听集里,对应回调处理函数ngx_channel_handler(); */        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]);        //下面是设置非阻塞和异步模式        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;        }        on = 1;        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;        }        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;        }        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;    }    ngx_process_slot = s;    //创建子进程    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: //子进程进入proc循环工作        ngx_pid = ngx_getpid();        proc(cycle, data);        break;    default:        break;    }    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);    ngx_processes[s].pid = pid;    ngx_processes[s].exited = 0;    if (respawn >= 0) {        return pid;    }    //主进程记录信息后return 子进程pid    ngx_processes[s].proc = proc;    ngx_processes[s].data = data;    ngx_processes[s].name = name;    ngx_processes[s].exiting = 0;    switch (respawn) {    case NGX_PROCESS_NORESPAWN:        ngx_processes[s].respawn = 0;        ngx_processes[s].just_spawn = 0;        ngx_processes[s].detached = 0;        break;    case NGX_PROCESS_JUST_SPAWN:        ngx_processes[s].respawn = 0;        ngx_processes[s].just_spawn = 1;        ngx_processes[s].detached = 0;        break;    case NGX_PROCESS_RESPAWN:        ngx_processes[s].respawn = 1;        ngx_processes[s].just_spawn = 0;        ngx_processes[s].detached = 0;        break;    case NGX_PROCESS_JUST_RESPAWN:        ngx_processes[s].respawn = 1;        ngx_processes[s].just_spawn = 1;        ngx_processes[s].detached = 0;        break;    case NGX_PROCESS_DETACHED:        ngx_processes[s].respawn = 0;        ngx_processes[s].just_spawn = 0;        ngx_processes[s].detached = 1;        break;    }    if (s == ngx_last_process) {        ngx_last_process++;    }    return pid;}

接下来看看:ngx_pass_open_channel,主进程如何告知之前创建的worker进程与新创建的worker进程通信

ngx_int_tngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,ngx_log_t *log){    ssize_t             n;    ngx_err_t           err;    struct iovec        iov[1];    struct msghdr       msg;#if (NGX_HAVE_MSGHDR_MSG_CONTROL)    union {        struct cmsghdr  cm;        char            space[CMSG_SPACE(sizeof(int))];    } cmsg;    if (ch->fd == -1) {        msg.msg_control = NULL;        msg.msg_controllen = 0;    } else {        msg.msg_control = (caddr_t) &cmsg;        msg.msg_controllen = sizeof(cmsg);        ngx_memzero(&cmsg, sizeof(cmsg));        cmsg.cm.cmsg_len = CMSG_LEN(sizeof(int));        cmsg.cm.cmsg_level = SOL_SOCKET;        cmsg.cm.cmsg_type = SCM_RIGHTS;        /*         * We have to use ngx_memcpy() instead of simple         *   *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;         * because some gcc 4.4 with -O2/3/s optimization issues the warning:         *   dereferencing type-punned pointer will break strict-aliasing rules         *         * Fortunately, gcc with -O1 compiles this ngx_memcpy()         * in the same simple assignment as in the code above         */        ngx_memcpy(CMSG_DATA(&cmsg.cm), &ch->fd, sizeof(int));    }    msg.msg_flags = 0;#else    if (ch->fd == -1) {        msg.msg_accrights = NULL;        msg.msg_accrightslen = 0;    } else {        msg.msg_accrights = (caddr_t) &ch->fd;        msg.msg_accrightslen = sizeof(int);    }#endif    iov[0].iov_base = (char *) ch;    iov[0].iov_len = size;    msg.msg_name = NULL;    msg.msg_namelen = 0;    msg.msg_iov = iov;    msg.msg_iovlen = 1;    //关于匿名套接字传递fd可查看    //http://blog.csdn.net/shujuliu818/article/details/52922284    n = sendmsg(s, &msg, 0);    if (n == -1) {        err = ngx_errno;        if (err == NGX_EAGAIN) {            return NGX_AGAIN;        }        ngx_log_error(NGX_LOG_ALERT, log, err, "sendmsg() failed");        return NGX_ERROR;    }    return NGX_OK;}

最后,就目前nginx代码来看,子进程并没有往父进程发送任何消息,子进程之间也没有相互通信的逻辑,也许是因为nginx有其它一些更好的进程通信方式,比如共享内存等,所以这种channel通信目前仅做为父进程往子进程发送消息使用,但由于有这个基础在这,如果未来要使用channel做这样的事情,的确是可以的。