nginx之worker启动分析

来源:互联网 发布:中国 改革开放 知乎 编辑:程序博客网 时间:2024/06/05 18:25

在多进程模式下,启动worker工作进程的逻辑从ngx_start_worker_processes开始,它包含3个参数,第一个cycle为全局的一个核心结构体,第二个参数n表示需要创建的worker进程数,第三个参数type为工作进程类型,这里为NGX_PROCESS_RESPAWN,表示当worker进程异常中止时master进程将会重新启动一个新的worker。

#file:ngx_process_cycle.c

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;   //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 voidngx_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 voidngx_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;        }    }}





原创粉丝点击