美文网首页
【笔记】php内核相关阅读笔记:php7、sapi、生命周期

【笔记】php内核相关阅读笔记:php7、sapi、生命周期

作者: 言十年 | 来源:发表于2019-03-27 21:38 被阅读0次

    此文章是读书笔记,个人底层欠火候。文章的图或找或自己试着画一下。尽量少的抄书。

    准备

    php 5.6、php 7.0.12 各一份

    使用 vscode ,配置(vscode代替source insight

    phpstudy 方便切换各种版本测代码。

    centos7的虚拟机,方便后续的使用。

    流程图用的是https://www.processon.com

    php 7变化

    抽象语法树

    php5.x

    PHP代码在语法解析阶段直接生成了ZendVM指令。zend_language_parse.y中生成opline指令

    缺点:编译器与执行器耦合在一起

    php7

    将php代码解析成抽象语法树,将抽象语法树编译为ZendVM指令

    优点: php的编译器与执行器很好地隔离开,编译器不需要关心指令的生成规则,然后执行器根据自己的规则将抽象语法树编译为对应的指令。

    Native TLS

    php 5.x :

    多线程环境不能简单通过全局变量实现,为适应多线程应用环境。

    php提供了一个线程安全资源管理器,将全局资源进行线程隔离,不同的线程互不干扰

    php 7

    使用Native TLS(线程局部存储)保存线程的资源池,__tread标识一个全局变量,全局变量就是线程独享,不同线程修改不会影响

    指定函数参数、返回值类型

    zval结构变化

    php 5.x

    zend.h

    zval

    struct _zval_struct {
        /* Variable information */
        zvalue_value value;     /* value */
        zend_uint refcount__gc;
        zend_uchar type;    /* active type */
        zend_uchar is_ref__gc;
    };
    

    value

    typedef union _zvalue_value {
        long lval;                  /* long value */
        double dval;                /* double value */
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;              /* hash table value */
        zend_object_value obj;
        zend_ast *ast;
    } zvalue_value;
    

    缺点

    php5.x的引用计数在在zval中而不是在 value中,复制变量需要复制两个结构,zval跟value 始终绑定在一起

    php 7

    zend_types.h

    zval

    struct _zval_struct {
        zend_value        value;            /* value */
        union {
            struct {
                ZEND_ENDIAN_LOHI_4(
                    zend_uchar    type,         /* active type */
                    zend_uchar    type_flags,
                    zend_uchar    const_flags,
                    zend_uchar    reserved)     /* call info for EX(This) */
            } v;
            uint32_t type_info;
        } u1;
        union {
            uint32_t     var_flags;
            uint32_t     next;                 /* hash collision chain */
            uint32_t     cache_slot;           /* literal cache slot */
            uint32_t     lineno;               /* line number (for ast nodes) */
            uint32_t     num_args;             /* arguments number for EX(This) */
            uint32_t     fe_pos;               /* foreach position */
            uint32_t     fe_iter_idx;          /* foreach iterator index */
        } u2;
    };
    

    value

    typedef union _zend_value {
        zend_long         lval;             /* long value */
        double            dval;             /* double value */
        zend_refcounted  *counted;
        zend_string      *str;
        zend_array       *arr;
        zend_object      *obj;
        zend_resource    *res;
        zend_reference   *ref;
        zend_ast_ref     *ast;
        zval             *zv;
        void             *ptr;
        zend_class_entry *ce;
        zend_function    *func;
        struct {
            uint32_t w1;
            uint32_t w2;
        } ww;
    } zend_value;
    

    优点:

    引用计数在具体value(元素zend_refcounted )中,zval只是载体,value才是真正的值

    php变量之间复制、传递更加简洁、易懂

    zval结构大小从24byte减少到了16byte,也是php7能够降低系统资源的一个优化点

    异常处理

    php5.x

    很多操作会抛出error错误

    php7

    将多数错误改为了异常抛出,这样就可以通过try catch 捕捉到了

    调用未定义函数。示例代码:

    try {
        call();
    } catch (Throwable $e) {
        echo $e->getMessage();
    }
    

    php5.6:Fatal error: Call to undefined function call() in E:\isoftbox\phpstudy\WWW\1.php on line 4

    php7输出:Call to undefined function call()

    HashTable

    php7,hashtable结构的大小从72byte减小到了56byte。数组元素bucket从72byte减少到了32byte

    php的构成

    php的构成

    SAPI层适配不同的执行场景。常用的如下:

     apache2handler
     cgi
     cli
     embed #嵌入式
     fpm
     litespeed #LiteSpeed 一种被特别设计用作大型网站的商业web服务器。 其中一个优势就是它能直接读取Apache 的配置信息。并轻易将它现有的产品结合在一起来代替Apache 。这种服务器是轻量级的就如它的名字暗示出非常快。
    

    ZendVM

    ZendVM两部分组成:编译器、执行器。

    Extension

    扩展分为PHP扩展、Zend扩展(应用于ZendVM比如Opcache)

    目录

    build/
    ext/
    main/
    netware/
    pear/
    sapi/
    scripts/
    tests/
    travis/
    TSRM/
    win32/
    Zend/
    

    生命周期

    模块初始化、请求初始化、执行脚本阶段、请求关闭阶段、模块关闭阶段

    php声明周期

    在main/main.c文件中能看到对应的函数定义。

    不同的sapi场景使用不同的方法。

    image.png

    拿fpm举例

    main() ,,在文件/sapi/fpm/fpm/fpm_main.c。main函数中能看到调用的情况。

    模块初始化阶段

    php_module_startup 函数(./main/main.c)

    未命名文件 (3).jpg

    主要干了:

    • 激活SAPI:sapi_activate()(函数的定义在./main/SAPI.c,之后看到sapi开头就找sapi.c)

    初始化请求信息

    ……
    /*初始化请求信息*/
    SG(sapi_headers).send_default_content_type = 1;
    
    /*
    SG(sapi_headers).http_response_code = 200;
    */
    SG(sapi_headers).http_status_line = NULL;
    SG(sapi_headers).mimetype = NULL;
    SG(headers_sent) = 0;
    ZVAL_UNDEF(&SG(callback_func));
    SG(read_post_bytes) = 0;
    SG(request_info).request_body = NULL;
    ……
    

    处理请求,读取post 数据,读取cookie。

    /* Handle request method 处理请求方法*/
    if (SG(server_context)) {
        if (PG(enable_post_data_reading)
        &&  SG(request_info).content_type
        &&  SG(request_info).request_method
        && !strcmp(SG(request_info).request_method, "POST")) {
            /* HTTP POST may contain form data to be processed into variables
                * depending on given content type 
                * HTTP POST可能包含要根据给定内容类型处理为变量的表单数据
                * */
            sapi_read_post_data();
        } else {
            SG(request_info).content_type_dup = NULL;
        }
    
        /* Cookies  读取Cookie*/
        SG(request_info).cookie_data = sapi_module.read_cookies();
    
        if (sapi_module.activate) {
            sapi_module.activate();
        }
    }
    if (sapi_module.input_filter_init) {
        sapi_module.input_filter_init();
    }
    
    • 启动php输出:php_output_startup()。
    PHPAPI void php_output_startup(void)
    {
        ZEND_INIT_MODULE_GLOBALS(output, php_output_init_globals, NULL);
        zend_hash_init(&php_output_handler_aliases, 8, NULL, NULL, 1);
        zend_hash_init(&php_output_handler_conflicts, 8, NULL, NULL, 1);
        zend_hash_init(&php_output_handler_reverse_conflicts, 8, NULL, reverse_conflict_dtor, 1);
        php_output_direct = php_output_stdout;
    }
    

    分析参考 跟厂长学PHP7内核(五):系统分析生命周期

    • 初始化垃圾回收器:gc_globals_ctor()(文件./Zend/zend_gc.c,含有gc_ 开头的你懂的),分配zend_gc_globals内存
    ZEND_API void gc_globals_ctor(void)
    {
    #ifdef ZTS /* 线程安全 执行ts_allocate_id,继续追这个函数就能看到tsrm_mutex_lock、tsrm_mutex_unlock*/
        ts_allocate_id(&gc_globals_id, sizeof(zend_gc_globals), (ts_allocate_ctor) gc_globals_ctor_ex, (ts_allocate_dtor) root_buffer_dtor);
    #else
        gc_globals_ctor_ex(&gc_globals);
    #endif
    }
    

    更多关于内存分配参考 PHP新的垃圾回收机制:Zend GC详解

    • 启动zend引擎:zend_startup()
    ……
    start_memory_manager(); /*启动内存池*/
    
    /* Set up utility functions and values 
        设置一些util函数句柄
        */
    zend_error_cb = utility_functions->error_function;
    zend_printf = utility_functions->printf_function;
    zend_write = (zend_write_func_t) utility_functions->write_function;
    zend_fopen = utility_functions->fopen_function;
    
    /* 设置 Zend 虚拟机编译、执行器的函数句柄 zend_compile_file、zend_execute_ex */
    #if HAVE_DTRACE
    /* build with dtrace support */
        zend_compile_file = dtrace_compile_file;
        zend_execute_ex = dtrace_execute_ex;
        zend_execute_internal = dtrace_execute_internal;
    #else
        zend_compile_file = compile_file;
        zend_execute_ex = execute_ex;
        zend_execute_internal = NULL;
    #endif /* HAVE_SYS_SDT_H */
        zend_compile_string = compile_string;
        zend_throw_exception_hook = NULL;
    
        /* Set up the default garbage collection implementation. 
            设置垃圾回收的函数句柄
        */
        gc_collect_cycles = zend_gc_collect_cycles;
    
        /*分配函数符号表(CG(function_table))、类符号表(CG(class_table))、常量符号表
    (EG(zend_constants))等,这个CG是非线程安全下的一个宏定义 # define GLOBAL_FUNCTION_TABLE      CG(function_table)*/
        GLOBAL_FUNCTION_TABLE = (HashTable *) malloc(sizeof(HashTable));
        GLOBAL_CLASS_TABLE = (HashTable *) malloc(sizeof(HashTable));
        GLOBAL_AUTO_GLOBALS_TABLE = (HashTable *) malloc(sizeof(HashTable));
        GLOBAL_CONSTANTS_TABLE = (HashTable *) malloc(sizeof(HashTable));
    
    
    #ifdef ZTS
        /*如果是多线程的话,还会分配编译器、执行器的全局变
    量*/
        ts_allocate_id(&executor_globals_id, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor);
    ……
    
    /* 注册 zend核心扩展,扩展是内核提供的,该过程将注册Zend核心扩展提供的函数,比如:strlen、define、func_get_args、class_exists等*/
    zend_startup_builtin_functions();
    /*注册 Zend 定义的标准常量:zend_register_standard_constants(),比如:E_ERROR、
    E_WARNING、E_ALL、TRUE、FALSE 等*/
    zend_register_standard_constants();
    /* 注册$GLOBALS 超全局变量的获取 handler */
    zend_register_auto_global(zend_string_init("GLOBALS", sizeof("GLOBALS") - 1, 1), 1, php_auto_globals_create_globals);
    /*分配 php.ini 配置的存储符号表:*/
    zend_ini_startup();
    
    • 注册php定义的常量
    REGISTER_MAIN_STRINGL_CONSTANT("PHP_VERSION", PHP_VERSION, sizeof(PHP_VERSION)-1, CONST_PERSISTENT | CONST_CS);
        REGISTER_MAIN_LONG_CONSTANT("PHP_MAJOR_VERSION", PHP_MAJOR_VERSION, CONST_PERSISTENT | CONST_CS);
        REGISTER_MAIN_LONG_CONSTANT("PHP_MINOR_VERSION", PHP_MINOR_VERSION, CONST_PERSISTENT | CONST_CS);
    ……
    
    • 解析php.ini:解析完成后所有的 php.ini 配置保存在 configuration_hash 哈希表中(php_init_config(),在php_ini.c)
    if (php_init_config() == FAILURE) {
        return FAILURE;
    }
    

    *映射 PHP、Zend 核心的 php.ini 配置:根据解析出的 php.ini,获取对应的配置值,将
    最终的配置插入 EG(ini_directives)哈希表。

    /* Register PHP core ini entries  注册 php 核心 ini 入口*/ 
        REGISTER_INI_ENTRIES();
    
    • 注册用于获取_GET、_POST、_COOKIE、_SERVER、_ENV、_REQUEST、$_FILES
      变量的 handler。
    php_startup_auto_globals();/* 具体定义在./main/php_variables.c*/
    
    • 注册静态编译的扩展:php_register_internal_extensions_func()

    • 注册动态加载的扩展:php_ini_register_extensions()

    • 回调各扩展定义的 module starup 钩子函数,即通过 PHP_MINIT_FUNCTION()定义的函数。

    • 禁用php.ini 配置的函数与类。默认disable_functions =disable_classes =

    php_disable_functions();
    php_disable_classes();
    

    请求初始化

    int php_request_startup(void)

    请求初始化

    主要处理:

    • 激活输出:php_output_activate()

    • 激活Zend引擎:zend_activate()

    ZEND_API void zend_activate(void) /* {{{ */
    {
    #ifdef ZTS
        virtual_cwd_activate();
    #endif
        gc_reset();/* 重置垃圾回收器 */
        init_compiler(); /* 初始化编译器 */
        init_executor(); /* 初始化执行器 */
        startup_scanner();/* 初始化词法扫描器*/
    }
    
    • 激活SAPI:sapi_activate()(注意:模块初始化阶段也做了这件事情)

    • 回调个扩展定义的request startup钩子函数:zend_activate_modules()(文件./Zend/zend_API.c)

    
    ZEND_API void zend_activate_modules(void) /* {{{ */
    {
        zend_module_entry **p = module_request_startup_handlers;
    
        while (*p) {
            zend_module_entry *module = *p;
    
            if (module->request_startup_func(module->type, module->module_number)==FAILURE) {
                zend_error(E_WARNING, "request_startup() for %s module failed", module->name);
                exit(1);
            }
            p++;
        }
    }
    

    执行脚本阶段

    php_execute_script(),包括php代码编译、执行两个核心阶段(zend引擎最重要的功能)。编译阶段,php脚本经历从PHP源码到抽象语法树再到opline指令的转化过程,生成zend引擎识别的执行指令(opline指令),指令被执行器执行,php解释执行的过程。

    执行脚本阶段
    ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...) /* {{{ */
    {
        va_list files;
        int i;
        zend_file_handle *file_handle;
        zend_op_array *op_array;
    
        va_start(files, file_count);
        for (i = 0; i < file_count; i++) {
            file_handle = va_arg(files, zend_file_handle *);
            if (!file_handle) {
                continue;
            }
            /* 编译 opcodes(词法、语法分析—)*/
            op_array = zend_compile_file(file_handle, type);
            if (file_handle->opened_path) {
                zend_hash_add_empty_element(&EG(included_files), file_handle->opened_path);
            }
            zend_destroy_file_handle(file_handle);
            if (op_array) {
                /* 指令执行  */
                zend_execute(op_array, retval);
                zend_exception_restore();
                zend_try_exception_handler();
                if (EG(exception)) {
                    zend_exception_error(EG(exception), E_ERROR);
                }
                destroy_op_array(op_array);
                efree_size(op_array, sizeof(zend_op_array));
            } else if (type==ZEND_REQUIRE) {
                va_end(files);
                return FAILURE;
            }
        }
        va_end(files);
    
        return SUCCESS;
    }
    
    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 {
            zend_bool original_in_compilation = CG(in_compilation);
            CG(in_compilation) = 1;
    
            CG(ast) = NULL;
            CG(ast_arena) = zend_arena_create(1024 * 32);
            /* yacc 不断调用re2cc扫描token生成抽象语法树*/
            if (!zendparse()) {
                zval retval_zv;
                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, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE);
                CG(active_op_array) = op_array;
                ZVAL_LONG(&retval_zv, 1);
    
                if (zend_ast_process) {
                    zend_ast_process(CG(ast));
                }
    
                zend_file_context_begin(&original_file_context);
                zend_oparray_context_begin(&original_oparray_context);
                /* 从抽象语法树 生成 op_array*/
                zend_compile_top_stmt(CG(ast));
                zend_emit_final_return(&retval_zv);
                op_array->line_start = 1;
                op_array->line_end = CG(zend_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;
        }
    
        zend_restore_lexical_state(&original_lex_state);
        return op_array;
    }
    

    请求关闭阶段

    php_request_shutdown()
    这个阶段将flush输出内容、发送HTTP应答header头、清理全局变量、关闭编译器、关闭执行器等。还会回调各扩展的request shutdown 钩子函数。这个阶段是请求初始化相反的操作,与初始化阶段处理一一对应。

    请求关闭阶段
        /* 1. Call all possible shutdown functions registered with register_shutdown_function() */
        if (PG(modules_activated)) zend_try {
            php_call_shutdown_functions();
        } zend_end_try();
    
        /* 2. Call all possible __destruct() functions */
        zend_try {
            zend_call_destructors();
        } zend_end_try();
    
        /* 3. Flush all output buffers */
        zend_try {
            zend_bool send_buffer = SG(request_info).headers_only ? 0 : 1;
    
            if (CG(unclean_shutdown) && PG(last_error_type) == E_ERROR &&
                (size_t)PG(memory_limit) < zend_memory_usage(1)
            ) {
                send_buffer = 0;
            }
    
            if (!send_buffer) {
                php_output_discard_all();
            } else {
                php_output_end_all();
            }
        } zend_end_try();
    
        /* 4. Reset max_execution_time (no longer executing php code after response sent) */
        zend_try {
            zend_unset_timeout();
        } zend_end_try();
    
        /* 5. Call all extensions RSHUTDOWN functions */
        if (PG(modules_activated)) {
            zend_deactivate_modules();
        }
    
        /* 6. Shutdown output layer (send the set HTTP headers, cleanup output handlers, etc.) */
        zend_try {
            php_output_deactivate();
        } zend_end_try();
    
        /* 7. Free shutdown functions */
        if (PG(modules_activated)) {
            php_free_shutdown_functions();
        }
    
        /* 8. Destroy super-globals */
        zend_try {
            int i;
    
            for (i=0; i<NUM_TRACK_VARS; i++) {
                zval_ptr_dtor(&PG(http_globals)[i]);
            }
        } zend_end_try();
    
        /* 9. free request-bound globals */
        php_free_request_globals();
    
        /* 10. Shutdown scanner/executor/compiler and restore ini entries */
        zend_deactivate();
    
        /* 11. Call all extensions post-RSHUTDOWN functions */
        zend_try {
            zend_post_deactivate_modules();
        } zend_end_try();
    
        /* 12. SAPI related shutdown (free stuff) */
        zend_try {
            sapi_deactivate();
        } zend_end_try();
    
        /* 13. free virtual CWD memory */
        virtual_cwd_deactivate();
    
        /* 14. Destroy stream hashes */
        zend_try {
            php_shutdown_stream_hashes();
        } zend_end_try();
    
        /* 15. Free Willy (here be crashes) */
        zend_interned_strings_restore();
        zend_try {
            shutdown_memory_manager(CG(unclean_shutdown) || !report_memleaks, 0);
        } zend_end_try();
    
        /* 16. Reset max_execution_time */
        zend_try {
            zend_unset_timeout();
        } zend_end_try();
    
    

    模块关闭阶段

    php_module_shutdown()

    该阶段与模块初始化阶段对应,主要进行资源清理、php各模块的关闭操作,回调各扩展的module shutdown钩子函数。

    php_module_shutdown()
        sapi_flush();
    
        zend_shutdown();/* 清理持久化符号表 */
    
        /* Destroys filter & transport registries too */
        php_shutdown_stream_wrappers(module_number);
    
        UNREGISTER_INI_ENTRIES(); /* 清理ini hashTable 元素*/
    
        /* close down the ini config */
        php_shutdown_config();
    
    #ifndef ZTS
        zend_ini_shutdown();
        shutdown_memory_manager(CG(unclean_shutdown), 1);
    #else
        zend_ini_global_shutdown(); /* 销毁 EG(ini_directive*/
    #endif
    
        php_output_shutdown(); /* 关闭output */
    
        module_initialized = 0;
    
    #ifndef ZTS
        core_globals_dtor(&core_globals); /*释放 PG*/
        gc_globals_dtor();
    #else
        ts_free_id(core_globals_id);
    #endif
    

    参考资料:

    相关文章

      网友评论

          本文标题:【笔记】php内核相关阅读笔记:php7、sapi、生命周期

          本文链接:https://www.haomeiwen.com/subject/tuxzvqtx.html