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

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

准备

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

参考资料:

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