fastcgi main
main函数里
当kill -TERM pid 时, http://redfoxli.github.io/php-fpm-signals.html 这篇文章 说是
1)master主进程接收到sigterm信号,并执行回调函数sig_handler,往sp[1]写字符T, 事先sp[0]已经写到epoll_ctl里,当执行epoll_wait时,从sp[0]读出T
2)执行相应的回调用函数fpm_got_signal,遍历进程池中每个进程,执行函数kill(子进程号, sigterm),并设置定时器,设置超时时间(也是写到epoll_ctl里), 就是初始化一个结构体, 设置当前时间加1s(默认),即 当前时间之后的1s就过期 放到定时器队列里(链表),
3)假设在规定的超时时间内,子进程结束了工作,并返回了sigchild信号,master主进程使用waitpid捕获了该信号,根据相应信息,判断子进程是意外退出,还是正常退出
如果在规定的超时时间内,子进程没有返回sigchild信号, 就触发了定时器,master主进程就向各个子进程执行 kill(子进程号, sigkill),sigkill是无条件执行,子进程必须退出,同时向master主进程返回sigchild信号,同时master主进程也要用waitpid函数判断相应信息
4)子进程向master主进程发送sigchild信号,主进程收到后,调用sig_handler函数,向sp[1]中写入C, 在epoll_wait中的sp[0]中读取C, 通过waitpid判断子进程退出状态, 当处理完最后一个子进程后,删除pid文件,主进程也退出了
这里有个疑问,sp[0]和sp[1]只是master主进程在使用,worker没有使用,感觉多此一兴趣
因为当master主进程接收到sigterm信号后,执行其回调函数sig_handler,然后马上遍历各个子进程,执行kill(子进程号, sigterm) 也就是执行第2,3步
后来看了这篇文章, 大意是说避免同时执行信号的回调函数 和IO事件的业务逻辑,我猜测了下,如果按照上面的方法,当接收到sigterm信号时, 除了执行相应的回调函数外,此时还要处理epoll_wait中的io事件,如果有的话,但还是不太明白?
php关于进程池的文章,参考这里
php脚本执行时间超时request_terminame_timeout 参考这里
nginx 502 bad gateway 要么是上面设置参数为0,或太大,要么是php-fpm的listen中的backlog设置太小,以至于nginx的请求无法进入php-fpm的全连接队列
nginx 504 timeout listen中的backlog设置太大了,等php-fpm处理完,向nginx的进程写数据时,nginx的超时时间到了,就断开了连接
php-fpm 中 listen 中的 backlog 详见这里
php-fpm 中关于信号的处理,详见这里,讲的很好
信号事件的处理
1)信号是指用户输入的sigquit,sigterm,sigint,以及子进程结束后向父进程返回的sigchild信号
a)利用socketpair创建全双工管道sp[2],这个管道通常来说是父子进程使用,但在这里,是父进程自己使用
b)利用sigaction设置信号以及相应的回调函数, 回调函数就是将各个信号的首个字母写到sp[1]里
c)利用pipe创建两个管道fd_stdout[2]和fd_stderr[2],用于子进程向父进程传递log数据
d)建socket套接字sfd,bind(),listen()
e)efd=epoll_create(sfd);epoll_ctl(efd,EPOLL_ADD,sfd,events);
f)epoll_ctl(efd,EPOLL_ADD,sp[0]);
2) io操作,参考这里
在主进程内完成
a)pipe(fd_stdout);pipe(fd_stderr);
b)epoll_ctl(efd,EPOLL_ADD,fd_stdout[0]); epoll_ctl(efd,EPOLL_ADD,fd_stderr[0]); 并注册回调函数 fpm_stdio_child_said ( 因为使用了 epoll 中的 边缘触发方式 ,和水平触发相比 ,需要我们不停的读、写,只有当状态从unread到read或unwrite到write的时候, 内核才会通知我们,而水平触发方式,只要缓冲区中有数据,内核会一直通知我们,但这样浪费 详见这里 这里)
见这个图
关于php-fpm的关闭有两个方法 参考这里
kill -TERM pid 和
kill -QUIT pid
当使用第一种方法时:
主进程向sp[1]里写入了字符 T,通过 epoll_wait知道sp[0]里有数据可读,调用相应的回调函数,发现是T,向各个子进程发送kill(子进程pid,SIGTERM),并且设定定时器(假设当前时间为2017-02-12 11:14:00,那么超时时间为2017-02-12 11:15:00),并放到定时器队列里,因为有许多的定时器
1)在这一分钟内,如果子进程结束自己的工作,向父进程发送了sigchild信号,父进程接收此信号后,向 sp[0]里写入字符C,通过epoll_wait得知sp[1]可读,并调用相应回调函数,通过C,进入函数fpm_children_bury,这里主进程通过waitpid得到子进程退出的原因,这里是通过信息导致的退出,这时有一个表示子进程个数的全局变量,这时要减1,就是说每waitpid等到一个子进程,这个全局变量就减1,当为0时,就将主进程的pid文件删除掉
2)当当前时间大于或等于2017-02-12 11:15:00时候,向各个子进程,执行kill(子进程pid,SIGKILL);这个时候子进程会放下手头工作,无条件的结束,接着执行1的操作
当使用第二种方法时
主进程向sp[1]里写入了字符 Q,通过 epoll_wait知道sp[0]里有数据可读,调用相应的回调函数,发现是Q,向各个子进程发送kill(子进程pid,SIGQUIT),并且设定定时器(假设当前时间为2017-02-12 11:14:00,那么超时时间为2017-02-12 11:15:00),并放到定时器队列里,因为有许多的定时器
1)在这一分钟内,如果子进程结束自己的工作,向父进程发送了sigchild信号,父进程接收此信号后,向 sp[0]里写入字符C,通过epoll_wait得知sp[1]可读,并调用相应回调函数,通过C,进入函数fpm_children_bury,这里主进程通过waitpid得到子进程退出的原因,这里是通过信息导致的退出,这时有一个表示子进程个数的全局变量,这时要减1,就是说每waitpid等到一个子进程,这个全局变量就减1,
然后执行fpm_pctl_child_exited ,当fpm_state等于FPM_PCTL_STATE_NORMAL时,这个函数就退出 ,但这个fpm_state为FPM_PCTL_STATE_FINISHING(也就是quit), 所以会判断 如果记录当前子进程个数那个全局变量为0时 ,且当前statie为terminate或finishing时 就将主进程的pid文件删除掉
2)当当前时间大于或等于2017-02-12 11:15:00时候,向各个子进程,执行kill(子进程pid,SIGTERM);这个时候子进程会放下手头工作,无条件的结束,接着执行1的操作
第二种方法是优雅的退出,第一种在发送SIGKILL时,可能子进程还没有完成工作
php-fpm某个子进程工作超时重启
php-fpm中当某个子进程执行时间大于request_terminate_timout时(终止处理),父进程发信号SIGTERM给子进程,然后再fork一个新的子进程
主进程向sp[1]里写入了字符 T,通过 epoll_wait知道sp[0]里有数据可读,调用相应的回调函数,发现是Q,向各个子进程发送kill(子进程pid,SIGTERM),不再设置定时器
1)子进程结束自己的工作,向父进程发送了sigchild信号,父进程接收此信号后,向 sp[0]里写入字符C,通过epoll_wait得知sp[1]可读,并调用相应回调函数,通过C,进入函数fpm_children_bury,这里主进程通过waitpid得到子进程退出的原因,这里是通过信息导致的退出,这时有一个表示子进程个数的全局变量,这时要减1,就是说每waitpid等到一个子进程,这个全局变量就减1
然后执行fpm_pctl_child_exited ,当fpm_state等于FPM_PCTL_STATE_NORMAL时,这个函数就退出, 这里是退出的,然后重新fork一个子进程 当为0时,就将主进程的pid文件删除掉,假设子进程个数为3,其中一个服务的子进程超时时,先关闭,所以这里的全局变量为2,还走不到这里,走到了fpm_children_make这里,执行fpm_pctl_can_spawn_children,判断return fpm_state 是否等于 FPM_PCTL_STATE_NORMAL,因为这里的state就是NORMAL,所以顺利的fork;如果是主进程收到SIGQUIT的时候,其实也会走到fpm_pctl_can_spawn_children,在判断state的时候,由于state的状态为finishing,所以不再fork
php-fpm某个子进程slow_log
设置最小的超时时间 在request_terminate_timeout和request_slowlog_timeout之间
加入定时器链表,假设超时时间为2S,当前时间为2017-02-13 10:00:00 那么超时的时间为2017-02-13 10:02:00
每个子进程工作时会记录当前的时间,在fpm_event_loop函数里,遍历这个定时器链表,如果当前时间 大于或等于2017-02-13 10:02:00的时候,会触发相应的回调函数,当前时间-每个子进程当时时间 >= request_slowlog_timeout时,父进程向子进程发送sigstop暂停信号,并向该子进程设置一个回调函数,子进程结束工作后,返回sigchild信号,父进程接收后,利用tracer收集子进程的信息,写入日志,再向子进程发送sigcont信号,子进程继续执行
php-fpm重启sigus2
每个子进程在工作的时候会先记录下当前的间,
主进程向sp[1]里写入了字符 2,通过 epoll_wait知道sp[0]里有数据可读,调用相应的回调函数,发现是2,向各个子进程发送kill(子进程pid,SIGQUIT),并且设定定时器(假设当前时间为2017-02-12 11:14:00,那么超时时间为2017-02-12 11:15:00),并放到定时器队列里,因为有许多的定时器
1)在这一分钟内,如果子进程结束自己的工作,向父进程发送了sigchild信号,父进程接收此信号后,向 sp[0]里写入字符C,通过epoll_wait得知sp[1]可读,并调用相应回调函数,通过C,进入函数fpm_children_bury,这里主进程通过waitpid得到子进程退出的原因,这里是通过信息导致的退出,这时有一个表示子进程个数的全局变量,这时要减1,就是说每waitpid等到一个子进程,这个全局变量就减1,
然后执行fpm_pctl_child_exited ,当fpm_state等于FPM_PCTL_STATE_NORMAL时,这个函数就退出 ,但这个fpm_state为FPM_PCTL_STATE_FINISHING(也就是quit), 所以会判断 如果记录当前子进程个数那个全局变量为0时 ,因为当前state为reloading,所以执行execvp(execvp就是用一个新的进程把自己替换掉,一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继承环境变量之类的信息。1>exec()函数调用并没有生成新进程,一个进程一旦调用exec函数,它本省就“死亡了”--就好比被鬼上身一样,身体还是你的,但灵魂和思想已经被替换了 --系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一保留的就是进程ID。也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。execvp执行方式是代码空间、数据空间、堆栈的替换);)exit(FPM_EXIT_SOFTWARE);) 参考这里 这里,然后被替换的老进程退出 ,fpm本身是守护进程 参考这里
2)当当前时间大于或等于2017-02-12 11:15:00时候,向各个子进程,执行kill(子进程pid,SIGTERM);这个时候子进程会放下手头工作,无条件的结束,接着执行1的操作
在一个非阻塞的socket上调用read/write函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK)
从字面上看, 意思是:
* EAGAIN: 再试一次
* EWOULDBLOCK: 如果这是一个阻塞socket, 操作将被block
* perror输出: Resource temporarily unavailable
总结:
这个错误表示资源暂时不够, 可能read时, 读缓冲区没有数据, 或者, write时,
写缓冲区满了.
遇到这种情况, 如果是阻塞socket, read/write就要阻塞掉.
而如果是非阻塞socket, read/write立即返回-1, 同 时errno设置为EAGAIN.
所以, 对于阻塞socket, read/write返回-1代表网络出错了.
但对于非阻塞socket, read/write返回-1不一定网络真的出错了.
可能是Resource temporarily unavailable. 这时你应该再试, 直到Resource available.
在子进程完成的工作
a)dup2(fd_stdout[1],STDOUT_FILENO); dup2(fd_stderr[1], STDERR_FILENO);
epoll 参考这里
php-fpm nginx通信 这里
php-fpm的主函数
int main(int argc, char *argv[])
{
int exit_status = FPM_EXIT_OK;
int cgi = , c, use_extended_info = ;
zend_file_handle file_handle; /* temporary locals */
int orig_optind = php_optind;
char *orig_optarg = php_optarg;
int ini_entries_len = ;
/* end of temporary locals */ int max_requests = ;
int requests = ;
int fcgi_fd = ;
fcgi_request request;
char *fpm_config = NULL;
char *fpm_prefix = NULL;
char *fpm_pid = NULL;
int test_conf = ;
int force_daemon = -;
int php_information = ;
int php_allow_to_run_as_root = ; sapi_startup(&cgi_sapi_module);
cgi_sapi_module.php_ini_path_override = NULL;
cgi_sapi_module.php_ini_ignore_cwd = ; fcgi_init(); while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, , )) != -) {
switch (c) {
case 'c':
if (cgi_sapi_module.php_ini_path_override) {
free(cgi_sapi_module.php_ini_path_override);
}
cgi_sapi_module.php_ini_path_override = strdup(php_optarg);
break; case 'n':
cgi_sapi_module.php_ini_ignore = ;
break; case 'd': {
/* define ini entries on command line */
int len = strlen(php_optarg);
char *val; if ((val = strchr(php_optarg, '='))) {
val++;
if (!isalnum(*val) && *val != '"' && *val != '\'' && *val != '\0') {
cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\"\"\n\0"));
memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, (val - php_optarg));
ini_entries_len += (val - php_optarg);
memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"", );
ini_entries_len++;
memcpy(cgi_sapi_module.ini_entries + ini_entries_len, val, len - (val - php_optarg));
ini_entries_len += len - (val - php_optarg);
memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"\n\0", sizeof("\"\n\0"));
ini_entries_len += sizeof("\n\0\"") - ;
} else {
cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\n\0"));
memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len);
memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "\n\0", sizeof("\n\0"));
ini_entries_len += len + sizeof("\n\0") - ;
}
} else {
cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("=1\n\0"));
memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len);
memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "=1\n\0", sizeof("=1\n\0"));
ini_entries_len += len + sizeof("=1\n\0") - ;
}
break;
} case 'y':
fpm_config = php_optarg;
break; case 'p':
fpm_prefix = php_optarg;
break; case 'g':
fpm_pid = php_optarg;
break; case 'e': /* enable extended info output */
use_extended_info = ;
break; case 't':
test_conf++;
break; case 'm': /* list compiled in modules */
cgi_sapi_module.startup(&cgi_sapi_module);
php_output_activate(TSRMLS_C);
SG(headers_sent) = ;
php_printf("[PHP Modules]\n");
print_modules(TSRMLS_C);
php_printf("\n[Zend Modules]\n");
print_extensions(TSRMLS_C);
php_printf("\n");
php_output_end_all(TSRMLS_C);
php_output_deactivate(TSRMLS_C);
fcgi_shutdown();
exit_status = FPM_EXIT_OK;
goto out; case 'i': /* php info & quit */
php_information = ;
break; case 'R': /* allow to run as root */
php_allow_to_run_as_root = ;
break; case 'D': /* daemonize */
force_daemon = ;
break; case 'F': /* nodaemonize */
force_daemon = ;
break; default:
case 'h':
case '?':
cgi_sapi_module.startup(&cgi_sapi_module);
php_output_activate(TSRMLS_C);
SG(headers_sent) = ;
php_cgi_usage(argv[]);
php_output_end_all(TSRMLS_C);
php_output_deactivate(TSRMLS_C);
fcgi_shutdown();
exit_status = (c == 'h') ? FPM_EXIT_OK : FPM_EXIT_USAGE;
goto out; case 'v': /* show php version & quit */
cgi_sapi_module.startup(&cgi_sapi_module);
if (php_request_startup(TSRMLS_C) == FAILURE) {
SG(server_context) = NULL;
php_module_shutdown(TSRMLS_C);
return FPM_EXIT_SOFTWARE;
}
SG(headers_sent) = ;
SG(request_info).no_headers = ; #if ZEND_DEBUG
php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) 1997-2015 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
#else
php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) 1997-2015 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
#endif
php_request_shutdown((void *) );
fcgi_shutdown();
exit_status = FPM_EXIT_OK;
goto out;
}
} if (php_information) {
cgi_sapi_module.phpinfo_as_text = ;
cgi_sapi_module.startup(&cgi_sapi_module);
if (php_request_startup(TSRMLS_C) == FAILURE) {
SG(server_context) = NULL;
php_module_shutdown(TSRMLS_C);
return FPM_EXIT_SOFTWARE;
}
SG(headers_sent) = ;
SG(request_info).no_headers = ;
php_print_info(0xFFFFFFFF TSRMLS_CC);
php_request_shutdown((void *) );
fcgi_shutdown();
exit_status = FPM_EXIT_OK;
goto out;
} /* No other args are permitted here as there is no interactive mode */
if (argc != php_optind) {
cgi_sapi_module.startup(&cgi_sapi_module);
php_output_activate(TSRMLS_C);
SG(headers_sent) = ;
php_cgi_usage(argv[]);
php_output_end_all(TSRMLS_C);
php_output_deactivate(TSRMLS_C);
fcgi_shutdown();
exit_status = FPM_EXIT_USAGE;
goto out;
} php_optind = orig_optind;
php_optarg = orig_optarg; cgi_sapi_module.additional_functions = additional_functions;
cgi_sapi_module.executable_location = argv[]; /* startup after we get the above ini override se we get things right */
if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) { return FPM_EXIT_SOFTWARE;
} if (use_extended_info) {
CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;
} /* check force_cgi after startup, so we have proper output */
if (cgi && CGIG(force_redirect)) {
/* Apache will generate REDIRECT_STATUS,
* Netscape and redirect.so will generate HTTP_REDIRECT_STATUS.
* redirect.so and installation instructions available from
* http://www.koehntopp.de/php.
* -- kk@netuse.de
*/
if (!getenv("REDIRECT_STATUS") &&
!getenv ("HTTP_REDIRECT_STATUS") &&
/* this is to allow a different env var to be configured
* in case some server does something different than above */
(!CGIG(redirect_status_env) || !getenv(CGIG(redirect_status_env)))
) {
zend_try {
SG(sapi_headers).http_response_code = ;
PUTS("<b>Security Alert!</b> The PHP CGI cannot be accessed directly.\n\n\
<p>This PHP CGI binary was compiled with force-cgi-redirect enabled. This\n\
means that a page will only be served up if the REDIRECT_STATUS CGI variable is\n\
set, e.g. via an Apache Action directive.</p>\n\
<p>For more information as to <i>why</i> this behaviour exists, see the <a href=\"http://php.net/security.cgi-bin\">\
manual page for CGI security</a>.</p>\n\
<p>For more information about changing this behaviour or re-enabling this webserver,\n\
consult the installation file that came with this distribution, or visit \n\
<a href=\"http://php.net/install.windows\">the manual page</a>.</p>\n");
} zend_catch {
} zend_end_try();
#if defined(ZTS) && !defined(PHP_DEBUG)
/* XXX we're crashing here in msvc6 debug builds at
* php_message_handler_for_zend:839 because
* SG(request_info).path_translated is an invalid pointer.
* It still happens even though I set it to null, so something
* weird is going on.
*/
tsrm_shutdown();
#endif
return FPM_EXIT_SOFTWARE;
}
}
//fpm_init的初始化
if ( > fpm_init(argc, argv, fpm_config ? fpm_config : CGIG(fpm_config), fpm_prefix, fpm_pid, test_conf, php_allow_to_run_as_root, force_daemon)) { if (fpm_globals.send_config_pipe[]) {
int writeval = ;
zlog(ZLOG_DEBUG, "Sending \"0\" (error) to parent via fd=%d", fpm_globals.send_config_pipe[]);
write(fpm_globals.send_config_pipe[], &writeval, sizeof(writeval));
close(fpm_globals.send_config_pipe[]);
}
return FPM_EXIT_CONFIG;
} if (fpm_globals.send_config_pipe[]) {
int writeval = ;
zlog(ZLOG_DEBUG, "Sending \"1\" (OK) to parent via fd=%d", fpm_globals.send_config_pipe[]);
write(fpm_globals.send_config_pipe[], &writeval, sizeof(writeval));
close(fpm_globals.send_config_pipe[]);
}
fpm_is_running = ;
//开始执行
fcgi_fd = fpm_run(&max_requests); //父进程处理的工作是在fpm_event_loop,是个无限死循环
parent = ; /* onced forked tell zlog to also send messages through sapi_cgi_log_fastcgi() */ zlog_set_external_logger(sapi_cgi_log_fastcgi); /* make php call us to get _ENV vars */ php_php_import_environment_variables = php_import_environment_variables; php_import_environment_variables = cgi_php_import_environment_variables;
//下面都是子进程要处理的工作了
/* library is already initialized, now init our request */
fcgi_init_request(&request, fcgi_fd); zend_first_try {
while (fcgi_accept_request(&request) >= ) {
char *primary_script = NULL;
request_body_fd = -;
SG(server_context) = (void *) &request;
init_request_info(TSRMLS_C);
CG(interactive) = ; fpm_request_info(); /* request startup only after we've done all we can to
* get path_translated */
if (php_request_startup(TSRMLS_C) == FAILURE) {
fcgi_finish_request(&request, );
SG(server_context) = NULL;
php_module_shutdown(TSRMLS_C);
return FPM_EXIT_SOFTWARE;
} /* check if request_method has been sent.
* if not, it's certainly not an HTTP over fcgi request */
if (!SG(request_info).request_method) {
goto fastcgi_request_done;
} if (fpm_status_handle_request(TSRMLS_C)) {
goto fastcgi_request_done;
} /* If path_translated is NULL, terminate here with a 404 */
if (!SG(request_info).path_translated) {
zend_try {
zlog(ZLOG_DEBUG, "Primary script unknown");
SG(sapi_headers).http_response_code = ;
PUTS("File not found.\n");
} zend_catch {
} zend_end_try();
goto fastcgi_request_done;
} if (fpm_php_limit_extensions(SG(request_info).path_translated)) {
SG(sapi_headers).http_response_code = ;
PUTS("Access denied.\n");
goto fastcgi_request_done;
} /*
* have to duplicate SG(request_info).path_translated to be able to log errrors
* php_fopen_primary_script seems to delete SG(request_info).path_translated on failure
*/
primary_script = estrdup(SG(request_info).path_translated); /* path_translated exists, we can continue ! */
if (php_fopen_primary_script(&file_handle TSRMLS_CC) == FAILURE) {
zend_try {
zlog(ZLOG_ERROR, "Unable to open primary script: %s (%s)", primary_script, strerror(errno));
if (errno == EACCES) {
SG(sapi_headers).http_response_code = ;
PUTS("Access denied.\n");
} else {
SG(sapi_headers).http_response_code = ;
PUTS("No input file specified.\n");
}
} zend_catch {
} zend_end_try();
/* we want to serve more requests if this is fastcgi
* so cleanup and continue, request shutdown is
* handled later */ goto fastcgi_request_done;
} fpm_request_executing(); php_execute_script(&file_handle TSRMLS_CC); fastcgi_request_done:
if (primary_script) {
efree(primary_script);
} if (request_body_fd != -) {
close(request_body_fd);
}
request_body_fd = -; if (EG(exit_status) == ) {
if (CGIG(error_header) && *CGIG(error_header)) {
sapi_header_line ctr = {}; ctr.line = CGIG(error_header);
ctr.line_len = strlen(CGIG(error_header));
sapi_header_op(SAPI_HEADER_REPLACE, &ctr TSRMLS_CC);
}
} fpm_request_end(TSRMLS_C);
fpm_log_write(NULL TSRMLS_CC); STR_FREE(SG(request_info).path_translated);
SG(request_info).path_translated = NULL; php_request_shutdown((void *) ); requests++;
if (max_requests && (requests == max_requests)) {
fcgi_finish_request(&request, );
break;
}
/* end of fastcgi loop */
}
fcgi_shutdown(); if (cgi_sapi_module.php_ini_path_override) {
free(cgi_sapi_module.php_ini_path_override);
}
if (cgi_sapi_module.ini_entries) {
free(cgi_sapi_module.ini_entries);
}
} zend_catch {
exit_status = FPM_EXIT_SOFTWARE;
} zend_end_try(); out: SG(server_context) = NULL;
if (parent) {
php_module_shutdown(TSRMLS_C);
sapi_shutdown();
} return exit_status;
}
fpm_init的初始化
int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon) /* {{{ */
{
fpm_globals.argc = argc;
fpm_globals.argv = argv;
if (config && *config) {
fpm_globals.config = strdup(config);
}
fpm_globals.prefix = prefix;
fpm_globals.pid = pid;
fpm_globals.run_as_root = run_as_root; if ( > fpm_php_init_main() ||
> fpm_stdio_init_main() || //io输出重定向到/dev/null
> fpm_conf_init_main(test_conf, force_daemon) || //加载php-fpm.conf配置文件
> fpm_unix_init_main() || //得到主进程pid,并写到fpm_globals.parent_pid里
> fpm_scoreboard_init_main() ||
> fpm_pctl_init_main() ||
> fpm_env_init_main() || //环境变量初始化
> fpm_signals_init_main() || //信号的初始化以及设置回调函数
> fpm_children_init_main() ||
> fpm_sockets_init_main() || //建立socket,绑定bind,监听listen
> fpm_worker_pool_init_main() ||
> fpm_event_init_main()) { //调用epoll_create;并将套接字放到epoll_ctl里 if (fpm_globals.test_successful) {
exit(FPM_EXIT_OK);
} else {
zlog(ZLOG_ERROR, "FPM initialization failed");
return -;
}
} if ( > fpm_conf_write_pid()) { //将主进程的pid写进文件
zlog(ZLOG_ERROR, "FPM initialization failed");
return -;
} fpm_stdio_init_final();
zlog(ZLOG_NOTICE, "fpm is running, pid %d", (int) fpm_globals.parent_pid); return ;
}
将php-fpm的输入输出重定向到 /dev/null里
int fpm_stdio_init_main() /* {{{ */
{
int fd = open("/dev/null", O_RDWR); if ( > fd) {
zlog(ZLOG_SYSERROR, "failed to init stdio: open(\"/dev/null\")");
return -;
} if ( > dup2(fd, STDIN_FILENO) || > dup2(fd, STDOUT_FILENO)) {
zlog(ZLOG_SYSERROR, "failed to init stdio: dup2()");
close(fd);
return -;
}
close(fd);
return ;
}
信号的初始化以及回调函数的设置
int fpm_signals_init_main() /* {{{ */
{
struct sigaction act;
//利用socketpair创建全双工管道,但只是主进程使用
if ( > socketpair(AF_UNIX, SOCK_STREAM, , sp)) {
zlog(ZLOG_SYSERROR, "failed to init signals: socketpair()");
return -;
} if ( > fd_set_blocked(sp[], ) || > fd_set_blocked(sp[], )) {
zlog(ZLOG_SYSERROR, "failed to init signals: fd_set_blocked()");
return -;
} if ( > fcntl(sp[], F_SETFD, FD_CLOEXEC) || > fcntl(sp[], F_SETFD, FD_CLOEXEC)) {
zlog(ZLOG_SYSERROR, "falied to init signals: fcntl(F_SETFD, FD_CLOEXEC)");
return -;
} memset(&act, , sizeof(act));
act.sa_handler = sig_handler;
sigfillset(&act.sa_mask); if ( > sigaction(SIGTERM, &act, ) ||
> sigaction(SIGINT, &act, ) ||
> sigaction(SIGUSR1, &act, ) ||
> sigaction(SIGUSR2, &act, ) ||
> sigaction(SIGCHLD, &act, ) ||
> sigaction(SIGQUIT, &act, )) { zlog(ZLOG_SYSERROR, "failed to init signals: sigaction()");
return -;
}
return ;
}
回调函数就是向管道sp[1]里写入信号的首字母,按理说马上就能从sp[0]里读出,但这里又把sp[0]放到了epoll_ctl里
static void sig_handler(int signo) /* {{{ */
{
static const char sig_chars[NSIG + ] = {
[SIGTERM] = 'T',
[SIGINT] = 'I',
[SIGUSR1] = '',
[SIGUSR2] = '',
[SIGQUIT] = 'Q',
[SIGCHLD] = 'C'
};
char s;
int saved_errno; if (fpm_globals.parent_pid != getpid()) {
/* prevent a signal race condition when child process
have not set up it's own signal handler yet */
return;
} saved_errno = errno;
s = sig_chars[signo];
write(sp[], &s, sizeof(s));
errno = saved_errno;
}
建立socket套接字, 绑定bind,监听listen
int fpm_sockets_init_main() /* {{{ */
{
unsigned i, lq_len;
struct fpm_worker_pool_s *wp;
char *inherited = getenv("FPM_SOCKETS");
struct listening_socket_s *ls;
if ( == fpm_array_init(&sockets_list, sizeof(struct listening_socket_s), )) {
return -;
} /* create all required sockets */
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
switch (wp->listen_address_domain) {
case FPM_AF_INET :
wp->listening_socket = fpm_socket_af_inet_listening_socket(wp);
break; case FPM_AF_UNIX :
if ( > fpm_unix_resolve_socket_premissions(wp)) {
return -;
}
wp->listening_socket = fpm_socket_af_unix_listening_socket(wp);
break;
} if (wp->listening_socket == -) {
return -;
} if (wp->listen_address_domain == FPM_AF_INET && fpm_socket_get_listening_queue(wp->listening_socket, NULL, &lq_len) >= ) {
fpm_scoreboard_update(-, -, -, (int)lq_len, -, -, , FPM_SCOREBOARD_ACTION_SET, wp->scoreboard);
}
} return ;
} static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */
{
struct addrinfo hints, *servinfo, *p;
for (p = servinfo; p != NULL; p = p->ai_next) {
inet_ntop(p->ai_family, fpm_get_in_addr(p->ai_addr), tmpbuf, INET6_ADDRSTRLEN);
if (sock < ) {
if ((sock = fpm_sockets_get_listening_socket(wp, p->ai_addr, p->ai_addrlen)) != -) {
zlog(ZLOG_DEBUG, "Found address for %s, socket opened on %s", dup_address, tmpbuf);
}
} else {
zlog(ZLOG_WARNING, "Found multiple addresses for %s, %s ignored", dup_address, tmpbuf);
} } static int fpm_sockets_get_listening_socket(struct fpm_worker_pool_s *wp, struct sockaddr *sa, int socklen) /* {{{ */
{
int sock; sock = fpm_sockets_hash_op(, sa, , wp->listen_address_domain, FPM_GET_USE_SOCKET);
if (sock >= ) {
return sock;
} sock = fpm_sockets_new_listening_socket(wp, sa, socklen);
fpm_sockets_hash_op(sock, sa, , wp->listen_address_domain, FPM_STORE_USE_SOCKET); return sock;
} static int fpm_sockets_new_listening_socket(struct fpm_worker_pool_s *wp, struct sockaddr *sa, int socklen) /* {{{ */
{
int flags = ;
int sock;
mode_t saved_umask = ; sock = socket(sa->sa_family, SOCK_STREAM, ); if ( > sock) {
zlog(ZLOG_SYSERROR, "failed to create new listening socket: socket()");
return -;
} if ( > setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags))) {
zlog(ZLOG_WARNING, "failed to change socket attribute");
} if (wp->listen_address_domain == FPM_AF_UNIX) {
if (fpm_socket_unix_test_connect((struct sockaddr_un *)sa, socklen) == ) {
zlog(ZLOG_ERROR, "An another FPM instance seems to already listen on %s", ((struct sockaddr_un *) sa)->sun_path);
close(sock);
return -;
}
unlink( ((struct sockaddr_un *) sa)->sun_path);
saved_umask = umask( ^ wp->socket_mode);
} if ( > bind(sock, sa, socklen)) {
zlog(ZLOG_SYSERROR, "unable to bind listening socket for address '%s'", wp->config->listen_address);
if (wp->listen_address_domain == FPM_AF_UNIX) {
umask(saved_umask);
}
close(sock);
return -;
} if (wp->listen_address_domain == FPM_AF_UNIX) {
char *path = ((struct sockaddr_un *) sa)->sun_path; umask(saved_umask); if (wp->socket_uid != - || wp->socket_gid != -) {
if ( > chown(path, wp->socket_uid, wp->socket_gid)) {
zlog(ZLOG_SYSERROR, "failed to chown() the socket '%s'", wp->config->listen_address);
close(sock);
return -;
}
}
} if ( > listen(sock, wp->config->listen_backlog)) {
zlog(ZLOG_SYSERROR, "failed to listen to address '%s'", wp->config->listen_address);
close(sock);
return -;
} return sock;
}
事件的初始化,这里指epoll
int fpm_event_init_main() /* {{{ */
{
struct fpm_worker_pool_s *wp;
int max;
//moudule是个全局变量
if (!module) {
zlog(ZLOG_ERROR, "no event module found");
return -;
} if (!module->wait) {
zlog(ZLOG_ERROR, "Incomplete event implementation. Please open a bug report on https://bugs.php.net.");
return -;
} /* count the max number of necessary fds for polling */
max = ; /* only one FD is necessary at startup for the master process signal pipe */
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
if (!wp->config) continue;
/*
*如果打开显示输出开头,那么max为最大子进程数量的2倍,再加1
*这个max就是epoll_create()函数里的参数,但意义不大,
*重要的是epoll_wait()中的第二个参数,它是个数组,类型为struct epoll_event events[max]
*假设开5个进程,那么max为5*2+1=11, 加1,这个1,就是信号(sigusr,sigquit等等)要使用,
*/
if (wp->config->catch_workers_output && wp->config->pm_max_children > ) {
max += (wp->config->pm_max_children * );
}
}
//这时调用的就是epoll_create;epoll_ctl(efd,EPOLL_CTL_ADD,fd, {event.data.fd=fd.event.events=EPOLLIN} );
if (module->init(max) < ) {
zlog(ZLOG_ERROR, "Unable to initialize the event module %s", module->name);
return -;
} zlog(ZLOG_DEBUG, "event module is %s and %d fds have been reserved", module->name, max); if ( > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_event_cleanup, NULL)) {
return -;
}
return ;
}
遍历进程池中每个进程
/* children: return listening socket
parent: never return */
int fpm_run(int *max_requests) /* {{{ */
{
struct fpm_worker_pool_s *wp; /* create initial children in all pools */
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
int is_parent; is_parent = fpm_children_create_initial(wp); if (!is_parent) {
goto run_child;
} /* handle error */
if (is_parent == ) {
fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET);
fpm_event_loop();
}
} /* run event loop forever */
fpm_event_loop(); run_child: /* only workers reach this point */ fpm_cleanups_run(FPM_CLEANUP_CHILD); *max_requests = fpm_globals.max_requests;
return fpm_globals.listening_socket;
}
int fpm_children_create_initial(struct fpm_worker_pool_s *wp) /* {{{ */
{
if (wp->config->pm == PM_STYLE_ONDEMAND) {
wp->ondemand_event = (struct fpm_event_s *)malloc(sizeof(struct fpm_event_s)); if (!wp->ondemand_event) {
zlog(ZLOG_ERROR, "[pool %s] unable to malloc the ondemand socket event", wp->config->name);
// FIXME handle crash
return ;
} memset(wp->ondemand_event, , sizeof(struct fpm_event_s));
fpm_event_set(wp->ondemand_event, wp->listening_socket, FPM_EV_READ | FPM_EV_EDGE, fpm_pctl_on_socket_accept, wp);
wp->socket_event_set = ;
fpm_event_add(wp->ondemand_event, ); return ;
}
return fpm_children_make(wp, /* not in event loop yet */, , );
}
循环fork子进程,直到子进程个数为上面的max
fork前 主进程做的工作有建立管道,用于子进程的stdout和stderr信息汇报给父进程,父进程收到后再写到日志里,让子进程专注于处理php请求
int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to_spawn, int is_debug) /* {{{ */
{
pid_t pid;
struct fpm_child_s *child;
int max;
static int warned = ; if (wp->config->pm == PM_STYLE_DYNAMIC) {
if (!in_event_loop) { /* starting */
max = wp->config->pm_start_servers;
} else {
max = wp->running_children + nb_to_spawn;
}
} else if (wp->config->pm == PM_STYLE_ONDEMAND) {
if (!in_event_loop) { /* starting */
max = ; /* do not create any child at startup */
} else {
max = wp->running_children + nb_to_spawn;
}
} else { /* PM_STYLE_STATIC */
max = wp->config->pm_max_children;
} /*
* fork children while:
* - fpm_pctl_can_spawn_children : FPM is running in a NORMAL state (aka not restart, stop or reload)
* - wp->running_children < max : there is less than the max process for the current pool
* - (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max):
* if fpm_global_config.process_max is set, FPM has not fork this number of processes (globaly)
*/
while (fpm_pctl_can_spawn_children() && wp->running_children < max && (fpm_global_config.process_max < || fpm_globals.running_children < fpm_global_config.process_max)) { warned = ;
child = fpm_resources_prepare(wp); //创建管道,注意这里是循环,所以会创建多个管道,不需要全双工,
//因为只需要子进程向父进程汇报工作,子进程不需要自已保存日志,可让子进程专心处理php请求 if (!child) {
return ;
} pid = fork(); switch (pid) { case :
fpm_child_resources_use(child); //子进程将stdout和stderr重定向到fd_stdout[1]和fd_stderr[1]中
fpm_globals.is_child = ;
fpm_child_init(wp);
return ; case - :
zlog(ZLOG_SYSERROR, "fork() failed");
fpm_resources_discard(child);
return ; default :
child->pid = pid;
fpm_clock_get(&child->started);
fpm_parent_resources_use(child);//父进程将fd_stdout[0]和fd_stderr[0]写到epoll里,进行监听
zlog(is_debug ? ZLOG_DEBUG : ZLOG_NOTICE, "[pool %s] child %d started", wp->config->name, (int) pid);
} } if (!warned && fpm_global_config.process_max > && fpm_globals.running_children >= fpm_global_config.process_max) {
warned = ;
zlog(ZLOG_WARNING, "The maximum number of processes has been reached. Please review your configuration and consider raising 'process.max'");
} return ; /* we are done */
}
父进程利用pipe创建两个管道,fd_stdout[2]和fd_stderr[2]
static struct fpm_child_s *fpm_resources_prepare(struct fpm_worker_pool_s *wp) /* {{{ */
{
struct fpm_child_s *c; c = fpm_child_alloc(); if (!c) {
zlog(ZLOG_ERROR, "[pool %s] unable to malloc new child", wp->config->name);
return ;
} c->wp = wp;
c->fd_stdout = -; c->fd_stderr = -; if ( > fpm_stdio_prepare_pipes(c)) { //创建两个管道
fpm_child_free(c);
return ;
} if ( > fpm_scoreboard_proc_alloc(wp->scoreboard, &c->scoreboard_i)) {
fpm_stdio_discard_pipes(c);
fpm_child_free(c);
return ;
} return c;
}
主进程利用pipe建立管道 fd_stout[2]和fd_stderr[2]
int fpm_stdio_prepare_pipes(struct fpm_child_s *child) /* {{{ */
{
if ( == child->wp->config->catch_workers_output) { /* not required */
return ;
} if ( > pipe(fd_stdout)) {
zlog(ZLOG_SYSERROR, "failed to prepare the stdout pipe");
return -;
} if ( > pipe(fd_stderr)) {
zlog(ZLOG_SYSERROR, "failed to prepare the stderr pipe");
close(fd_stdout[]);
close(fd_stdout[]);
return -;
} if ( > fd_set_blocked(fd_stdout[], ) || > fd_set_blocked(fd_stderr[], )) {
zlog(ZLOG_SYSERROR, "failed to unblock pipes");
close(fd_stdout[]);
close(fd_stdout[]);
close(fd_stderr[]);
close(fd_stderr[]);
return -;
}
return ;
}
父进程将fd_stdour[0]和fd_stderr[0]放到epoll中,进行监视,如果可读,说明子进程向父进程发来了数据,父进程写到日志里
static void fpm_parent_resources_use(struct fpm_child_s *child) /* {{{ */
{
fpm_stdio_parent_use_pipes(child);
fpm_child_link(child);
} int fpm_stdio_parent_use_pipes(struct fpm_child_s *child) /* {{{ */
{
if ( == child->wp->config->catch_workers_output) { /* not required */
return ;
} close(fd_stdout[]);
close(fd_stderr[]); child->fd_stdout = fd_stdout[];
child->fd_stderr = fd_stderr[]; fpm_event_set(&child->ev_stdout, child->fd_stdout, FPM_EV_READ, fpm_stdio_child_said, child);
fpm_event_add(&child->ev_stdout, ); fpm_event_set(&child->ev_stderr, child->fd_stderr, FPM_EV_READ, fpm_stdio_child_said, child);
fpm_event_add(&child->ev_stderr, );
return ;
} static void fpm_child_link(struct fpm_child_s *child) /* {{{ */
{
struct fpm_worker_pool_s *wp = child->wp; ++wp->running_children;
++fpm_globals.running_children; child->next = wp->children;
if (child->next) {
child->next->prev = child;
}
child->prev = ;
wp->children = child;
}
子进程 通过dup2将stdout,stderr 重定向到fd_stdout[1]和fd_stderr[1]中,因为只需要子进程向父进程汇报信息,故单工即可
static void fpm_child_resources_use(struct fpm_child_s *child) /* {{{ */
{
struct fpm_worker_pool_s *wp;
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
if (wp == child->wp) {
continue;
}
fpm_scoreboard_free(wp->scoreboard);
} fpm_scoreboard_child_use(child->wp->scoreboard, child->scoreboard_i, getpid());
fpm_stdio_child_use_pipes(child);
fpm_child_free(child);
} void fpm_stdio_child_use_pipes(struct fpm_child_s *child) /* {{{ */
{
if (child->wp->config->catch_workers_output) {
dup2(fd_stdout[], STDOUT_FILENO);
dup2(fd_stderr[], STDERR_FILENO);
close(fd_stdout[]); close(fd_stdout[]);
close(fd_stderr[]); close(fd_stderr[]);
} else {
/* stdout of parent is always /dev/null */
dup2(STDOUT_FILENO, STDERR_FILENO);
}
}
主进程 的工作,是个无限循环
void fpm_event_loop(int err) /* {{{ */
{
static struct fpm_event_s signal_fd_event; /* sanity check */
if (fpm_globals.parent_pid != getpid()) {
return;
}
//将sp[0]放到epoll中进行监听
fpm_event_set(&signal_fd_event, fpm_signals_get_fd(), FPM_EV_READ, &fpm_got_signal, NULL);
fpm_event_add(&signal_fd_event, ); /* add timers */
if (fpm_globals.heartbeat > ) {
fpm_pctl_heartbeat(NULL, , NULL); //慢日志和超时处理
} if (!err) {
fpm_pctl_perform_idle_server_maintenance_heartbeat(NULL, , NULL); zlog(ZLOG_DEBUG, "%zu bytes have been reserved in SHM", fpm_shm_get_size_allocated());
zlog(ZLOG_NOTICE, "ready to handle connections"); #ifdef HAVE_SYSTEMD
fpm_systemd_heartbeat(NULL, , NULL);
#endif
} while () {
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 = ;
} else {
timersub(&ms, &now, &tmp);
timeout = (tmp.tv_sec * ) + (tmp.tv_usec / ) + ;
} ret = module->wait(fpm_event_queue_fd, timeout); /* is a child, nothing to do here */
if (ret == -) {
return;
} if (ret > ) {
zlog(ZLOG_DEBUG, "event module triggered %d events", ret);
}
//定时器触发,比如主进程接收到用户的sigterm信号,向sp[1]内写入T,epoll发现sp[0]里有数据,然后调用回调函数,向每个进程发送sigterm信号,(kill函数),
同时注册一个定时器,假设现在时间为1:02,那么1S后到期,超时时间为 2:02,这个无限循环就是一起遍历这个定时器链表,当当前时间大于或等于这个超时时间时,就触发相应
回调函数,向每个子进程发送sigkill指令,再根据返回的sigchild信号,处理相应的事情
/* 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;
}
}
}
慢日志和超时处理 参数为NULL,0,NULL, 如果发现某个子进程的运行时间超过terminate_timeout,则向子进程发送sigterm, 当父进程收到sigchild后,会重新fork一个子进程
#define FPM_EV_TIMEOUT (1 << 0)
void fpm_pctl_heartbeat(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
{
static struct fpm_event_s heartbeat;
struct timeval now; if (fpm_globals.parent_pid != getpid()) {
return; /* sanity check */
} if (which == FPM_EV_TIMEOUT) {
fpm_clock_get(&now);
fpm_pctl_check_request_timeout(&now);
return;
} /* ensure heartbeat is not lower than FPM_PCTL_MIN_HEARTBEAT */
fpm_globals.heartbeat = MAX(fpm_globals.heartbeat, FPM_PCTL_MIN_HEARTBEAT); /* first call without setting to initialize the timer */
zlog(ZLOG_DEBUG, "heartbeat have been set up with a timeout of %dms", fpm_globals.heartbeat);
fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_heartbeat, NULL);
fpm_event_add(&heartbeat, fpm_globals.heartbeat);
}
static void fpm_pctl_check_request_timeout(struct timeval *now) /* {{{ */
{
struct fpm_worker_pool_s *wp; for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
int terminate_timeout = wp->config->request_terminate_timeout;
int slowlog_timeout = wp->config->request_slowlog_timeout;
struct fpm_child_s *child; if (terminate_timeout || slowlog_timeout) {
for (child = wp->children; child; child = child->next) {
fpm_request_check_timed_out(child, now, terminate_timeout, slowlog_timeout);
}
}
}
} void fpm_request_check_timed_out(struct fpm_child_s *child, struct timeval *now, int terminate_timeout, int slowlog_timeout) /* {{{ */
{
struct fpm_scoreboard_proc_s proc, *proc_p; proc_p = fpm_scoreboard_proc_acquire(child->wp->scoreboard, child->scoreboard_i, );
if (!proc_p) {
zlog(ZLOG_WARNING, "failed to acquire scoreboard");
return;
} proc = *proc_p;
fpm_scoreboard_proc_release(proc_p); #if HAVE_FPM_TRACE
if (child->slow_logged.tv_sec) {
if (child->slow_logged.tv_sec != proc.accepted.tv_sec || child->slow_logged.tv_usec != proc.accepted.tv_usec) {
child->slow_logged.tv_sec = ;
child->slow_logged.tv_usec = ;
}
}
#endif if (proc.request_stage > FPM_REQUEST_ACCEPTING && proc.request_stage < FPM_REQUEST_END) {
char purified_script_filename[sizeof(proc.script_filename)];
struct timeval tv; timersub(now, &proc.accepted, &tv); #if HAVE_FPM_TRACE
if (child->slow_logged.tv_sec == && slowlog_timeout &&
proc.request_stage == FPM_REQUEST_EXECUTING && tv.tv_sec >= slowlog_timeout) { str_purify_filename(purified_script_filename, proc.script_filename, sizeof(proc.script_filename)); child->slow_logged = proc.accepted;
child->tracer = fpm_php_trace; fpm_trace_signal(child->pid); zlog(ZLOG_WARNING, "[pool %s] child %d, script '%s' (request: \"%s %s\") executing too slow (%d.%06d sec), logging",
child->wp->config->name, (int) child->pid, purified_script_filename, proc.request_method, proc.request_uri,
(int) tv.tv_sec, (int) tv.tv_usec);
}
else
#endif
if (terminate_timeout && tv.tv_sec >= terminate_timeout) {
str_purify_filename(purified_script_filename, proc.script_filename, sizeof(proc.script_filename));
fpm_pctl_kill(child->pid, FPM_PCTL_TERM); zlog(ZLOG_WARNING, "[pool %s] child %d, script '%s' (request: \"%s %s\") execution timed out (%d.%06d sec), terminating",
child->wp->config->name, (int) child->pid, purified_script_filename, proc.request_method, proc.request_uri,
(int) tv.tv_sec, (int) tv.tv_usec);
}
}
}
当terminate_timeout超时后,主进程接收到子进程的sigchild后,由于是收到信号sigterm退出的,所以restart_child=1,然后fork一个新的子进程
注意:当主进程发送sigterm时,
void fpm_children_bury() /* {{{ */
{
int status;
pid_t pid;
struct fpm_child_s *child; while ( (pid = waitpid(-, &status, WNOHANG | WUNTRACED)) > ) {
char buf[];
int severity = ZLOG_NOTICE;
int restart_child = ; child = fpm_child_find(pid); 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 = ;
} if (WEXITSTATUS(status) != FPM_EXIT_OK) {
severity = ZLOG_WARNING;
} } 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 = ;
} if (WTERMSIG(status) != SIGQUIT) { /* possible request loss */
severity = ZLOG_WARNING;
}
} else if (WIFSTOPPED(status)) { zlog(ZLOG_NOTICE, "child %d stopped for tracing", (int) pid); if (child && child->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.%06d 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 has been killed by the process management after %ld.%06d seconds from start", child->wp->config->name, (int) pid, tv2.tv_sec, (int) tv2.tv_usec);
} fpm_child_close(child, /* in event_loop */); fpm_pctl_child_exited(); if (last_faults && (WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS)) {
time_t now = tv1.tv_sec;
int restart_condition = ;
int i; last_faults[fault++] = now; if (fault == fpm_global_config.emergency_restart_threshold) {
fault = ;
} for (i = ; i < fpm_global_config.emergency_restart_threshold; i++) {
if (now - last_faults[i] > fpm_global_config.emergency_restart_interval) {
restart_condition = ;
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) {
fpm_children_make(wp, /* in event loop */, , ); 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);
}
}
}
fcgi_request *fcgi_init_request(int listen_socket)
{
fcgi_request *req = (fcgi_request*)calloc(, sizeof(fcgi_request));
req->listen_socket = listen_socket;
req->fd = -;
req->id = -; req->in_len = ;
req->in_pad = ; req->out_hdr = NULL;
req->out_pos = req->out_buf; #ifdef TCP_NODELAY
req->nodelay = ;
#endif fcgi_hash_init(&req->env); return req;
}
子进程 会调用 fcgi_accept_request 函数,
int fcgi_accept_request(fcgi_request *req)
{
while () {
if (req->fd < ) {
while () {
if (in_shutdown) {
return -;
} {
int listen_socket = req->listen_socket; sa_t sa;
socklen_t len = sizeof(sa); FCGI_LOCK(req->listen_socket);
req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len);
FCGI_UNLOCK(req->listen_socket);
if (req->fd >= ) {
if (((struct sockaddr *)&sa)->sa_family == AF_INET) { if (allowed_clients) {
int n = ;
int allowed = ; while (allowed_clients[n] != INADDR_NONE) {
if (allowed_clients[n] == sa.sa_inet.sin_addr.s_addr) {
allowed = ;
break;
}
n++;
}
if (!allowed) {
fprintf(stderr, "Connection from disallowed IP address '%s' is dropped.\n", inet_ntoa(sa.sa_inet.sin_addr));
closesocket(req->fd);
req->fd = -;
continue;
}
} }
}
} if (req->fd < && (in_shutdown || (errno != EINTR && errno != ECONNABORTED))) { return -;
} if (req->fd >= ) {
struct pollfd fds;
int ret; fds.fd = req->fd;
fds.events = POLLIN;
fds.revents = ;
do {
errno = ;
ret = poll(&fds, , );
} while (ret < && errno == EINTR); //相当于 当事件没有发生时,持续等待5000ms,当这段时间内依然没有 事件发生生时,返回ret为0,否则>0
if (ret > && (fds.revents & POLLIN)) {
break;
}
fcgi_close(req, , ); } }
} else if (in_shutdown) {
return -;
}
if (fcgi_read_request(req)) { return req->fd;
} else {
fcgi_close(req, , );
}
}
}
这里用到了poll机制,我的印象中poll也是IO复用的一种,但这里poll函数的第二个参数为1,也就是监听1个请求,5000超时时间,按理说当 ret 大于0时,需要遍历fds[]这个数组,但这个数组现在只有一个元素,按理说应该是多个元素,比如监听1000个请求,看哪个请求经三次握手后已经携带了数据
后来查资料,发现这个poll就是个唤醒机制,当ret为0时,该子进程进入休眠, 每隔5000毫秒时,再唤醒,在睡眠阶段,如果fd的状态为我们期待状态,该子进程立刻被唤醒,否则一直睡到5000毫秒,再唤醒 。好处是在用户态就可以让进程睡眠,避免死循环,浪费CPU
http://yongyong.blog.chinaunix.net/uid-30592332-id-5599907.html
http://blog.csdn.net/lizuobin2/article/details/52703976
http://www.lxway.net/826466416.html
但这里看,php-fpm的一个进程只能处理一个进程,阻塞模式的
http://www.jianshu.com/p/542935a3bfa8
可理解为
while(1){
ret = poll(fd,1,500);
if(ret > 0){
break;
}
}
poll相当于open("/dev/xxx",O_RDWR)阻塞打开文件,区别在于当设备文件无数据可读时poll只导致程序休眠固定时间,而open将导致程序一直休眠到有数据为止。因为 有可能 直到有数据的时间 要 远远大于 自己主动休眠的固定时间
fastcgi main的更多相关文章
- 后端程序员之路 3、fastcgi、fastcgi++
CGI与FastCGI - wanghetao - 博客园http://www.cnblogs.com/wanghetao/p/3934350.html eddic/fastcgipp: A C++ ...
- nginx+fastcgi+c/cpp
参考:http://github.tiankonguse.com/blog/2015/01/19/cgi-nginx-three/ 跟着做了一遍,然后根据记忆写的,不清楚有没错漏步骤,希望多多评论多多 ...
- Nginx+php+fastcgi在win7下的配置
首先装载php 1.从www.php.net上下载php对应版本 2.解压之后放到c盘下(其实放哪无所谓,Apache会有配置指向,但是Nginx不用) 3.因为用的5.3.17版本,已经有了php- ...
- Nginx + CGI/FastCGI + C/Cpp
接着上篇<Nginx安装与使用>,本篇介绍CGI/FASTCGI的原理.及如何使用C/C++编写简单的CGI/FastCGI,最后将CGI/FASTCGI部署到nginx.内容大纲如下: ...
- [转]nginx+fastcgi+c/c++搭建高性能Web框架
FROM : http://blog.csdn.net/marising/article/details/3932938 1.Nginx 1.1.安装 Nginx 的中文维基 http://wiki. ...
- Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布
FastCGI编程包括四部分:初始化编码.接收请求循环.响应内容.响应结束循环. FCGX_Request request; FCGX_Init(); ); FCGX_InitRequest(& ...
- lighttpd与fastcgi+cgilua原理、代码分析与安装
原理 http://www.cnblogs.com/skynet/p/4173450.html 快速通用网关接口(Fast Common Gateway Interface/FastCGI)是通用网关 ...
- Nginx + FastCgi + Spawn-fcgi + c 的架构
参考: nginx+c/c++ fastcgi:http://www.yis.me/web/2011/11/01/66.htm cgi探索之路:http://github.tiankonguse.co ...
- mac os x安装ngigx+php fastcgi+mysql+memcache详细流程
Part 1: MacPorts Mac上装软件常用的是MacPorts和homebrew,这个软件会很方便地提供软件的安装.装这些前先得装Xcode,Xcode在appstore上有,一个多G,下载 ...
随机推荐
- Kubuntu上连接PPTP
生活在天朝,如果没备几招FQ的本领,都不敢说自己还活着... 前两天从朋友那抢了个VPN帐号,使用的是PPTP的,在google上找了一会,发现网上大都是讲VPN服务搭建的,就算是介绍客户端的,也大都 ...
- Opencv 发现轮廓 findContours
vector<vector<Point>> vec_p; vector<Vec4i> vec_4f; findContours(img_canny1, vec_p, ...
- javascript运算符优先级顺序
1 ()2 !.-(负号).++.-- 3 *./.%4 +.- 10-55 <.<=.<.>=6 ==.!=.===.!==.7 &&8 ||9 ?:10 = ...
- [SoapUI] context.expand 和 groovyUtils.getXmlHolder 有什么不一样
context.expand 和 groovyUtils.getXmlHolder 有什么不一样?互相之间怎么转换 import com.eviware.soapui.support.GroovyUt ...
- GitLab服务器IP地址修改
gitlab安装介绍:https://about.gitlab.com/downloads/#centos7 刚搭建好的gitlab在GitLab上新建一个项目test_gitlab,刚开始仓库地址是 ...
- UVa 12099 The Bookcase (DP)
题意:有 n 本书,每本书有一个高度和宽度,然后让你制作一个3层的书架,可以放下所有的书,并且要高*宽尽量小. 析:先把所有的书按高度进行排序,然后dp[i][j][k] 表示 前 i 本书,第二 层 ...
- 编写高质量代码改善C#程序的157个建议——建议137:委托和事件类型应添加上级后缀
建议137:委托和事件类型应添加上级后缀 委托类型本身是一个类,考虑让派生类的名字以基类名字作为后缀.事件类型是一类特殊的委托,所以事件类型也遵循本建议. 委托和事件的正确的命名方式有: public ...
- Karma和Jasmine 自动化单元测试环境搭建
最近初学AngularJS ,看到的一些教程中经常有人推荐使用Karma+Jasmine来进行单元测试.自己之前也对Jasmine有些了解,jasmine也是一个不错的测试框架. 1. karma介绍 ...
- C# 自带的.net类库 实现得到本机IP以及网关地址
今天需要用到一个功能,获取主机名和本机的IP 准备用API实现的,然后稍微查了一下,发现.net类库已经有了 就在System.Net命名空间中的DNS类中 GetHostName 获取本地计算机的主 ...
- .net Tuple特性
.net 4.0 引入了 Tuple特性: 在C# 4.0之前我们函数有多个返回值,通常是使用ref,out .到了c# 4.0 应当使用元组Tuple而不是使用输出参数,在任何时候都应避免使用ref ...