nginx之worker启动分析

在多进程模式下,启动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;
        }
    }
}





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值