nginxワーカープロセスループの実装

nginxワーカープロセスループの実装

ワーカープロセスは、起動されると、まず自身の動作に必要な環境を初期化し、次に実行する必要があるイベントがあるかどうかを継続的にチェックし、イベントを処理するループに入ります。このプロセスでは、ワーカープロセスもマスタープロセスと対話する必要があります。さらに、子プロセスとして、ワーカープロセスはコマンドライン命令(kill など)を受信して​​、対応する論理処理を実行することもできます。では、ワーカー プロセスはマスターまたはコマンド ラインの指示とどのように対話するのでしょうか?この記事では、まずワーカー プロセスがマスター プロセスとどのように対話するか、ワーカー プロセスがコマンド ライン命令をどのように処理するかを説明し、次にソース コードからワーカー プロセスの対話のワークフロー全体を紹介します。

1. ワーカープロセスとマスタープロセスの相互作用

ここで最初に説明する必要があるのは、マスターまたは外部コマンド方式のいずれであっても、nginx は対応する命令をフラグを通じて処理するということです。つまり、命令を受信すると (マスターまたは外部コマンドのいずれであっても)、ワーカーはコールバック メソッドで命令に対応するフラグを設定し、ワーカー プロセスは独自のループでイベントを処理した後、これらのフラグが true かどうかを順番に確認します。そうであれば、フラグの機能に従って対応するロジックを実行します。

ワーカー プロセスとマスター プロセス間のやり取りは、ソケット パイプを通じて実行されます。 ngx_process_t 構造体は ngx_process.h ファイルで宣言されています。ここでは主にそのチャネル属性に焦点を当てます。

typedef構造体{
  // 残りのプロパティ...
  
  ngx_socket_t チャネル[2];
} ngx_process_t;

ここでの ngx_process_t 構造体の機能は、pid、チャネル、ステータスなど、プロセスに関連する情報を保存することです。各プロセスには ngx_processes 配列があり、配列要素はここでは ngx_process_t 構造体です。つまり、各プロセスは ngx_processes 配列を通じて他のプロセスの基本情報を保存します。その声明は次の通りです。

// nginx のすべての子プロセスの配列を格納します。各子プロセスには、対応する ngx_process_t 構造体があり、それをマークします。
外部 ngx_process_t ngx_processes[NGX_MAX_PROCESSES];
ここで、各プロセスには対応するチャネル配列があり、この配列の長さは 2 であることがわかります。これは、マスター プロセスと対話するためのパイプライン フローです。マスター プロセスが各子プロセスを作成する前に、チャネル配列が作成されます。配列は次のように作成されます。

int socketpair(int ドメイン, int タイプ, int プロトコル, int sv[2]);
このメソッドの主な機能は、匿名の接続ソケットのペアを作成することです。つまり、1 つのソケットにデータが書き込まれると、書き込まれたデータは他のソケットで受信できます。このように、親プロセスでパイプの片側にデータを書き込むと、子プロセスは反対側でそのデータを受信できるため、親プロセスと子プロセス間のデータ通信が実現します。

マスター プロセスが子プロセスを開始すると、子プロセスは、ここでのチャネル配列を含む、マスター プロセス内の対応するデータを保持します。このようにして、マスター プロセスはチャネル配列を介して子プロセスと通信できます。

2. ワーカーは外部コマンドを処理する

外部コマンドの場合、基本的にはシグナル配列で定義されたさまざまなシグナルとコールバック メソッドを通じて処理されます。マスター プロセスが基本環境を初期化すると、シグナル配列で指定されたシグナル コールバック メソッドが対応するシグナルに設定されます。ワーカープロセスはマスタープロセスの基本環境を継承するため、ワーカープロセスはここで設定されたシグナルを受信した後、対応するコールバックメソッドも呼び出します。このコールバック メソッドの主なロジックは、対応するフラグ ビットの値を設定することだけです。 nginx がシグナルを受信した後に、対応するフラグを設定する方法については、以前の記事 (nginx マスター作業サイクルのハイパーリンク) を参照してください。ここでは繰り返しません。

3. ソースコードの説明

マスター プロセスは、ngx_start_worker_processes() メソッドを通じて各子プロセスを開始します。以下はこのメソッドのソース コードです。

/**
 * n 個のワーカー子プロセスを開始し、各子プロセスとマスター親プロセスの間にソケットペアを設定します。
 * システムコールによって確立されたソケットハンドル通信メカニズム*/
静的 void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t 型) {
 ngx_int_t i;
 ngx_channel_t ch;
 
 ngx_memzero(&ch, sizeof(ngx_channel_t));
 ch.command = NGX_CMD_OPEN_CHANNEL;

 (i = 0; i < n; i++) の場合 {

  // spawn は卵を産むこと、つまり子プロセスを生成することを意味します。子プロセスのイベント ループは // ngx_worker_process_cycle() メソッドです。ここで、ngx_worker_process_cycle はワーカー プロセスがイベントを処理するサイクルです。
  // ワーカー プロセスは無限の for ループ内にあり、対応するイベント モデルに対応するイベントがあるかどうかを常にチェックします。
  // 次に、受け入れイベントと読み取りおよび書き込みイベントを2つのキューに分割し、最後にイベントループでイベントを継続的に処理します ngx_spawn_process(cycle, ngx_worker_process_cycle, 
           (void *) (intptr_t) i、「ワーカープロセス」、タイプ);

  // 次のコードの主な機能は、新しいプロセスのイベントを他のプロセスに通知することです。上記の // ​​ch.command = NGX_CMD_OPEN_CHANNEL; では、NGX_CMD_OPEN_CHANNEL は現在新しいプロセスが作成されていることを意味します。
  // ngx_process_slotは新しいプロセスが格納される配列の場所を格納します。ここでブロードキャストが必要な理由は、
  // 各子プロセスが作成されると、そのメモリ データは親プロセスからコピーされますが、各プロセスには ngx_processes 配列のコピーが保持されます。
  // したがって、配列内で最初に作成されたサブプロセスには、後で作成されたサブプロセスのデータは含まれませんが、マスタープロセスにはすべてのサブプロセスのデータが含まれます。
  // したがって、マスタープロセスが子プロセスを作成した後、現在のブロードキャストイベントをngx_processes配列の各プロセスのchannel[0]、つまりここではchに書き込みます。このようにして、各子プロセスがこのイベントを受信すると、
  // 保存された ngx_processes データ情報を更新しようとします ch.pid = ngx_processes[ngx_process_slot].pid;
  ch.slot = ngx_process_slot;
  ch.fd = ngx_processes[ngx_process_slot].channel[0];

  // イベントをブロードキャストします ngx_pass_open_channel(cycle, &ch);
 }
}

ここでは、主に上記の子プロセスを開始するメソッド呼び出し、つまり ngx_spawn_process() メソッドに注目する必要があります。このメソッドの 2 番目のパラメーターはメソッドです。子プロセスを開始した後、子プロセスはこのメソッドで指定されたループに入ります。 ngx_spawn_process() メソッドでは、マスター プロセスは、新しく作成された子プロセスが現在の子プロセスと通信するためのチャネル配列を作成します。以下は ngx_spawn_process() メソッドのソース コードです。

ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle、ngx_spawn_proc_pt proc、void *data、char *name、ngx_int_t respawn) {
 u_long オン;
 ngx_pid_t pid;
 ngx_int_t s;

 (リスポーン> = 0)の場合{
  s = リスポーン;

 } それ以外 {
  // 現在作成されているすべてのプロセスは ngx_processes 配列に格納され、ngx_last_process は現在 ngx_processes に記録されている最後のプロセスの次の位置のインデックスですが、ngx_processes に記録されているプロセスの一部は期限切れになっている可能性があります。現在のループは、プロセスが失敗したかどうかを確認するために最初から開始されます。失敗した場合は、プロセスの位置が再利用されます。
  // それ以外の場合は、ngx_last_process が指す位置を直接使用します for (s = 0; s < ngx_last_process; s++) {
   ngx_processes[s].pid == -1の場合{
    壊す;
   }
  }

  // これは作成されたプロセス数が最大値に達したことを示します if (s == NGX_MAX_PROCESSES) {
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、0、
          "%d 個を超えるプロセスは生成できません"、
          NGX_MAX_PROCESSES);
   NGX_INVALID_PID を返します。
  }
 }

 // NGX_PROCESS_DETACHED フラグは、現在のフォークされたプロセスが元の親プロセスと関係がないことを示します。たとえば、nginx をアップグレードする場合、
 // 新しく生成されたマスタープロセスは元のマスタープロセスとは何の関係もありません if (respawn != NGX_PROCESS_DETACHED) {

  /* Solaris 9 にはまだ AF_LOCAL がありません */

  // ここでの socketpair() メソッドの主な機能は、メイン プロセスと子プロセス間の通信用のソケット ストリームのペアを生成することです。このソケットのペアは、ngx_processes[s].channel に格納されます。基本的に、このフィールドは長さ 2 の整数配列です。メイン プロセスと子プロセスが通信する前に、メイン プロセスはいずれかのプロセスを閉じ、子プロセスはもう一方のプロセスを閉じます。その後、閉じられていないもう一方のファイル記述子からデータを書き込んだり読み取ったりすることで通信できるようになります。
  // AF_UNIX は、UNIX ファイル形式のソケット アドレス ファミリが現在使用されていることを示します。// SOCK_STREAM は、現在のソケットによって確立された通信方法がパイプ ストリームであり、このパイプ ストリームが双方向であることを指定します。
  // パイプの両側で読み取りと書き込み操作を実行できます // 3 番目のパラメータ protocol は 0 である必要があります
  ソケットペア(AF_UNIX、SOCK_STREAM、0、ngx_processes[s].channel) == -1の場合{
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "socketpair() は \"%s\" の生成中に失敗しました", name);
   NGX_INVALID_PID を返します。
  }

  ngx_log_debug2(NGX_LOG_DEBUG_CORE、サイクル->ログ、0、
          "チャンネル %d:%d",
          ngx_processes[s].チャネル[0]、
          ngx_processes[s].チャネル[1]);

  // ngx_processes[s].channel[0]を非ブロッキングモードに設定する if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          ngx_非ブロッキング_n
            " \"%s\" の生成中に失敗しました",
          名前);
   ngx_close_channel(ngx_processes[s].channel, cycle->log);
   NGX_INVALID_PID を返します。
  }

  // ngx_processes[s].channel[1]を非ブロッキングモードに設定する if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          ngx_非ブロッキング_n
            " \"%s\" の生成中に失敗しました",
          名前);
   ngx_close_channel(ngx_processes[s].channel, cycle->log);
   NGX_INVALID_PID を返します。
  }

  オン = 1;
  // ngx_processes[s].channel[0]ソケットパイプを非同期モードに設定する if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "\"%s\" の生成中に ioctl(FIOASYNC) が失敗しました"、名前);
   ngx_close_channel(ngx_processes[s].channel, cycle->log);
   NGX_INVALID_PID を返します。
  }

  // 現在メインプロセスにあり、ここでの ngx_pid はメインプロセスのプロセス ID を指します。このメソッドの主な機能は、メインプロセスに // ngx_processes[s].channel[0] の操作権限を設定することです。つまり、メインプロセスは、 // ngx_processes[s].channel[0] にデータを書き込んだり読み込んだりして子プロセスと通信します。if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "fcntl(F_SETOWN) が \"%s\" の生成中に失敗しました"、名前);
   ngx_close_channel(ngx_processes[s].channel, cycle->log);
   NGX_INVALID_PID を返します。
  }

  // FD_CLOEXEC は、現在指定されているソケットパイプが子プロセスで使用できるが、execl() によって実行されるプログラムで使用できないことを示します。if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "fcntl(FD_CLOEXEC) が \"%s\" の生成中に失敗しました",
          名前);
   ngx_close_channel(ngx_processes[s].channel, cycle->log);
   NGX_INVALID_PID を返します。
  }

  // FD_CLOEXEC は、現在指定されているソケットパイプが子プロセスで使用できるが、execl() によって実行されるプログラムで使用できないことを示します。if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "fcntl(FD_CLOEXEC) が \"%s\" の生成中に失敗しました",
          名前);
   ngx_close_channel(ngx_processes[s].channel, cycle->log);
   NGX_INVALID_PID を返します。
  }

  // ngx_processes[s].channel[1] は、子プロセスの関連イベントをリッスンするために使用されます。親プロセスが ngx_processes[s].channel[0] にイベントを発行すると、ngx_processes[s].channel[1] は対応するイベントを受信し、対応する処理を実行します。 ngx_channel = ngx_processes[s].channel[1];

 } それ以外 {
  // NGX_PROCESS_DETACHED モードの場合、現在のプロセスは新しいマスター プロセスであるため、パイプライン値は -1 に設定されます。
  ngx_processes[s].channel[0] = -1;
  ngx_processes[s].channel[1] = -1;
 }

 ngx_process_slot は s です。


 // fork() メソッドは新しいプロセスを生成します。このプロセスと親プロセスの関係は、子プロセスのメモリ データが親プロセスを完全にコピーすることです。
 // また、fork() の子プロセスによって実行されるコードは fork() の後から始まるのに対し、親プロセスでは、
 // このメソッドの戻り値は親プロセス ID ですが、子プロセスの場合、このメソッドの戻り値は 0 です。そのため、if-else ステートメントを通じて、親プロセスと子プロセスはそれぞれ異なる後続のコード スニペットを呼び出すことができます。pid = fork();

 スイッチ (pid) {

  ケース-1:
   // フォークエラー ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
          "\"%s\" の生成中に fork() が失敗しました", name);
   ngx_close_channel(ngx_processes[s].channel, cycle->log);
   NGX_INVALID_PID を返します。

  ケース0:
   // 子プロセス実行の分岐。ここでは proc() メソッドが外部から渡されます。つまり、現在のメソッドは新しいプロセスを作成するだけです。
   // 特定のプロセス処理ロジックは、外部コード ブロックによって定義されます。ngx_getpid() メソッドは、新しく作成された子プロセスのプロセス ID を取得します。
   ngx_pid = ngx_getpid();
   proc(サイクル、データ);
   壊す;

  デフォルト:
   //親プロセスはここに移動 break;
 }

 ngx_log_error(NGX_LOG_NOTICE、cycle->log、0、"%s %P を開始"、名前、pid);

 // 親プロセスはここに来ます。現在の pid は、fork() 後に親プロセスによって取得された新しく作成された子プロセスの pid です。
 ngx_processes[s].pid = pid;
 ngx_processes[s].exited = 0;

 (リスポーン> = 0)の場合{
  pid を返します。
 }

 // 現在のプロセスのさまざまな属性を設定し、ngx_processes 配列の対応する位置に格納します。ngx_processes[s].proc = proc;
 ngx_processes[s].data = データ;
 ngx_processes[s].name = 名前;
 ngx_processes[s].exiting = 0;

 スイッチ(リスポーン){

  NGX_PROCESS_NORESPAWNの場合:
   ngx_processes[s].respawn = 0;
   ngx_processes[s].just_spawn = 0;
   ngx_processes[s].detached = 0;
   壊す;

  NGX_PROCESS_JUST_SPAWNの場合:
   ngx_processes[s].respawn = 0;
   ngx_processes[s].just_spawn = 1;
   ngx_processes[s].detached = 0;
   壊す;

  NGX_PROCESS_RESPAWNの場合:
   ngx_processes[s].respawn = 1;
   ngx_processes[s].just_spawn = 0;
   ngx_processes[s].detached = 0;
   壊す;

  NGX_PROCESS_JUST_RESPAWNの場合:
   ngx_processes[s].respawn = 1;
   ngx_processes[s].just_spawn = 1;
   ngx_processes[s].detached = 0;
   壊す;

  NGX_PROCESS_DETACHEDの場合:
   ngx_processes[s].respawn = 0;
   ngx_processes[s].just_spawn = 0;
   ngx_processes[s].detached = 1;
   壊す;
 }

 s == ngx_last_process の場合 {
  ngx_last_process++;
 }

 pid を返します。
}

ngx_spawn_process() メソッドは、最終的に子プロセスを fork() して、2 番目のパラメータで指定されたコールバック メソッドを実行します。しかしその前に、socketpair() メソッド呼び出しを通じて匿名ソケットのペアを作成し、それを現在のプロセスのチャネル配列に格納することで、チャネル配列の作成が完了することを説明する必要があります。

ワーカー プロセスが開始されると、ngx_worker_process_cycle() メソッドが実行されます。このメソッドは、継承されたチャネル配列の処理を含め、最初にワーカー プロセスを初期化します。マスター プロセスとワーカー プロセスの両方がチャネル配列によって参照されるソケット記述子を保持しているため、本質的には、マスター プロセスと各ワーカー プロセスは配列の片側の記述子のみを保持する必要があります。したがって、初期化プロセス中に、ワーカー プロセスは反対側に保存されている記述子を閉じます。 nginx では、マスター プロセスはチャネル配列の位置 0 のソケット記述子を均一に保持し、位置 1 のソケット記述子を閉じますが、ワーカー プロセスは位置 0 のソケット記述子を閉じ、位置 1 の記述子を保持します。このように、マスタープロセスがワーカープロセスと通信する必要がある場合は、チャネル[0]にデータを書き込むだけでよく、ワーカープロセスはチャネル[1]をリッスンしてマスタープロセスによって書き込まれたデータを受信します。ここではまず、ワーカー プロセスの初期化メソッド ngx_worker_process_init() のソース コードを見てみましょう。

/**
 * ここでは主に現在のプロセスを初期化し、優先度やオープンファイルの制限などのパラメータを設定します。
 * 最後に、チャネル[1]をリッスンするための接続が現在のプロセスに追加され、マスタープロセスからメッセージを継続的に読み取り、対応する処理を実行します*/
静的 void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t ワーカー) {
 sigset_t セット;
 ngx_int_t の引数は n です。
 ngx_time_t *tp;
 ngx_uint_t i;
 ngx_cpuset_t *CPU_アフィニティ;
 構造体 rlimit rlmt;
 ngx_core_conf_t *ccf;
 ngx_listening_t *ls;

 // タイムゾーン関連情報を設定する if (ngx_set_environment(cycle, NULL) == NULL) {
  /* 致命的 */
  終了(2);
 }

 ccf は ngx_core_conf_t に格納されます。

 // 現在のプロセスの優先度を設定します if (worker >= 0 && ccf->priority != 0) {
  (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1)の場合{
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "setpriority(%d) が失敗しました", ccf->priority);
  }
 }

 // 現在のプロセスが開くことができるファイルハンドルの数を設定します if (ccf->rlimit_nofile != NGX_CONF_UNSET) {
  rlmt.rlim_cur = (rlim_t) ccf->rlimit_nofile;
  rlmt.rlim_max = (rlim_t) ccf->rlimit_nofile;

  (setrlimit(RLIMIT_NOFILE, &rlmt) == -1)の場合{
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "setrlimit(RLIMIT_NOFILE, %i) が失敗しました",
          ccf->rlimit_nofile);
  }
 }

 // ワーカー プロセスのコア ファイルの最大サイズの制限 (RLIMIT_CORE) を変更します。
 // つまり、コアファイルが使用できる最大サイズを設定します。if (ccf->rlimit_core != NGX_CONF_UNSET) {
  rlmt.rlim_cur = (rlim_t) ccf->rlimit_core;
  rlmt.rlim_max = (rlim_t) ccf->rlimit_core;

  (setrlimit(RLIMIT_CORE, &rlmt) == -1)の場合{
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "setrlimit(RLIMIT_CORE, %O) が失敗しました",
          ccf->rlimit_core);
  }
 }

 // geteuid() は現在のプログラムを実行するユーザー ID を返します。ここで、0 はルートユーザーかどうかを示します if (geteuid() == 0) {
  // setgid() メソッドの目的はグループ ID を変更することです
  (setgid(ccf->group) == -1)の場合{
   ngx_log_error(NGX_LOG_EMERG、サイクル->ログ、ngx_errno、
          "setgid(%d) が失敗しました", ccf->group);
   /* 致命的 */
   終了(2);
  }

  // initgroups() は追加グループの ID を変更します
  initgroups(ccf->username, ccf->group) == -1の場合{
   ngx_log_error(NGX_LOG_EMERG、サイクル->ログ、ngx_errno、
          "initgroups(%s, %d) が失敗しました",
          ccf->ユーザー名、ccf->グループ);
  }

  // ユーザーのIDを変更する
  (setuid(ccf->user) == -1)の場合{
   ngx_log_error(NGX_LOG_EMERG、サイクル->ログ、ngx_errno、
          "setuid(%d) が失敗しました", ccf->user);
   /* 致命的 */
   終了(2);
  }
 }

 // キャッシュ マネージャーおよびキャッシュ ローダー プロセスの場合、ここでのワーカーは -1 を渡すことに注意してください。
 // これら2つのプロセスは求核性を設定する必要がないことを示します if (worker >= 0) {
  // 現在のワーカーの CPU アフィニティを取得します。 cpu_affinity = ngx_get_cpu_affinity(worker);

  (CPUアフィニティ)の場合{
   // ワーカーのアフィニティ コアを設定します ngx_setaffinity(cpu_affinity, cycle->log);
  }
 }

#if (NGX_HAVE_PR_SET_DUMPABLE)
 (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1)の場合{
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "prctl(PR_SET_DUMPABLE) が失敗しました");
 }

#終了

 if (ccf->working_directory.len) {
  // chdir() の機能は、現在の作業ディレクトリをパラメータとして渡されたパスに変更することです if (chdir((char *) ccf->working_directory.data) == -1) {
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "chdir(\"%s\") が失敗しました", ccf->working_directory.data);
   /* 致命的 */
   終了(2);
  }
 }

 // 空セット命令セットを初期化します sigemptyset(&set);

 // ◆ SIG_BLOCK: setパラメータが指すシグナルセット内のシグナルをシグナルマスクに追加します。
 // ◆ SIG_UNBLOCK: setパラメータが指すシグナルセット内のシグナルをシグナルマスクから削除します。
 // ◆ SIG_SETMASK: setパラメータが指すシグナルセットをシグナルマスクに設定します。
 // ここではブロックするシグナルセットを直接初期化します。デフォルトは空のセットです。if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
  ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
         "sigprocmask() が失敗しました");
 }

 tp = ngx_timeofday();
 srandom(((unsigned) ngx_pid << 16) ^ tp->sec ^ tp->msec);

 ls = サイクル->listening.elts;
 (i = 0; i < cycle->listening.nelts; i++) {
  ls[i].前 = NULL;
 }

 // ここで、各モジュールのinit_process()メソッドが呼び出され、プロセスモジュールが初期化されます。 for (i = 0; cycle->modules[i]; i++) {
  サイクル->モジュール[i]->init_process)の場合{
   サイクル->モジュール[i]->init_process(サイクル) == NGX_ERRORの場合{
    /* 致命的 */
    終了(2);
   }
  }
 }

 // これは主に、現在のプロセス内の他のプロセスのチャネル[1]パイプハンドルを閉じるためのものです。 for (n = 0; n < ngx_last_process; n++) {

  ngx_processes[n].pid == -1の場合{
   続く;
  }

  (n == ngx_process_slot)の場合{
   続く;
  }

  ngx_processes[n].channel[1] == -1の場合{
   続く;
  }

  ngx_processes[n].channel[1] == -1 の場合、
   ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
          "close() チャネルに失敗しました");
  }
 }

 // 現在のプロセスのチャネル[0]パイプハンドルを閉じます。if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
  ngx_log_error(NGX_LOG_ALERT、サイクル->ログ、ngx_errno、
         "close() チャネルに失敗しました");
 }

#0の場合
 ngx_last_process = 0;
#終了

 // ngx_channelは現在のプロセスのchannel[1]ハンドルを指します。これは、マスタープロセスによって送信されたメッセージをリッスンするハンドルでもあります。
 // 現在のメソッドでは、まず現在のハンドルの接続オブジェクトが作成され、イベントとしてカプセル化されます。次に、イベントが対応するイベント モデル キューに追加され、現在のハンドルのイベントをリッスンします。イベント処理ロジックは主に ngx_channel_handler() にあります。
 // メソッドは続行されます。ここでの ngx_channel_handler の主な処理ロジックは、現在受信されているメッセージに応じて現在のプロセスのいくつかのフラグを設定することです。
 // または、キャッシュされたデータの一部を更新して、現在のイベント ループでこれらのフラグを継続的にチェックすることにより、実際のロジックがイベント プロセスで処理されるようにします。したがって、ここでのngx_channel_handlerの処理効率は非常に高いです。(ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
              ngx_channel_handler)
   == NGX_ERROR) {
  /* 致命的 */
  終了(2);
 }
}

このメソッドは主にワーカー プロセスを初期化するために使用されます。ここでは主に、現在の nginx 内の各プロセスの関連情報を格納する ngx_processes 配列の最終的なトラバースに注意する必要があります。トラバーサルプロセス中、現在のプロセスが保持している他のプロセスのチャネル[1]ハンドルは閉じられますが、チャネル[0]ハンドルは保持されます。このように、現在のプロセスが他のプロセスと通信する必要がある場合は、ターゲットプロセスのチャネル[0]にデータを書き込むだけで済みます。トラバーサルが完了すると、現在のプロセスは独自のチャネル[0]ハンドルを閉じ、チャネル[1]ハンドルを保持します。最後に、ngx_add_channel_event() メソッドは、現在のプロセスのチャネル [1] のリスニング イベントを追加するために使用されます。ngx_add_channel_event() メソッドを呼び出すときに渡される 2 番目のパラメーターは ngx_channel です。これは、前の ngx_spawn_process() メソッドで割り当てられ、現在のプロセスのチャネル [1] のソケット ハンドルを指します。

ngx_add_channel_event() メソッドに関して言えば、その本質は、ngx_event_t 構造体のイベントを作成し、それを現在使用されているイベント モデルのハンドル (epoll など) に追加することです。このメソッドの実装ソース コードはここでは繰り返しませんが、注意する必要があるのは、イベントがトリガーされたときのコールバック メソッド、つまり、ngx_add_channel_event() メソッドを呼び出すときに渡される ngx_channel_handler() メソッドの 3 番目のパラメーターです。以下はこのメソッドのソースコードです。

静的 void ngx_channel_handler(ngx_event_t *ev) {
 ngx_int_t の引数は n です。
 ngx_channel_t ch;
 ngx_connection_t *c;

 タイムアウトの場合
  ev->タイムアウト = 0;
  戻る;
 }

 c = ev->データ;

 のために (;;) {

  // マスタープロセスによって送信されたメッセージを無限の for ループで継続的に読み取ります。n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);

  // メッセージの読み取り中にエラーが発生した場合、現在のハンドルが無効である可能性があり、現在の接続を閉じる必要があります if (n == NGX_ERROR) {
   ngx_event_flags と NGX_USE_EPOLL_EVENT の場合 {
    ngx_del_conn(c, 0);
   }

   ngx_close_connection() 関数は、接続をクローズします。
   戻る;
  }

  ngx_event_flags と NGX_USE_EVENTPORT_EVENT の場合 {
   ngx_add_event(ev, NGX_READ_EVENT, 0) == NGX_ERRORの場合{
    戻る;
   }
  }

  (n == NGX_AGAIN)の場合{
   戻る;
  }

  // 送信されたメッセージを処理する switch (ch.command) {
   // 終了メッセージの場合は終了フラグを設定します。case NGX_CMD_QUIT:
    ngx_quit = 1;
    壊す;

    // メッセージが終了したら、終了フラグを設定します。case NGX_CMD_TERMINATE:
    ngx_terminate = 1;
    壊す;

    // 再開メッセージの場合は再開フラグを設定します。case NGX_CMD_REOPEN:
    ngx_reopen = 1;
    壊す;

    // 新しいプロセス メッセージの場合は、現在の ngx_processes 配列の対応する位置にあるデータを更新します。ケース NGX_CMD_OPEN_CHANNEL:
    ngx_processes[ch.slot].pid = ch.pid;
    ngx_processes[ch.slot].channel[0] = ch.fd;
    壊す;

    // チャネルを閉じるメッセージの場合は、ngx_processes 配列の対応する位置にあるハンドルを閉じます。ケース NGX_CMD_CLOSE_CHANNEL:
    ngx_processes[ch.slot].channel[0] == -1 の場合、閉じます。
     ngx_log_error(NGX_LOG_ALERT、ev->log、ngx_errno、
            "close() チャネルに失敗しました");
    }

    ngx_processes[ch.slot].channel[0] = -1;
    壊す;
  }
 }
}

ngx_channel_handler() メソッドの主な機能は、監視対象のソケット ハンドル内のデータを読み取ることです。データは ngx_channel_t 構造体によって運ばれます。この ngx_channel_t は、マスター プロセスとワーカー プロセス間の通信に nginx が使用する構造体です。現在発生しているイベントの種類と、イベントが発生したプロセス情報を指定します。以下は ngx_channel_t 構造体の宣言です。

typedef構造体{
  // 現在のイベント タイプ ngx_uint_t コマンド;
  // イベントのPID
  ngx_pid_t pid;
  // ngx_processes 配列内でイベントが発生したプロセスの添え字 ngx_int_t スロット。
  // イベントが発生したプロセスのchannel[0]記述子の値 ngx_fd_t fd;
} ngx_channel_t;

ngx_channel_handler()メソッドは、現在のプロセスのチャネル[1]からngx_channel_t構造体のデータを読み取った後、発生したイベントの種類に応じて対応するフラグビットのステータスを更新し、現在のプロセスのngx_processes配列内のイベントが発生したプロセスのステータス情報を更新します。

マスター プロセスによって送信されたイベントを処理した後、ワーカー プロセスはループを続行し、関係するフラグのステータスをチェックし、これらのステータスに基づいて対応するロジックを実行します。以下はワーカー プロセスの作業ループのソース コードです。

/**
 * ワーカープロセスの作業ループに入る*/
静的 void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) {
 ngx_int_t ワーカー = (intptr_t) データ;

 ngx_process = NGX_PROCESS_WORKER;
 ngx_worker = ワーカー;

 // ワーカー プロセスを初期化します。このメソッドのソース コードは上記で説明されています。ngx_worker_process_init(cycle, worker);

 ngx_setproctitle("ワーカープロセス");

 のために (;;) {

  ngx_exiting の場合
   // ここでは主に、キャンセル不可能な状態のイベントがあるかどうか、つまりすべてのイベントがキャンセルされたかどうかを確認します。もしそうなら、
   // NGX_OK を返します。ここでのロジックは、次のように理解できます。ngx_exiting としてマークされている場合、この時点でまだキャンセルされていないイベントがある場合は、次の ngx_process_events_and_timers() メソッドに進み、未完了のイベントが処理されます。
   // 次にループ内でこの位置に再度移動し、最終的にif条件が真となり、ワーカープロセスを終了する作業を実行します。if (ngx_event_no_timers_left() == NGX_OK) {
    ngx_log_error(NGX_LOG_NOTICE、cycle->log、0、"終了しています");
    ngx_worker_process_exit(サイクル);
   }
  }

  ngx_log_debug0(NGX_LOG_DEBUG_EVENT、cycle->log、0、"ワーカー サイクル");

  // ここでは、対応するイベント モデルに対応するイベントがあるかどうかを確認し、処理のためにキューに入れます。
  // 以下は、イベントを処理するワーカー プロセスのコア メソッドです ngx_process_events_and_timers(cycle);

  // ここで ngx_terminate は nginx を強制的にシャットダウンするオプションです。 nginx を強制的にシャットダウンするコマンドが nginx に送信された場合、現在のプロセスは直接終了します if (ngx_terminate) {
   ngx_log_error(NGX_LOG_NOTICE、cycle->log、0、"終了しています");
   ngx_worker_process_exit(サイクル);
  }

  // ここで ngx_quit は正常な終了のためのオプションです。ここで、ngx_exiting は 1 に設定され、現在のプロセスを終了する必要があることを示します。
  // 次に、次の 3 つのタスクが実行されます。
  // 1. 現在アクティブな接続を処理するイベントをイベント キューに追加し、そのクローズ フラグを 1 に設定し、接続の現在の処理メソッドを実行して、接続イベントをできるだけ早く完了します。
  // 2. 現在のサイクルで監視されているソケット ハンドルを閉じます。
  // 3. 現在アイドル状態のすべての接続のクローズ ステータスを 1 としてマークし、接続処理メソッドを呼び出します。
  ngx_quitの場合{
   ngx_quit = 0;
   ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "正常にシャットダウンしています");
   ngx_setproctitle("ワーカープロセスがシャットダウンしています");

   ngx_exiting の場合
    ngx_exiting = 1;
    ngx_set_shutdown_timer(サイクル);
    ngx_close_listening_sockets(サイクル);
    ngx_close_idle_connections(サイクル);
   }
  }

  // ngx_reopen は主に nginx ログファイルの切り替えなど、すべての nginx ファイルを再度開きます。if (ngx_reopen) {
   ngx_reopen = 0;
   ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "ログを再開しています");
   ngx_reopen_files(サイクル、-1);
  }
 }
}

ワーカー プロセスは主に nginx が終了するかどうかに関連するフラグ ビットを処理し、nginx が設定ファイルを再読み込みするかどうかのフラグ ビットも処理していることがわかります。

4. まとめ

この記事では、まずマスター プロセスとワーカー プロセスの相互作用の基本原理について説明し、次にソース コードを詳しく調べて、nginx がマスター プロセスとワーカー プロセス間の相互通信をどのように実装するかを説明します。

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • Nginx リバース プロキシでセッション永続性を実装する 2 つの方法の詳細な説明
  • Nginx+Tomcat によるセッション管理の実装
  • nginx+redisはセッション共有を実現します
  • nginx+tomcatは負荷分散を実装し、redisセッション共有を使用します
  • Nginxでの共有セッション設定方法の例
  • Nginx 負荷分散マルチサイト共有セッション
  • Nginxポーリングアルゴリズムの基本的な実装方法の詳細な説明
  • NginxはURLのパスに応じてアップストリームに動的に転送します
  • PythonベースのWeb環境を構築するためのnginxの実装手順
  • Nginx セッション共有問題の解決策の分析

<<:  Vueコンポーネント間の通信の非常に詳細な要約

>>:  MySQLのページング制限のパフォーマンス問題についての簡単な説明

推薦する

CSS はこのように使用できますか?気まぐれなグラデーションの芸術

前回の記事「1行のCSSコードの魅力」では、たった1行のCSSコードで生成できる美しい(奇妙な感じと...

クールなIoT大画面機能を実現するHTML+VUEページング

効果デモ.html <html> <ヘッド> <メタ文字セット=&qu...

電子署名を実装するWeChatミニプログラム

この記事では、WeChatミニプログラムで電子署名を実装するための具体的なコードを参考までに紹介しま...

MySQL でストリーミングクエリを使用してデータ OOM を回避する

目次1. はじめに2. JDBCはストリーミングクエリを実装する3. パフォーマンステスト3.1. ...

CentOS8 yum/dnfで国内ソースを設定する方法

CentOS 8 ではソフトウェア パッケージのインストール プログラムが変更され、yum 構成方法...

JavaScript のコールバック関数の理解と使用

目次概要コールバックまたは高階関数とは何ですか?コールバック関数はどのように機能しますか?コールバッ...

便利でシンプルなMySQL関数10個

関数0. 現在の時刻を表示するコマンド: select now()。機能: 現在の時刻を表示します。...

Windows での MySQL 8.0.18 インストール チュートリアル (図解)

ダウンロードダウンロードアドレス: https://dev.mysql.com/downloads/...

WeChatアプレットを少なく使う方法(最適な方法)

序文私は less/sass を書くことに慣れていますが、小さなプログラムを開発するときには、まだ ...

Mysql 5.7.19 無料インストール バージョンで遭遇した落とし穴 (コレクション)

1. 公式ウェブサイトから 64 ビットの zip ファイルをダウンロードします。 2. インスト...

Reactフックの仕組み

目次1. React フックと純粋関数2. シンプルなmyUseState 3. myUseStat...

Vue の一般的な問題と解決策の概要 (推奨)

Vue に限定されず、他の種類の SPA プロジェクトにも当てはまる問題がいくつかあります。 1....

タグ li はブロックレベル要素ですか?

なぜ高さを設定できるのでしょうか。<h1 /> などの要素とは異なり、「セミインライン」...

Grafana+Prometheus を使用して MySQL サービスのパフォーマンスを監視する

Prometheus (プロメテウスとも呼ばれる) 公式サイト: https://prometheu...

vue3 再帰コンポーネントカプセル化の全プロセス記録

目次序文1. 再帰コンポーネント2. 右クリックメニューコンポーネント要約する序文今日、プロジェクト...