一. 參考《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。
Nginx---父子進程通信
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.