Reading notes related to PHP kernel: php7, sapi, life cycle

prepare

php 5.6 and php 7.0.12

Using vscode, configure( vscode replaces source insight)

phpstudy is convenient to switch various versions of test code.

CentOS 7 virtual machine, convenient for subsequent use.

The flow chart uses https://www.processon.com

php 7 changes

Abstract syntax tree

php5.x

The PHP code directly generates the ZendVM instruction in the syntax parsing phase. The opline instruction is generated in zend_language_parse.y

Disadvantages: the compiler is coupled with the actuator

php7

Parse the php code into an abstract syntax tree and compile the abstract syntax tree into ZendVM instructions

Advantages: the php compiler is well separated from the executor. The compiler does not need to care about the generation rules of instructions, and then the executor compiles the abstract syntax tree into corresponding instructions according to its own rules.

Native TLS

php 5.x :

Multithreading environment can not be simply realized through global variables. In order to adapt to multithreading application environment.

php provides a thread safe resource manager to isolate global resources from each other, and different threads do not interfere with each other

php 7

Use Native TLS (thread local storage) to save the resource pool of threads, and _tread identifies a global variable. The global variable is exclusive to threads, and the modification of different threads will not affect it

Specify function parameters and return value types

Structural change of 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;

shortcoming

The reference count of php5.x is in zval instead of value. To copy variables, you need to copy two structures. Zval and value are always bound together

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;

advantage:

Reference count in the specific value (element zend_refcounted), zval is only the carrier, and value is the real value

Copying and passing between php variables is more concise and easy to understand

The size of zval structure is reduced from 24 bytes to 16 bytes, which is also an optimization point for php7 to reduce system resources

exception handling

php5.x

Many operations throw an error

php7

Change most errors to exception throwing, so that they can be caught through try catch

Calling an undefined function. Example code:

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 output: Call to undefined function call()

HashTable

php7, the size of hashtable structure is reduced from 72 bytes to 56 bytes, and the bucket of array element is reduced from 72 bytes to 32 bytes

Composition of php

Composition of php

SAPI layer adapts to different execution scenarios. Common are as follows:

 apache2handler
 cgi
 cli
 embed #Embedded
 fpm
 litespeed #LiteSpeed is a commercial web server specially designed for large websites. One advantage is that it can directly read Apache configuration information. It can easily combine its existing products to replace Apache. This server is lightweight, as its name implies that it is very fast.

ZendVM

ZendVM consists of two parts: compiler and executor.

Extension

Extensions are divided into PHP extensions and Zend extensions (applied to Zend VM, such as Opcache)

catalogue

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

life cycle

Module initialization, request initialization, script execution phase, request shutdown phase, module shutdown phase

php declaration cycle

You can see the corresponding function definition in the main/main.c file.

Different sapi scenarios use different methods.

image.png

Take fpm for example

main(),, you can see the call in the file / sapi/fpm/fpm/fpm_main.c.

Module initialization phase

php_module_startup function (. / main/main.c)

Unnamed file (3).jpg

Mainly:

  • Activate SAPI: sapi_activate() (the function is defined in. / main/SAPI.c, and then find sapi.c at the beginning of SAPI)

Initialization request information

......
/*Initialization request information*/
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;
......

Process requests, read post data, and read cookie s.

/* Handle request method Processing 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 May contain form data to be processed as variables based on a given content type
            * */
        sapi_read_post_data();
    } else {
        SG(request_info).content_type_dup = NULL;
    }

    /* Cookies  Read cookies*/
    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();
}
  • Start PHP output: 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;
}

Analysis reference Learn PHP7 kernel from the factory director (V): system analysis life cycle

  • Initialize the garbage collector: gc_globals_ctor() (file. / Zend/zend_gc.c, containing the beginning of gc_, you know), allocate zend_gc_globals memory
ZEND_API void gc_globals_ctor(void)
{
#ifdef ZTS / * thread safely executes ts_allocate_id. if you continue to trace this function, you can see tsrm_mutex_lock and 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
}

More about memory allocation references PHP's new garbage collection mechanism: Zend GC explanation

  • Start Zend engine: zend_startup()
......
start_memory_manager(); /*Start memory pool*/

/* Set up utility functions and values 
    Set some util function handles
    */
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;

/* Set zend_compile_file and zend_execute_ex function handles of Zend virtual machine compiler and executor */
#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. 
        Set function handle for garbage collection
    */
    gc_collect_cycles = zend_gc_collect_cycles;

    /*Allocation function symbol table (CG(function_table)), class symbol table (CG(class_table)), constant symbol table
(EG(zend_constants))Etc. This CG is a macro definition # define global_function_table CG (function_table) under non thread safety*/
    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
    /*If it is multithreaded, it will also allocate the global variables of the compiler and executor
 amount*/
    ts_allocate_id(&executor_globals_id, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor);
......

/* Register the Zend core extension. The extension is provided by the kernel. This process will register the functions provided by the Zend core extension, such as strlen, define, func_get_args, class_exists, etc*/
zend_startup_builtin_functions();
/*Register the standard constants defined by Zend: zend_register_standard_constants(), such as E_ERROR
E_WARNING,E_ALL,TRUE,FALSE etc.*/
zend_register_standard_constants();
/* Register the get handler of $GLOBALS super global variable */
zend_register_auto_global(zend_string_init("GLOBALS", sizeof("GLOBALS") - 1, 1), 1, php_auto_globals_create_globals);
/*Allocate the storage symbol table of php.ini configuration:*/
zend_ini_startup();
  • Register php defined constants
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);
......
  • Parsing php.ini: after parsing, all php.ini configurations are saved in the configuration_hash hash table (php_init_config(), in php_ini.c)
if (php_init_config() == FAILURE) {
    return FAILURE;
}

*Map php.ini configuration of PHP and Zend core: obtain the corresponding configuration value according to the parsed php.ini, and The final configuration is inserted into the EG(ini_directives) hash table.

/* Register PHP core ini entries  Register php core ini entry*/ 
    REGISTER_INI_ENTRIES();
php_startup_auto_globals();/* Specifically defined in. / main/php_variables.c*/
  • Register statically compiled extensions: php_register_internal_extensions_func()
  • Register dynamically loaded extensions: php_ini_register_extensions()
  • Callback the module starup hook function defined by each extension, that is, the function defined through PHP_MINIT_FUNCTION().
  • Disable functions and classes configured by php.ini. The default is disable_functions =, disable_classes =.
php_disable_functions();
php_disable_classes();

Request initialization

int php_request_startup(void)

Request initialization

Main treatment:

  • Activate output: php_output_activate()
  • Activate Zend engine: zend_activate()
ZEND_API void zend_activate(void) /* {{{ */
{
#ifdef ZTS
    virtual_cwd_activate();
#endif
    gc_reset();/* Reset garbage collector */
    init_compiler(); /* Initialize compiler */
    init_executor(); /* Initialize actuator */
    startup_scanner();/* Initialize lexical scanner*/
}
  • Activate SAPI: sapi_activate() (Note: This is also done during module initialization)
  • Callback a request startup hook function defined by extension: zend_activate_modules() (file. / 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++;
    }
}

Execute script phase

php_execute_script(), including two core stages of PHP code compilation and execution (the most important function of zend engine). In the compilation stage, PHP script goes through the transformation process from PHP source code to abstract syntax tree and then to opline instruction, generating the execution instruction (opline instruction) recognized by zend engine. The instruction is executed by the actuator, and PHP explains the execution process.

Execute script phase

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;
        }
        /* Compile opcodes (lexical and syntactic analysis -)*/
        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) {
            /* Instruction execution  */
            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 Constantly call re2cc to scan token s to generate abstract syntax trees*/
        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);
            /* Generate op_array from abstract syntax tree*/
            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;
}

Request close phase

php_request_shutdown() This stage will flush the output content, send HTTP response header header, clean up global variables, close the compiler, close the actuator, etc. It also calls back the request shutdown hook function of each extension. This stage is the opposite operation of requesting initialization, which corresponds to the processing in the initialization stage one by one.

Request close phase

    /* 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();

Module shutdown phase

php_module_shutdown()

This stage corresponds to the module initialization stage, which is mainly used to clean up resources, close php modules, and call back the extended module shutdown hook function.

php_module_shutdown()

    sapi_flush();

    zend_shutdown();/* Cleaning up persistent symbol tables */

    /* Destroys filter & transport registries too */
    php_shutdown_stream_wrappers(module_number);

    UNREGISTER_INI_ENTRIES(); /* Cleaning up ini hashTable elements*/

    /* 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(); /* Destroy EG(ini_directive)*/
#endif

    php_output_shutdown(); /* Close output */

    module_initialized = 0;

#ifndef ZTS
    core_globals_dtor(&core_globals); /*Release PG*/
    gc_globals_dtor();
#else
    ts_free_id(core_globals_id);
#endif

reference material:

Tags: PHP

Posted on Fri, 03 Dec 2021 05:52:15 -0500 by lxndr