main函数里
当kill -TERM pid 时, 这篇文章 说是
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的全连接队列
//代码效果参考:http://hnjlyzjd.com/xl/wz_24517.html
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,SIG_x005f_x0008_TERM),不再设置定时器
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的操作
slowlog 配置详见这里 自己的分析见这里
在一个非阻塞的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_v