首页 > 技术文章 > fastcgi main

taek 2017-03-28 22:08 原文

 

  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的操作

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_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将导致程序一直休眠到有数据为止。因为 有可能 直到有数据的时间 要 远远大于 自己主动休眠的固定时间

 

推荐阅读