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統計連接數,這個全局變量也放於共享內存中。





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