internals:extensions
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
internals:extensions [2013/04/09 16:12] – created jpauli | internals:extensions [2017/09/22 13:28] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
Line 11: | Line 11: | ||
Both extension kinds share lots of stuff. The difference between both types is mainly in hooks they register into the Engine. | Both extension kinds share lots of stuff. The difference between both types is mainly in hooks they register into the Engine. | ||
+ | Remember that, despite it is very uncommon, an extension can be both a PHP extension and a Zend extension at the same time. Xdebug is a good example. | ||
- | All extensions may be built statically or dynamically. | + | Here are the PHP extension structures : |
- | When built statically, the extension is loaded earlier than when its built dynamically | + | <code c> |
+ | typedef struct _zend_module_entry zend_module_entry; | ||
+ | typedef struct _zend_module_dep zend_module_dep; | ||
+ | |||
+ | struct _zend_module_entry { | ||
+ | unsigned short size; | ||
+ | unsigned int zend_api; | ||
+ | unsigned char zend_debug; | ||
+ | unsigned char zts; | ||
+ | const struct _zend_ini_entry *ini_entry; | ||
+ | const struct _zend_module_dep *deps; | ||
+ | const char *name; | ||
+ | const struct _zend_function_entry *functions; | ||
+ | int (*module_startup_func)(INIT_FUNC_ARGS); | ||
+ | int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS); | ||
+ | int (*request_startup_func)(INIT_FUNC_ARGS); | ||
+ | int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS); | ||
+ | void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS); | ||
+ | const char *version; | ||
+ | size_t globals_size; | ||
+ | #ifdef ZTS | ||
+ | ts_rsrc_id* globals_id_ptr; | ||
+ | #else | ||
+ | void* globals_ptr; | ||
+ | #endif | ||
+ | void (*globals_ctor)(void *global TSRMLS_DC); | ||
+ | void (*globals_dtor)(void *global TSRMLS_DC); | ||
+ | int (*post_deactivate_func)(void); | ||
+ | int module_started; | ||
+ | unsigned char type; | ||
+ | void *handle; | ||
+ | int module_number; | ||
+ | const char *build_id; | ||
+ | }; | ||
+ | |||
+ | struct _zend_module_dep { | ||
+ | const char *name; /* module name */ | ||
+ | const char *rel; /* version relationship: | ||
+ | const char *version; | ||
+ | unsigned char type; /* dependency type */ | ||
+ | }; | ||
+ | </ | ||
+ | And here are the Zend extension structures : | ||
+ | <code c> | ||
+ | /* Typedef' | ||
+ | typedef int (*startup_func_t)(zend_extension *extension); | ||
+ | typedef void (*shutdown_func_t)(zend_extension *extension); | ||
+ | typedef void (*activate_func_t)(void); | ||
+ | typedef void (*deactivate_func_t)(void); | ||
+ | |||
+ | typedef void (*message_handler_func_t)(int message, void *arg); | ||
+ | |||
+ | typedef void (*op_array_handler_func_t)(zend_op_array *op_array); | ||
+ | |||
+ | typedef void (*statement_handler_func_t)(zend_op_array *op_array); | ||
+ | typedef void (*fcall_begin_handler_func_t)(zend_op_array *op_array); | ||
+ | typedef void (*fcall_end_handler_func_t)(zend_op_array *op_array); | ||
+ | |||
+ | typedef void (*op_array_ctor_func_t)(zend_op_array *op_array); | ||
+ | typedef void (*op_array_dtor_func_t)(zend_op_array *op_array); | ||
+ | |||
+ | typedef struct _zend_extension { | ||
+ | char *name; | ||
+ | char *version; | ||
+ | char *author; | ||
+ | char *URL; | ||
+ | char *copyright; | ||
+ | |||
+ | startup_func_t startup; | ||
+ | shutdown_func_t shutdown; | ||
+ | activate_func_t activate; | ||
+ | deactivate_func_t deactivate; | ||
+ | |||
+ | message_handler_func_t message_handler; | ||
+ | |||
+ | op_array_handler_func_t op_array_handler; | ||
+ | |||
+ | statement_handler_func_t statement_handler; | ||
+ | fcall_begin_handler_func_t fcall_begin_handler; | ||
+ | fcall_end_handler_func_t fcall_end_handler; | ||
+ | |||
+ | op_array_ctor_func_t op_array_ctor; | ||
+ | op_array_dtor_func_t op_array_dtor; | ||
+ | |||
+ | int (*api_no_check)(int api_no); | ||
+ | int (*build_id_check)(const char* build_id); | ||
+ | void *reserved3; | ||
+ | void *reserved4; | ||
+ | void *reserved5; | ||
+ | void *reserved6; | ||
+ | void *reserved7; | ||
+ | void *reserved8; | ||
+ | |||
+ | DL_HANDLE handle; | ||
+ | int resource_number; | ||
+ | } zend_extension; | ||
+ | |||
+ | typedef struct _zend_extension_version_info { | ||
+ | int zend_extension_api_no; | ||
+ | char *build_id; | ||
+ | } zend_extension_version_info; | ||
+ | </ | ||
+ | |||
+ | ===== Statically compiled extensions ===== | ||
+ | |||
+ | Lots of extensions may be built statically or dynamically. Some require one or the other compilation mode, but they are uncommon. | ||
+ | When built statically, the extension is loaded | ||
So, the first thing to remember is that an extension may behave differently if it is loaded statically against dynamically, | So, the first thing to remember is that an extension may behave differently if it is loaded statically against dynamically, | ||
+ | |||
+ | When you prepare your sources, part of the prepare work is to write a file '' | ||
+ | This files contains an array with the pointers to all the to-be-statically-compiled extensions. Here is an example : | ||
+ | <code c> | ||
+ | static zend_module_entry *php_builtin_extensions[] = { | ||
+ | phpext_date_ptr, | ||
+ | phpext_ereg_ptr, | ||
+ | phpext_pcre_ptr, | ||
+ | phpext_foo_ptr, | ||
+ | phpext_reflection_ptr, | ||
+ | phpext_spl_ptr, | ||
+ | phpext_standard_ptr, | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | Extensions such as '' | ||
+ | **Only PHP extensions may be built statically, Zend extensions may not. Extensions which are both PHP and Zend, may be built statically** | ||
+ | |||
+ | The first thing PHP does when it loads, is to get those pointers out, and load those extensions. | ||
+ | The pointers are pointers to the '' | ||
+ | |||
+ | Example: | ||
+ | <code c> | ||
+ | #define phpext_standard_ptr & | ||
+ | |||
+ | zend_module_entry basic_functions_module = { /* {{{ */ | ||
+ | STANDARD_MODULE_HEADER_EX, | ||
+ | NULL, | ||
+ | standard_deps, | ||
+ | " | ||
+ | basic_functions, | ||
+ | PHP_MINIT(basic), | ||
+ | PHP_MSHUTDOWN(basic), | ||
+ | PHP_RINIT(basic), | ||
+ | PHP_RSHUTDOWN(basic), | ||
+ | PHP_MINFO(basic), | ||
+ | PHP_VERSION, | ||
+ | STANDARD_MODULE_PROPERTIES | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | To register a new PHP extension, the engine calls for '' | ||
+ | This does few things : | ||
+ | * Checks for extension dependencies, | ||
+ | * Checks if the extension has already been registered, if it is the case, warns | ||
+ | * Registers the PHP extension functions into the global function table, calling '' | ||
+ | |||
+ | ===== Parsing the ini file to get more extensions ===== | ||
+ | |||
+ | '' | ||
+ | It then adds the names of the extensions to two different lists, one for PHP exts : extension_lists.functions, | ||
+ | You can find the code by looking for the function '' | ||
+ | |||
+ | Before PHP5.5, Zend extensions needed to be provided the full path to the extension (ini token " | ||
+ | As of 5.5, this in not the case any more and both PHP and Zend extensions are loaded providing only the file name, not the full path. | ||
+ | | ||
+ | At this stage, PHP and Zend extensions have been parsed from ini files, but nothing more has been done. | ||
+ | |||
+ | ===== Registering additional extensions from the SAPI module ===== | ||
+ | Again before loading/ | ||
+ | Those get registered here, the same way statically compiled got registered just few steps before, to recall : | ||
+ | * Checks for extension dependencies, | ||
+ | * Checks if the extension has already been registered, if it is the case, warns | ||
+ | * Registers the PHP extension functions into the global function table, calling '' | ||
+ | |||
+ | For example, the Apache SAPI (apxs2), registers a PHP extension that provides functions related to Apache. Each SAPI may register one or more PHP extension providing additional functionality for itself. Apache is a good example. | ||
+ | The CLI SAPI doesn' | ||
+ | | ||
+ | ===== Loading the ini parsed extensions ===== | ||
+ | |||
+ | Here comes time where PHP will effectively **load** Zend and PHP extensions, calling some hooks onto them. '' | ||
+ | First thing to know : **Zend extensions are loaded before PHP extensions** | ||
+ | |||
+ | ==== Loading Zend extensions ==== | ||
+ | '' | ||
+ | * zend_extension_entry | ||
+ | * extension_version_info | ||
+ | |||
+ | If those symbols are missing, the Zend extension will fail to load | ||
+ | |||
+ | Then API compatibility is checked from '' | ||
+ | After that, buildid is checked. Buildid is a combination of the previously checked API number, debug flag and thread safety flag (ZTS). Other components may be used for buildid check. Zend extension' | ||
+ | |||
+ | | ||
+ | |||
+ | After those checks, the Zend extensions get registered. '' | ||
+ | A message is dispatched to all previously registered Zend extension to make them know a new Zend extension is being registered. Then, '' | ||
+ | This system has been designed so that Zend extensions may know each other, and eventually fail loading if they guess they are not compatible with each other. | ||
+ | |||
+ | | ||
+ | |||
+ | Finally, the Zend extension is registered, it is then added to an exported global register storing Zend extensions pointers : '' | ||
+ | |||
+ | ==== Loading PHP extensions ==== | ||
+ | After Zend extensions got registered, comes the PHP extensions turn. '' | ||
+ | So, I repeat again : to register a new PHP extension, the engine calls for '' | ||
+ | This does few things : | ||
+ | * Checks for extension dependencies, | ||
+ | * Checks if the extension has already been registered, if it is the case, warns | ||
+ | * Registers the PHP extension functions into the global function table, calling '' | ||
+ | |||
+ | At this stage, the extensions order is not relevant. Extensions are simply registered, nothing more will happen here | ||
+ | |||
+ | PHP extensions registration is quite the same as Zend extensions registration : dlopen() the shared object, and checks for compatibility. | ||
+ | PHP extensions must export a '' | ||
+ | |||
+ | The '' | ||
+ | <code c> | ||
+ | #define ZEND_GET_MODULE(name) \ | ||
+ | BEGIN_EXTERN_C()\ | ||
+ | ZEND_DLEXPORT zend_module_entry *get_module(void) { return & | ||
+ | END_EXTERN_C() | ||
+ | |||
+ | #ifdef COMPILE_DL_FOO | ||
+ | ZEND_GET_MODULE(foo) | ||
+ | #endif | ||
+ | </ | ||
+ | |||
+ | Then, the two fields '' | ||
+ | |||
+ | After that, the loading mechanism fills in the '' | ||
+ | Now the PHP extension is going to be registered into a shared global variable, called '' | ||
+ | The registration step will perform two checks against the PHP extension being registered : | ||
+ | * If the PHP extension is already present in the registry, warning then failure at registration | ||
+ | * PHP extension dependencies **conflicts only** are checked | ||
+ | |||
+ | The system checks for the eventually attached '' | ||
+ | * A PHP extension can tell the system that it is not compatible with another already registered extension, so it will fail loading itself into the engine | ||
+ | * A PHP extension can tell the system that it requires another PHP extension to be loaded before itself | ||
+ | |||
+ | Here, **only conflicts are checked**, and as there is no particular registration order, a PHP extension A could declare being in conflict with B, but B could be loaded after A, hence the check won't work and you'll probably get some trouble. | ||
+ | When dealing with conflicts, it is better to sort the extensions in the php.ini file as a FIFO. | ||
+ | **PHP extensions will be registered in the exact same order they' | ||
+ | |||
+ | ==== Activating PHP extensions ==== | ||
+ | After registration, | ||
+ | PHP extensions are activated before Zend extensions | ||
+ | |||
+ | '' | ||
+ | Then comes the " | ||
+ | It checks the extension field '' | ||
+ | Note that conflicts requirements have already been checked against, at extension loading (see last chapter) | ||
+ | After that, the extension globals are registered (call to '' | ||
+ | |||
+ | ==== Activating Zend extensions ==== | ||
+ | Zend extensions are then activated **after** PHP extensions | ||
+ | '' | ||
+ | | ||
+ | '' | ||
+ | | ||
+ | |||
+ | ===== Extensions lifetime ===== | ||
+ | You may know PHP's lifetime. Very basically, '' | ||
+ | We already detailed php_module_startup() previously, to show how both PHP extensions and Zend extensions live into this stage. | ||
+ | |||
+ | ==== Request startup ==== | ||
+ | Zend extensions come first, and their '' | ||
+ | PHP extensions come second, and their '' | ||
+ | |||
+ | ==== Request shutdown ==== | ||
+ | PHP extensions come first, and their '' | ||
+ | Zend extensions come after, and their '' | ||
+ | A third hook is called : '' | ||
+ | |||
+ | ==== PHP shutdown ==== | ||
+ | PHP is ending, so as we are clean guys, we wrote code for PHP to clean up its environment in a right way and don't rely on the process terminating to destroy all the environment. | ||
+ | PHP extensions are first destroyed. This is done internally by relying on the Hashtable functionalities. As the '' | ||
+ | It calls '' | ||
+ | Finally, if the extension were to be loaded with '' | ||
+ | |||
+ | After PHP extensions have all been shut down and unloaded, Zend extensions will be. | ||
+ | '' | ||
+ | |||
+ | ===== Main schema ===== | ||
+ | |||
+ | {{: |
internals/extensions.1365523961.txt.gz · Last modified: 2017/09/22 13:28 (external edit)