>  기사  >  php教程  >  php_php 기본 사항에서 ini 구성을 동적으로 수정합니다.

php_php 기본 사항에서 ini 구성을 동적으로 수정합니다.

WBOY
WBOY원래의
2016-05-16 08:59:562021검색

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 adjust_type, int stage, int force_change TSRMLS_DC) /* {{{ */
{
zend_ini_entry *ini_entry;
문자 *중복;
zend_bool 수정 가능;
zend_bool 수정됨;

// EG에서 해당 ini_entry
를 찾습니다(ini_directives). If (zend_hash_find(EG(ini_directives), name, name_length, (void **) &ini_entry) == FAILURE) {
        실패를 반환합니다.
}

//수정 여부 및 수정 가능 여부
수정 가능 = ini_entry->수정 가능;
수정됨 = ini_entry->수정됨;

if (단계 == ZEND_INI_STAGE_ACTIVATE && 수정_유형 == ZEND_INI_SYSTEM) {
ini_entry->수정 가능 = ZEND_INI_SYSTEM;
}

// 강제수정 여부
If (!force_change) {
If (!(ini_entry->수정 가능 및 수정 유형)) {
              실패를 반환합니다.
}
}

// 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를 복원할 수 있도록
(!modified) {
ini_entry->orig_value = ini_entry->value;
ini_entry->orig_value_length = ini_entry->value_length;
ini_entry->orig_modifying = 수정 가능;
ini_entry->modified = 1;
        zend_hash_add(EG(modified_ini_directives), name, name_length, &ini_entry, sizeof(zend_ini_entry*), NULL);
}

중복 = estrndup(new_value, new_value_length);

// 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) == 성공) {
                 // 위와 동일, 여러 번 수정한 경우 이전에 수정한 값을 해제해야 함
If (수정된 && ini_entry->orig_value != ini_entry->value) {
              efree(ini_entry->value);
}
ini_entry->값 = 중복;
ini_entry->value_length = new_value_length;
} 그 밖의 {
        efree(중복);
        실패를 반환합니다.
}

성공을 반환합니다.
}

주의 깊게 이해해야 할 세 가지 논리가 있습니다.

1) ini_entry의 수정된 필드는 구성이 동적으로 수정되었는지 여부를 나타내는 데 사용됩니다. ini 구성이 수정되면 수정됨이 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_modifying = 수정 가능;
ini_entry->modified = 1;
zend_hash_add(EG(modified_ini_directives), 이름, name_length, &ini_entry, sizeof(zend_ini_entry*), NULL);
}

이 코드는 PHP 코드에서 ini_set을 몇 번 호출하더라도 첫 번째 ini_set만이 이 논리를 입력하고 orig_value를 설정한다는 것을 의미합니다. ini_set에 대한 두 번째 호출부터는 이 시점에서 수정된 값이 1로 설정되어 있으므로 이 분기는 다시 실행되지 않습니다. 따라서 ini_entry->orig_value는 항상 첫 번째 수정 전의 구성 값(즉, 가장 원래의 구성)을 저장합니다.

2) ini_set으로 수정한 구성을 즉시 적용하려면 on_modify 콜백 함수가 필요합니다.

이전 기사에서 언급했듯이 on_modify는 모듈의 전역 변수를 업데이트할 수 있도록 호출됩니다. 우선, 모듈 전역 변수의 구성은 더 이상 문자열 유형이 아닙니다. bool을 사용해야 할 때는 bool을 사용하고 int를 사용해야 할 때는 int를 사용하세요. 둘째, 각 ini_entry는 모듈의 전역 변수의 주소와 해당 오프셋을 저장하므로 on_modify가 메모리를 빠르게 수정할 수 있습니다. 또한 on_modify가 호출된 후에도 EG(ini_directives)의 구성 값이 최신이 되도록 ini_entry->value를 추가로 업데이트해야 한다는 점을 잊지 마세요.

3) 여기에 새로운 해시 테이블인 EG(modified_ini_directives)가 나타납니다.

EG(modified_ini_directives)는 동적으로 수정된 ini 구성을 저장하는 데만 사용됩니다. ini 구성이 동적으로 수정되면 EG(ini_directives)와 EG(modified_ini_directives) 모두에 존재합니다. 각 ini_entry에는 수정된 필드가 표시되어 있으므로 수정된 모든 구성을 얻기 위해 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. 스캐너/실행기/컴파일러를 종료하고 ini 항목을 복원합니다 */
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) /* {{{ */
{
Jika (EG(modified_ini_directives)) {
// Lintas jadual ini dalam EG (modified_ini_directives)
// Panggil zend_restore_ini_entry_wrapper
untuk setiap ini_entry         zend_hash_apply(EG(modified_ini_directives), (apply_func_t) zend_restore_ini_entry_wrapper TSRMLS_CC);
         
             // Operasi kitar semula
        zend_hash_destroy(EG(modified_ini_directives));
FREE_HASHTABLE(EG(modified_ini_directives));
        Cth(modified_ini_directives) = NULL;
}
Kembali BERJAYA;
}

Daripada zend_hash_apply, tugas sebenar untuk memulihkan ini akhirnya jatuh kepada fungsi panggil balik zend_restore_ini_entry_wrapper.

Salin kod Kod adalah seperti berikut:

int statik zend_restore_ini_entry_wrapper(zend_ini_entry **ini_entry TSRMLS_DC)
{
// zend_restore_ini_entry_wrapper ialah enkapsulasi zend_restore_ini_entry_cb
zend_restore_ini_entry_cb(*ini_entry, ZEND_INI_STAGE_DEACTIVATE TSRMLS_CC);
Pulangan 1;
}

int statik zend_restore_ini_entry_cb(zend_ini_entry *ini_entry, int stage TSRMLS_DC)
{
int result = GAGAL;

// Hanya lihat item ini yang diubah suai
Jika (ini_entry->diubah suai) {
Jika (ini_entry->on_modify) {
//Gunakan orig_value untuk menetapkan semula medan yang berkaitan dalam XXX_G
            zend_try {
hasil = ini_entry->on_modify(ini_entry, ini_entry->orig_value, ini_entry->asal_value_length, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->_LS,🎉);                } zend_end_try();
}
Jika (peringkat == ZEND_INI_STAGE_RUNTIME && keputusan == GAGAL) {
                    /* kegagalan masa jalan adalah OK */
              kembali 1;
}
Jika (ini_entry->value != ini_entry->orig_value) {
              percuma(ini_entry->value);
}
         
​​​​ // ini_entry sendiri dipulihkan kepada nilai asalnya
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->diubah suai = 0;
ini_entry->orig_value = NULL;
ini_entry->orig_value_length = 0;
ini_entry->orig_modifiable = 0;
}
Pulangan 0;
}

Logiknya agak jelas, saya percaya pembaca boleh memahaminya. Untuk meringkaskan proses pemulihan konfigurasi ini:

Salin kod Kod adalah seperti berikut:
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가 수행하는 주요 작업은 EG(ini_directives) 테이블에서 특정 모듈의 ini_entry 구성을 삭제하는 것입니다. 삭제 후 ini_entry 자체의 공간은 회수되지만, ini_entry->value는 회수되지 않을 수 있습니다.

모든 모듈의 PHP_MSHUTDOWN_FUNCTION이 UNREGISTER_INI_ENTRIES를 한 번 호출한 후에는 핵심 모듈의 ini 구성만 EG(ini_directives)에 남습니다. 이때 핵심 모듈 구성 삭제를 완료하려면 UNREGISTER_INI_ENTRIES를 수동으로 호출해야 합니다.

코드 복사 코드는 다음과 같습니다.

무효 php_module_shutdown(TSRMLS_D)
{
...
 
// zend_shutdown은 Core를 제외한 모든 PHP 모듈을 순차적으로 종료합니다
// 닫을 때 각 모듈의 PHP_MSHUTDOWN_FUNCTION
이 호출됩니다. zend_shutdown(TSRMLS_C);
 
...

// 이 시점에서는 EG(ini_directives)에 Core 모듈의 구성만 남습니다.
// 여기에서 수동으로 정리
UNREGISTER_INI_ENTRIES();
 
// 구성_해시 재활용
php_shutdown_config();

// EG 재활용(ini_directives)
zend_ini_shutdown(TSRMLS_C);

...
}

UNREGISTER_INI_ENTRIES에 대한 수동 호출이 완료된 후 EG(ini_directives)에는 더 이상 어떤 요소도 포함되지 않습니다. 이론적으로 현재 EG(ini_directives)는 빈 해시 테이블입니다.

2. EG(ini_directives) 이후에 Configuration_hash 재활용이 발생합니다. 위에 게시된 코드에는 php_shutdown_config에 대한 함수 호출이 포함되어 있습니다. php_shutdown_config는 주로 Configuration_hash 재활용을 담당합니다.

코드 복사 코드는 다음과 같습니다.

int php_shutdown_config(void)
{
// 구성_해시 재활용
zend_hash_destroy(&configuration_hash);
 
...
 
성공을 반환합니다.
}

zend_hash_destroy는 XXX_G에서 액세스하는 모듈의 전역 공간과 마찬가지로 구성_해시 자체의 공간을 해제하지 않습니다. 구성_해시도 전역 변수이므로 수동으로 재활용할 필요가 없습니다.

3. php_shutdown_config가 완료되면 EG(ini_directives)의 자체 공간만 해제되지 않습니다. 따라서 마지막 단계에서는 zend_ini_shutdown을 호출합니다. zend_ini_shutdown은 EG(ini_directives)를 해제하는 데 사용됩니다. 앞서 언급한 바와 같이 이때의 EG(ini_directives)는 이론적으로 빈 해시 테이블이기 때문에 HashTable 자체가 차지하는 공간을 해제해야 한다.

코드 복사 코드는 다음과 같습니다.

ZEND_API int zend_ini_shutdown(TSRMLS_D)
{
// EG(ini_directives)는 동적으로 할당된 공간이므로 재활용해야 합니다.
zend_hash_destroy(EG(ini_directives));
free(EG(ini_directives));
성공을 반환합니다.
}

4, 요약
그림을 사용하여 ini 구성과 관련된 프로세스를 대략적으로 설명합니다.

php_php 기본 사항에서 ini 구성을 동적으로 수정합니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.