nginx 进程通信

     运行在多进程模型的nginx在正常工作时,自然就会有多个进程实例,比如下图是在配置“worker_processes  4;”情况下的显示,nginx设置的进程title能很好的帮助我们区分监控进程与工作进程,不过带上选项f的ps命令以树目录的形式打印各个进程信息也能帮助我们做这个区分。多进程联合工作必定要牵扯到进程之间的通信问题,下面就来看看nginx是如何做的。

  采用socketpair()函数创造一对未命名的unix域套接字来进行进程之间的双向通信.

 基本的数据结构:

      

typedef struct {
    /**
     * 进程id
     */
    ngx_pid_t           pid;

    /**
     * 进程退出的状态
     */
    int                 status;

    /**
     * 进程的channel,通过socketpair创建的
     */
    ngx_socket_t        channel[2];

    /**
     * 进程的初始化函数,在每次创建完worker进程时调用
     */
    ngx_spawn_proc_pt   proc;

    /**
     * 向进程初始化函数传递的参数
     */
    void               *data;
    char               *name;

    /**
     * 进程的状态
     */
    unsigned            respawn:1;
    unsigned            just_spawn:1;
    unsigned            detached:1;
    unsigned            exiting:1;
    unsigned            exited:1;
} ngx_process_t;
typedef struct {
     /**
      * 向worker进程发送的命令
      */
     ngx_uint_t  command;

     /**
      * 对应进程的id
      */
     ngx_pid_t   pid;

     /**
      * 对应的进程在ngx_processes数组中的下标
      */
     ngx_int_t   slot;

     /**
      * 文件描述符
      */
     ngx_fd_t    fd;
} ngx_channel_t;
static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
     .....for (i = 0; i < n; i++) {

        cpu_affinity = ngx_get_cpu_affinity(i);

        ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
                          "worker process", type);  //创建子进程后调用 ngx_worker_process_cycle

......
       ngx_pass_open_channel(cycle, &ch); 
}
}

ngx_spawn_process函数:

#define NGX_MAX_PROCESSES         1024
ngx_process_t    ngx_processes[NGX_MAX_PROCESSES];
ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn)
{
       if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
        {........
         }
         .............. 
         ngx_channel = ngx_processes[s].channel[1];
         .........
          pid = fork(); //创建进程
          ngx_close_channel(ngx_processes[s].channel, cycle->log);//子进程调用
          proc(cycle, data); //主进程掉用,ngx_worker_process_cycle
 }

    在该函数进行fork()之前,先调用了socketpair()创建一对socket描述符,存放在变量ngx_processes[s].chanel内,(其中s标志在ngx_processes数组内第一个可用元素的下标,比如最开始产生第一个工作进程时,可用元素的下标s为0),而在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();

    ngx_worker_process_init() //worker进程初始化

static void
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)
{
      ......
      if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler)
}     

    而在父进程fork()生成一个新子进程后,就会立即通过ngx_pass_open_channel()函数把这个子进程的相关信息告知给其前面已生成的子进程:

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_write_channel(ngx_processes[i].channel[0],
                          ch, sizeof(ngx_channel_t), cycle->log);
    }
}

     其中参数ch里包括刚创建新子进程的pid,进程信息全局数组的ngx_processes的下标,socket描述符channel[0]等信息,这里通过循环所有的存活的其他子进程,然后调用函数ngx_write_channel()通过继承的channel[0]描述符进行信息主动告知,而接收到这信息的子进程将执行回调函数ngx_channel_handler(),把接收到的新子进程A的相关信息存储在ngx_processes全局数组内。 

static void
ngx_channel_handler(ngx_event_t *ev)
{
      ......
      for ( ;; ) {

        n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);//读取channel
        ........
        switch (ch.command) {
              .........
              case NGX_CMD_OPEN_CHANNEL:
            ngx_processes[ch.slot].pid = ch.pid;
            ngx_processes[ch.slot].channel[0] = ch.fd;
            break;
         .....
} }

 

  

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

 

       

 

 

 

posted @ 2012-11-26 14:02  风去无痕  阅读(1659)  评论(0编辑  收藏  举报