set_limit_time()、ini_set()解析

問題發現

今天寫了一個腳本,提交代碼的時候京哥給我cr,果斷幫我指出這個腳本的運行時間限制不要這麼寫php

ini_set('max_execution_time','30');

要這麼寫html

set_limit_time(30);

而後給我講了一堆原理,什麼 set_limit_time() 直接進內存啊,ini_set('max_execution_time',) 須要暫時修改原配置啊...恩,仍是高工懂得多,因而我開始對兩個函數進行了測試。java

測試

測試代碼以下:後端

<?php
//測試ini_set
ini_set('max_execution_time',1);
sleep(10);
echo 'begin';
while(true){
}
<?php
//測試set_time_limit
set_time_limit(1);
sleep(10);
echo 'begin';
while(true){
}

這不測沒關係,一測就發現了問題,兩次測試都是先sleep了10s,而後返回php7

beginFatal error: Maximum execution time of 1 second exceeded in /Users/jdq/test.php on line 6

難道sleep不算腳本執行的時間?答案應該是確定的,但是我之前測試後端接口超時的時候確實用的sleep,並且也超時返回了504,思考了一下應該是php-fpm的配置覆蓋了php的ini配置的緣由吧,因此sleep的時間也視爲一個cgi進程的執行時間。(此處推斷有待肯定)迴歸正題,立刻修改了測試代碼函數

<?php
//測試ini_set
ini_set('max_execution_time',1);
echo 'begin';
while(true){
}
<?php
//測試set_time_limit
set_time_limit(1);
echo 'begin';
while(true){
}

兩個腳本都是執行了1s,直接fatal。那這兩個函數又是在什麼階段起做用的呢,修改測試代碼爲php-fpm

<?php
echo time(),PHP_EOL;
register_shutdown_function('func');
function func(){
    echo time(),PHP_EOL;
}
sleep(5);
ini_set('max_execution_time',5);
echo 'begin',PHP_EOL;
while(true){
}
<?php
echo time(),PHP_EOL;
register_shutdown_function('func');
function func(){
    echo time(),PHP_EOL;
}
sleep(5);
set_time_limit(5);
echo 'begin',PHP_EOL;
while(true){
}

返回結果分別爲測試

ini_set結果:
1528297536
begin
Fatal error: Maximum execution time of 5 seconds exceeded in /Users/jdq/test.php on line 11
1528297546

set_time_limit結果:
1528297751
begin
Fatal error: Maximum execution time of 5 seconds exceeded in /Users/jdq/test.php on line 11
1528297761

這兩個函數都是在執行的時候纔開始限定腳本執行時間,感受並無什麼區別,因此我找了找兩個函數的源碼。code

函數源碼

set_limit_time()

源碼以下(如下均爲php7.1源碼)htm

PHP_FUNCTION(set_time_limit)
{
    zend_long new_timeout;
    char *new_timeout_str;
    int new_timeout_strlen;
    zend_string *key;
    //作了一些參數校驗
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &new_timeout) == FAILURE) {
        return;
    }

    new_timeout_strlen = (int)zend_spprintf(&new_timeout_str, 0, ZEND_LONG_FMT, new_timeout);
    //看到配置項max_execution_time這裏我內心就開始哈哈哈了
    key = zend_string_init("max_execution_time", sizeof("max_execution_time")-1, 0);
    //其實調用了zend_alter_ini_entry_chars_ex這個函數
    if (zend_alter_ini_entry_chars_ex(key, new_timeout_str, new_timeout_strlen, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0) == SUCCESS) {
        RETVAL_TRUE;
    } else {
        RETVAL_FALSE;
    }
    zend_string_release(key);
    efree(new_timeout_str);
}

經過源碼咱們能夠看出,set_limit_time 函數就是調用了 zend_alter_ini_entry_chars_ex 對配置項 max_execution_time 進行了一番操做,這個函數的源代碼

ZEND_API int zend_alter_ini_entry_chars_ex(zend_string *name, const char *value, size_t value_length, int modify_type, int stage, int force_change) /* {{{ */
{
    int ret;
    zend_string *new_value;

    new_value = zend_string_init(value, value_length, !(stage & ZEND_INI_STAGE_IN_REQUEST));
    //執行了zend_alter_ini_entry_ex這個函數
    ret = zend_alter_ini_entry_ex(name, new_value, modify_type, stage, force_change);
    zend_string_release(new_value);
    return ret;
}

因此能夠看出,set_limit_time 最終實現要是 zend_alter_ini_entry_ex ,下面咱們將討論這個函數。

ini_set

源碼以下

PHP_FUNCTION(ini_set)
{
    zend_string *varname;
    zend_string *new_value;
    zend_string *val;
//參數處理
    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_STR(varname)
        Z_PARAM_STR(new_value)
    ZEND_PARSE_PARAMETERS_END();
//去一張hash表根據配置項名字尋找當前value,下面會說到,這個value一般會被釋放掉
    val = zend_ini_get_value(varname);

    /* copy to return here, because alter might free it! */
    if (val) {
        if (ZSTR_IS_INTERNED(val)) {
            RETVAL_INTERNED_STR(val);
        } else if (ZSTR_LEN(val) == 0) {
            RETVAL_EMPTY_STRING();
        } else if (ZSTR_LEN(val) == 1) {
            RETVAL_INTERNED_STR(ZSTR_CHAR((zend_uchar)ZSTR_VAL(val)[0]));
        } else if (!(GC_FLAGS(val) & GC_PERSISTENT)) {
            ZVAL_NEW_STR(return_value, zend_string_copy(val));
        } else {
            ZVAL_NEW_STR(return_value, zend_string_init(ZSTR_VAL(val), ZSTR_LEN(val), 0));
        }
    } else {
        RETVAL_FALSE;
    }
 //一堆我也不知道要幹什麼的校驗
#define _CHECK_PATH(var, var_len, ini) php_ini_check_path(var, var_len, ini, sizeof(ini))
    /* open basedir check */
    if (PG(open_basedir)) {
        if (_CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "error_log") ||
            _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "java.class.path") ||
            _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "java.home") ||
            _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "mail.log") ||
            _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "java.library.path") ||
            _CHECK_PATH(ZSTR_VAL(varname), ZSTR_LEN(varname), "vpopmail.directory")) {
            if (php_check_open_basedir(ZSTR_VAL(new_value))) {
                zval_dtor(return_value);
                RETURN_FALSE;
            }
        }
    }
#undef _CHECK_PATH
//最終要執行zend_alter_ini_entry_ex這個函數
    if (zend_alter_ini_entry_ex(varname, new_value, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0) == FAILURE) {
        zval_dtor(return_value);
        RETURN_FALSE;
    }
}

ini_set 的實現也是要靠函數 zend_alter_ini_entry_ex ,而 set_limit_time 只是其中一個配置項(參數)爲 max_execution_time 的實現而已,原來這兩個函數實現機制是同樣的,這時京哥的臉色已經發生了微微的變化,哈哈哈......

那麼zend_alter_ini_entry_ex又是如何實現的呢?

zend_alter_ini_entry_ex

源碼以下

ZEND_API int zend_alter_ini_entry_ex(zend_string *name, zend_string *new_value, int modify_type, int stage, int force_change) /* {{{ */
{
    zend_ini_entry *ini_entry;
    zend_string *duplicate;
    zend_bool modifiable;
    zend_bool modified;
//EG(modified_ini_directives)用於存放被修改過的ini_entry,根據name(配置名稱)尋找到對應ini_entry
    if ((ini_entry = zend_hash_find_ptr(EG(ini_directives), name)) == NULL) {
        return FAILURE;
    }

    modifiable = ini_entry->modifiable;
    modified = ini_entry->modified;

    if (stage == ZEND_INI_STAGE_ACTIVATE && modify_type == ZEND_INI_SYSTEM) {
        ini_entry->modifiable = ZEND_INI_SYSTEM;
    }

    if (!force_change) {
        if (!(ini_entry->modifiable & modify_type)) {
            return FAILURE;
        }
    }

    if (!EG(modified_ini_directives)) {
        ALLOC_HASHTABLE(EG(modified_ini_directives));
        zend_hash_init(EG(modified_ini_directives), 8, NULL, NULL, 0);
    }
    //無論咱們前後在php代碼中調用幾回ini_set,只有第一次ini_set時纔會進入這段邏輯,設置orig_value。從第二次調用ini_set開始,便不會再次執行這段分支,由於此時的modified已經被置爲1了。所以,ini_entry->orig_value始終保存的是第一次修改以前的配置值(即最原始的配置)
    if (!modified) {
        ini_entry->orig_value = ini_entry->value;
        ini_entry->orig_modifiable = modifiable;
        ini_entry->modified = 1;
        zend_hash_add_ptr(EG(modified_ini_directives), ini_entry->name, ini_entry);
    }

    duplicate = zend_string_copy(new_value);
    
//調用on_modify是爲了可以更新模塊的全局變量。每個ini_entry中都存儲了該模塊全局變量的地址以及對應的偏移量,使得on_modify能夠很迅速的進行內存修改。
    if (!ini_entry->on_modify
        || ini_entry->on_modify(ini_entry, duplicate, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage) == SUCCESS) {
        if (modified && ini_entry->orig_value != ini_entry->value) { /* we already changed the value, free the changed value */
            zend_string_release(ini_entry->value);
        }
        ini_entry->value = duplicate;
    } else {
        zend_string_release(duplicate);
        return FAILURE;
    }

    return SUCCESS;
}

能夠看出該函數是同過經過 on_modify 回調函數直接修改了內存中的全局變量而達到控制執行時間的目的,因此這也解釋了爲何ini_set 在執行結束就會失效。先說這些,過幾天我會整理一下把整個PHP生命週期的ini加載過程詳細總結一下。

參考文章:http://www.cnblogs.com/driftc...

相關文章
相關標籤/搜索