Nginx---父子進程通信

一. 參考《Nginx核心講解》後加上參考源碼,小結下Nginx中父子進程、子進程間如何通信。

實現原理網上都可以查出來,主要是通過socketpair()函數實現的,下面捋一下內部流程:
1. 話說要從ngx_start_worker_processes函數講起,由於代碼不多,貼出來:

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

    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;
        ch.slot = ngx_process_slot;
        ch.fd = ngx_processes[ngx_process_slot].channel[0];
		
		/*父進程中執行該函數,主要像前面各個子進程的channel[0]發送消息*/
        ngx_pass_open_channel(cycle, &ch);
    }
}

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_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
					  "socketpair() failed while spawning \"%s\"", name);
		return NGX_INVALID_PID;
	}
	
	//...
	/*各種套接字屬性設置*/
	ngx_channel = ngx_processes[s].channel[1];//ngx_channel幹啥用的?留個懸念
	//...
	
	/*重點來了,創建子進程*/
	pid = fork();
    switch (pid) {
    case -1:
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "fork() failed while spawning \"%s\"", name);
        ngx_close_channel(ngx_processes[s].channel, cycle->log);
        return NGX_INVALID_PID;

    case 0:
        ngx_pid = ngx_getpid();
		/*調用ngx_worker_process_cycle函數,子進程死循環處理流程,下面再講*/
        proc(cycle, data);
        break;

    default:
        break;
    }

	//...
	/*對子進程的相關信息進行保存*/
	ngx_processes[s].pid
	//...
	/*數組下標加1*/
	if (s == ngx_last_process) {
        ngx_last_process++;
    }
    return pid;
}

ngx_spawn_process()函數是在一個for循環中調用,假如有4個子進程,也就是說會進行四次
socketpair()的創建執行,每一次的創建,ngx_processes[s].channel都會有兩個fd生成,
假設進行第一次循環時,ngx_processes[0].channel裏面已經有ngx_processes[0].channel[0]和
ngx_processes[0].channel[1],繼續往下執行,執行子進程創建執行proc(cycle, data);時,調用
static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
	//...
	/*子進程初始化相關工作*/
	ngx_worker_process_init(cycle, worker);
	//...
	for( ;; )
	{
		/*子進程死循環,雖然裏面內容很重要,但與本節無關*/
	}
}
static void
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{
	//...
	//...
	/*for循環,進行了下異常處理*/
    for (n = 0; n < ngx_last_process; n++) {
	}
	
	/*在子進程中關閉掉channel[0],只需要用channel[1]*/
	if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
	ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
				  "close() channel failed");
	}
	/*這個比較重要,但還沒理解透*/
    if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
                              ngx_channel_handler)
        == NGX_ERROR)
    {
        /* fatal */
        exit(2);
    }
}
ngx_int_t
ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,
    ngx_event_handler_pt handler)
{
	//...
	//...
    ev = (event == NGX_READ_EVENT) ? rev : wev;
	/*將ngx_channel_handler函數掛在ev->handler上*/
    ev->handler = handler;

	/*如果是epoll,則執行ngx_epoll_add_connection函數,在裏面可以看到
	    if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
        return NGX_ERROR;
    }
	將描述符第二個參數fd,而fd就是ngx_channel加入到了事件中
	ngx_channel在哪裏?
	ngx_channel來自於ngx_spawn_process()中
	ngx_channel = ngx_processes[s].channel[1];
*/
    if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
        if (ngx_add_conn(c) == NGX_ERROR) {
            ngx_free_connection(c);
            return NGX_ERROR;
        }

    } else {
		/*添加事件,一旦有可讀事件到來時,執行ev->handler*/
        if (ngx_add_event(ev, event, 0) == NGX_ERROR) {
            ngx_free_connection(c);
            return NGX_ERROR;
        }
    }

    return NGX_OK;
}

/*當有可讀事件來的時候,觸發該函數*/
static void
ngx_channel_handler(ngx_event_t *ev)
{
	//...
	ngx_connection_t  *c;
	c = ev->data; //下面的c->fd來自哪裏?
	/*ev->data,*/
	//...
	for ( ;; ) {
		
		/*裏面就是調用recvmsg(s, &msg, 0);讀取消息*/
        n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
		switch()
		{
			//...
			case NGX_CMD_OPEN_CHANNEL:
			//...
			/*保存各個槽位的子進程信息*/
			//...
		}
	}
	//...
}
從上面可以小結一下,感覺一下,子進程擁有套接字ngx_processes[s].channel[1],並加入
了可讀事件中,一直等待着讀,即等待着調用recvmsg(),那麼由誰來sendmsg呢?通過哪個
套接字呢?
繼續:
視線回到ngx_spawn_process()函數中,該函數帶領我們一步步走進子進程的處理過程,回到
該函數調用的地方即函數ngx_start_worker_processes()中,它下面接着執行ch.pid、ch.slot
、ch.fd的賦值,用處在下面:接着調用
/*注意調用該函數是在父進程中執行*/
static void
ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
{
	/*for循環,細細體味下,假如多個子進程時,通過該循環向不同的channel[0]發送msg*/
    for (i = 0; i < ngx_last_process; i++) {
	/*一些異常處理*/
	//...
	/*該函數實際上就是調用sendmsg(s, &msg, 0);進行消息的發送*/
	/*注意參數:第一個參數ngx_processes[i].channel[0]就是要發送的fd,其實也在ch裏面包含着*/
	ngx_write_channel(ngx_processes[i].channel[0],
					  ch, sizeof(ngx_channel_t), cycle->log);
	
	}
}
綜上: 創建一個子進程時,父進程就會向各個channel[0]中sendmsg,子進程從channel[1]recvmsg。



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