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 = 0, c, use_extended_info = 0; zend_file_handle file_handle; /* temporary locals */ int orig_optind = php_optind; char *orig_optarg = php_optarg; int ini_entries_len = 0; /* end of temporary locals */ int max_requests = 500; int requests = 0; int fcgi_fd = 0; fcgi_request request; char *fpm_config = NULL; char *fpm_prefix = NULL; char *fpm_pid = NULL; int test_conf = 0; int force_daemon = -1; int php_information = 0; int php_allow_to_run_as_root = 0; sapi_startup(&cgi_sapi_module); cgi_sapi_module.php_ini_path_override = NULL; cgi_sapi_module.php_ini_ignore_cwd = 1; fcgi_init(); while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { 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 = 1; 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, "\"", 1); 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\"") - 2; } 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") - 2; } } 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") - 2; } 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 = 1; 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) = 1; 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 = 1; break; case 'R': /* allow to run as root */ php_allow_to_run_as_root = 1; break; case 'D': /* daemonize */ force_daemon = 1; break; case 'F': /* nodaemonize */ force_daemon = 0; break; default: case 'h': case '?': cgi_sapi_module.startup(&cgi_sapi_module); php_output_activate(TSRMLS_C); SG(headers_sent) = 1; php_cgi_usage(argv[0]); 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) = 1; SG(request_info).no_headers = 1; #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 *) 0); fcgi_shutdown(); exit_status = FPM_EXIT_OK; goto out; } } if (php_information) { cgi_sapi_module.phpinfo_as_text = 1; 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) = 1; SG(request_info).no_headers = 1; php_print_info(0xFFFFFFFF TSRMLS_CC); php_request_shutdown((void *) 0); 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) = 1; php_cgi_usage(argv[0]); 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[0]; /* 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 = 400; 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 (0 > 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[1]) { int writeval = 0; zlog(ZLOG_DEBUG, "Sending \"0\" (error) to parent via fd=%d", fpm_globals.send_config_pipe[1]); write(fpm_globals.send_config_pipe[1], &writeval, sizeof(writeval)); close(fpm_globals.send_config_pipe[1]); } return FPM_EXIT_CONFIG; } if (fpm_globals.send_config_pipe[1]) { int writeval = 1; zlog(ZLOG_DEBUG, "Sending \"1\" (OK) to parent via fd=%d", fpm_globals.send_config_pipe[1]); write(fpm_globals.send_config_pipe[1], &writeval, sizeof(writeval)); close(fpm_globals.send_config_pipe[1]); } fpm_is_running = 1;
//开始执行 fcgi_fd = fpm_run(&max_requests);
//父进程处理的工作是在fpm_event_loop,是个无限死循环
parent = 0; /* 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) >= 0) { char *primary_script = NULL; request_body_fd = -1; SG(server_context) = (void *) &request; init_request_info(TSRMLS_C); CG(interactive) = 0; 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, 1); 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 = 404; 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 = 403; 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 = 403; PUTS("Access denied.\n"); } else { SG(sapi_headers).http_response_code = 404; 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 != -1) { close(request_body_fd); } request_body_fd = -2; if (EG(exit_status) == 255) { if (CGIG(error_header) && *CGIG(error_header)) { sapi_header_line ctr = {0}; 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 *) 0); requests++; if (max_requests && (requests == max_requests)) { fcgi_finish_request(&request, 1); 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 (0 > fpm_php_init_main() || 0 > fpm_stdio_init_main() || //io输出重定向到/dev/null 0 > fpm_conf_init_main(test_conf, force_daemon) || //加载php-fpm.conf配置文件 0 > fpm_unix_init_main() || //得到主进程pid,并写到fpm_globals.parent_pid里 0 > fpm_scoreboard_init_main() || 0 > fpm_pctl_init_main() || 0 > fpm_env_init_main() || //环境变量初始化 0 > fpm_signals_init_main() || //信号的初始化以及设置回调函数 0 > fpm_children_init_main() || 0 > fpm_sockets_init_main() || //建立socket,绑定bind,监听listen 0 > fpm_worker_pool_init_main() || 0 > 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 -1; } } if (0 > fpm_conf_write_pid()) { //将主进程的pid写进文件 zlog(ZLOG_ERROR, "FPM initialization failed"); return -1; } fpm_stdio_init_final(); zlog(ZLOG_NOTICE, "fpm is running, pid %d", (int) fpm_globals.parent_pid); return 0; }
将php-fpm的输入输出重定向到 /dev/null里
int fpm_stdio_init_main() /* {{{ */ { int fd = open("/dev/null", O_RDWR); if (0 > fd) { zlog(ZLOG_SYSERROR, "failed to init stdio: open(\"/dev/null\")"); return -1; } if (0 > dup2(fd, STDIN_FILENO) || 0 > dup2(fd, STDOUT_FILENO)) { zlog(ZLOG_SYSERROR, "failed to init stdio: dup2()"); close(fd); return -1; } close(fd); return 0; }
信号的初始化以及回调函数的设置
int fpm_signals_init_main() /* {{{ */ { struct sigaction act; //利用socketpair创建全双工管道,但只是主进程使用 if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) { zlog(ZLOG_SYSERROR, "failed to init signals: socketpair()"); return -1; } 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; } 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; 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; }
回调函数就是向管道sp[1]里写入信号的首字母,按理说马上就能从sp[0]里读出,但这里又把sp[0]放到了epoll_ctl里
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 have 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; }
建立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 (0 == fpm_array_init(&sockets_list, sizeof(struct listening_socket_s), 10)) { return -1; } /* 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 (0 > fpm_unix_resolve_socket_premissions(wp)) { return -1; } wp->listening_socket = fpm_socket_af_unix_listening_socket(wp); break; } if (wp->listening_socket == -1) { return -1; } if (wp->listen_address_domain == FPM_AF_INET && fpm_socket_get_listening_queue(wp->listening_socket, NULL, &lq_len) >= 0) { fpm_scoreboard_update(-1, -1, -1, (int)lq_len, -1, -1, 0, FPM_SCOREBOARD_ACTION_SET, wp->scoreboard); } } return 0; } 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 < 0) { if ((sock = fpm_sockets_get_listening_socket(wp, p->ai_addr, p->ai_addrlen)) != -1) { 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(0, sa, 0, wp->listen_address_domain, FPM_GET_USE_SOCKET); if (sock >= 0) { return sock; } sock = fpm_sockets_new_listening_socket(wp, sa, socklen); fpm_sockets_hash_op(sock, sa, 0, 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 = 1; int sock; mode_t saved_umask = 0; sock = socket(sa->sa_family, SOCK_STREAM, 0); if (0 > sock) { zlog(ZLOG_SYSERROR, "failed to create new listening socket: socket()"); return -1; } if (0 > 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) == 0) { zlog(ZLOG_ERROR, "An another FPM instance seems to already listen on %s", ((struct sockaddr_un *) sa)->sun_path); close(sock); return -1; } unlink( ((struct sockaddr_un *) sa)->sun_path); saved_umask = umask(0777 ^ wp->socket_mode); } if (0 > 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 -1; } if (wp->listen_address_domain == FPM_AF_UNIX) { char *path = ((struct sockaddr_un *) sa)->sun_path; umask(saved_umask); if (wp->socket_uid != -1 || wp->socket_gid != -1) { if (0 > chown(path, wp->socket_uid, wp->socket_gid)) { zlog(ZLOG_SYSERROR, "failed to chown() the socket '%s'", wp->config->listen_address); close(sock); return -1; } } } if (0 > listen(sock, wp->config->listen_backlog)) { zlog(ZLOG_SYSERROR, "failed to listen to address '%s'", wp->config->listen_address); close(sock); return -1; } 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 -1; } if (!module->wait) { zlog(ZLOG_ERROR, "Incomplete event implementation. Please open a bug report on https://bugs.php.net."); return -1; } /* count the max number of necessary fds for polling */ max = 1; /* 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 > 0) { max += (wp->config->pm_max_children * 2); } } //这时调用的就是epoll_create;epoll_ctl(efd,EPOLL_CTL_ADD,fd, {event.data.fd=fd.event.events=EPOLLIN} ); if (module->init(max) < 0) { zlog(ZLOG_ERROR, "Unable to initialize the event module %s", module->name); return -1; } zlog(ZLOG_DEBUG, "event module is %s and %d fds have been reserved", module->name, max); if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_event_cleanup, NULL)) { return -1; } return 0; }
遍历进程池中每个进程
/* 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 == 2) { fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET); fpm_event_loop(1); } } /* run event loop forever */ fpm_event_loop(0); 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 1; } memset(wp->ondemand_event, 0, 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 = 1; fpm_event_add(wp->ondemand_event, 0); return 1; } return fpm_children_make(wp, 0 /* not in event loop yet */, 0, 1); }
循环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 = 0; 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 = 0; /* 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 < 1 || fpm_globals.running_children < fpm_global_config.process_max)) { warned = 0; child = fpm_resources_prepare(wp); //创建管道,注意这里是循环,所以会创建多个管道,不需要全双工,
//因为只需要子进程向父进程汇报工作,子进程不需要自已保存日志,可让子进程专心处理php请求 if (!child) { return 2; } pid = fork(); switch (pid) { case 0 : fpm_child_resources_use(child); //子进程将stdout和stderr重定向到fd_stdout[1]和fd_stderr[1]中 fpm_globals.is_child = 1; fpm_child_init(wp); return 0; case -1 : zlog(ZLOG_SYSERROR, "fork() failed"); fpm_resources_discard(child); return 2; 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 > 0 && fpm_globals.running_children >= fpm_global_config.process_max) { warned = 1; zlog(ZLOG_WARNING, "The maximum number of processes has been reached. Please review your configuration and consider raising 'process.max'"); } return 1; /* 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 0; } c->wp = wp; c->fd_stdout = -1; c->fd_stderr = -1; if (0 > fpm_stdio_prepare_pipes(c)) { //创建两个管道 fpm_child_free(c); return 0; } if (0 > fpm_scoreboard_proc_alloc(wp->scoreboard, &c->scoreboard_i)) { fpm_stdio_discard_pipes(c); fpm_child_free(c); return 0; } return c; }
主进程利用pipe建立管道 fd_stout[2]和fd_stderr[2]
int fpm_stdio_prepare_pipes(struct fpm_child_s *child) /* {{{ */ { if (0 == child->wp->config->catch_workers_output) { /* not required */ return 0; } if (0 > pipe(fd_stdout)) { zlog(ZLOG_SYSERROR, "failed to prepare the stdout pipe"); return -1; } if (0 > pipe(fd_stderr)) { zlog(ZLOG_SYSERROR, "failed to prepare the stderr pipe"); close(fd_stdout[0]); close(fd_stdout[1]); return -1; } if (0 > fd_set_blocked(fd_stdout[0], 0) || 0 > fd_set_blocked(fd_stderr[0], 0)) { zlog(ZLOG_SYSERROR, "failed to unblock pipes"); close(fd_stdout[0]); close(fd_stdout[1]); close(fd_stderr[0]); close(fd_stderr[1]); return -1; } return 0; }
父进程将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 (0 == child->wp->config->catch_workers_output) { /* not required */ return 0; } close(fd_stdout[1]); close(fd_stderr[1]); child->fd_stdout = fd_stdout[0]; child->fd_stderr = fd_stderr[0]; fpm_event_set(&child->ev_stdout, child->fd_stdout, FPM_EV_READ, fpm_stdio_child_said, child); fpm_event_add(&child->ev_stdout, 0); fpm_event_set(&child->ev_stderr, child->fd_stderr, FPM_EV_READ, fpm_stdio_child_said, child); fpm_event_add(&child->ev_stderr, 0); return 0; } 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 = 0; 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[1], STDOUT_FILENO); dup2(fd_stderr[1], STDERR_FILENO); close(fd_stdout[0]); close(fd_stdout[1]); close(fd_stderr[0]); close(fd_stderr[1]); } 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, 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 have 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 * 1000) + (tmp.tv_usec / 1000) + 1; } ret = module->wait(fpm_event_queue_fd, timeout); /* is a child, nothing to do here */ if (ret == -2) { return; } if (ret > 0) { 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, 1); 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 = 0; child->slow_logged.tv_usec = 0; } } #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 == 0 && 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(-1, &status, WNOHANG | WUNTRACED)) > 0) { char buf[128]; int severity = ZLOG_NOTICE; int restart_child = 1; 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 = 0; } 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 = 0; } 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, 1 /* 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 = 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) { 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); } } }
fcgi_request *fcgi_init_request(int listen_socket) { fcgi_request *req = (fcgi_request*)calloc(1, sizeof(fcgi_request)); req->listen_socket = listen_socket; req->fd = -1; req->id = -1; req->in_len = 0; req->in_pad = 0; req->out_hdr = NULL; req->out_pos = req->out_buf; #ifdef TCP_NODELAY req->nodelay = 0; #endif fcgi_hash_init(&req->env); return req; }
子进程 会调用 fcgi_accept_request 函数,
int fcgi_accept_request(fcgi_request *req) { while (1) { if (req->fd < 0) { while (1) { if (in_shutdown) { return -1; } { 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 >= 0) { if (((struct sockaddr *)&sa)->sa_family == AF_INET) { if (allowed_clients) { int n = 0; int allowed = 0; while (allowed_clients[n] != INADDR_NONE) { if (allowed_clients[n] == sa.sa_inet.sin_addr.s_addr) { allowed = 1; 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 = -1; continue; } } } } } if (req->fd < 0 && (in_shutdown || (errno != EINTR && errno != ECONNABORTED))) { return -1; } if (req->fd >= 0) { struct pollfd fds; int ret; fds.fd = req->fd; fds.events = POLLIN; fds.revents = 0; do { errno = 0; ret = poll(&fds, 1, 5000); } while (ret < 0 && errno == EINTR); //相当于 当事件没有发生时,持续等待5000ms,当这段时间内依然没有 事件发生生时,返回ret为0,否则>0 if (ret > 0 && (fds.revents & POLLIN)) { break; } fcgi_close(req, 1, 0); } } } else if (in_shutdown) { return -1; } if (fcgi_read_request(req)) { return req->fd; } else { fcgi_close(req, 1, 1); } } }
这里用到了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将导致程序一直休眠到有数据为止。因为 有可能 直到有数据的时间 要 远远大于 自己主动休眠的固定时间