1,运行时改变配置
在前一篇中曾经谈到,ini_set函数可以在php执行的过程中,动态修改php的部分配置。注意,仅仅是部分,并非所有的配置都可以动态修改。关于ini配置的可修改性,参见:http://php.net/manual/zh/configuration.changes.modes.php
我们直接进入ini_set的实现,函数虽然有点长,但是逻辑很清晰:
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 함수에 대한 후속 조치는 계속됩니다:
// 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로 설정됩니다. 위 코드에는 중요한 부분이 있습니다:
이 코드는 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에 대한 복구 처리를 확인할 수 있습니다.
zend_deactivate를 입력하면 zend_ini_deactivate 함수가 호출되고 zend_ini_deactivate가 PHP 구성 복원을 담당하는 것을 자세히 확인할 수 있습니다.
zend_ini_deactivate 구현을 자세히 살펴보겠습니다.
Daripada zend_hash_apply, tugas sebenar untuk memulihkan ini akhirnya jatuh kepada fungsi panggil balik zend_restore_ini_entry_wrapper.
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;
}
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를 수동으로 호출해야 합니다.
// 이 시점에서는 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 재활용을 담당합니다.
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 자체가 차지하는 공간을 해제해야 한다.
4, 요약
그림을 사용하여 ini 구성과 관련된 프로세스를 대략적으로 설명합니다.