系統城裝機大師 - 唯一官網:www.farandoo.com!

當前位置:首頁 > server > anz > 詳細頁面

nginx worker進程循環的實現

時間:2020-02-06來源:系統城作者:電腦系統城

worker進程啟動后,其首先會初始化自身運行所需要的環境,然后會進入一個循環,在該循環中不斷檢查是否有需要執行的事件,然后處理事件。在這個過程中,worker進程也是需要與master進程交互的,更有甚者,worker進程作為一個子進程,也是可以接收命令行指令(比如kill等)以進行相應邏輯的處理的。那么worker進程是如何與master或者命令行指令進行交互的呢?本文首先會對worker進程與master進程交互方式,以及worker進程如何處理命令行指令的流程進行講解,然后會從源碼上對worker進程交互的整個工作流程進行介紹。

1. worker與master進程交互方式

這里首先需要說明的是,無論是master還是外部命令的方式,nginx都是通過標志位的方式來處理相應的指令的,也即在接收到一個指令(無論是master還是外部命令)的時候,worker會在其回調方法中設置與該指令相對應的標志位,然后在worker進程在其自身的循環中處理完事件之后會依次檢查這些標志位是否為真,是則根據該標志位的作用執行相應的邏輯。

對于worker進程與master進程的交互,其是通過socket管道的方式進行的。在ngx_process.h文件中聲明了一個ngx_process_t結構體,這里我們主要關注其channel屬性:


 
  1. typedef struct {
  2. // 其余屬性...
  3.  
  4. ngx_socket_t channel[2];
  5. } ngx_process_t;

        這里的ngx_process_t結構體的作用是存儲某個進程相關的信息的,比如pid、channel、status等。每個進程中都有一個ngx_processes數組,數組元素就是這里的ngx_process_t結構體,也就是說每個進程都會通過ngx_processes數組保存其余進程的基本信息。其聲明如下:

// 存儲了nginx中所有的子進程數組,每個子進程都有一個對應的ngx_process_t結構體進行標記
extern ngx_process_t ngx_processes[NGX_MAX_PROCESSES];
        這里我們就可以看出,每個進程都會一個與之對應的channel數組,這個數組的長度為2,其是與master進程進行交互的管道流。在master進程創建每一個子進程的之前,都會創建一個channel數組,該數組的創建方法為:

int socketpair(int domain, int type, int protocol, int sv[2]);
        這個方法的主要作用是創建一對匿名的已經連接的套接字,也就是說,如果在一個套接字中寫入數據,那么在另一個套接字中就可以接收到寫入的數據。通過這種方式,如果在父進程中往管道的一邊寫入數據,那么在子進程就可以在另一邊接收到數據,這樣就可以實現父子進程的數據通信了。

        在master進程啟動完子進程之后,子進程會保有master進程中相應的數據,也包括這里的channel數組。如此,master進程就可以通過channel數組實現與子進程的通信了。

2. worker處理外部命令

        對于外部命令,其本質上是通過signals數組中定義的各個信號以及回調方法進行處理的。在master進程初始化基本環境的時候,會將signals數組中指定的信號回調方法設置到對應的信號中。由于worker進程會繼承master進程的基本環境,因而worker進程在接收到這里設置的信號之后,也會調用對應的回調方法。而該回調方法的主要邏輯也僅僅只是設置相應的標志位的值。關于nginx接收到信號之后如何設置對應的標志位,可以參照本人前面的文章(nginx master工作循環 超鏈接),這里不再贅述。

3. 源碼講解

        master進程是通過ngx_start_worker_processes()方法啟動各個子進程的,如下是該方法源碼:


 
  1. /**
  2. * 啟動n個worker子進程,并設置好每個子進程與master父進程之間使用socketpair
  3. * 系統調用建立起來的socket句柄通信機制
  4. */
  5. static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type) {
  6. ngx_int_t i;
  7. ngx_channel_t ch;
  8.  
  9. ngx_memzero(&ch, sizeof(ngx_channel_t));
  10. ch.command = NGX_CMD_OPEN_CHANNEL;
  11.  
  12. for (i = 0; i < n; i++) {
  13.  
  14. // spawn是產卵的意思,這里就是生成一個子進程的意思,而該子進程所進行的事件循環就是
  15. // ngx_worker_process_cycle()方法,這里的ngx_worker_process_cycle是worker進程處理事件的循環,
  16. // worker進程在一個無限for循環中,不斷的檢查相應的事件模型中是否存在對應的事件,
  17. // 然后將accept事件和read、write事件分開放入兩個隊列中,最后在事件循環中不斷的處理事件
  18. ngx_spawn_process(cycle, ngx_worker_process_cycle,
  19. (void *) (intptr_t) i, "worker process", type);
  20.  
  21. // 下面的這段代碼的主要作用是將新建進程這個事件通知到其他的進程,上面的
  22. // ch.command = NGX_CMD_OPEN_CHANNEL;中NGX_CMD_OPEN_CHANNEL表示的就是當前是新建了一個進程,
  23. // 而ngx_process_slot存儲的就是該新建進程所存放的數組位置,這里需要進行廣播的原因在于,
  24. // 每個子進程被創建后,其內存數據都是復制的父進程的,但是ngx_processes數組是每個進程都有一份的,
  25. // 因而數組中先創建的子進程是沒有后創建的子進程的數據的,但是master進程是有所有子進程的數據的,
  26. // 因而這里master進程創建子進程之后,其就會向ngx_processes數組的每個進程的channel[0]上
  27. // 寫入當前廣播的事件,也即這里的ch,通過這種方式,每個子進程接收到這個事件之后,
  28. // 都會嘗試更新其所保存的ngx_processes數據信息
  29. ch.pid = ngx_processes[ngx_process_slot].pid;
  30. ch.slot = ngx_process_slot;
  31. ch.fd = ngx_processes[ngx_process_slot].channel[0];
  32.  
  33. // 廣播事件
  34. ngx_pass_open_channel(cycle, &ch);
  35. }
  36. }

        這里我們主要需要關注上面的啟動子進程的方法調用,也即這里的ngx_spawn_process()方法,該方法的第二個參數是一個方法,在啟動子進程之后,子進程就會進入該方法所指定的循環中。而在ngx_spawn_process()方法中,master進程會為當前新創建的子進程創建一個channel數組,以用于與當前子進程進行通信。如下是ngx_spawn_process()方法的源碼:


 
  1. ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,char *name, ngx_int_t respawn) {
  2. u_long on;
  3. ngx_pid_t pid;
  4. ngx_int_t s;
  5.  
  6. if (respawn >= 0) {
  7. s = respawn;
  8.  
  9. } else {
  10. // 在ngx_processes數組中存儲了當前創建的所有進程,而ngx_last_process則是當前當前記錄的最后一個
  11. // process在ngx_processes中的下一個位置的索引,只不過ngx_processes中記錄的進程有可能有部分
  12. // 已經失效了。當前循環就是從頭開始查找是否有某個進程已經失效了,如果已經失效了,則復用該進程位置,
  13. // 否則直接使用ngx_last_process所指向的位置
  14. for (s = 0; s < ngx_last_process; s++) {
  15. if (ngx_processes[s].pid == -1) {
  16. break;
  17. }
  18. }
  19.  
  20. // 這里說明所創建的進程數達到了最大限度
  21. if (s == NGX_MAX_PROCESSES) {
  22. ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
  23. "no more than %d processes can be spawned",
  24. NGX_MAX_PROCESSES);
  25. return NGX_INVALID_PID;
  26. }
  27. }
  28.  
  29. // NGX_PROCESS_DETACHED標志表示當前fork出來的進程與原來的父進程沒有任何關系,比如進行nginx升級時,
  30. // 新生成的master進程就與原先的master進程沒有關系
  31. if (respawn != NGX_PROCESS_DETACHED) {
  32.  
  33. /* Solaris 9 still has no AF_LOCAL */
  34.  
  35. // 這里的socketpair()方法的主要作用是生成一對套接字流,用于主進程和子進程的通信,這一對套接字會
  36. // 存儲在ngx_processes[s].channel中,本質上這個字段是一個長度為2的整型數組。在主進程和子進程
  37. // 進行通信的之前,主進程會關閉其中一個,而子進程會關閉另一個,然后相互之間往未關閉的另一個文件描述符中
  38. // 寫入或讀取數據即可實現通信。
  39. // AF_UNIX表示當前使用的是UNIX文件形式的socket地址族
  40. // SOCK_STREAM指定了當前套接字建立的通信方式是管道流,并且這個管道流是雙向的,
  41. // 即管道雙方都可以進行讀寫操作
  42. // 第三個參數protocol必須為0
  43. if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) {
  44. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  45. "socketpair() failed while spawning \"%s\"", name);
  46. return NGX_INVALID_PID;
  47. }
  48.  
  49. ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
  50. "channel %d:%d",
  51. ngx_processes[s].channel[0],
  52. ngx_processes[s].channel[1]);
  53.  
  54. // 將ngx_processes[s].channel[0]設置為非阻塞模式
  55. if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
  56. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  57. ngx_nonblocking_n
  58. " failed while spawning \"%s\"",
  59. name);
  60. ngx_close_channel(ngx_processes[s].channel, cycle->log);
  61. return NGX_INVALID_PID;
  62. }
  63.  
  64. // 將ngx_processes[s].channel[1]設置為非阻塞模式
  65. if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
  66. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  67. ngx_nonblocking_n
  68. " failed while spawning \"%s\"",
  69. name);
  70. ngx_close_channel(ngx_processes[s].channel, cycle->log);
  71. return NGX_INVALID_PID;
  72. }
  73.  
  74. on = 1;
  75. // 將ngx_processes[s].channel[0]套接字管道設置為異步模式
  76. if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
  77. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  78. "ioctl(FIOASYNC) failed while spawning \"%s\"", name);
  79. ngx_close_channel(ngx_processes[s].channel, cycle->log);
  80. return NGX_INVALID_PID;
  81. }
  82.  
  83. // 當前還處于主進程中,這里的ngx_pid指向了主進程的進程id,當前方法的作用主要是將
  84. // ngx_processes[s].channel[0]的操作權限設置給主進程,也就是說主進程通過向
  85. // ngx_processes[s].channel[0]寫入和讀取數據來與子進程進行通信
  86. if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
  87. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  88. "fcntl(F_SETOWN) failed while spawning \"%s\"", name);
  89. ngx_close_channel(ngx_processes[s].channel, cycle->log);
  90. return NGX_INVALID_PID;
  91. }
  92.  
  93. // FD_CLOEXEC表示當前指定的套接字管道在子進程中可以使用,但是在execl()執行的程序中不可使用
  94. if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
  95. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  96. "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
  97. name);
  98. ngx_close_channel(ngx_processes[s].channel, cycle->log);
  99. return NGX_INVALID_PID;
  100. }
  101.  
  102. // FD_CLOEXEC表示當前指定的套接字管道在子進程中可以使用,但是在execl()執行的程序中不可使用
  103. if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
  104. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  105. "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
  106. name);
  107. ngx_close_channel(ngx_processes[s].channel, cycle->log);
  108. return NGX_INVALID_PID;
  109. }
  110.  
  111. // ngx_processes[s].channel[1]是用于給子進程監聽相關事件使用的,當父進程向
  112. // ngx_processes[s].channel[0]發布事件之后,ngx_processes[s].channel[1]中就會接收到
  113. // 對應的事件,從而進行相應的處理
  114. ngx_channel = ngx_processes[s].channel[1];
  115.  
  116. } else {
  117. // 如果是NGX_PROCESS_DETACHED模式,則表示當前是另外新起的一個master進程,因而將其管道值都置為-1
  118. ngx_processes[s].channel[0] = -1;
  119. ngx_processes[s].channel[1] = -1;
  120. }
  121.  
  122. ngx_process_slot = s;
  123.  
  124.  
  125. // fork()方法將產生一個新的進程,這個進程與父進程的關系是子進程的內存數據將完全復制父進程的。
  126. // 還需要注意的是,fork()出來的子進程執行的代碼是從fork()之后開始執行的,而對于父進程而言,
  127. // 該方法的返回值為父進程id,而對于子進程而言,該方法返回值為0,因而通過if-else語句就可以讓父進程
  128. // 和子進程分別調用后續不同的代碼片段
  129. pid = fork();
  130.  
  131. switch (pid) {
  132.  
  133. case -1:
  134. // fork出錯
  135. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  136. "fork() failed while spawning \"%s\"", name);
  137. ngx_close_channel(ngx_processes[s].channel, cycle->log);
  138. return NGX_INVALID_PID;
  139.  
  140. case 0:
  141. // 子進程執行的分支,這里的proc()方法是外部傳進來的,也就是說,當前方法只是創建一個新的進程,
  142. // 具體的進程處理邏輯,將交由外部代碼塊進行定義ngx_getpid()方法獲取的就是當前新創建的子進程的進程id
  143. ngx_pid = ngx_getpid();
  144. proc(cycle, data);
  145. break;
  146.  
  147. default:
  148. // 父進程會走到這里
  149. break;
  150. }
  151.  
  152. ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);
  153.  
  154. // 父進程會走到這里,當前的pid是fork()之后父進程得到的新創建的子進程的pid
  155. ngx_processes[s].pid = pid;
  156. ngx_processes[s].exited = 0;
  157.  
  158. if (respawn >= 0) {
  159. return pid;
  160. }
  161.  
  162. // 設置當前進程的各個屬性,并且存儲到ngx_processes數組中的對應位置
  163. ngx_processes[s].proc = proc;
  164. ngx_processes[s].data = data;
  165. ngx_processes[s].name = name;
  166. ngx_processes[s].exiting = 0;
  167.  
  168. switch (respawn) {
  169.  
  170. case NGX_PROCESS_NORESPAWN:
  171. ngx_processes[s].respawn = 0;
  172. ngx_processes[s].just_spawn = 0;
  173. ngx_processes[s].detached = 0;
  174. break;
  175.  
  176. case NGX_PROCESS_JUST_SPAWN:
  177. ngx_processes[s].respawn = 0;
  178. ngx_processes[s].just_spawn = 1;
  179. ngx_processes[s].detached = 0;
  180. break;
  181.  
  182. case NGX_PROCESS_RESPAWN:
  183. ngx_processes[s].respawn = 1;
  184. ngx_processes[s].just_spawn = 0;
  185. ngx_processes[s].detached = 0;
  186. break;
  187.  
  188. case NGX_PROCESS_JUST_RESPAWN:
  189. ngx_processes[s].respawn = 1;
  190. ngx_processes[s].just_spawn = 1;
  191. ngx_processes[s].detached = 0;
  192. break;
  193.  
  194. case NGX_PROCESS_DETACHED:
  195. ngx_processes[s].respawn = 0;
  196. ngx_processes[s].just_spawn = 0;
  197. ngx_processes[s].detached = 1;
  198. break;
  199. }
  200.  
  201. if (s == ngx_last_process) {
  202. ngx_last_process++;
  203. }
  204.  
  205. return pid;
  206. }
  207.  

        ngx_spawn_process()方法最后會fork()一個子進程以執行其第二個參數所指定的回調方法。但是在這之前,我們需要說明的是,其通過socketpair()方法調用會創建一對匿名的socket,然后將其存儲在當前進程的channel數組中,如此就完成了channel數組的創建。

        worker進程啟動之后會執行ngx_worker_process_cycle()方法,該方法首先會對worker進程進行初始化,其中就包括對繼承而來的channel數組的處理。由于master進程和worker進程都保有channel數組所指代的socket描述符,而本質上master進程和各個worker進程只需要保有該數組的某一邊的描述符即可。因而這里worker進程在初始化過程中,會關閉其所保存的另一邊的描述符。在nginx中,master進程統一的會保留channel數組的0號位的socket描述符,關閉1號位的socket描述符,而worker進程則會關閉0號位的socket描述符,保留1號位的描述符。這樣master進程需要與worker進程通信時,就只需要往channel[0]中寫入數據,而worker進程則會監聽channel[1],從而接收到master進程的數據寫入。這里我們首先看一下worker進程的初始化方法ngx_worker_process_init()的源碼:


 
  1. /**
  2. * 這里主要是對當前進程進行初始化,為其設置優先級和打開的文件限制等參數。
  3. * 最后會為當前進程添加一個監聽channel[1]的連接,以不斷讀取master進程的消息,從而進行相應的處理
  4. */
  5. static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker) {
  6. sigset_t set;
  7. ngx_int_t n;
  8. ngx_time_t *tp;
  9. ngx_uint_t i;
  10. ngx_cpuset_t *cpu_affinity;
  11. struct rlimit rlmt;
  12. ngx_core_conf_t *ccf;
  13. ngx_listening_t *ls;
  14.  
  15. // 設置時區相關的信息
  16. if (ngx_set_environment(cycle, NULL) == NULL) {
  17. /* fatal */
  18. exit(2);
  19. }
  20.  
  21. ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
  22.  
  23. // 設置當前進程的優先級
  24. if (worker >= 0 && ccf->priority != 0) {
  25. if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
  26. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  27. "setpriority(%d) failed", ccf->priority);
  28. }
  29. }
  30.  
  31. // 設置當前進程能夠打開的文件句柄數
  32. if (ccf->rlimit_nofile != NGX_CONF_UNSET) {
  33. rlmt.rlim_cur = (rlim_t) ccf->rlimit_nofile;
  34. rlmt.rlim_max = (rlim_t) ccf->rlimit_nofile;
  35.  
  36. if (setrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
  37. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  38. "setrlimit(RLIMIT_NOFILE, %i) failed",
  39. ccf->rlimit_nofile);
  40. }
  41. }
  42.  
  43. // Changes the limit on the largest size of a core file(RLIMIT_CORE) for worker processes.
  44. // 簡而言之就是設置核心文件能夠使用的最大大小
  45. if (ccf->rlimit_core != NGX_CONF_UNSET) {
  46. rlmt.rlim_cur = (rlim_t) ccf->rlimit_core;
  47. rlmt.rlim_max = (rlim_t) ccf->rlimit_core;
  48.  
  49. if (setrlimit(RLIMIT_CORE, &rlmt) == -1) {
  50. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  51. "setrlimit(RLIMIT_CORE, %O) failed",
  52. ccf->rlimit_core);
  53. }
  54. }
  55.  
  56. // geteuid()返回執行當前程序的用戶id,這里的0表示是否為root用戶
  57. if (geteuid() == 0) {
  58. // setgid()方法的作用是更改組的id
  59. if (setgid(ccf->group) == -1) {
  60. ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
  61. "setgid(%d) failed", ccf->group);
  62. /* fatal */
  63. exit(2);
  64. }
  65.  
  66. // initgroups()是更改附加組的id
  67. if (initgroups(ccf->username, ccf->group) == -1) {
  68. ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
  69. "initgroups(%s, %d) failed",
  70. ccf->username, ccf->group);
  71. }
  72.  
  73. // 更改用戶的id
  74. if (setuid(ccf->user) == -1) {
  75. ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
  76. "setuid(%d) failed", ccf->user);
  77. /* fatal */
  78. exit(2);
  79. }
  80. }
  81.  
  82. // 需要注意的是,對于cache manager和cache loader進程,這里的worker傳入的是-1,
  83. // 表示這兩個進程不需要設置親核性
  84. if (worker >= 0) {
  85. // 獲取當前worker的CPU親核性
  86. cpu_affinity = ngx_get_cpu_affinity(worker);
  87.  
  88. if (cpu_affinity) {
  89. // 設置worker的親核心
  90. ngx_setaffinity(cpu_affinity, cycle->log);
  91. }
  92. }
  93.  
  94. #if (NGX_HAVE_PR_SET_DUMPABLE)
  95. if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
  96. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  97. "prctl(PR_SET_DUMPABLE) failed");
  98. }
  99.  
  100. #endif
  101.  
  102. if (ccf->working_directory.len) {
  103. // chdir()的作用是將當前的工作目錄更改為其參數所傳入的路徑
  104. if (chdir((char *) ccf->working_directory.data) == -1) {
  105. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  106. "chdir(\"%s\") failed", ccf->working_directory.data);
  107. /* fatal */
  108. exit(2);
  109. }
  110. }
  111.  
  112. // 初始化空的set指令集合
  113. sigemptyset(&set);
  114.  
  115. // ◆ SIG_BLOCK:將 set 參數指向信號集中的信號加入到信號掩碼中。
  116. // ◆ SIG_UNBLOCK:將 set 參數指向的信號集中的信號從信號掩碼中刪除。
  117. // ◆ SIG_SETMASK:將 set 參數指向信號集設置為信號掩碼。
  118. // 這里就是直接初始化要阻塞的信號集,默認為空集
  119. if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
  120. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  121. "sigprocmask() failed");
  122. }
  123.  
  124. tp = ngx_timeofday();
  125. srandom(((unsigned) ngx_pid << 16) ^ tp->sec ^ tp->msec);
  126.  
  127. ls = cycle->listening.elts;
  128. for (i = 0; i < cycle->listening.nelts; i++) {
  129. ls[i].previous = NULL;
  130. }
  131.  
  132. // 這里調用各個模塊的init_process()方法進行進程模塊的初始化
  133. for (i = 0; cycle->modules[i]; i++) {
  134. if (cycle->modules[i]->init_process) {
  135. if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
  136. /* fatal */
  137. exit(2);
  138. }
  139. }
  140. }
  141.  
  142. // 這里主要是關閉當前進程中其他各個進程的channel[1]管道句柄
  143. for (n = 0; n < ngx_last_process; n++) {
  144.  
  145. if (ngx_processes[n].pid == -1) {
  146. continue;
  147. }
  148.  
  149. if (n == ngx_process_slot) {
  150. continue;
  151. }
  152.  
  153. if (ngx_processes[n].channel[1] == -1) {
  154. continue;
  155. }
  156.  
  157. if (close(ngx_processes[n].channel[1]) == -1) {
  158. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  159. "close() channel failed");
  160. }
  161. }
  162.  
  163. // 關閉當前進程的channel[0]管道句柄
  164. if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
  165. ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
  166. "close() channel failed");
  167. }
  168.  
  169. #if 0
  170. ngx_last_process = 0;
  171. #endif
  172.  
  173. // ngx_channel指向的是當前進程的channel[1]句柄,也即監聽master進程發送消息的句柄。
  174. // 當前方法中,首先會為當前的句柄創建一個connection對象,并且將其封裝為一個事件,然后將該事件添加到
  175. // 對應的事件模型隊列中以監聽當前句柄的事件,事件的處理邏輯則主要有這里的ngx_channel_handler()
  176. // 方法進行。這里的ngx_channel_handler的主要處理邏輯是,根據當前收到的消息設置當前進程的一些標志位,
  177. // 或者更新某些緩存數據,如此,在當前進行的事件循環中,通過不斷檢查這些標志位,從而實現在事件進程中
  178. // 處理真正的邏輯。因而這里的ngx_channel_handler的處理效率是非常高的
  179. if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
  180. ngx_channel_handler)
  181. == NGX_ERROR) {
  182. /* fatal */
  183. exit(2);
  184. }
  185. }
  186.  

        該方法主要是對worker進程進行初始化,這里我們主要需要關注最后會遍歷ngx_processes數組,這個數組中保存了當前nginx中各個進程的相關信息。在遍歷過程中,會關閉當前進程保有的其余進程的channel[1]句柄,而保留有channel[0]句柄,這樣當前進程如果需要與其他進程通信,也只需要往目標進程的channel[0]中寫入數據即可。在遍歷完成之后,當前進程就會關閉自身的channel[0]句柄,而保留channel[1]句柄。最后,會通過ngx_add_channel_event()方法為當前進程添加對channel[1]的監聽事件,這里在調用ngx_add_channel_event()方法時傳入的第二個參數是ngx_channel,該參數是在前面的ngx_spawn_process()方法中賦值的,指向的就是當前進程的channel[1]的socket句柄。

        關于ngx_add_channel_event()方法,其本質就是創建一個ngx_event_t結構體的事件,然后將其添加到當前所使用的事件模型(比如epoll)句柄中。這里不再贅述該方法的實現源碼,不過我們需要關注的是該事件觸發時的回調方法,即調用ngx_add_channel_event()方法時傳入的第三個參數ngx_channel_handler()方法。如下是該方法的源碼:


 
  1. static void ngx_channel_handler(ngx_event_t *ev) {
  2. ngx_int_t n;
  3. ngx_channel_t ch;
  4. ngx_connection_t *c;
  5.  
  6. if (ev->timedout) {
  7. ev->timedout = 0;
  8. return;
  9. }
  10.  
  11. c = ev->data;
  12.  
  13. for (;;) {
  14.  
  15. // 在無限for循環中不斷讀取master進程發過來的消息
  16. n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
  17.  
  18. // 如果讀取消息出錯,說明當前的句柄可能失效了,就需要關閉當前連接
  19. if (n == NGX_ERROR) {
  20. if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
  21. ngx_del_conn(c, 0);
  22. }
  23.  
  24. ngx_close_connection(c);
  25. return;
  26. }
  27.  
  28. if (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
  29. if (ngx_add_event(ev, NGX_READ_EVENT, 0) == NGX_ERROR) {
  30. return;
  31. }
  32. }
  33.  
  34. if (n == NGX_AGAIN) {
  35. return;
  36. }
  37.  
  38. // 對發送過來的消息進行處理
  39. switch (ch.command) {
  40. // 如果是quit消息,則設置quit標志位
  41. case NGX_CMD_QUIT:
  42. ngx_quit = 1;
  43. break;
  44.  
  45. // 如果terminate消息,則設置terminate標志位
  46. case NGX_CMD_TERMINATE:
  47. ngx_terminate = 1;
  48. break;
  49.  
  50. // 如果是reopen消息,則設置reopen標志位
  51. case NGX_CMD_REOPEN:
  52. ngx_reopen = 1;
  53. break;
  54.  
  55. // 如果是新建進程消息,則更新當前ngx_processes數組對應位置的數據
  56. case NGX_CMD_OPEN_CHANNEL:
  57. ngx_processes[ch.slot].pid = ch.pid;
  58. ngx_processes[ch.slot].channel[0] = ch.fd;
  59. break;
  60.  
  61. // 如果是關閉channel的消息,則關閉ngx_processes數組對應位置的句柄
  62. case NGX_CMD_CLOSE_CHANNEL:
  63. if (close(ngx_processes[ch.slot].channel[0]) == -1) {
  64. ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
  65. "close() channel failed");
  66. }
  67.  
  68. ngx_processes[ch.slot].channel[0] = -1;
  69. break;
  70. }
  71. }
  72. }
  73.  

        在ngx_channel_handler()方法中,主要是讀取所監聽的socket句柄中的數據,而數據是以一個ngx_channel_t結構體所承載的,這個ngx_channel_t是nginx所統一使用的master與worker進程進行通信的結構體,其會指定當前發生的事件類型,以及發生該事件的進程信息。如下是ngx_channel_t結構體的聲明:


 
  1. typedef struct {
  2. // 當前發生的事件類型
  3. ngx_uint_t command;
  4. // 發生事件的pid
  5. ngx_pid_t pid;
  6. // 發生事件的進程在ngx_processes數組中的下標
  7. ngx_int_t slot;
  8. // 發生事件的進程的channel[0]描述符的值
  9. ngx_fd_t fd;
  10. } ngx_channel_t;

       在從當前進程的channel[1]中讀取了ngx_channel_t結構體的數據之后,ngx_channel_handler()方法會根據發生的事件類型更新相應的標志位的狀態,并且會更新當前進程的ngx_processes數組中對應的發生事件的進程的狀態信息。

        在處理了master進程所發送的事件之后,worker進程就會繼續其循環,在該循環中會檢查其所關注的標志位的狀態,然后會根據這些狀態執行對應的邏輯。如下是worker進程工作的循環的源碼:


 
  1. /**
  2. * 進入worker進程工作的循環
  3. */
  4. static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) {
  5. ngx_int_t worker = (intptr_t) data;
  6.  
  7. ngx_process = NGX_PROCESS_WORKER;
  8. ngx_worker = worker;
  9.  
  10. // 初始化worker進程,前面對該方法的源碼進行了講解
  11. ngx_worker_process_init(cycle, worker);
  12.  
  13. ngx_setproctitle("worker process");
  14.  
  15. for (;;) {
  16.  
  17. if (ngx_exiting) {
  18. // 這里主要是檢查有沒有事件是非cancelable狀態的,也就是說是否所有的事件都已經取消了,如果取消了,
  19. // 就會返回NGX_OK。這里的邏輯可以理解為,如果被標記為了ngx_exiting,那么此時,如果還有未取消的
  20. // 事件存在,則會走到下面的ngx_process_events_and_timers()方法,如此就會處理未完成的事件,
  21. // 然后在循環中再次走到這個位置,最終if條件為true,從而執行退出worker進程的工作
  22. if (ngx_event_no_timers_left() == NGX_OK) {
  23. ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
  24. ngx_worker_process_exit(cycle);
  25. }
  26. }
  27.  
  28. ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
  29.  
  30. // 這里通過檢查相應的事件模型中是否存在對應的事件,然后將其放入隊列中進行處理,
  31. // 這里是worker進程處理事件的核心方法
  32. ngx_process_events_and_timers(cycle);
  33.  
  34. // 這里ngx_terminate是強制關閉nginx的選項,如果向nginx發送了強制關閉nginx命令,則當前進程會直接退出
  35. if (ngx_terminate) {
  36. ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
  37. ngx_worker_process_exit(cycle);
  38. }
  39.  
  40. // 這里ngx_quit是優雅退出的選項。這里主要是將ngx_exiting置為1,用于表征當前進程需要退出,
  41. // 然后會執行如下三個工作:
  42. // 1. 往事件隊列中添加一個事件,用于處理當前處于活躍狀態的連接,將其close標志位置為1,并且執行該連接
  43. // 當前的處理方法,以盡快完成連接事件;
  44. // 2. 關閉當前cycle中監聽的socket句柄;
  45. // 3. 將當前所有處于空閑狀態的連接的close狀態標記為1,然后調用其連接處理方法.
  46. if (ngx_quit) {
  47. ngx_quit = 0;
  48. ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "gracefully shutting down");
  49. ngx_setproctitle("worker process is shutting down");
  50.  
  51. if (!ngx_exiting) {
  52. ngx_exiting = 1;
  53. ngx_set_shutdown_timer(cycle);
  54. ngx_close_listening_sockets(cycle);
  55. ngx_close_idle_connections(cycle);
  56. }
  57. }
  58.  
  59. // ngx_reopen主要是重新打開nginx的所有文件,比如切換nginx的日志文件等等
  60. if (ngx_reopen) {
  61. ngx_reopen = 0;
  62. ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
  63. ngx_reopen_files(cycle, -1);
  64. }
  65. }
  66. }
  67.  

        可以看到,worker進程主要處理了nginx是否退出相關的標志位,還處理了nginx是否重新讀取了配置文件的標志位。

4. 小結

        本文首先對master-worker進程交互的基本原理進行了講解,然后深入到源碼中講解了nginx是如何實現master和worker進程的相互通信的。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持我們。

分享到:

相關信息

系統教程欄目

欄目熱門教程

人氣教程排行

站長推薦

熱門系統下載

jlzzjlzz亚洲乱熟在线播放