从nginx角度看服务器多进程模型设计(一)

多进程你可能很熟悉,也许有一套自己的使用习惯和方法。这东西没有什么权威建议,书上只是给出了基本知识点,至于具体怎么去用,因人而异。nginx在多进程设计方面有很多值得学习和借鉴的东西,我认为是一套比较好的实现方案。你也许认为这东西很简单,是老生常谈的东西了,但是我这里要提醒你一下,俗话道酒是陈的香,越经典的东西越值得去琢磨,不要对自己太自信。善于思考的家伙总是会在一些老的技术上给你许多新鲜的见解,这种牛人你不会没遇到过吧!扯淡罢了,回到正题。

看函数ngx_spawn_process的骨架:
for (s = 0; s < ngx_last_process; s++) {
   ...
}


...


/*
 * 实现异步通知的两个步骤,在nginx事件模型的rtsig机制中会用到 
 * 参考:http://blog.csdn.net/adc0809608/article/details/7368119
 */
if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
    ...
}
if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
    ...
}


...


ngx_channel = ngx_processes[s].channel[1];


...


// 当前产生的子进程在ngx_processes数组中的下标
ngx_process_slot = s;


/*
 * 在fork之后,父进程的ngx_processes数组,“传递”给了子进程,但是这时子进程拿到的数组是截至创建该进程之前其他进程的信息。
 * 由于子进程是父进程fork得到的,那么在之后父进程的操作结果在子进程中就不可见了。假设当前诞生的是进程1,用p1表示,当父进程
 * 创建p5时,那么p2-p5的进程信息在p1中是缺失的,那么p1需要这些信息吗?如果需要的话,该通过什么手段给它呢?
 * 见ngx_start_worker_processes相关分析
 */
ngx_pid = fork();
switch (pid) {
    ...
    case 0:
        ngx_pid = ngx_getpid();
        proc(cycle, data);
        break;

    default:
        break;
    }


...


ngx_processes[s].pid = pid;


...


if (s == ngx_last_process) {
     ngx_last_process++;
}


/*
 * 父进程,也就是master进程,依次创建work子进程,为了确保ngx_processes数组在子进程间同步,每次创建完一个子进程,
 * 就通过ngx_pass_open_channel,做一次广播,告诉先前已经创建的子进程: "新进程诞生,注意更新进程数组相关项"
 * 那么这些子进程又是如何更新这些信息的呢?答案在ngx_worker_process_init中。
*/
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
    ngx_int_t      i;
    ngx_channel_t  ch;

    ch.command = NGX_CMD_OPEN_CHANNEL;

    for (i = 0; i < n; i++) {

        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);
    }
}

在worker进程的进程信息数组中,把当前worker进程信息结构(即ngx_process_slot对应的位置)中的channel[0]关掉,
同时会把其他进程的channel[1]关掉,而只把他们的channel[0]留着,如下代码所示。那么这样做的意图是什么?
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority) {
    ...

    for (n = 0; n < ngx_last_process; n++) {

        if (ngx_processes[n].pid == -1) {
            continue;
        }

        if (n == ngx_process_slot) {
            continue;
        }

        if (ngx_processes[n].channel[1] == -1) {
            continue;
        }

        if (close(ngx_processes[n].channel[1]) == -1) {
             ...
        }
    }

    if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
         ...
    }

}

在ngx_worker_process_init的最后,调用了函数ngx_add_channel_event:
ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler)
主要的目的是向epoll中注册读事件。读数据的fd为变量ngx_channel,处理函数是ngx_channel_handler,那么ngx_channel是什么, 这个处理函数又做了些什么呢?
在ngx_spawn_process函数中,有句:ngx_channel = ngx_processes[s].channel[1],我们知道s是本次用来放置新创建进程信息的(在ngx_processes中)位置,即也是ngx_process_slot变量,因为这两个变量,ngx_channel和ngx_process_slot是在fork之前设置的,那么在随后子进程的init时,就会用到这两个刚刚在父进程中设置的值,ngx_add_channel_event中的ngx_channel就是当前子进程的channle[1],所以这里的意图也就明了了,该worker进程就是通过监听channel[1]的读事件来获取信息。好了,然后我们搜遍所有代码,并没有看到在某个channel上监听写事件的动作,那么我们要问了,子进程有没有需要写一些数据的时候呢?好吧,我们全文搜索"channel[0]",看看会发现什么。我想你肯定发现了ngx_write_channel函数,它的作用就是往channel[0]中去写数据,那么是都是谁在写呢?

我们找到了ngx_pass_open_channel,也就是我上文所说的“广播”。你要知道的一点就是,父进程中通过向channle[0]里写数据,可以在子进程中相应的channel[1]中读取。好了到这里我们前面提到的一些疑问基本上都有了答案,父进程通过向各个channel[0]中“广播”数据,子进程在其自己的channel[1]中读取相应的数据,他们正是通过这种方式来通信的。那么nginx进程间通信的机制仅仅就是这些吗?远不止。。。

我们前面提到父进程写channel[0],子进程读channel[1],那么父进程都传了些啥?我们最容易看到的载体是ngx_channel_t结构,在ngx_pass_open_channel中,参数ch就是这样的一个结构:
/*
 * command传递的命令
 * pid本次产生的新进程pid
 * slot新进程在ngx_processes数组中的位置
 * fd新进程中channel[0]
 */
typedef struct {
     ngx_uint_t  command;
     ngx_pid_t   pid;
     ngx_int_t   slot;
     ngx_fd_t    fd;
} ngx_channel_t;

对于command,在当前为NGX_CMD_OPEN_CHANNEL,也就是告诉其他的子进程,“某个新的进程刚刚诞生,注意同步相关信息”。那么子进程得到这个信息会怎么做呢?
还记得这个ngx_channel_handler函数吗,在注册读事件时设置的,它里面会调用ngx_read_channel来或者这个channel数据。如果发现是NGX_CMD_OPEN_CHANNEL命令,那么就在ngx_processes的相应位置上更新新诞生进程的信息:

ngx_processes[ch.slot].pid = ch.pid;
ngx_processes[ch.slot].channel[0] = ch.fd;

这个NGX_CMD_OPEN_CHANNEL算是最简单的命令了,其他的处理有什么特别的地方?

以上我们讨论的这些算是基础设施了,在这之上,nginx做了很多的好东西。目前看到的只是在nginx初始化时做的事情,那么在实际运行中,进程间又有哪些交互和通信呢?后面的文章讨论。一篇讨论文章太长的话,大家都蛋疼,你懂得。。。
 
 
 
 
 
 
 
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值