Nginx源码学习——进程通信

    Nginx服务器在使用Master-Worker模型时,会涉及到三类通信:Linux系统与Nginx通信,Master进程与Worker进程通信,Worker进程间通信,也采用了三种不同的通信机制。


Linux信号

    Linux 系统与Nginx是通过信号进行通信的,例如在Linux命令行敲下 ./nginx -s stop ,实际系统会新开一个Master进程,该进程负责向原Master发送信号,发送完信号该进程就挂了,原Master进程接收到信号后执行相应的操作。

1.信号定义与注册

Nginx定义了一个ngx_signal_t结构体用于描述接收到信号的行为

typedef struct { //signals
    int     signo;   //需要处理的信号
    char   *signame; //信号对应的字符串名称
    char   *name;    //这个信号对应着的Nginx命令
    void  (*handler)(int signo); //收到signo信号后就会回调handler方法
} ngx_signal_t;

Nginx在启动时会调用ngx_init_signals函数,该函数通过sigaction系统调用初始化所有信号,并注册相应的信号处理函数。完成所有初始化工作后,Master进程调用ngx_master_process_cycle函数进入自身事件循环,负责监听信号,一旦收到信号后,执行对应信号处理函数。

ngx_int_t ngx_init_signals(ngx_log_t *log)
{
    ngx_signal_t      *sig;
    struct sigaction   sa;

    for (sig = signals; sig->signo != 0; sig++) {
        ngx_memzero(&sa, sizeof(struct sigaction));
        sa.sa_handler = sig->handler;
        sigemptyset(&sa.sa_mask);
        if (sigaction(sig->signo, &sa, NULL) == -1) {
#if (NGX_VALGRIND)
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          "sigaction(%s) failed, ignored", sig->signame);
#else
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                          "sigaction(%s) failed", sig->signame);
            return NGX_ERROR;
#endif
        }
    }
    return NGX_OK;
}

2.信号发送

执行 ./nginx -s stop 命令将会开启新的Master进程,该进程根据-s参数知道用户要给Nginx发送信号,通过ngx_get_options()函数解析出所要发送的信号,然后将执行ngx_signal_process()函数,该函数首先获取原Master进程的pid,然后给原Master进程发送具体的信号,完成通信。

case 's':
                if (*p) {
                    ngx_signal = (char *) p;

                } else if (argv[++i]) {
                    ngx_signal = argv[i];

                } else {
                    ngx_log_stderr(0, "option \"-s\" requires parameter");
                    return NGX_ERROR;
                }

                if (ngx_strcmp(ngx_signal, "stop") == 0
                    || ngx_strcmp(ngx_signal, "quit") == 0
                    || ngx_strcmp(ngx_signal, "reopen") == 0
      /* 
         reload实际上是执行reload的nginx进程向原master+worker中的master进程发送reload信号,源master收到后,启动新的worker进程,同时向源worker
         进程发送quit信号,等他们处理完已有的数据信息后,退出,这样就只有新的worker进程运行。见ngx_signal_handler
      */
                    || ngx_strcmp(ngx_signal, "reload") == 0)
                {
                    ngx_process = NGX_PROCESS_SIGNALLER;
                    goto next;
                }
/*
该函数作用:
读取ngx_core_module模块的配置结构ngx_core_conf_t;
根据配置结构找到其工作进程文件,如"/usr/local/nginx/logs/nginx.pid"(该文件保存nginx进程ID,即pid);
打开该文件,读取pid;
调用ngx_os_signal_process()发送信号;
*/
ngx_int_t ngx_signal_process(ngx_cycle_t *cycle, char *sig)
{
    ...
    //打开存放master进程的文件NGX_PID_PATH
    file.name = ccf->pid;
    file.log = cycle->log;

    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY,
                            NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS);

    if (file.fd == NGX_INVALID_FILE) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno,
                      ngx_open_file_n " \"%s\" failed", file.name.data);
        return 1;
    }

    n = ngx_read_file(&file, buf, NGX_INT64_LEN + 2, 0);

    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", file.name.data);
    }

    if (n == NGX_ERROR) {
        return 1;
    }

    while (n-- && (buf[n] == CR || buf[n] == LF)) { /* void */ }

    pid = ngx_atoi(buf, ++n); //master进程id

    if (pid == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
                      "invalid PID number \"%*s\" in \"%s\"",
                      n, buf, file.name.data);
        return 1;
    }

    return ngx_os_signal_process(cycle, sig, pid);
}


Channel

Nginx中利用socketpair()函数,创建一对相互连接的socket,实现父子进程的通信,即Master进程与Worker进程通信,在Nginx中,将这种通信定义为频道——channel

1.channel定义

typedef struct { 
     ngx_uint_t  command; //对端将要做得命令  取值为NGX_CMD_OPEN_CHANNEL等
     ngx_pid_t   pid;  //当前的子进程id   进程ID,一般是发送命令方的进程ID
     ngx_int_t   slot; //在全局进程表中的位置    表示发送命令方在ngx_processes进程数组间的序号
     ngx_fd_t    fd; //传递的fd   通信的套接字句柄
} ngx_channel_t;

2.channel注册

Master进程通过fork()函数创建子进程,在fork之前首先调用socketpair()创建一对关联的套接字,用于父子进程间的通信,子进程也将会获得该套接字,在子进程初始化的时候,将套接字加入epoll等待父进程的消息,并注册消息处理函数。父进程通过ngx_write_channel()发送消息,子进程通过ngx_read_channel()读取消息。

ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn) //respawn取值为NGX_PROCESS_RESPAWN等,或者为进程在ngx_processes[]中的序号
{
    ...
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) //在ngx_worker_process_init中添加到事件集
        {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "socketpair() failed while spawning \"%s\"", name);
            return NGX_INVALID_PID;
        }

        /* 设置master的channel[0](即写端口),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;
        }
    ...

    pid = fork();
}
static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{ 
    //调用epoll add 把ngx_chanel 加入epoll 中  
    if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
                              ngx_channel_handler) //在ngx_spawn_process中赋值
        == NGX_ERROR)
    {
        /* fatal */
        exit(2);
    }
}


共享内存

Nginx中各Worker进程是通过共享内存进行通信的,共享内存是Linux下提供的最基本的进程间通信方式,它通过mmap和shmget系统调用在内存中创建了一块连续的线性地址空间,而通过munmap或者shmdt系统调用可以释放这块内存。使用共享内存的好处是当多个进程使用同一块共享内存时,在任何一个进程修改了共享内存中的内容后,其他进程通过访问这段共享内存都能够得到修改后的内容。

Nginx定义了ngx_shm_t结构体,用于描述一块共享内存

typedef struct {
    u_char      *addr; //共享内存起始地址  
    size_t       size; //共享内存空间大小
    ngx_str_t    name; //这块共享内存的名称
    ngx_log_t   *log;  //shm.log = cycle->log; 记录日志的ngx_log_t对象
    ngx_uint_t   exists;   /* unsigned  exists:1;  */ //表示共享内存是否已经分配过的标志位,为1时表示已经存在
} ngx_shm_t;

操作ngx_shm_t结构体的方法有两个:ngx_shm_alloc(基于mmap实现)用于分配新的共享内存,而ngx_shm_free(基于munmap实现)用于释放已经存在的共享内存。共享内存由Master进程创建,Worker进程共享。Nginx 解决惊群问题,是通过设置互斥锁,只有拥有互斥锁的工作进程才能担负与客户建立连接的任务,这个互斥锁就放于共享内存中。另外,ngin统计连接数,这个全局变量也放于共享内存中。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章