搜索
首页php教程php手册php中动态修改ini配置_php基础

1,运行时改变配置
在前一篇中曾经谈到,ini_set函数可以在php执行的过程中,动态修改php的部分配置。注意,仅仅是部分,并非所有的配置都可以动态修改。关于ini配置的可修改性,参见:http://php.net/manual/zh/configuration.changes.modes.php

我们直接进入ini_set的实现,函数虽然有点长,但是逻辑很清晰:

复制代码 代码如下:

PHP_FUNCTION(ini_set)
{
    char *varname, *new_value;
    int varname_len, new_value_len;
    char *old_value;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &varname, &varname_len, &new_value, &new_value_len) == FAILURE) {
        return;
    }

    // 去EG(ini_directives)中获取配置的值
    old_value = zend_ini_string(varname, varname_len + 1, 0);

    /* copy to return here, because alter might free it! */
    if (old_value) {
        RETVAL_STRING(old_value, 1);
    } else {
        RETVAL_FALSE;
    }

    // 如果开启了安全模式,那么如下这些ini配置可能涉及文件操作,需要要辅助检查uid
#define _CHECK_PATH(var, var_len, ini) php_ini_check_path(var, var_len, ini, sizeof(ini))
    /* safe_mode & basedir check */
    if (PG(safe_mode) || PG(open_basedir)) {
        if (_CHECK_PATH(varname, varname_len, "error_log") ||
            _CHECK_PATH(varname, varname_len, "java.class.path") ||
            _CHECK_PATH(varname, varname_len, "java.home") ||
            _CHECK_PATH(varname, varname_len, "mail.log") ||
            _CHECK_PATH(varname, varname_len, "java.library.path") ||
            _CHECK_PATH(varname, varname_len, "vpopmail.directory")) {
            if (PG(safe_mode) && (!php_checkuid(new_value, NULL, CHECKUID_CHECK_FILE_AND_DIR))) {
                zval_dtor(return_value);
                RETURN_FALSE;
            }
            if (php_check_open_basedir(new_value TSRMLS_CC)) {
                zval_dtor(return_value);
                RETURN_FALSE;
            }
        }
    }

    // 在安全模式下,如下这些ini受到保护,不会被动态修改
    if (PG(safe_mode)) {
        if (!strncmp("max_execution_time", varname, sizeof("max_execution_time")) ||
            !strncmp("memory_limit", varname, sizeof("memory_limit")) ||
            !strncmp("child_terminate", varname, sizeof("child_terminate"))
        ) {
            zval_dtor(return_value);
            RETURN_FALSE;
        }
    }

    // 调用zend_alter_ini_entry_ex去动态修改ini配置
    if (zend_alter_ini_entry_ex(varname, varname_len + 1, new_value, new_value_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0 TSRMLS_CC) == FAILURE) {
        zval_dtor(return_value);
        RETURN_FALSE;
    }
}

可以看到,除了一些必要的验证工作,主要就是调用zend_alter_ini_entry_ex。

我们继续跟进到zend_alter_ini_entry_ex函数中:

复制代码 代码如下:

ZEND_API int zend_alter_ini_entry_ex(char *name, uint name_length, char *new_value, uint new_value_length, int modify_type, int stage, int force_change TSRMLS_DC) /* {{{ */
{
    zend_ini_entry *ini_entry;
    char *duplicate;
    zend_bool modifiable;
    zend_bool modified;

    // 找出EG(ini_directives)中对应的ini_entry
    if (zend_hash_find(EG(ini_directives), name, name_length, (void **) &ini_entry) == FAILURE) {
        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;
        }
    }

    // EG(modified_ini_directives)用于存放被修改过的ini_entry
    // 主要用做恢复
    if (!EG(modified_ini_directives)) {
        ALLOC_HASHTABLE(EG(modified_ini_directives));
        zend_hash_init(EG(modified_ini_directives), 8, NULL, NULL, 0);
    }
   
    // 将ini_entry中的值,值的长度,可修改范围,保留到orig_xxx中去
    // 以便在请求结束的时候,可以对ini_entry做恢复
    if (!modified) {
        ini_entry->orig_value = ini_entry->value;
        ini_entry->orig_value_length = ini_entry->value_length;
        ini_entry->orig_modifiable = modifiable;
        ini_entry->modified = 1;
        zend_hash_add(EG(modified_ini_directives), name, name_length, &ini_entry, sizeof(zend_ini_entry*), NULL);
    }

    duplicate = estrndup(new_value, new_value_length);

    // 调用modify来更新XXX_G中对应的ini配置
    if (!ini_entry->on_modify || ini_entry->on_modify(ini_entry, duplicate, new_value_length, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage TSRMLS_CC) == SUCCESS) {
        // 同上面,如果多次修改,则需要释放前一次修改的值
        if (modified && ini_entry->orig_value != ini_entry->value) {
            efree(ini_entry->value);
        }
        ini_entry->value = duplicate;
        ini_entry->value_length = new_value_length;
    } else {
        efree(duplicate);
        return FAILURE;
    }

    return SUCCESS;
}

有3处逻辑需要我们仔细体会:

1)ini_entry中的modified字段用来表示该配置是否被动态修改过。一旦该ini配置发生修改,modified就会被置为1。上述代码中有一段很关键:

复制代码 代码如下:

// 如果多次调用ini_set,则orig_value等始终保持最原始的值
if (!modified) {
    ini_entry->orig_value = ini_entry->value;
    ini_entry->orig_value_length = ini_entry->value_length;
    ini_entry->orig_modifiable = modifiable;
    ini_entry->modified = 1;
    zend_hash_add(EG(modified_ini_directives), name, name_length, &ini_entry, sizeof(zend_ini_entry*), NULL);
}

这段代码表示,不管我们先后在php代码中调用几次ini_set,只有第一次ini_set时才会进入这段逻辑,设置好orig_value。从第二次调用ini_set开始,便不会再次执行这段分支,因为此时的modified已经被置为1了。因此,ini_entry->orig_value始终保存的是第一次修改之前的配置值(即最原始的配置)。

2)为了能使ini_set修改的配置立即生效,需要on_modify回调函数。

如前一篇文中所述,调用on_modify是为了能够更新模块的全局变量。再次回忆下,首先,模块全局变量中的配置已经不是字符串类型了,该用bool用bool、该用int用int。其次,每一个ini_entry中都存储了该模块全局变量的地址以及对应的偏移量,使得on_modify可以很迅速的进行内存修改。此外不要忘记,on_modify调用完了之后,仍需进一步更新ini_entry->value,这样EG(ini_directives)中的配置值就是最新的了。

3)这里出现了一张新的hash表,EG(modified_ini_directives)。

EG(modified_ini_directives)只用于存放被动态修改过的ini配置,如果一个ini配置被动态修改过,那么它既存在于EG(ini_directives)中,又存在于EG(modified_ini_directives)中。既然每一个ini_entry都有modified字段做标记,那岂不是可以遍历EG(ini_directives)来获得所有被修改过的配置呢?

答案是肯定的。个人觉得,这里的EG(modified_ini_directives)主要还是为了提升性能,酱直接遍历EG(modified_ini_directives)就足够了。此外,把EG(modified_ini_directives)的初始化推迟到zend_alter_ini_entry_ex中,也可以看出php在细节上的性能优化点。

2,恢复配置
ini_set的作用时间和php.ini文件的作用时间是不一样的,一旦请求执行结束,则ini_set会失效。此外,当我们代码中调用了ini_restore函数,则之前通过ini_set设置的配置也会失效。

每一个php请求执行完毕之后,会触发php_request_shutdown,它和php_request_startup是两个相对应过程。如果php是挂接在apache/nginx下,则每处理完一个http请求,就会调用php_request_shutdown;如果php以CLI模式来运行,则脚本执行完毕之后,也会调用php_request_shutdown。

在php_request_shutdown中,我们可以看到针对ini的恢复处理:

复制代码 代码如下:

/* 7. Shutdown scanner/executor/compiler and restore ini entries */
zend_deactivate(TSRMLS_C);

进入zend_deactivate,可以进一步看到调用了zend_ini_deactivate函数,由zend_ini_deactivate来负责将php的配置进行恢复。

复制代码 代码如下:

zend_try {
    zend_ini_deactivate(TSRMLS_C);
} zend_end_try();

具体来看看zend_ini_deactivate的实现:

复制代码 代码如下:

ZEND_API int zend_ini_deactivate(TSRMLS_D) /* {{{ */
{
    if (EG(modified_ini_directives)) {
        // 遍历EG(modified_ini_directives)中这张表
        // 对每一个ini_entry调用zend_restore_ini_entry_wrapper
        zend_hash_apply(EG(modified_ini_directives), (apply_func_t) zend_restore_ini_entry_wrapper TSRMLS_CC);
       
        // 回收操作
        zend_hash_destroy(EG(modified_ini_directives));
        FREE_HASHTABLE(EG(modified_ini_directives));
        EG(modified_ini_directives) = NULL;
    }
    return SUCCESS;
}

从zend_hash_apply来看,真正恢复ini的任务最终落地到了zend_restore_ini_entry_wrapper回调函数。

复制代码 代码如下:

static int zend_restore_ini_entry_wrapper(zend_ini_entry **ini_entry TSRMLS_DC)
{
    // zend_restore_ini_entry_wrapper就是zend_restore_ini_entry_cb的封装
    zend_restore_ini_entry_cb(*ini_entry, ZEND_INI_STAGE_DEACTIVATE TSRMLS_CC);
    return 1;
}

static int zend_restore_ini_entry_cb(zend_ini_entry *ini_entry, int stage TSRMLS_DC)
{
    int result = FAILURE;

    // 只看修改过的ini项
    if (ini_entry->modified) {
        if (ini_entry->on_modify) {
            // 使用orig_value,对XXX_G内的相关字段进行重新设置
            zend_try {
                result = ini_entry->on_modify(ini_entry, ini_entry->orig_value, ini_entry->orig_value_length, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage TSRMLS_CC);
            } zend_end_try();
        }
        if (stage == ZEND_INI_STAGE_RUNTIME && result == FAILURE) {
            /* runtime failure is OK */
            return 1;
        }
        if (ini_entry->value != ini_entry->orig_value) {
            efree(ini_entry->value);
        }
       
        // ini_entry本身恢复到最原始的值
        ini_entry->value = ini_entry->orig_value;
        ini_entry->value_length = ini_entry->orig_value_length;
        ini_entry->modifiable = ini_entry->orig_modifiable;
        ini_entry->modified = 0;
        ini_entry->orig_value = NULL;
        ini_entry->orig_value_length = 0;
        ini_entry->orig_modifiable = 0;
    }
    return 0;
}

逻辑都蛮清晰的,相信读者可以看明白。总结一下关于ini配置的恢复流程:

复制代码 代码如下:

php_request_shutdown--->zend_deactivate--->zend_ini_deactivate--->zend_restore_ini_entry_wrapper--->zend_restore_ini_entry_cb

3,配置的销毁
在sapi生命周期结束的时候,比如apache关闭,cli程序执行完毕等等。一旦进入到这个阶段,之前所说的configuration_hash,EG(ini_directives)等都需要被销毁,其用到的内存空间需要被释放。

1,php会依次结束所有的模块,在每个模块的PHP_MSHUTDOWN_FUNCTION中调用UNREGISTER_INI_ENTRIES。UNREGISTER_INI_ENTRIES和REGISTER_INI_ENTRIES对应,但是UNREGISTER_INI_ENTRIES并不负责模块全局空间的释放,XXX_globals这块内存放在静态数据区上,无需人为回收。

UNREGISTER_INI_ENTRIES主要做的事情,是将某个模块的ini_entry配置从EG(ini_directives)表中删除。删除之后,ini_entry本身的空间会被回收,但是ini_entry->value不一定会被回收。

当所有模块的PHP_MSHUTDOWN_FUNCTION都调用UNREGISTER_INI_ENTRIES一遍之后,EG(ini_directives)中只剩下了Core模块的ini配置。此时,就需要手动调用UNREGISTER_INI_ENTRIES,来完成对Core模块配置的删除工作。

复制代码 代码如下:

void php_module_shutdown(TSRMLS_D)
{
    ...
   
    // zend_shutdown会依次关闭除了Core之外的所有php模块
    // 关闭时会调用各个模块的PHP_MSHUTDOWN_FUNCTION
    zend_shutdown(TSRMLS_C);
   
    ...

    // 至此,EG(ini_directives)中只剩下了Core模块的配置
    // 这里手动清理一下
    UNREGISTER_INI_ENTRIES();
   
    // 回收configuration_hash
    php_shutdown_config();

    // 回收EG(ini_directives)
    zend_ini_shutdown(TSRMLS_C);

    ...
}

当手动调用UNREGISTER_INI_ENTRIES完成之后,EG(ini_directives)已经不包含任何的元素,理论上讲,此时的EG(ini_directives)是一张空的hash表。

2,configuration_hash的回收发生在EG(ini_directives)之后,上面贴出的代码中有关于php_shutdown_config的函数调用。php_shutdown_config主要负责回收configuration_hash。

复制代码 代码如下:

int php_shutdown_config(void)
{
    // 回收configuration_hash
    zend_hash_destroy(&configuration_hash);
   
    ...
   
    return SUCCESS;
}

注意zend_hash_destroy并不会释放configuration_hash本身的空间,同XXX_G访问的模块全局空间一样,configuration_hash也是一个全局变量,无需手动回收。

3,当php_shutdown_config完成时,只剩下EG(ini_directives)的自身空间还没被释放。因此最后一步调用zend_ini_shutdown。zend_ini_shutdown用于释放EG(ini_directives)。在前文已经提到,此时的EG(ini_directives)理论上是一张空的hash表,因此该HashTable本身所占用的空间需要被释放。

复制代码 代码如下:

ZEND_API int zend_ini_shutdown(TSRMLS_D)
{
    // EG(ini_directives)是动态分配出的空间,需要回收
    zend_hash_destroy(EG(ini_directives));
    free(EG(ini_directives));
    return SUCCESS;
}

4,总结
用一张图大致描述一下和ini配置相关的流程:

php中动态修改ini配置_php基础

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
php怎么把负数转为正整数php怎么把负数转为正整数Apr 19, 2022 pm 08:59 PM

php把负数转为正整数的方法:1、使用abs()函数将负数转为正数,使用intval()函数对正数取整,转为正整数,语法“intval(abs($number))”;2、利用“~”位运算符将负数取反加一,语法“~$number + 1”。

php怎么实现几秒后执行一个函数php怎么实现几秒后执行一个函数Apr 24, 2022 pm 01:12 PM

实现方法:1、使用“sleep(延迟秒数)”语句,可延迟执行函数若干秒;2、使用“time_nanosleep(延迟秒数,延迟纳秒数)”语句,可延迟执行函数若干秒和纳秒;3、使用“time_sleep_until(time()+7)”语句。

php怎么除以100保留两位小数php怎么除以100保留两位小数Apr 22, 2022 pm 06:23 PM

php除以100保留两位小数的方法:1、利用“/”运算符进行除法运算,语法“数值 / 100”;2、使用“number_format(除法结果, 2)”或“sprintf("%.2f",除法结果)”语句进行四舍五入的处理值,并保留两位小数。

php怎么根据年月日判断是一年的第几天php怎么根据年月日判断是一年的第几天Apr 22, 2022 pm 05:02 PM

判断方法:1、使用“strtotime("年-月-日")”语句将给定的年月日转换为时间戳格式;2、用“date("z",时间戳)+1”语句计算指定时间戳是一年的第几天。date()返回的天数是从0开始计算的,因此真实天数需要在此基础上加1。

php字符串有没有下标php字符串有没有下标Apr 24, 2022 am 11:49 AM

php字符串有下标。在PHP中,下标不仅可以应用于数组和对象,还可应用于字符串,利用字符串的下标和中括号“[]”可以访问指定索引位置的字符,并对该字符进行读写,语法“字符串名[下标值]”;字符串的下标值(索引值)只能是整数类型,起始值为0。

php怎么读取字符串后几个字符php怎么读取字符串后几个字符Apr 22, 2022 pm 08:31 PM

在php中,可以使用substr()函数来读取字符串后几个字符,只需要将该函数的第二个参数设置为负值,第三个参数省略即可;语法为“substr(字符串,-n)”,表示读取从字符串结尾处向前数第n个字符开始,直到字符串结尾的全部字符。

php怎么替换nbsp空格符php怎么替换nbsp空格符Apr 24, 2022 pm 02:55 PM

方法:1、用“str_replace(" ","其他字符",$str)”语句,可将nbsp符替换为其他字符;2、用“preg_replace("/(\s|\&nbsp\;||\xc2\xa0)/","其他字符",$str)”语句。

php怎么判断有没有小数点php怎么判断有没有小数点Apr 20, 2022 pm 08:12 PM

php判断有没有小数点的方法:1、使用“strpos(数字字符串,'.')”语法,如果返回小数点在字符串中第一次出现的位置,则有小数点;2、使用“strrpos(数字字符串,'.')”语句,如果返回小数点在字符串中最后一次出现的位置,则有。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。