SOURCE version: Php5.6.15
SOURCE directory: SAPI/FPM/FPM
Description: The main function of the source code is directly annotated above
=============>>start<<================================
Main process signal initialization, processing according to the type of signal received
int Fpm_signals_init_main ()/* {{{* *
{
struct Sigaction Act; Create sigaction structure and bind signal handle function
Create Sockpair, multi-channel IO multiplexing
if (0 > Socketpair (Af_unix, sock_stream, 0, sp)) {
Zlog (Zlog_syserror, "Failed to init Signals:socketpair ()");
return-1;
}
Set non-blocking
if (0 > fd_set_blocked (sp[0], 0) | | 0 > fd_set_blocked (sp[1], 0)) {
Zlog (Zlog_syserror, "Failed to init signals:fd_set_blocked ()");
return-1;
}
Set FD to hold state, do not close
if (0 > Fcntl (sp[0], F_SETFD, fd_cloexec) | | 0 > fcntl (sp[1], F_SETFD, fd_cloexec)) {
Zlog (Zlog_syserror, "Falied to Init signals:fcntl (F_SETFD, Fd_cloexec)");
return-1;
}
memset (&act, 0, sizeof (ACT));
Act.sa_handler = Sig_handler; Set up signal processing functions
Sigfillset (&act.sa_mask); Initializing the signal set
Adding a set of processed signals
if (0 > Sigaction (SIGTERM, &act, 0) | |
0 > Sigaction (SIGINT, &act, 0) | |
0 > Sigaction (SIGUSR1, &act, 0) | |
0 > Sigaction (SIGUSR2, &act, 0) | |
0 > Sigaction (SIGCHLD, &act, 0) | |
0 > Sigaction (sigquit, &act, 0)) {
Zlog (Zlog_syserror, "Failed to init signals:sigaction ()");
return-1;
}
return 0;
}
/* }}} */
Signal processing function, according to the corresponding signal, the corresponding character is written to Sp[1]
static void Sig_handler (int signo)/* {{{* * *
{
static const char Sig_chars[nsig + 1] = {
[SIGTERM] = ' T ',
[SIGINT] = ' I ',
[SIGUSR1] = ' 1 ',
[SIGUSR2] = ' 2 ',
[Sigquit] = ' Q ',
[SIGCHLD] = ' C '
};
char s;
int Saved_errno;
if (fpm_globals.parent_pid! = Getpid ()) {
/* Prevent a signal race condition when child process
There is not set up it s own signal handler yet */
Return
}
Saved_errno = errno;
s = Sig_chars[signo];
Write (sp[1], &s, sizeof (s));
errno = Saved_errno;
}
/* }}} */
Child process signal initialization, processed according to the type of signal received
int Fpm_signals_init_child ()/* {{{* *
{
struct Sigaction Act, ACT_DFL;
memset (&act, 0, sizeof (ACT));
memset (&ACT_DFL, 0, sizeof (ACT_DFL));
Act.sa_handler = &sig_soft_quit; Call signal processing function, processing only sigquit signal
Act.sa_flags |= Sa_restart;
Act_dfl.sa_handler = SIG_DFL; Default Signal Processing method
Close the socket pair
Close (sp[0]);
Close (sp[1]);
if (0 > Sigaction (SIGTERM, &ACT_DFL, 0) | |
0 > Sigaction (SIGINT, &ACT_DFL, 0) | |
0 > Sigaction (SIGUSR1, &ACT_DFL, 0) | |
0 > Sigaction (SIGUSR2, &ACT_DFL, 0) | |
0 > Sigaction (SIGCHLD, &ACT_DFL, 0) | |
0 > Sigaction (sigquit, &act, 0)) {
Zlog (Zlog_syserror, "Failed to init child signals:sigaction ()");
return-1;
}
return 0;
}
/* }}} */
Sub-process sigquit signal processing function
static void Sig_soft_quit (int signo)/* {{{* * *
{
int saved_errno = errno;
/* Closing FASTCGI listening socket would force fcgi_accept () exit immediately */
Close (0);
if (0 > socket (af_unix, sock_stream, 0)) {
Zlog (Zlog_warning, "failed to create a new socket");
}
Fpm_php_soft_quit ();
errno = Saved_errno;
}
/* }}} */
void Fpm_event_loop (int err)/* {{{* * *
{
static struct fpm_event_s signal_fd_event;
/* Sanity Check */
if (fpm_globals.parent_pid! = Getpid ()) {
Return
}
Create an FD collection, put fd[0] into the FD set to listen for FD changes, and set the corresponding processing function fpm_got_signal
Fpm_event_set (&signal_fd_event, FPM_SIGNALS_GET_FD (), Fpm_ev_read, &fpm_got_signal, NULL);
Fpm_event_add (&signal_fd_event, 0);
/* Add timers */
if (Fpm_globals.heartbeat > 0) {
Fpm_pctl_heartbeat (NULL, 0, NULL);
}
if (!err) {
Fpm_pctl_perform_idle_server_maintenance_heartbeat (NULL, 0, NULL);
Zlog (zlog_debug, "%zu bytes has been reserved in SHM", fpm_shm_get_size_allocated ());
Zlog (Zlog_notice, "ready to handle connections");
#ifdef HAVE_SYSTEMD
Fpm_systemd_heartbeat (NULL, 0, NULL);
#endif
}
while (1) {
struct fpm_event_queue_s *q, *q2;
struct Timeval ms;
struct Timeval tmp;
struct Timeval now;
unsigned long int timeout;
int ret;
/* Sanity Check */
if (fpm_globals.parent_pid! = Getpid ()) {
Return
}
Fpm_clock_get (&now);
Timerclear (&MS);
/* Search in the timeout queue for the next timer to trigger */
Q = Fpm_event_queue_timer;
while (q) {
if (!timerisset (&ms)) {
ms = q->ev->timeout;
} else {
if (timercmp (&q->ev->timeout, &ms, <)) {
ms = q->ev->timeout;
}
}
Q = q->next;
}
/* 1s timeout if none has been set */
if (!timerisset (&ms) | | timercmp (&MS, &now, <) | | timercmp (&MS, &now, = =)) {
timeout = 1000;
} else {
Timersub (&ms, &now, &tmp);
Timeout = (TMP.TV_SEC * +) + (tmp.tv_usec/1000) + 1;
}
ret = module->wait (FPM_EVENT_QUEUE_FD, timeout);
/* is a child, nothing to does here */
if (ret = =-2) {
Return
}
if (Ret > 0) {
Zlog (Zlog_debug, "event module triggered%d events", ret);
}
/* Trigger Timers */
Q = Fpm_event_queue_timer;
while (q) {
Fpm_clock_get (&now);
if (Q->ev) {
if (timercmp (&now, &q->ev->timeout, >) | | timercmp (&now, &q->ev->timeout, = =)) {
Fpm_event_fire (Q->ev);
/* Sanity Check */
if (fpm_globals.parent_pid! = Getpid ()) {
Return
}
if (Q->ev->flags & fpm_ev_persist) {
Fpm_event_set_timeout (Q->ev, now);
} else {/* Delete the event */
q2 = q;
if (Q->prev) {
Q->prev->next = q->next;
}
if (Q->next) {
Q->next->prev = q->prev;
}
if (q = = Fpm_event_queue_timer) {
Fpm_event_queue_timer = q->next;
if (Fpm_event_queue_timer) {
Fpm_event_queue_timer->prev = NULL;
}
}
Q = q->next;
Free (q2);
Continue
}
}
}
Q = q->next;
}
}
}
/* }}} */
static void Fpm_got_signal (struct fpm_event_s *ev, short which, void *arg)/* {{* * *
{
char c;
int res, RET;
int FD = ev->fd;
do {
do {
res = read (FD, &c, 1); Take the value from fd[0] and put the variable C
} while (res = =-1 && errno = = eintr);
if (res <= 0) {
if (Res < 0 && errno! = Eagain && errno! = Ewouldblock) {
Zlog (Zlog_syserror, "unable to read from the signal pipe");
}
Return
}
Depending on the signal C, different logic is executed
Switch (c) {
Sub-signal exits or pauses
Case ' C ':/* SIGCHLD */
Zlog (Zlog_debug, "received SIGCHLD");
Fpm_children_bury ();
Break
Receive signal to terminate process
Case ' I ':/* SIGINT */
Zlog (Zlog_debug, "received SIGINT");
Zlog (Zlog_notice, "terminating ...");
Fpm_pctl (fpm_pctl_state_terminating, Fpm_pctl_action_set);
Break
Ditto
Case ' T ':/* SIGTERM */
Zlog (Zlog_debug, "received SIGTERM");
Zlog (Zlog_notice, "terminating ...");
Fpm_pctl (fpm_pctl_state_terminating, Fpm_pctl_action_set);
Break
Process Exit signal
Case ' Q ':/* sigquit */
Zlog (Zlog_debug, "received Sigquit");
Zlog (Zlog_notice, "finishing ...");
Fpm_pctl (fpm_pctl_state_finishing, Fpm_pctl_action_set);
Break
Re-open log signal
Case ' 1 ':/* SIGUSR1 */
Zlog (Zlog_debug, "received SIGUSR1");
if (0 = = Fpm_stdio_open_error_log (1)) {
Zlog (zlog_notice, "error log file re-opened");
} else {
Zlog (Zlog_error, "Unable to re-opened ERROR log file");
}
ret = Fpm_log_open (1);
if (ret = = 0) {
Zlog (zlog_notice, "Access log file re-opened");
} else if (ret = =-1) {
Zlog (Zlog_error, "Unable to re-opened access log file");
}
/* Else no access log is set */
Break
Reload Signal
Case ' 2 ':/* SIGUSR2 */
Zlog (Zlog_debug, "received SIGUSR2");
Zlog (Zlog_notice, "reloading in progress ...");
Fpm_pctl (fpm_pctl_state_reloading, Fpm_pctl_action_set);
Break
}
if (fpm_globals.is_child) {
Break
}
} while (1);
Return
}
/* }}} */
Below the processing of various signals to do a bit of analysis
Sub-signal exit or pause is send signal sigchld, the most understand will call Fpm_children_bury function, in FPM_CHILDREN.C
void Fpm_children_bury ()/* {{{* * *
{
int status;
pid_t pid;
struct fpm_child_s *child;
The child process ends, returns the PID execution loop, or exits
while (PID = Waitpid ( -1, &status, Wnohang | wuntraced)) > 0) {
Char buf[128];
int severity = Zlog_notice;
int restart_child = 1;
Child = Fpm_child_find (PID); Gets the retired child process
Normal exit
if (wifexited (status)) {
snprintf (buf, sizeof (BUF), "with code%d", Wexitstatus (status));
/* If it ' s been killed because of dynamic process management
* don ' t restart it automaticaly
*/
if (child && Child->idle_kill) {
Restart_child = 0;
}
if (wexitstatus (status) = Fpm_exit_ok) {
Severity = zlog_warning;
}
Receive Exit signal
} else if (wifsignaled (status)) {
const char *signame = FPM_SIGNAL_NAMES[WTERMSIG (status)];
const char *have_core = wcoredump (status)? "-Core dumped": "";
if (Signame = = NULL) {
Signame = "";
}
snprintf (buf, sizeof (BUF), "on signal%d (%s%s)", Wtermsig (status), Signame, Have_core);
/* If it ' s been killed because of dynamic process management
* don ' t restart it automaticaly
*/
if (Child && Child->idle_kill && wtermsig (status) = = Sigquit) {
Restart_child = 0;
}
if (wtermsig (status) = Sigquit) {/* Possible request loss */
Severity = zlog_warning;
}
Pause Exit Signal
} else if (wifstopped (status)) {
Zlog (Zlog_notice, "child%d stopped for tracing", (int) PID);
if (child && child->tracer) {
Master process recovers child process through tracer
Child->tracer (child);
}
Continue
}
if (child) {
struct fpm_worker_pool_s *wp = child->wp;
struct Timeval TV1, TV2;
Fpm_child_unlink (child);
Fpm_scoreboard_proc_free (Wp->scoreboard, child->scoreboard_i);
Fpm_clock_get (&TV1);
Timersub (&TV1, &child->started, &TV2);
if (restart_child) {
if (!fpm_pctl_can_spawn_children ()) {
Severity = Zlog_debug;
}
Zlog (Severity, "[Pool%s] Child%d exited%s after%ld.%0 6d seconds from Start ", Child->wp->config->name, (int) PID, buf, Tv2.tv_sec, (int) tv2.tv_usec);
} else {
Zlog (Zlog_debug, "[Pool%s] Child%d have been killed by the process management after%ld.%0 6d seconds from Start ", Child->wp->config->name, (int) PID, tv2.tv_sec, (int) tv2.tv_usec);
}
Fpm_child_close (Child, 1/* in Event_loop */);
Fpm_pctl_child_exited (); Main process exit
The number of abnormally exited threads is greater than the configuration item, reload
if (Last_faults && wtermsig (status) = = SIGSEGV | | Wtermsig (status) = = Sigbus)) {
time_t now = tv1.tv_sec;
int restart_condition = 1;
int i;
last_faults[fault++] = now;
if (fault = = Fpm_global_config.emergency_restart_threshold) {
fault = 0;
}
for (i = 0; i < Fpm_global_config.emergency_restart_threshold; i++) {
if (Now-last_faults[i] > Fpm_global_config.emergency_restart_interval) {
restart_condition = 0;
Break
}
}
if (restart_condition) {
Zlog (zlog_warning, "Failed processes threshold (%d in%d sec.) is reached, initiating reload", Fpm_global_config.emergency_ Restart_threshold, Fpm_global_config.emergency_restart_interval);
Fpm_pctl (fpm_pctl_state_reloading, Fpm_pctl_action_set);
}
}
if (restart_child) {
If the child process exits abnormally, fork a
Fpm_children_make (WP, 1/* in event loop */, 1, 0);
if (fpm_globals.is_child) {
Break
}
}
} else {
Zlog (Zlog_alert, "Oops, Unknown Child (%d) exited%s. Please open a bug report (https://bugs.php.net). ", PID, BUF);
}
}
}
/* }}} */
PHP uses most of the reload operation, corresponding to the SIGUSR2 signal, corresponding to the processing function fpm_pctl
void Fpm_pctl (int new_state, int action)/* * {{{*/
{
Switch (action) {
Case fpm_pctl_action_set:
if (fpm_s Tate = = new_state) {/* already in progress-just ignore duplicate signal */
return;
}
//fpm_state
Switch (fpm_state) {/* Check which states can be overridden */
Case Fpm_pctl_state_normal:
/* ' Normal ' can is overridden by any and state */
break;
Case fpm_pctl_state_reloading:
/* ' reloading ' can is overridden by ' finishing ' */
if (new_state = = Fpm_pctl_state_f inishing) break;
Case fpm_pctl_state_finishing:
/* ' reloading ' and ' finishing ' can is overridden by ' terminating ' */
if (new_sta Te = = fpm_pctl_state_terminating) break;
Case fpm_pctl_state_terminating:
/No can override ' terminating ' state */
Zlog (Zlog_debug, "not Switching to '%s ', because already in '%s ' state ',
Fpm_state_names[new_state], fpm_state_names[fpm_state]);
return;
}
fpm_signal_sent = 0;
Fpm_state = new_state;
Zlog (Zlog_debug, "Switching to '%s ' state", fpm_state_names[fpm_state]);
/* Fall Down */
Because there is no break statement, Fpm_pctl_action_next will execute
Case Fpm_pctl_action_timeout:
Fpm_pctl_action_next ();
Break
Case fpm_pctl_action_last_child_exited:
Fpm_pctl_action_last ();
Break
}
}
/* }}} */
/** function Description
* * Send a signal to all processes
Register a timer, re-send the signal at the end of the child process, and write C to sp[1] if the normal end, execute Fpm_children_bury
* Final call to fpm_pctl_child_exited until the end of the last child process.
**/
static void Fpm_pctl_action_next ()/* {{{* * *
{
int sig, timeout;
if (!fpm_globals.running_children) {
Fpm_pctl_action_last ();
}
if (fpm_signal_sent = = 0) {
if (fpm_state = = fpm_pctl_state_terminating) {
sig = SIGTERM;
} else {
sig = Sigquit;
}
Timeout = fpm_global_config.process_control_timeout;
} else {
if (fpm_signal_sent = = Sigquit) {
sig = SIGTERM;
} else {
sig = SIGKILL;
}
Timeout = 1;
}
Fpm_pctl_kill_all (SIG); Send Signal
Fpm_signal_sent = sig;
Fpm_pctl_timeout_set (timeout); Set the timeout function and eventually call Fpm_pctl (Fpm_pctl_state_unspecified, fpm_pctl_action_timeout) to continue signaling
}
/* }}} */
static void Fpm_pctl_action (struct fpm_event_s *ev, short which, void *arg)/* {{* * *
{
Fpm_pctl (fpm_pctl_state_unspecified, fpm_pctl_action_timeout);
}
/* }}} */
If the child process exits normally, it will want to sp[1] write the sigchild signal, perform a new round of loops, execute the fpm_children_bury-->fpm_pctl_child_exited
When all child processes are processed, the fpm_pctl_action_last is called and the reload operation is performed
static void Fpm_pctl_action_last ()/* {{{* * *
{
Switch (fpm_state) {
Case fpm_pctl_state_reloading://Perform reload operation
Fpm_pctl_exec ();
Break
Case fpm_pctl_state_finishing:
Case fpm_pctl_state_terminating:
Fpm_pctl_exit ();
Break
}
}
static void Fpm_pctl_exec ()/* {{{* * *
{
Zlog (Zlog_notice, "RELOADING:EXECVP (\"%s\ ", {\"%s\ ""
"%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
"%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s"
"})",
Saved_argv[0], saved_argv[0],
Optional_arg (1),
Optional_arg (2),
Optional_arg (3),
Optional_arg (4),
Optional_arg (5),
Optional_arg (6),
Optduiional_arg (7),
Optional_arg (8),
Optional_arg (9),
Optional_arg (10)
);
Fpm_cleanups_run (fpm_cleanup_parent_exec);
EXECVP (Saved_argv[0], saved_argv); Process substitution, EXECVP will empty the old Process code area, data, stack, reload
Zlog (Zlog_syserror, "failed to RELOAD:EXECVP () failed");
Exit (Fpm_exit_software);
}
/* }}} */
The above source tracing, finally summarizes the reload logic process, its signal processing logic is no longer recalled.
1.FPM main process received SIGUSR2 signal, callback function to sp[1] Write 2 this character;
2.FPM Fpm_event_loop Hear data write (IO multiplexing) call callback function fpm_got_signal read signal identifier 2;
3. Execute fpm_pctl--> fpm_pctl_action_next, signal each sub-process and register a timer;
4. If the child process exits timeout, the main process will re-signal, normal exit the main process received sigchild signal, write identifier c corresponding to Sigchild;
5. Repeat 2, remove the C signal identifier to execute fpm_children_bury-->fpm_pctl_child_exited;
6. After all child processes are finished, enter Fpm_pctl_action_last interrupt reload operation, mainly call EXECVP to create a new process (Execvp execution method is code space, data space, stack substitution);
Analysis of source code of operation mechanism of PHP5.6.15-FPM