요약: 다시 로드하면 절전 모드가 일찍 종료되므로 fpm의 다시 로드 작업을 구현하는 방법을 살펴보았습니다.
php-fpm reload process
이 글은 PHP7.0 fpm에서 분석되었으며, process_control_timeout이 0이 아닙니다.
Restart 신호
우선, fpm의 reload 작업이 실제로 USR2 신호를 fpm 프로세스에 보내는 것을 알 수 있습니다.
fpm의 마스터 프로세스에서 신호 처리 기능은 다음을 통해 등록됩니다.
int fpm_signals_init_main() /* {{{ */{ struct sigactionact; // 。。。。。。 memset(&act, 0, sizeof(act)); act.sa_handler = sig_handler; sigfillset(&act.sa_mask); 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; }/* }}} */
간단히 를 통해 모든 신호를 차단하도록 설정한 다음 sigaction을 통해 해당 신호 처리 기능을 설정합니다.
fpm을 다시 로드하면 systemctl은 USR2 신호를 fpm의 마스터 프로세스에 보내고 다음 함수를 실행합니다.
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; // *** s = sig_chars[signo]; zend_quiet_write(sp[1], &s, sizeof(s)); errno = saved_errno; }/* }}} */
핵심은 zend_quiet_write입니다. sig_handler 함수는 문자열 2를 sp[1]에 씁니다.
여기서 sp[0] 및 sp[1]은 를 통해 생성된 로컬 소켓이라는 점에 유의해야 합니다.
마스터가 다시 시작됩니다. 신호가 발생하면 이전 신호 처리 기능이 호출되지만 프로그램의 기본 논리는 여전히 중단되지 않습니다. 그러면 fpm 마스터 프로세스는 다시 로드되는 것을 어떻게 알 수 있습니까?
답은 마스터 프로세스의 이벤트 루프인 에 있습니다.
루핑하기 전에 sp[0]을 사용하여 fpm_event_s 구조체를 청취 fd에 추가해야 합니다.
int fpm_event_set(struct fpm_event_s *ev, int fd, int flags, void (*callback)(struct fpm_event_s *, short, void *), void *arg) /* {{{ */{ if (!ev || !callback || fd < -1) { return -1; } memset(ev, 0, sizeof(struct fpm_event_s)); ev->fd = fd; ev->callback = callback; ev->arg = arg; ev->flags = flags; return 0; }/* }}} */
그런 다음 코드의 ev인 fpm_event_s 구조체를 청취 fd에 추가합니다.
사실 이 추가 프로세스는 fpm의 다양한 비동기 모델과도 관련이 있습니다(모두 fpm_event_module_s에 해당하는 add 메소드로 구현됨). 예를 들어 전체 ev 매개변수는 epoll_event의 data.ptr에 배치됩니다. (설문 추가에 대해서는 를 참조하세요.)
모든 fd가 추가된 후(물론 신호 관련 fd뿐만 아니라) 이벤트가 오기를 기다리는 데 사용할 수 있습니다. (epoll과 poll도 각각 wait 메소드를 구현합니다.)
좋아요, sig_handler로 돌아가서 문자열 2를 sp[1]에 씁니다. wait 메소드는 신호를 수신하고 해당 ev를 가져와 호출합니다. 실제로는 다음과 같이 호출됩니다.
static void fpm_got_signal(struct fpm_event_s *ev, short which, void *arg) /* {{{ */{ char c; int res, ret; int fd = ev->fd; do { res = read(fd, &c, 1); switch (c) { // 。。。。。。 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; }/* }}} */
문자열 2를 수신하면
fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET)
를 실행합니다. 실제로는 다음과 같습니다.
void fpm_pctl(int new_state, int action) /* {{{ */{ switch (action) { case FPM_PCTL_ACTION_SET : //。。。。。。 fpm_signal_sent = 0; fpm_state = new_state; zlog(ZLOG_DEBUG, "switching to '%s' state", fpm_state_names[fpm_state]); /* fall down */ case FPM_PCTL_ACTION_TIMEOUT : fpm_pctl_action_next(); break; //。。。。。 } }/* }}} */
즉, fpm_state를 FPM_PCTL_STATE_RELOADING 이후로 설정하면 중단이 없고 실행이 계속됩니다.
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); fpm_signal_sent = sig; fpm_pctl_timeout_set(timeout); } /* }}} */
즉, SIGQUIT 신호가 모든 하위 프로세스로 전송됩니다.
여기에 또 다른 것이 있는데, 이에 대해서는 나중에 논의하겠습니다.
자식 프로세스가 신호를 처리합니다. 상위 프로세스가 신호 전송을 마친 후에는 하위 프로세스가 이를 처리할 차례입니다.
하위 프로세스는 처리를 위해 sig_soft_quit에만 넘겨질 수 있습니다. 하위 프로세스가 초기화된 후 SIGQUIT 신호가 수신되고 sig_soft_quit에 의해 처리됩니다. 최종 호출 처리:
void fcgi_terminate(void){ in_shutdown = 1; }
는 in_shutdown을 1로 설정하는 것입니다.
하위 프로세스가 종료됩니다. 하위 프로세스의 루프 본문은 in_shutdown에 더 많은 판단이 포함된 fcgi_accept_request에 있습니다. 1이면 바로 종료됩니다.
Timeout 처리는 앞서 언급했습니다. 다음 작업이 수행되었습니다.
fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_TIMEOUT);
이 조건에서 은(는) 하위 프로세스를 직접 종료했습니다.
수면이 방해되는 이유는 무엇인가요? 시스템이 sleep을 호출하는 것을 볼 수 있습니다(php_sleep은 sleep의 매크로입니다):
/* {{{ proto void sleep(int seconds) Delay for a given number of seconds */PHP_FUNCTION(sleep) { zend_longnum; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &num) == FAILURE) { RETURN_FALSE; } php_sleep((unsigned int)num); }/* }}} */
sleep 함수가 실행되면 이때 프로세스의 상태는 S입니다.
interruptiblesleep 이때 신호가 트리거되면, 신호는 우리의 것과 같이 즉시 처리됩니다. 방금 언급한 SIGQUIT 이후에 절전 모드가 완료되었음을 알았습니다.
내가 썼기 때문에:
<b>sleep</b>() makesthecallingthreadsleepuntil <i>seconds</i> secondshave elapsedor a signalarriveswhichis not ignored.
주의해야 할 점은 신호가 절전 모드를 중단하더라도 절전 모드를 건너뛰고 실행을 계속한다는 것입니다.
위 내용은 php-fpm 다시 로드 프로세스의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!