Implementierung der Nginx-Arbeitsprozessschleife

Implementierung der Nginx-Arbeitsprozessschleife

Nach dem Start initialisiert der Worker-Prozess zunächst die für seinen eigenen Betrieb erforderliche Umgebung und tritt dann in eine Schleife ein, in der er kontinuierlich prüft, ob Ereignisse vorliegen, die ausgeführt werden müssen, und dann die Ereignisse verarbeitet. In diesem Prozess muss der Worker-Prozess auch mit dem Master-Prozess interagieren. Darüber hinaus kann der Worker-Prozess als untergeordneter Prozess auch Befehlszeilenanweisungen (wie Kill usw.) empfangen, um entsprechende logische Verarbeitungen durchzuführen. Wie interagiert also der Arbeitsprozess mit dem Master oder den Befehlszeilen-Anweisungen? In diesem Artikel wird zunächst erläutert, wie der Arbeitsprozess mit dem Masterprozess interagiert und wie der Arbeitsprozess Befehlszeilen-Anweisungen verarbeitet. Anschließend wird der gesamte Arbeitsablauf der Arbeitsprozessinteraktion aus dem Quellcode vorgestellt.

1. Wie Worker- und Masterprozesse interagieren

Als Erstes muss hier erklärt werden, dass nginx die entsprechenden Anweisungen über Flags verarbeitet, unabhängig davon, ob es sich um die Master- oder externe Befehlsmethode handelt. Das heißt, wenn ein Befehl empfangen wird (unabhängig davon, ob es sich um den Master- oder externen Befehl handelt), setzt der Worker in seiner Rückrufmethode das Flag, das dem Befehl entspricht. Anschließend prüft der Worker-Prozess, ob diese Flags wahr sind, nachdem er das Ereignis in seiner eigenen Schleife verarbeitet hat. Wenn ja, führt er die entsprechende Logik entsprechend der Funktion des Flags aus.

Die Interaktion zwischen dem Worker-Prozess und dem Master-Prozess erfolgt über Socket-Pipes. Eine ngx_process_t-Struktur wird in der Datei ngx_process.h deklariert. Hier konzentrieren wir uns hauptsächlich auf ihr Kanalattribut:

typedef-Struktur {
  // Die restlichen Eigenschaften ...
  
  ngx_socket_t Kanal[2];
} ngx_process_t;

Die Funktion der ngx_process_t-Struktur besteht hier darin, prozessbezogene Informationen wie PID, Kanal, Status usw. zu speichern. Jeder Prozess verfügt über ein ngx_processes-Array, und die Array-Elemente sind hier die ngx_process_t-Strukturen. Dies bedeutet, dass jeder Prozess die grundlegenden Informationen anderer Prozesse über das ngx_processes-Array speichert. Die Erklärung lautet:

// Speichert ein Array aller untergeordneten Prozesse in nginx. Jeder untergeordnete Prozess hat eine entsprechende ngx_process_t-Struktur, um ihn zu markieren
extern ngx_process_t ngx_processes[NGX_MAX_PROCESSES];
Hier können wir sehen, dass jeder Prozess ein entsprechendes Kanal-Array hat. Die Länge dieses Arrays beträgt 2. Dies ist der Pipeline-Flow für die Interaktion mit dem Masterprozess. Bevor der Masterprozess die einzelnen untergeordneten Prozesse erstellt, wird ein Kanalarray erstellt. Das Array wird wie folgt erstellt:

int Socketpaar (int Domäne, int Typ, int Protokoll, int sv[2]);
Die Hauptfunktion dieser Methode besteht darin, ein Paar anonymer verbundener Sockets zu erstellen, d. h. wenn Daten in einen Socket geschrieben werden, können die geschriebenen Daten im anderen Socket empfangen werden. Wenn auf diese Weise Daten auf eine Seite der Pipe im übergeordneten Prozess geschrieben werden, kann der untergeordnete Prozess die Daten auf der anderen Seite empfangen und so eine Datenkommunikation zwischen den übergeordneten und untergeordneten Prozessen realisieren.

Nachdem der Masterprozess den untergeordneten Prozess gestartet hat, behält der untergeordnete Prozess die entsprechenden Daten im Masterprozess bei, einschließlich des Kanalarrays hier. Auf diese Weise kann der Masterprozess über das Kanalarray mit dem Kindprozess kommunizieren.

2. Worker verarbeitet externe Befehle

Bei externen Befehlen erfolgt die Verarbeitung im Wesentlichen über die verschiedenen Signale und Rückrufmethoden, die im Signal-Array definiert sind. Wenn der Masterprozess die Basisumgebung initialisiert, wird die im Signal-Array angegebene Signal-Callback-Methode auf das entsprechende Signal eingestellt. Da der Worker-Prozess die grundlegende Umgebung des Master-Prozesses erbt, ruft der Worker-Prozess nach Erhalt des hier festgelegten Signals auch die entsprechende Rückrufmethode auf. Die Hauptlogik dieser Rückrufmethode besteht lediglich darin, den Wert des entsprechenden Flag-Bits festzulegen. Informationen zum Setzen des entsprechenden Flags, nachdem Nginx das Signal empfangen hat, finden Sie in meinem vorherigen Artikel (Hyperlink zum Nginx-Master-Arbeitszyklus), der hier nicht wiederholt wird.

3. Quellcode-Erklärung

Der Masterprozess startet jeden Kindprozess über die Methode ngx_start_worker_processes(). Nachfolgend sehen Sie den Quellcode dieser Methode:

/**
 * Starten Sie n Worker-Child-Prozesse und richten Sie Socketpaare zwischen jedem Child-Prozess und dem Master-Paarprozess ein.
 * Socket-Handle-Kommunikationsmechanismus durch Systemaufruf eingerichtet*/
statischer void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t Typ) {
 ngx_int_t ich;
 ngx_channel_t ch;
 
 ngx_memzero(&ch, Größe von(ngx_channel_t));
 ch.command = NGX_CMD_OPEN_CHANNEL;

 für (i = 0; i < n; i++) {

  // Spawn bedeutet Eier legen, was bedeutet, einen Kindprozess zu erzeugen. Die Ereignisschleife des Kindprozesses ist die // ngx_worker_process_cycle()-Methode. Hier ist ngx_worker_process_cycle der Zyklus des Arbeitsprozesses, der Ereignisse verarbeitet.
  // Der Workerprozess befindet sich in einer unendlichen For-Schleife und prüft ständig, ob im entsprechenden Ereignismodell ein entsprechendes Ereignis vorliegt.
  // Dann trennen Sie das Akzeptanzereignis und die Lese- und Schreibereignisse in zwei Warteschlangen und verarbeiten die Ereignisse schließlich kontinuierlich in der Ereignisschleife ngx_spawn_process(cycle, ngx_worker_process_cycle, 
           (void *) (intptr_t) i, „Arbeitsprozess“, Typ);

  // Die Hauptfunktion des folgenden Codes besteht darin, andere Prozesse über das Ereignis des neuen Prozesses zu benachrichtigen. Im obigen // ​​ch.command = NGX_CMD_OPEN_CHANNEL; bedeutet NGX_CMD_OPEN_CHANNEL, dass gerade ein neuer Prozess erstellt wird.
  // ngx_process_slot speichert den Array-Speicherort, an dem der neue Prozess gespeichert wird. Der Grund, warum hier Broadcasting erforderlich ist, ist, dass
  // Nachdem jeder untergeordnete Prozess erstellt wurde, werden seine Speicherdaten vom übergeordneten Prozess kopiert, aber jeder Prozess verfügt über eine Kopie des Arrays ngx_processes.
  // Daher verfügt der zuerst erstellte Unterprozess im Array nicht über die Daten des später erstellten Unterprozesses, aber der Masterprozess verfügt über die Daten aller Unterprozesse.
  // Daher schreibt der Masterprozess, nachdem er einen untergeordneten Prozess erstellt hat, das aktuelle Broadcast-Ereignis in den Kanal [0] jedes Prozesses im Array ngx_processes, also hier ch. Auf diese Weise erhält jeder untergeordnete Prozess dieses Ereignis,
  // Versucht, die gespeicherten ngx_processes-Dateninformationen zu aktualisieren. ch.pid = ngx_processes[ngx_process_slot].pid;
  ch.slot = ngx_process_slot;
  ch.fd = ngx_processes[ngx_process_slot].Kanal[0];

  // Broadcast-Ereignis ngx_pass_open_channel(cycle, &ch);
 }
}

Hier müssen wir uns hauptsächlich auf den Methodenaufruf zum Starten des untergeordneten Prozesses oben konzentrieren, also hier auf die Methode ngx_spawn_process(). Der zweite Parameter dieser Methode ist eine Methode. Nach dem Starten des untergeordneten Prozesses tritt der untergeordnete Prozess in die von dieser Methode angegebene Schleife ein. In der Methode ngx_spawn_process() erstellt der Masterprozess ein Kanalarray für den neu erstellten untergeordneten Prozess, um mit dem aktuellen untergeordneten Prozess zu kommunizieren. Nachfolgend sehen Sie den Quellcode der Methode ngx_spawn_process():

ngx_pid_t ngx_spawn_process(ngx_cycle_t *Zyklus, ngx_spawn_proc_pt proc, void *Daten, char *Name, ngx_int_t respawn) {
 u_long an;
 ngx_pid_t pid;
 ngx_int_t s;

 wenn (respawn >= 0) {
  s = Wiederauferstehung;

 } anders {
  // Alle aktuell erstellten Prozesse werden im Array ngx_processes gespeichert, und ngx_last_process ist der Index der nächsten Position des letzten // Prozesses, der aktuell in ngx_processes aufgezeichnet ist, aber einige der in ngx_processes aufgezeichneten Prozesse sind möglicherweise // ​​abgelaufen. Die aktuelle Schleife startet von vorne, um herauszufinden, ob ein Prozess fehlgeschlagen ist. Wenn dies der Fall ist, wird die Prozessposition erneut verwendet.
  // Andernfalls verwenden Sie direkt die Position, auf die ngx_last_process zeigt for (s = 0; s < ngx_last_process; s++) {
   wenn (ngx_processes[s].pid == -1) {
    brechen;
   }
  }

  // Dies zeigt an, dass die Anzahl der erstellten Prozesse das Maximum erreicht hat, wenn (s == NGX_MAX_PROCESSES) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, 0,
          "es können nicht mehr als %d Prozesse erzeugt werden",
          NGX_MAX_PROCESSES);
   gib NGX_INVALID_PID zurück;
  }
 }

 // Das Flag NGX_PROCESS_DETACHED zeigt an, dass der aktuelle gegabelte Prozess keine Beziehung zum ursprünglichen übergeordneten Prozess hat. Wenn Sie beispielsweise nginx aktualisieren,
 // Der neu generierte Masterprozess hat nichts mit dem ursprünglichen Masterprozess zu tun if (respawn != NGX_PROCESS_DETACHED) {

  /* Solaris 9 hat immer noch kein AF_LOCAL */

  // Die Hauptfunktion der Methode socketpair() besteht hier darin, ein Paar Socket-Streams für die Kommunikation zwischen dem Hauptprozess und dem untergeordneten Prozess zu generieren. Dieses Socket-Paar wird // in ngx_processes[s].channel gespeichert. Im Wesentlichen ist dieses Feld ein ganzzahliges Array der Länge 2. Bevor der Hauptprozess und der untergeordnete Prozess // kommunizieren, schließt der Hauptprozess einen von ihnen und der untergeordnete Prozess schließt den anderen. Anschließend können sie kommunizieren, indem sie Daten aus dem anderen nicht geschlossenen Dateideskriptor schreiben oder lesen.
  // AF_UNIX gibt an, dass aktuell die Socket-Adressfamilie in Form einer UNIX-Datei verwendet wird. // SOCK_STREAM gibt an, dass die vom aktuellen Socket eingerichtete Kommunikationsmethode ein Pipe-Stream ist und dieser Pipe-Stream bidirektional ist.
  // Beide Seiten der Pipe können Lese- und Schreibvorgänge ausführen // Der dritte Parameter Protokoll muss 0 sein
  wenn (Socketpaar(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          „socketpair() ist beim Erstellen von \"%s\" fehlgeschlagen“, Name);
   gib NGX_INVALID_PID zurück;
  }

  ngx_log_debug2(NGX_LOG_DEBUG_CORE, Zyklus->Protokoll, 0,
          "Kanal %d:%d",
          ngx_processes[s].channel[0],
          ngx_processes[s].channel[1]);

  // Setze ngx_processes[s].channel[0] auf nicht blockierenden Modus, wenn (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          ngx_nonblocking_n
            " Fehler beim Erstellen von \"%s\"",
          Name);
   ngx_close_channel(ngx_processes[s].channel, Zyklus->Protokoll);
   gib NGX_INVALID_PID zurück;
  }

  // Setze ngx_processes[s].channel[1] auf nicht blockierenden Modus, wenn (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          ngx_nonblocking_n
            " Fehler beim Erstellen von \"%s\"",
          Name);
   ngx_close_channel(ngx_processes[s].channel, Zyklus->Protokoll);
   gib NGX_INVALID_PID zurück;
  }

  an = 1;
  // Setze die Socket-Pipe ngx_processes[s].channel[0] auf asynchronen Modus, wenn (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          „ioctl(FIOASYNC) ist beim Erstellen von \"%s\" fehlgeschlagen“, Name);
   ngx_close_channel(ngx_processes[s].channel, Zyklus->Protokoll);
   gib NGX_INVALID_PID zurück;
  }

  // Derzeit im Hauptprozess zeigt ngx_pid hier auf die Prozess-ID des Hauptprozesses. Die Hauptfunktion dieser Methode besteht darin, die Betriebsberechtigung von // ngx_processes[s].channel[0] auf den Hauptprozess festzulegen, d. h. der Hauptprozess kommuniziert mit dem untergeordneten Prozess, indem er Daten in // ngx_processes[s].channel[0] schreibt und liestif (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          „fcntl(F_SETOWN) ist beim Erstellen von \"%s\" fehlgeschlagen“, Name);
   ngx_close_channel(ngx_processes[s].channel, Zyklus->Protokoll);
   gib NGX_INVALID_PID zurück;
  }

  // FD_CLOEXEC gibt an, dass die aktuell angegebene Socket-Pipe im untergeordneten Prozess verwendet werden kann, aber nicht im von execl() ausgeführten Programm, wenn (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          "fcntl(FD_CLOEXEC) ist beim Erstellen von \"%s\" fehlgeschlagen",
          Name);
   ngx_close_channel(ngx_processes[s].channel, Zyklus->Protokoll);
   gib NGX_INVALID_PID zurück;
  }

  // FD_CLOEXEC gibt an, dass die aktuell angegebene Socket-Pipe im untergeordneten Prozess verwendet werden kann, aber nicht im von execl() ausgeführten Programm, wenn (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          "fcntl(FD_CLOEXEC) ist beim Erstellen von \"%s\" fehlgeschlagen",
          Name);
   ngx_close_channel(ngx_processes[s].channel, Zyklus->Protokoll);
   gib NGX_INVALID_PID zurück;
  }

  // ngx_processes[s].channel[1] wird verwendet, um auf verwandte Ereignisse für den untergeordneten Prozess zu hören. Wenn der übergeordnete Prozess ein Ereignis an // ngx_processes[s].channel[0] veröffentlicht, empfängt ngx_processes[s].channel[1] das // entsprechende Ereignis und führt die entsprechende Verarbeitung durch ngx_channel = ngx_processes[s].channel[1];

 } anders {
  // Wenn es sich um den Modus NGX_PROCESS_DETACHED handelt, bedeutet dies, dass der aktuelle Prozess ein neuer Masterprozess ist, sodass sein Pipeline-Wert auf -1 gesetzt ist
  ngx_processes[s].channel[0] = -1;
  ngx_processes[s].channel[1] = -1;
 }

 ngx_process_slot = s;


 // Die fork () -Methode generiert einen neuen Prozess. Die Beziehung zwischen diesem Prozess und dem übergeordneten Prozess besteht darin, dass die Speicherdaten des untergeordneten Prozesses den übergeordneten Prozess vollständig kopieren.
 // Es sollte auch beachtet werden, dass der vom Kindprozess von fork() ausgeführte Code nach fork() beginnt, während für den Elternprozess
 // Der Rückgabewert dieser Methode ist die ID des übergeordneten Prozesses, während für den untergeordneten Prozess der Rückgabewert dieser Methode 0 ist. Daher können der übergeordnete Prozess // und der untergeordnete Prozess durch die if-else-Anweisung jeweils unterschiedliche nachfolgende Codeausschnitte aufrufen pid = fork();

 Schalter (PID) {

  Fall -1:
   // Fork-Fehler ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
          „fork() ist beim Erstellen von \"%s\" fehlgeschlagen“, Name);
   ngx_close_channel(ngx_processes[s].channel, Zyklus->Protokoll);
   gib NGX_INVALID_PID zurück;

  Fall 0:
   // Der Zweig der Ausführung des untergeordneten Prozesses. Die Methode proc() wird hier von außen übergeben. Das heißt, die aktuelle Methode erstellt einfach einen neuen Prozess.
   // Die spezifische Prozessverarbeitungslogik wird durch den externen Codeblock definiert. Die Methode ngx_getpid () erhält die Prozess-ID des neu erstellten untergeordneten Prozesses.
   ngx_pid = ngx_getpid();
   proc(Zyklus, Daten);
   brechen;

  Standard:
   //Der übergeordnete Prozess geht hierher break;
 }

 ngx_log_error(NGX_LOG_NOTICE, Zyklus->Protokoll, 0, „Start %s %P“, Name, PID);

 // Der übergeordnete Prozess wird hierher kommen, die aktuelle PID ist die PID des neu erstellten untergeordneten Prozesses, die der übergeordnete Prozess nach fork () erhalten hat.
 ngx_processes[s].pid = pid;
 ngx_processes[s].exited = 0;

 wenn (respawn >= 0) {
  pid zurückgeben;
 }

 // Legen Sie die verschiedenen Attribute des aktuellen Prozesses fest und speichern Sie sie an der entsprechenden Position im Array ngx_processes ngx_processes[s].proc = proc;
 ngx_processes[s].data = Daten;
 ngx_processes[s].name = Name;
 ngx_processes[s].exiting = 0;

 wechseln (Respawn) {

  Fall NGX_PROCESS_NORESPAWN:
   ngx_processes[s].respawn = 0;
   ngx_processes[s].just_spawn = 0;
   ngx_processes[s].detached = 0;
   brechen;

  Fall NGX_PROCESS_JUST_SPAWN:
   ngx_processes[s].respawn = 0;
   ngx_processes[s].just_spawn = 1;
   ngx_processes[s].detached = 0;
   brechen;

  Fall NGX_PROCESS_RESPAWN:
   ngx_processes[s].respawn = 1;
   ngx_processes[s].just_spawn = 0;
   ngx_processes[s].detached = 0;
   brechen;

  Fall NGX_PROCESS_JUST_RESPAWN:
   ngx_processes[s].respawn = 1;
   ngx_processes[s].just_spawn = 1;
   ngx_processes[s].detached = 0;
   brechen;

  Fall NGX_PROCESS_DETACHED:
   ngx_processes[s].respawn = 0;
   ngx_processes[s].just_spawn = 0;
   ngx_processes[s].detached = 1;
   brechen;
 }

 wenn (s == ngx_last_process) {
  ngx_last_process++;
 }

 pid zurückgeben;
}

Die Methode ngx_spawn_process() führt schließlich einen untergeordneten Prozess mit fork() aus, um die durch ihren zweiten Parameter angegebene Rückrufmethode auszuführen. Zuvor müssen wir jedoch erklären, dass durch den Methodenaufruf socketpair() ein Paar anonymer Sockets erstellt und diese dann im Kanal-Array des aktuellen Prozesses gespeichert werden, wodurch die Erstellung des Kanal-Arrays abgeschlossen wird.

Nachdem der Worker-Prozess gestartet wurde, wird die Methode ngx_worker_process_cycle() ausgeführt. Diese Methode initialisiert zunächst den Worker-Prozess, einschließlich der Verarbeitung des geerbten Kanal-Arrays. Da sowohl der Masterprozess als auch der Workerprozess den Socket-Deskriptor enthalten, auf den das Kanal-Array verweist, müssen der Masterprozess und jeder Workerprozess im Wesentlichen nur den Deskriptor einer Seite des Arrays enthalten. Daher schließt der Arbeitsprozess während des Initialisierungsprozesses den auf der anderen Seite gespeicherten Deskriptor. In nginx behält der Masterprozess den Socket-Deskriptor einheitlich an Position 0 des Kanal-Arrays bei und schließt den Socket-Deskriptor an Position 1, während der Workerprozess den Socket-Deskriptor an Position 0 schließt und den Deskriptor an Position 1 beibehält. Wenn der Masterprozess auf diese Weise mit dem Workerprozess kommunizieren muss, muss er nur Daten in Kanal [0] schreiben, und der Workerprozess hört auf Kanal [1], um die vom Masterprozess geschriebenen Daten zu empfangen. Hier schauen wir uns zunächst den Quellcode der Initialisierungsmethode ngx_worker_process_init() des Arbeitsprozesses an:

/**
 * Hier initialisieren wir hauptsächlich den aktuellen Prozess und legen Parameter wie Priorität und Limit für geöffnete Dateien dafür fest.
 * Schließlich wird dem aktuellen Prozess eine Verbindung zum Abhören von Kanal[1] hinzugefügt, um kontinuierlich Nachrichten vom Masterprozess zu lesen und entsprechende Verarbeitungsvorgänge durchzuführen*/
statischer void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker) {
 sigset_t gesetzt;
 ngx_int_t n;
 ngx_time_t *tp;
 ngx_uint_t ich;
 ngx_cpuset_t *cpu_affinität;
 Struktur rlimit rlmt;
 ngx_core_conf_t *ccf;
 ngx_listening_t *ls;

 // Zeitzonenbezogene Informationen festlegen if (ngx_set_environment(cycle, NULL) == NULL) {
  /* fatal */
  Ausgang (2);
 }

 ccf = (ngx_core_conf_t *) ngx_get_conf(Zyklus->conf_ctx, ngx_core_module);

 // Setze die Priorität des aktuellen Prozesses if (worker >= 0 && ccf->priority != 0) {
  wenn (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          „setpriority(%d) fehlgeschlagen“, ccf->priority);
  }
 }

 // Legen Sie die Anzahl der Dateihandles fest, die der aktuelle Prozess öffnen kann, wenn (ccf->rlimit_nofile != NGX_CONF_UNSET) {
  rlmt.rlim_cur = (rlim_t) ccf->rlimit_nofile;
  rlmt.rlim_max = (rlim_t) ccf->rlimit_nofile;

  wenn (setrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          "setrlimit(RLIMIT_NOFILE, %i) fehlgeschlagen",
          ccf->rlimit_nofile);
  }
 }

 // Ändert die Begrenzung der größten Größe einer Core-Datei (RLIMIT_CORE) für Arbeitsprozesse.
 // Kurz gesagt, es legt die maximale Größe fest, die die Core-Datei verwenden kann, wenn (ccf->rlimit_core != NGX_CONF_UNSET) {
  rlmt.rlim_cur = (rlim_t) ccf->rlimit_core;
  rlmt.rlim_max = (rlim_t) ccf->rlimit_core;

  wenn (setrlimit(RLIMIT_CORE, &rlmt) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          "setrlimit(RLIMIT_CORE, %O) fehlgeschlagen",
          ccf->rlimit_core);
  }
 }

 // geteuid() gibt die Benutzer-ID zurück, die das aktuelle Programm ausführt. Dabei gibt 0 an, ob es sich um den Root-Benutzer handelt if (geteuid() == 0) {
  // Der Zweck der Methode setgid() besteht darin, die Gruppen-ID zu ändern
  wenn (setgid(ccf->group) == -1) {
   ngx_log_error(NGX_LOG_EMERG, Zyklus->Protokoll, ngx_errno,
          „setgid(%d) fehlgeschlagen“, ccf->group);
   /* fatal */
   Ausgang (2);
  }

  // initgroups() dient zum Ändern der ID der zusätzlichen Gruppe
  wenn (initgroups(ccf->Benutzername, ccf->Gruppe) == -1) {
   ngx_log_error(NGX_LOG_EMERG, Zyklus->Protokoll, ngx_errno,
          "initgroups(%s, %d) fehlgeschlagen",
          ccf->Benutzername, ccf->Gruppe);
  }

  //Ändern Sie die ID des Benutzers
  wenn (setuid(ccf->user) == -1) {
   ngx_log_error(NGX_LOG_EMERG, Zyklus->Protokoll, ngx_errno,
          „setuid(%d) fehlgeschlagen“, ccf->Benutzer);
   /* fatal */
   Ausgang (2);
  }
 }

 // Beachten Sie, dass der Worker hier für die Cache-Manager- und Cache-Loader-Prozesse -1 übergibt.
 // Gibt an, dass diese beiden Prozesse keine Nukleophilie einstellen müssen, wenn (Worker >= 0) {
  // CPU-Affinität des aktuellen Workers abrufen cpu_affinity = ngx_get_cpu_affinity(worker);

  wenn (CPU-Affinität) {
   // Legen Sie den Affinitätskern des Workers fest ngx_setaffinity(cpu_affinity, cycle->log);
  }
 }

#wenn (NGX_HAVE_PR_SET_DUMPABLE)
 wenn (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          "prctl(PR_SET_DUMPABLE) ist fehlgeschlagen");
 }

#endif

 wenn (ccf->Arbeitsverzeichnis.länge) {
  // Die Funktion von chdir() besteht darin, das aktuelle Arbeitsverzeichnis in den als Parameter übergebenen Pfad zu ändern, if (chdir((char *) ccf->working_directory.data) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          „chdir(\"%s\") ist fehlgeschlagen", ccf->working_directory.data);
   /* fatal */
   Ausgang (2);
  }
 }

 // Initialisiere den leeren Befehlssatz sigemptyset(&set);

 // ◆ SIG_BLOCK: Fügt der Signalmaske das Signal im Signalsatz hinzu, auf den der Set-Parameter zeigt.
 // ◆ SIG_UNBLOCK: Löscht das Signal im Signalsatz, auf den der Set-Parameter zeigt, aus der Signalmaske.
 // ◆ SIG_SETMASK: Setzt den Signalsatz, auf den der Set-Parameter zeigt, auf die Signalmaske.
 // Hier wird der zu blockierende Signalsatz direkt initialisiert, der Standardwert ist ein leerer Satz if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
  ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
         „sigprocmask() ist fehlgeschlagen“);
 }

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

 ls = Zyklus->Zuhören.elts;
 für (i = 0; i < Zyklus->listening.nelts; i++) {
  ls[i].vorheriges = NULL;
 }

 // Hier wird die Methode init_process() jedes Moduls aufgerufen, um das Prozessmodul für (i = 0; cycle->modules[i]; i++) { zu initialisieren.
  wenn (Zyklus->Module[i]->Init_Prozess) {
   wenn (Zyklus->Module[i]->Init_Prozess(Zyklus) == NGX_ERROR) {
    /* fatal */
    Ausgang (2);
   }
  }
 }

 // Dies dient hauptsächlich dazu, die Pipe-Handles channel[1] anderer Prozesse im aktuellen Prozess zu schließen for (n = 0; n < ngx_last_process; n++) {

  wenn (ngx_processes[n].pid == -1) {
   weitermachen;
  }

  wenn (n == ngx_process_slot) {
   weitermachen;
  }

  wenn (ngx_processes[n].channel[1] == -1) {
   weitermachen;
  }

  wenn (schließen(ngx_processes[n].channel[1]) == -1) {
   ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
          "Kanal schließen() fehlgeschlagen");
  }
 }

 // Schließe den Pipe-Handle channel[0] des aktuellen Prozesses, wenn (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
  ngx_log_error(NGX_LOG_ALERT, Zyklus->Protokoll, ngx_errno,
         "Kanal schließen() fehlgeschlagen");
 }

#wenn 0
 ngx_last_process = 0;
#endif

 // ngx_channel zeigt auf den Kanal-Handle[1] des aktuellen Prozesses, der gleichzeitig der Handle ist, der auf Nachrichten hört, die vom Master-Prozess gesendet werden.
 // In der aktuellen Methode wird zuerst ein Verbindungsobjekt für den aktuellen Handle erstellt und als Ereignis gekapselt. Dann wird das Ereignis zur // entsprechenden Ereignismodellwarteschlange hinzugefügt, um auf die Ereignisse des aktuellen Handles zu hören. Die Ereignisverarbeitungslogik ist hier hauptsächlich ngx_channel_handler()
 // Die Methode wird fortgesetzt. Die Hauptverarbeitungslogik von ngx_channel_handler besteht hier darin, einige Flags des aktuellen Prozesses entsprechend der aktuell empfangenen Nachricht zu setzen.
 // Oder aktualisiere einige zwischengespeicherte Daten, sodass in der aktuellen Ereignisschleife durch ständiges Überprüfen dieser Flags // die eigentliche Logik im Ereignisprozess verarbeitet wird. Daher ist die Verarbeitungseffizienz von ngx_channel_handler hier sehr hoch, wenn (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
              ngx_channel_handler)
   == NGX_ERROR) {
  /* fatal */
  Ausgang (2);
 }
}

Diese Methode wird hauptsächlich zum Initialisieren des Arbeitsprozesses verwendet. Hier müssen wir hauptsächlich auf die endgültige Durchquerung des Arrays ngx_processes achten, in dem die relevanten Informationen jedes Prozesses im aktuellen nginx gespeichert sind. Während des Durchlaufs werden die vom aktuellen Prozess gehaltenen Kanal[1]-Handles anderer Prozesse geschlossen, während das Kanal[0]-Handle beibehalten wird. Auf diese Weise muss der aktuelle Prozess, wenn er mit anderen Prozessen kommunizieren muss, nur Daten in den Kanal[0] des Zielprozesses schreiben. Nachdem die Durchquerung abgeschlossen ist, schließt der aktuelle Prozess seinen eigenen Kanal[0]-Handle und behält den Kanal[1]-Handle bei. Schließlich wird die Methode ngx_add_channel_event() verwendet, um ein Abhörereignis für Kanal[1] für den aktuellen Prozess hinzuzufügen. Der zweite Parameter, der beim Aufruf der Methode ngx_add_channel_event() übergeben wird, ist ngx_channel, der in der vorherigen Methode ngx_spawn_process() zugewiesen wurde und auf den Socket-Handle von Kanal[1] des aktuellen Prozesses zeigt.

In Bezug auf die Methode ngx_add_channel_event() besteht ihr Wesen darin, ein Ereignis der Struktur ngx_event_t zu erstellen und es dann dem Handle des aktuell verwendeten Ereignismodells (z. B. epoll) hinzuzufügen. Der Implementierungsquellcode dieser Methode wird hier nicht wiederholt. Wir müssen jedoch auf die Rückrufmethode achten, wenn das Ereignis ausgelöst wird, d. h. den dritten Parameter der Methode ngx_channel_handler(), der beim Aufruf der Methode ngx_add_channel_event() übergeben wird. Nachfolgend sehen Sie den Quellcode dieser Methode:

statischer void ngx_channel_handler(ngx_event_t *ev) {
 ngx_int_t n;
 ngx_channel_t ch;
 ngx_connection_t *c;

 wenn (ev->timedout) {
  ev->Zeitüberschreitung = 0;
  zurückkehren;
 }

 c = ev->Daten;

 für (;;) {

  // Lesen Sie kontinuierlich die vom Masterprozess gesendeten Nachrichten in einer unendlichen For-Schleife n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);

  // Wenn beim Lesen der Nachricht ein Fehler auftritt, ist der aktuelle Handle möglicherweise ungültig und Sie müssen die aktuelle Verbindung schließen, wenn (n == NGX_ERROR) {
   wenn (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
    ngx_del_conn(c, 0);
   }

   ngx_close_connection(c);
   zurückkehren;
  }

  wenn (ngx_event_flags & NGX_USE_EVENTPORT_EVENT) {
   wenn (ngx_add_event(ev, NGX_READ_EVENT, 0) == NGX_ERROR) {
    zurückkehren;
   }
  }

  wenn (n == NGX_AGAIN) {
   zurückkehren;
  }

  // Verarbeite die gesendete Nachricht switch (ch.command) {
   // Wenn es sich um eine Beendigungsnachricht handelt, setzen Sie das Beendigungsflag case NGX_CMD_QUIT:
    ngx_quit = 1;
    brechen;

    // Wenn die Nachricht beendet wird, setze das Beendigungsflag case NGX_CMD_TERMINATE:
    ngx_terminate = 1;
    brechen;

    // Wenn es sich um eine Wiederöffnungsnachricht handelt, setzen Sie das Wiederöffnungsflag case NGX_CMD_REOPEN:
    ngx_reopen = 1;
    brechen;

    // Wenn es sich um eine neue Prozessnachricht handelt, aktualisieren Sie die Daten an der entsprechenden Position des aktuellen ngx_processes-Arrays. Fall NGX_CMD_OPEN_CHANNEL:
    ngx_processes[ch.slot].pid = ch.pid;
    ngx_processes[ch.slot].channel[0] = ch.fd;
    brechen;

    // Wenn es sich um eine Nachricht zum Schließen des Kanals handelt, schließen Sie den Handle an der entsprechenden Position im Array ngx_processes. Fall NGX_CMD_CLOSE_CHANNEL:
    wenn (schließen(ngx_processes[ch.slot].channel[0]) == -1) {
     ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
            "Kanal schließen() fehlgeschlagen");
    }

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

In der Methode ngx_channel_handler() besteht die Hauptfunktion darin, die Daten im überwachten Socket-Handle zu lesen, und die Daten werden von einer ngx_channel_t-Struktur übertragen. Diese ngx_channel_t ist eine Struktur, die von nginx zur Kommunikation zwischen den Master- und Worker-Prozessen verwendet wird. Sie gibt den Typ des aktuell auftretenden Ereignisses und die Prozessinformationen an, bei denen das Ereignis auftritt. Das Folgende ist die Deklaration der ngx_channel_t-Struktur:

typedef-Struktur {
  // Der aktuelle Ereignistyp ngx_uint_t-Befehl;
  // Die PID des Ereignisses
  ngx_pid_t pid;
  // Der Index ngx_int_t-Slot des Prozesses, in dem das Ereignis im Array ngx_processes aufgetreten ist;
  // Der Wert des Kanaldeskriptors[0] des Prozesses, bei dem das Ereignis aufgetreten ist ngx_fd_t fd;
} ngx_channel_t;

Nachdem die Daten der ngx_channel_t-Struktur aus dem Kanal[1] des aktuellen Prozesses gelesen wurden, aktualisiert die Methode ngx_channel_handler() den Status des entsprechenden Flag-Bits entsprechend der Art des aufgetretenen Ereignisses und aktualisiert die Statusinformationen des Prozesses, bei dem das Ereignis im Array ngx_processes des aktuellen Prozesses aufgetreten ist.

Nach der Verarbeitung der vom Masterprozess gesendeten Ereignisse setzt der Workerprozess seine Schleife fort, in der er den Status der betreffenden Flags überprüft und dann basierend auf diesen Status die entsprechende Logik ausführt. Nachfolgend sehen Sie den Quellcode der Arbeitsschleife des Arbeitsprozesses:

/**
 * Geben Sie die Arbeitsschleife des Arbeitsprozesses ein */
statischer void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) {
 ngx_int_t Worker = (intptr_t) Daten;

 ngx_process = NGX_PROCESS_WORKER;
 ngx_worker = Arbeiter;

 // Initialisieren Sie den Worker-Prozess. Der Quellcode dieser Methode wird oben erklärt. ngx_worker_process_init(cycle, worker);

 ngx_setproctitle("Arbeitsprozess");

 für (;;) {

  wenn (ngx_exiting) {
   // Hier prüfen wir hauptsächlich, ob es Ereignisse in einem nicht stornierbaren Zustand gibt, d. h. ob alle Ereignisse storniert wurden. Wenn ja,
   // Es wird NGX_OK zurückgegeben. Die Logik hier kann wie folgt verstanden werden: Wenn es als ngx_exiting markiert ist, wird, wenn zu diesem Zeitpunkt noch nicht abgebrochene // Ereignisse vorhanden sind, zur folgenden Methode ngx_process_events_and_timers() gewechselt, sodass die nicht abgeschlossenen Ereignisse verarbeitet werden.
   // Gehen Sie dann in der Schleife erneut zu dieser Position und stellen Sie sicher, dass die if-Bedingung erfüllt ist, damit der Worker-Prozess beendet werden kann. if (ngx_event_no_timers_left() == NGX_OK) {
    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "wird beendet");
    ngx_worker_process_exit(Zyklus);
   }
  }

  ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "Arbeitszyklus");

  // Hier prüfen wir, ob im entsprechenden Ereignismodell ein entsprechendes Ereignis vorhanden ist, und stellen es dann zur Verarbeitung in die Warteschlange.
  // Hier ist die Kernmethode des Arbeitsprozesses zum Verarbeiten von Ereignissen ngx_process_events_and_timers(cycle);

  // Hier ist ngx_terminate eine Option, um nginx zum Herunterfahren zu zwingen. Wenn ein Befehl zum erzwungenen Herunterfahren von nginx an nginx gesendet wird, wird der aktuelle Prozess direkt beendet, wenn (ngx_terminate) {
   ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "wird beendet");
   ngx_worker_process_exit(Zyklus);
  }

  // Hier ist ngx_quit eine Option für ordnungsgemäßes Beenden. Hier wird ngx_exiting auf 1 gesetzt, um anzuzeigen, dass der aktuelle Prozess beendet werden muss.
  // Anschließend werden folgende drei Aufgaben ausgeführt:
  // 1. Fügen Sie der Ereigniswarteschlange ein Ereignis hinzu, um die aktuell aktive Verbindung zu verarbeiten, setzen Sie das Schließflag auf 1 und führen Sie die aktuelle Verarbeitungsmethode der Verbindung aus, um das Verbindungsereignis so schnell wie möglich abzuschließen.
  // 2. Schließen Sie den im aktuellen Zyklus überwachten Socket-Handle.
  // 3. Markieren Sie den Schließstatus aller aktuell inaktiven Verbindungen als 1 und rufen Sie dann ihre Verbindungsverarbeitungsmethoden auf.
  wenn (ngx_quit) {
   ngx_quit = 0;
   ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, „wird ordnungsgemäß heruntergefahren“);
   ngx_setproctitle("Arbeitsprozess wird heruntergefahren");

   wenn (!ngx_exiting) {
    ngx_exiting = 1;
    ngx_set_shutdown_timer(Zyklus);
    ngx_close_listening_sockets(Zyklus);
    ngx_close_idle_connections(Zyklus);
   }
  }

  // ngx_reopen öffnet hauptsächlich alle Nginx-Dateien erneut, z. B. das Wechseln der Nginx-Protokolldateien usw. if (ngx_reopen) {
   ngx_reopen = 0;
   ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "Protokolle erneut öffnen");
   ngx_reopen_files(Zyklus, -1);
  }
 }
}

Es ist ersichtlich, dass der Arbeitsprozess hauptsächlich die Flag-Bits verarbeitet, die sich darauf beziehen, ob nginx beendet wird, und auch die Flag-Bits verarbeitet, die angeben, ob nginx die Konfigurationsdatei erneut liest.

4. Zusammenfassung

In diesem Artikel werden zunächst die Grundprinzipien der Interaktion zwischen Master- und Worker-Prozessen erläutert. Anschließend wird ausführlich im Quellcode erklärt, wie nginx die gegenseitige Kommunikation zwischen Master- und Worker-Prozessen implementiert.

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, er wird für jedermanns Studium hilfreich sein. Ich hoffe auch, dass jeder 123WORDPRESS.COM unterstützen wird.

Das könnte Sie auch interessieren:
  • Detaillierte Erläuterung zweier Möglichkeiten zur Implementierung der Sitzungspersistenz im Nginx-Reverse-Proxy
  • Implementierung der Sitzungsverwaltung mit Nginx+Tomcat
  • nginx + redis realisiert Sitzungsfreigabe
  • nginx+tomcat implementiert Lastenausgleich und verwendet Redis-Sitzungsfreigabe
  • Beispiel einer Methode zur gemeinsamen Sitzungskonfiguration in Nginx
  • Nginx-Lastausgleich für gemeinsam genutzte Sitzungen an mehreren Standorten
  • Detaillierte Erläuterung der zugrunde liegenden Implementierungsmethode des Nginx-Polling-Algorithmus
  • Nginx leitet dynamisch an Upstream weiter, entsprechend dem Pfad in der URL
  • Implementierungsschritte von nginx zum Erstellen einer Python-basierten Webumgebung
  • Analyse der Lösung für das Problem der gemeinsamen Nutzung von Nginx-Sitzungen

<<:  So erlauben Sie den externen Netzwerkzugriff auf MySQL und ändern das MySQL-Kontokennwort

>>:  Eine kurze Diskussion über die Leistungsprobleme des MySQL-Paging-Limits

Artikel empfehlen

Detaillierte Erklärung der HTML-Style-Tags und der zugehörigen CSS-Referenzen

HTML-Style-Tag Stil-Tag - Verwenden Sie dieses Ta...

Docker startet Redis und legt das Passwort fest

Redis verwendet das Apline-Image (Alps) von Redis...

Detaillierte Erklärung des Prinzips zum Erstellen von Tomcat in Eclipse

Beim Erstellen eines Tomcat-Servers auf einem lok...

JS implementiert einen einfachen TodoList-Effekt (Notizblock)

Das Notizblockprogramm wird mithilfe der drei wic...

Lösung für mehrere 302-Antworten im Nginx-Proxy (Nginx Follow 302)

Proxying mehrerer 302er mit proxy_intercept_error...

Nexus verwendet Nginx-Proxy zur Unterstützung des HTTPS-Protokolls

Hintergrund Alle Unternehmenswebsites müssen das ...

Detaillierte Erläuterung der Grundkenntnisse zur Front-End-Komponentenbildung

Inhaltsverzeichnis Grundlegende Konzepte von Komp...

Natives JS zur Implementierung der Dropdown-Box-Auswahlkomponente

In diesem Artikelbeispiel wird der spezifische JS...

React+Koa-Beispiel zur Implementierung des Datei-Uploads

Inhaltsverzeichnis Hintergrund Serverabhängigkeit...

Detaillierte Erklärung zur Verwendung von HTML-Header-Tags

HTML besteht aus zwei Teilen: Kopf und Text ** Da...

Lösung für „Ubuntu kann keine Verbindung zum Netzwerk herstellen“

Effektive Lösung für Ubuntu, wenn in einer virtue...

Ein Artikel, der Ihnen zeigt, wie Sie Vue-Komponenten erstellen und verwenden

Inhaltsverzeichnis 1. Was ist eine Komponente? 2....

Ergänzender Artikel zur Front-End-Performance-Optimierung

Vorwort Ich habe mir die zuvor veröffentlichten A...