php-fpm 初始化过程

1 main 函数

sapi\fpm\fpm\fpm_main.c 的大约 1570 行

2 调用startup 初始化扩展和其他内容
这个函数会启动所有的扩展的初始化函数 也就是PHP_MINIT

    /* startup after we get the above ini override se we get things right */
    if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) {    // 初始化

3 初始化完成之后调用 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, force_stderr)) {   

        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]);
            zend_quiet_write(fpm_globals.send_config_pipe[1], &writeval, sizeof(writeval));
            close(fpm_globals.send_config_pipe[1]);
        }
        return FPM_EXIT_CONFIG;
    }
  • 然后我们看看fpm_init 具体定义,fpm_init 会初始化非常多的东西 ,我有空会重新看一下这些初始化函数的具体内容
//  sapi\fpm\fpm\fpm.c
int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon, int force_stderr) /* {{{ */
{
    ...

    if (0 > fpm_php_init_main()           ||
        0 > fpm_stdio_init_main()         ||
        0 > fpm_conf_init_main(test_conf, force_daemon) ||
        0 > fpm_unix_init_main()          ||
        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()       ||
        0 > fpm_worker_pool_init_main()   ||
        0 > fpm_event_init_main()) {

           ...
}

4 调用fpm_run

  • 这里估计会调用fork (linux有fork 函数但是Windows我就不知道了) 函数
  • 具体定义
//  sapi\fpm\fpm\fpm.c

/*  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;
}

5 调用请求相关的初始化函数fpm_init_request

·   /* library is already initialized, now init our request */
    request = fpm_init_request(fcgi_fd);

6 调用完 fpm_init_reques 后,我们继续往main函数下走,走到我今天要讨论的 核心循环

        while (EXPECTED(fcgi_accept_request(request) >= 0)) {  // (1)  内部主要是accept 函数 
            char *primary_script = NULL;
            request_body_fd = -1;
            SG(server_context) = (void *) request;  // (2) 设置上下文
            init_request_info();

            fpm_request_info();

            /* request startup only after we've done all we can to
             *            get path_translated */
            if (UNEXPECTED(php_request_startup() == FAILURE)) {
                fcgi_finish_request(request, 1);
                SG(server_context) = NULL;
                php_module_shutdown();
                return FPM_EXIT_SOFTWARE;
            }

            /* check if request_method has been sent.
             * if not, it's certainly not an HTTP over fcgi request */
            if (UNEXPECTED(!SG(request_info).request_method)) {
                goto fastcgi_request_done;
            }

            if (UNEXPECTED(fpm_status_handle_request())) {
                goto fastcgi_request_done;
            }

            /* If path_translated is NULL, terminate here with a 404 */
            if (UNEXPECTED(!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 (UNEXPECTED(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 (UNEXPECTED(php_fopen_primary_script(&file_handle) == FAILURE)) {  //(3) 转换路径
                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);  // (4)核心函数 执行脚本

fastcgi_request_done:
            if (EXPECTED(primary_script)) {
                efree(primary_script);
            }

            if (UNEXPECTED(request_body_fd != -1)) {
                close(request_body_fd);
            }
            request_body_fd = -2;

            if (UNEXPECTED(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);
                }
            }

            fpm_request_end();
            fpm_log_write(NULL);

            efree(SG(request_info).path_translated);
            SG(request_info).path_translated = NULL;

            php_request_shutdown((void *) 0);

            requests++;
            if (UNEXPECTED(max_requests && (requests == max_requests))) {
                fcgi_request_set_keep(request, 0);
                fcgi_finish_request(request, 0);
                break;
            }
            /* end of fastcgi loop */
        }
  • (1) 首先我们看到 while 循环里面有宏 EXPECTED 以及函数fcgi_accept_request
    while (EXPECTED(fcgi_accept_request(request) >= 0)) { // (1) 内部主要是accept 函数
// 这里去掉了win32 相关的代码
int fcgi_accept_request(fcgi_request *req)
{
    ...
        int listen_socket = req->listen_socket;
    ...
        req->hook.on_accept();

        FCGI_LOCK(req->listen_socket);
        req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len);
        FCGI_UNLOCK(req->listen_socket);

    ...
        if (fcgi_read_request(req)) {
            return req->fd;
    ...
}

不考虑网络模型的话(epoll kpoll 等等),其实就是一个accept 函数 然后返回一个文件描述符return req->fd;

  • (2) 调用完fcgi_accept_request 后就对全局变量赋值fcgi_accept_request
  • (3) 路径转换 php_fopen_primary_script
            /* path_translated exists, we can continue ! */
            if (UNEXPECTED(php_fopen_primary_script(&file_handle) == FAILURE)) {
  • (4) 核心函数 ,执行脚本

分割线会详细讲述

zend_file_handle 结构 ,我们很容易可以看出是一个描述文件的结构其实和文件描述符是类似的

typedef struct _zend_file_handle {
    union {
        int           fd;
        FILE          *fp;
        zend_stream   stream;
    } handle;
    const char        *filename;
    zend_string       *opened_path;
    zend_stream_type  type;
    zend_bool free_filename;
} zend_file_handle;

执行函数php_execute_script,会调用 zend_execute_scripts

PHPAPI int php_execute_script(zend_file_handle *primary_file){
    ...
    retval = (zend_execute_scripts(ZEND_REQUIRE, NULL, 3, prepend_file_p, primary_file, append_file_p) == SUCCESS);
    ...

}

然后我们看一下zend_execute_scripts定义

zend_execute_scripts 定义

ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...) /* {{{ */
{
    ...
        op_array = zend_compile_file(file_handle, type); // 编译获得opcode
    ...
        if (op_array) {
            zend_execute(op_array, retval);    // 执行opcode 的函数
            zend_exception_restore();
            zend_try_exception_handler();
            if (EG(exception)) {
                zend_exception_error(EG(exception), E_ERROR);
            }
            ...
        }   
    ...
}

zend_compile_file 是一个函数指针,指向 他等于 compile_file

然后我们看一下函数compile_file 的定义,其中compile_file 会调用函数zend_compile

ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, int type)
{
    zend_lex_state original_lex_state;
    zend_op_array *op_array = NULL;
    zend_save_lexical_state(&original_lex_state);

    if (open_file_for_scanning(file_handle)==FAILURE) {
        if (type==ZEND_REQUIRE) {
            zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, file_handle->filename);
            zend_bailout();
        } else {
            zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, file_handle->filename);
        }
    } else {
        op_array = zend_compile(ZEND_USER_FUNCTION);
    }

    zend_restore_lexical_state(&original_lex_state);
    return op_array;
}

核心函数 zend_compile 是由 lex/yacc 或者bison 这类工具去生成相应的c 源文件,所以你会在 zend_language_scanner.c 也会看到zend_compile ,但是其实是在php-7.1.8-src\Zend\zend_language_scanner.l 文件下生成的

// php-7.1.8-src\Zend\zend_language_scanner.l 下的`zend_compile` 函数
static zend_op_array *zend_compile(int type)   // 核心函数 貌似没有注释
{
    zend_op_array *op_array = NULL;
    zend_bool original_in_compilation = CG(in_compilation);

    CG(in_compilation) = 1;
    CG(ast) = NULL;
    CG(ast_arena) = zend_arena_create(1024 * 32);

    if (!zendparse()) {         //////////////////// 重点函数
        int last_lineno = CG(zend_lineno);
        zend_file_context original_file_context;
        zend_oparray_context original_oparray_context;
        zend_op_array *original_active_op_array = CG(active_op_array);

        op_array = emalloc(sizeof(zend_op_array));
        init_op_array(op_array, type, INITIAL_OP_ARRAY_SIZE);
        CG(active_op_array) = op_array;

        if (zend_ast_process) {
            zend_ast_process(CG(ast));
        }

        zend_file_context_begin(&original_file_context);
        zend_oparray_context_begin(&original_oparray_context);
        zend_compile_top_stmt(CG(ast));
        CG(zend_lineno) = last_lineno;
        zend_emit_final_return(type == ZEND_USER_FUNCTION);
        op_array->line_start = 1;
        op_array->line_end = last_lineno;
        pass_two(op_array);
        zend_oparray_context_end(&original_oparray_context);
        zend_file_context_end(&original_file_context);

        CG(active_op_array) = original_active_op_array;
    }

    zend_ast_destroy(CG(ast));
    zend_arena_destroy(CG(ast_arena));

    CG(in_compilation) = original_in_compilation;

    return op_array;
}

快到了最后的几步了

我们看到

static zend_op_array *zend_compile(int type)   // 核心函数 貌似没有注释
{

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容