This article does not describe the purpose of an INI configuration item in detail, as explained in the manual. I just want to dig into PHP's implementation mechanism from a specific point of view, which involves some knowledge of the PHP kernel:-)
The students who use PHP know that the php.ini configuration will take effect throughout the SAPI life cycle. During the execution of a PHP script, if you manually modify the INI configuration, it will not function. If you cannot restart Apache or Nginx, then you can only explicitly invoke the Ini_set interface in your PHP code. Ini_set is a function that PHP provides to us to dynamically modify the configuration, it is important to note that the configuration set by the Ini_set and the configuration set in the INI file, the time range for which it takes effect is not the same. After the execution of the PHP script is complete, the Ini_set settings are invalidated.
Therefore, this article intends to divide two articles, the first one describes the php.ini configuration principle, the second chapter on dynamic modification of PHP configuration.
The configuration of php.ini will roughly involve three pieces of data, Configuration_hash,eg (ini_directives) and PG, BG, Pcre_g, Json_g, xxx_g, etc. If it is not clear that the meaning of these three kinds of data is not related, the following will be explained in detail.
1, parsing INI configuration file
Since php.ini needs to be in effect during the SAPI process, parsing the INI file and building the PHP configuration accordingly must be the beginning of the SAPI. In other words, that must happen in PHP's startup process. PHP requires that any actual request arrives before it has been built into its internal configuration.
The kernel that is reflected in PHP is the Php_module_startup function.
Php_module_startup is primarily responsible for initiating PHP, which is usually called at the beginning of SAPI. BTW, there is also a common function is php_request_startup, which is responsible for each request will be initialized at the moment of arrival, Php_module_startup and Php_request_startup is two of the identity of the action, But the analysis of them is not within the scope of this article.
For example, when PHP is hooked up to a module under Apache, then Apache activates all of these module, including the PHP module. When the PHP module is activated, it is called to Php_module_startup. The Php_module_startup function completes a lot of work, and once the php_module_startup call ends it means that ok,php has started and can now accept the request and respond.
In the Php_module_startup function, the implementation associated with parsing the INI file is:
/* This would read in php.ini, set up the configuration parameters, load Zend Extensions and register PHP function exte Nsions to be loaded later */if (Php_init_config (tsrmls_c) = = FAILURE) { return FAILURE;}
As you can see, it is actually called the Php_init_config function to complete the parse of the INI file. Parse works primarily for Lex&grammar analysis and extracts key and value key values from INI files and saves them. The format of the php.ini is simple, the left side of the equals sign is key, and the right is value. When a pair of kv is extracted, where does PHP store it? The answer is the Configuration_hash mentioned earlier.
Static HashTable Configuration_hash;
Configuration_hash declaration in PHP_INI.C, it is a hashtable type of data structure. As the name implies, is actually a hash table. aside, the version before php5.3 is not able to get configuration_hash because it is a static variable of the Php_ini.c file. Later php5.3 added the Php_ini_get_configuration_hash interface, which directly returns &configuration_hash, making PHP extensions easy to glimpse Configuration_ Hash full picture ... It's really a joy to run ...
Note Four points:
First, Php_init_config does not do anything other than lexical syntax. That is, if we add a line of Hello=world to the INI file, as long as this is a well-formed configuration item, then the final configuration_hash will contain a key hello, the value of the world element, Configuration_ Hash to maximize the reflection of the INI file.
Second, the INI file allows us to configure it in the form of an array. For example, the following three lines are written in the INI file:
Drift.arr[]=1drift.arr[]=2drift.arr[]=3
Then in the resulting Configuration_hash table, there will be an element with a key of Drift.arr, whose value is an array of three numbers containing the three-by-one. This is a very rare configuration method.
Third, PHP also allows us to build some INI files in addition to the default php.ini file (exactly Php-%s.ini). These INI files are placed in an additional directory. The directory is specified by the environment variable Php_ini_scan_dir, and when php_init_config resolves the php.ini, it scans the directory again and then finds all the. INI files in the directory for analysis. The KV key pairs generated in these additional INI files will also be added to the Configuration_hash.
This is an occasionally useful feature, assuming we develop PHP's own extensions, but do not want to mix the configuration php.ini, you can write an additional ini, and through Php_ini_scan_dir tell PHP where to find it. Of course, its drawbacks are obvious, and it needs to be supported by setting additional environment variables. A better solution is for the developer to call Php_parse_user_ini_file or zend_parse_ini_file in the extension to parse the corresponding INI file.
Four, in Configuration_hash, key is a string, so what is the type of the value? The answer is also a string (except for the very special array above). Specifically, for example, the following configuration:
Display_errors = Onlog_errors = Offlog_errors_max_len = 1024
So the last configuration_hash in the actual stored key value pairs are:
Key: "Display_errors" val: "1" Key: "Log_errors" val: "" "Key:" Log_errors_max_len "val:" 1024 "
Note that log_errors, whose stored value is not even "0", is a truly empty string. Also, Log_errors_max_len is not a number, but a string of 1024.
Analysis to this point, basically parse INI file related content is clear. Briefly summarize:
1, parsing ini occurs in php_module_startup phase
2, the analytic results are stored in Configuration_hash.
2, Configuration action to module
The approximate structure of PHP can be seen as the bottom of a Zend engine, which is responsible for interacting with the OS, compiling PHP code, providing memory hosting, and so on, on the top of the Zend engine, there are many modules lined up. One of the most core of a core module, and others such as standard,pcre,date,session and so on ... These modules also have another name called PHP extensions. We can simply understand that each module will provide a set of functional interfaces for the developer to invoke, for example, built-in functions such as Explode,trim,array are provided by the standard module.
Why do you need to talk about this, because in php.ini in addition to PHP itself, that is, for the core module configuration (such as safe_mode,display_errors,max_execution_time, etc.), There are also quite a few configurations for other different modules.
For example, the date module, which provides functions such as the common date, time,strtotime, and so on. In php.ini, its related configuration is as follows:
[Date];d ate.timezone = ' Asia/shanghai ';d ate.default_latitude = 31.7667;date.default_longitude = 35.2333;date.sunrise _zenith = 90.583333;date.sunset_zenith = 90.583333
In addition to these modules have a separate configuration, the Zend engine is also available, but the Zend engine is very small, only error_reporting,zend.enable_gc and Detect_unicode three items.
As we mentioned in the previous section, Php_module_startup calls Php_init_config, which is intended to parse the INI file and generate Configuration_hash. So what's going to happen next in Php_module_startup? It is obvious that the configuration in Configuration_hash will be applied to different modules such as ZEND,CORE,STANDARD,SPL. Of course, this is not a one-off process, because PHP usually contains a lot of modules, PHP boot process These modules will be started in turn. Then, the process of configuring module A occurs during the boot process of module A.
Students with extended development experience will directly point out that the start of module A is not in php_minit_function (a)?
Yes, if module a needs to be configured, then in Php_minit_function, you can call Register_ini_entries () to complete. Based on the configuration item name required by the current module, Register_ini_entries will configuration_hash to find the user-set configuration value and update it to the module's own global space.
2.1, the global space of the module
To understand how to configure the INI configuration from Configuration_hash to each module, it is necessary to understand the global space of the PHP module first. For different PHP modules, you can open up a piece of your own storage space, and this block of space for the module, is globally visible. Generally, it is used to store the INI configuration required for the module. That is, the configuration items in the Configuration_hash are eventually stored in the global space. In the execution of the module, only the direct access to the global space, you can get the user's settings for the module. Of course, it is also often used to record intermediate data during the execution of a module.
We illustrate with the Bcmath module that Bcmath is a PHP module that provides an interface for mathematical computing, first we look at what INI configuration it has:
Php_ini_begin () std_php_ini_entry ("Bcmath.scale", "0", Php_ini_all, Onupdatelonggezero, Bc_precision, Zend_ Bcmath_globals, Bcmath_globals) php_ini_end ()
Bcmath has only one configuration item, we can configure the Bcmath module with Bcmath.scale in the php.ini.
Next, look at the global space definition for the Bcmatch module. The following declarations are made in PHP_BCMATH.H:
Zend_begin_module_globals (Bcmath) bc_num _zero_; Bc_num _one_; Bc_num _two_; Long bc_precision; Zend_end_module_globals (Bcmath)
After the macro is expanded, it is:
typedef struct _ZEND_BCMATH_GLOBALS {
Bc_num _zero_;
Bc_num _one_;
Bc_num _two_;
Long bc_precision;
} zend_bcmath_globals;
In fact, the zend_bcmath_globals type is the global space type in the Bcmath module. Only the zend_bcmath_globals structure is declared here, and there are specific instantiation definitions in the BCMATH.C:
After unfolding, it is zend_bcmath_globals bcmath_globals; Zend_declare_module_globals (Bcmath)
As can be seen, the definition of variable bcmath_globals is completed with Zend_declare_module_globals.
Bcmath_globals is a real global space that contains four of fields. Its last field, Bc_precision, corresponds to Bcmath.scale in the INI configuration. We set the value of Bcmath.scale in PHP.ini, and then the value of Bcmath.scale is updated to bcmath_globals.bc_precision when the Bcmath module is started.
The value in Configuration_hash is updated to the xxx_globals variable defined by each module, which is called the INI configuration action to the module. Once the module is started, these configurations are also in place. Therefore, in the subsequent execution phase, the PHP module does not need to revisit the Configuration_hash, the module only needs to access its own xxx_globals, you can get the user-defined configuration.
Bcmath_globals, in addition to having a field INI configuration item, what other three fields do you mean? This is the second function of the module global space, which, in addition to the INI configuration, can also store some of the data in the process of module execution.
Another example is the JSON module, which is a very common module in PHP:
Zend_begin_module_globals (JSON) int error_code; Zend_end_module_globals (JSON)
You can see that the JSON module does not require an INI configuration, and its global space has only one field error_code. Error_code records the last time an error occurred in the execution of Json_decode or Json_encode. The Json_last_error function returns this error_code to help the user locate the cause of the error.
In order to easily access the module global space variables, PHP has introduced a number of macros. For example, we want to visit json_globals in the Error_code, of course, can write directly json_globals.error_code (not in multithreaded environment), but the more general way is to define the JSON_G macro:
#define JSON_G (v) (JSON_GLOBALS.V)
We use Json_g (error_code) to access Json_globals.error_code. At the beginning of this article, I mentioned PG, BG, Json_g, Pcre_g,xxx_g and so on, and these macros are also common in PHP source code. Now we can easily understand them, PG macros can access global variables of the core module, BG accesses the global variables of the standard module, and PCRE_G accesses the global variables of the PCRE module.
#define PG (v) (CORE_GLOBALS.V) #define BG (v) (BASIC_GLOBALS.V)
2.2, how to determine which configuration a module requires?
What INI configuration The module requires is defined by itself in each module. For example, for a core module, a configuration item definition is as follows:
Php_ini_begin () ... STD_PHP_INI_ENTRY_EX ("Display_errors", "1", Php_ini_all, onupdatedisplayerrors, Display_errors, php_core_ Globals, Core_globals, Display_errors_mode) Std_php_ini_boolean ("Enable_dl", "1", Php_ini_system, Onupdatebool, enable_dl, php_core_globals, core_globals) Std_php_ini_boolean ("expose_php", "1", Php_ini_system, Onupdatebool, expose_php, php_core_globals, core_globals) Std_php_ini_boolean (" Safe_mode ", " 0 ", Php_ini_system, Onupdatebool, Safe_mode, php_core_globals, core_globals) ...... Php_ini_end ()
The above code can be found in the Php-src\main\main.c file, probably 450+ line. There are more macros involved, there are Zend_ini_begin, Zend_ini_end, PHP_INI_ENTRY_EX, Std_php_ini_boolean, etc., this article does not repeat, interested readers can analyze their own.
The above code is expanded with the following macro:
static const Zend_ini_entry ini_entries[] = {.. {0, Php_ini_all, "Display_errors", sizeof ("display_errors"), Onupdatedisplayerrors, (void *) Xtoffsetof (php_core_ Globals, display_errors), (void *) &core_globals, NULL, "1", sizeof ("1")-1, NULL, 0, 0, 0, Display_errors_mode}, { 0, Php_ini_system, "Enable_dl", sizeof ("Enable_dl"), Onupdatebool, (void *) Xtoffsetof (php_core_globals, en ABLE_DL), (void *) &core_globals, NULL, "1", sizeof ("1")-1, NULL, 0, 0, 0, ZEND_INI_BOOLEAN_DISPLAYER_CB}, {0 , Php_ini_system, "expose_php", sizeof ("expose_php"), Onupdatebool, (void *) Xtoffsetof (php_core_globals, exp ose_php), (void *) &core_globals, NULL, "1", sizeof ("1")-1, NULL, 0, 0, 0, ZEND_INI_BOOLEAN_DISPLAYER_CB}, {0, Php_ini_system, "Safe_mode", sizeof ("Safe_mode"), Onupdatebool, (void *) Xtoffsetof (php_core_globals, safe _mode), (void *) &core_globals, NULL, "0", sizeof ("0")-1, NULL, 0, 0, 0, Zend_ini_boolean_diSPLAYER_CB}, ... {0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0, null}};
We see that the definition of a configuration item is essentially a definition of an array of type Zend_ini_entry. The specific meaning of a field in a zend_ini_entry struct is:
struct _zend_ini_entry { int module_number; ID of the module int modifiable; Modifiable ranges, such as Php.ini,ini_set char *name; The name of the configuration item UINT Name_length; ZEND_INI_MH ((*on_modify)); A callback function that calls void *MH_ARG1 when the configuration item is registered or modified ; Typically the offset of the configuration item field in Xxx_g is void *mh_arg2; Usually xxx_g void *mh_arg3; Usually reserved fields, rarely use char *value; The value of the configuration item UINT Value_length; char *orig_value; The original value of the configuration item UINT Orig_value_length; int orig_modifiable; The original modifiable int modified of the configuration item ; If any modifications have occurred, Orig_value will save the pre-modified value void (*displayer) (zend_ini_entry *ini_entry, int type);};
2.3, apply the configuration to the module--register_ini_entries
It is often possible to see register_ini_entries in different extended php_minit_function. Register_ini_entries is mainly responsible for the completion of two things, first, the module's global space Xxx_g to fill, synchronous Configuration_hash in the value of Xxx_g. Second, it also generates eg (ini_directives).
Register_ini_entries is also a macro, after unfolding is actually the Zend_register_ini_entries method. Specifically, the implementation of Zend_register_ini_entries:
ZEND_API int zend_register_ini_entries (const zend_ini_entry *ini_entry, int module_number tsrmls_dc)/* {{{*/{//Ini_ Entry is an array of zend_ini_entry types, and P is a pointer to each item in the set of const zend_ini_entry *p = Ini_entry; Zend_ini_entry *hashed_ini_entry; Zval Default_value; EG (ini_directives) is registered_zend_ini_directives HashTable *directives = registered_zend_ini_directives; Zend_bool config_directive_success = 0; Remember ini_entry last item fixed to {0, 0, NULL, ...} while (p->name) {config_directive_success = 0; Add P-pointing zend_ini_entry to eg (ini_directives) if (Zend_hash_add (directives, P->name, P->name_length, (void*) p, s Izeof (zend_ini_entry), (void * *) &hashed_ini_entry) = = FAILURE) {zend_unregister_ini_entries (module_number TSRMLS_CC); return FAILURE; } hashed_ini_entry->module_number = Module_number; According to the name to Configuration_hash query, the results are placed in the Default_value//note that the value of default_value is compared to the originalNumbers, strings, arrays, and so on, depending on the php.ini (Zend_get_configuration_directive (P->name, P->name_length, &default _value) = = SUCCESS) {//Call On_modify update to module's global space Xxx_g in if (!hashed_ini_entry->on_modify | | hashed_ Ini_entry->on_modify (Hashed_ini_entry, Z_strval (default_value), Z_strlen (default_value), hashed_ini_entry-> Mh_arg1, Hashed_ini_entry->mh_arg2, Hashed_ini_entry->mh_arg3, zend_ini_stage_startup TSRMLS_CC) = = SUCCESS) { Hashed_ini_entry->value = Z_strval (default_value); Hashed_ini_entry->value_length = Z_strlen (default_value); config_directive_success = 1; }}//If not found in Configuration_hash, use default if (!config_directive_success && hashed_ini_entry-> ; on_modify) {hashed_ini_entry->on_modify (Hashed_ini_entry, Hashed_ini_entry->value, Hashed_ini_entry-> ; Value_length, Hashed_ini_entry->mh_arg1, HASHED_INI_ENTRY->MH_ARG2, Hashed_INI_ENTRY->MH_ARG3, Zend_ini_stage_startup tsrmls_cc); } p++; } return SUCCESS;}
Simply put, the logic of the above code can be expressed as:
1, add the INI configuration entry for the module declaration to eg (ini_directives). Note that the value of the INI configuration item may subsequently be modified.
2, try to go to Configuration_hash to find the required INI for each module.
- If it can be found, the user is configured with this value in the 叜 INI file.
- If not found, OK, no relationship, because the module when declaring the INI, will bring the default value.
3, the INI value is synchronized to xx_g inside. After all, in the course of PHP execution, these xxx_globals are still working. The specific process is to invoke each INI configuration corresponding to the On_modify method is completed, on_modify by the module when declaring the INI is specified.
Let's take a concrete look at on_modify, which is actually a function pointer, to see the configuration declarations of two specific core modules:
Std_php_ini_boolean ("Log_errors", "0", php_ini_all, Onupdatebool, Log_errors, php_core_globals, core_ Globals) Std_php_ini_entry ("Log_errors_max_len", "1024x768", Php_ini_all, Onupdatelong, Log_errors_max_len, php_core_ Globals, Core_globals)
For Log_errors, its on_modify is set to Onupdatebool, and for Log_errors_max_len, On_modify is set to Onupdatelong.
Further assume that our configuration in php.ini is:
Log_errors = Onlog_errors_max_len = 1024
Specifically, consider the Onupdatebool function:
Zend_api ZEND_INI_MH (onupdatebool) { zend_bool *p; Base represents the address of the core_globals char *base = (char *) mh_arg2; P means the address of the Core_globals plus the offset of the log_errors field //Gets the address of the Log_errors field p = (Zend_bool *) (base+ (size_t) mh_arg1); if (new_value_length = = 2 && strcasecmp ("on", new_value) = = 0) { *p = (zend_bool) 1; } else if (new_value_length = = 3 && strcasecmp ("yes", new_value) = = 0) { *p = (zend_bool) 1; } else if (new_value_length = = 4 && strcasecmp ("true", new_value) = = 0) { *p = (zend_bool) 1; } The value stored in else {//Configuration_hash is the string "1" instead of "on" //So here Atoi is converted to the number 1 *p = (zend_bool) atoi (new_ value); } return SUCCESS;}
The most puzzling estimate is mh_arg1 and mh_arg2, in fact, compared with the zend_ini_entry definition described earlier, MH_ARG1,MH_ARG2 is very easy to fathom. MH_ARG1 represents the byte offset, and MH_ARG2 represents the address of the xxx_globals. Therefore, the result of (char *) MH_ARG2+MH_ARG1 is the address of a field in Xxx_globals. In this case, the address of log_errors in the core _globals is calculated. So, when Onupdatebool finally executes to
*p = (zend_bool) atoi (New_value);
Its role is equivalent to
Core _globals. Log_errors= (Zend_bool) atoi ("1");
After analyzing the Onupdatebool, we come to see Onupdatelong and feel at a glance:
Zend_api ZEND_INI_MH (onupdatelong) { long *p; Char *base = (char *) mh_arg2; Get Log_errors_max_len address p = (long *) (base+ (size_t) mh_arg1); Convert "1024" to long and assign value to Core_globals.log_errors_max_len *p = Zend_atol (New_value, new_value_length); return SUCCESS;}
Finally, it is important to note that in the Zend_register_ini_entries function, if there is a configuration in Configuration_hash, the value and value in Hashed_ini_entry after the call on_modify ends _length will be updated. That is, if the user is configured in php.ini, then eg (ini_directives) holds the actual configured value. If the user does not match, EG (ini_directives) holds the default value given when declaring Zend_ini_entry.
The default_value variable in zend_register_ini_entries is poorly named, and it's quite easy to misunderstand. In fact, Default_value does not represent a default value, but rather a value that is actually configured by the user.
3, Summary
At this point, three pieces of data Configuration_hash,eg (Ini_directives) and PG, BG, Pcre_g, Json_g, Xxx_g ... Have all been explained clearly.
To summarize:
1,configuration_hash, store the configuration in the php.ini file, do not check, its value is a string.
2,eg (ini_directives), which holds zend_ini_entry defined in each module, is replaced with Configuration_hash if the user is configured in php.ini (Configuration_ exists) The value in the hash, the type is still a string.
3,xxx_g, the macro is used to access the module's global space, which can be used to hold the INI configuration and be updated by on_modify the specified function, whose data type is determined by the field declaration in Xxx_g.
for more details, please read on to the next page. Highlights : http://www.linuxidc.com/Linux/2016-02/128442p2.htm