接著上篇來講php生命週期,看看擴充哪些鉤子做哪些事,php生命週期大概5個階段,模組初始化階段php_module_startup
,請求初始化階段php_request_startup
,腳本執行階段php_execute_script
,請求關閉階段php_request_shutdown
## ,模組關閉階段php_module_shutdown
,下面以cli模式介紹。
php_module_startup
先看看這個階段做了什麼,如果不知道php入口檔在哪,用gdb看看呼叫棧,gdb ./ php
在php_module_startup
打斷點,執行,在看下呼叫棧,
b php_module_startup (gdb) r test.php bt php_module_startup (sf=0x1406460 <cli_sapi_module>, additional_modules=0x0, num_additional_modules=0) at /www/test/php/php-7.4.3/main/main.c:2098 #1 0x00000000008bae7c in php_cli_startup ( sapi_module=0x1406460 <cli_sapi_module>) at /www/test/php/php-7.4.3/sapi/cli/php_cli.c:407 #2 0x00000000008bcc80 in main (argc=2, argv=0x1425af0) at /www/test/php/php-7.4.3/sapi/cli/php_cli.c:1323
在呼叫堆疊可以清楚看到執行流程,現在到/ main/main.c檔看看做了哪些事情,也可以用gdb一步一步的看,這裡就講與php擴展有關的幾個地方,這裡做的初始化工作,像垃圾回收,請求初始化,註冊常量, php.ini設定檔載入等,
先來看看怎麼載入模組的
/* startup extensions statically compiled in */ if (php_register_internal_extensions_func() == FAILURE) { php_printf("Unable to start builtin modules\n"); return FAILURE; }
這裡是載入php內建的模組,這裡只貼出核心功能,先檢查依賴
/* Check module dependencies */ if (module->deps) { const zend_module_dep *dep = module->deps; while (dep->name) { if (dep->type == MODULE_DEP_CONFLICTS) { name_len = strlen(dep->name); lcname = zend_string_alloc(name_len, 0); zend_str_tolower_copy(ZSTR_VAL(lcname), dep->name, name_len); if (zend_hash_exists(&module_registry, lcname) || zend_get_extension(dep->name)) { zend_string_efree(lcname); /* TODO: Check version relationship */ zend_error(E_CORE_WARNING, "Cannot load module '%s' because conflicting module '%s' is already loaded", module->name, dep->name); return NULL; } zend_string_efree(lcname); } ++dep; } }
if (module->functions && zend_register_functions(NULL, module->functions, NULL, module->type)==FAILURE) { zend_hash_del(&module_registry, lcname); zend_string_release(lcname); EG(current_module) = NULL; zend_error(E_CORE_WARNING,"%s: Unable to register functions, unable to load", module->name); return NULL; }
這是內建模組載入原理,現在看看ini裡的擴充功能怎麼載入
php_ini_register_extensions();
zend_llist_apply(&extension_lists.functions, php_load_php_extension_cb);
利用這個函數載入
php_load_extension(char *filename, int type, int start_now)
這裡面也執行了載入內建模組的功能。
是呼叫了module->functions
,進行模組功能函數註冊,現在知道為什麼功能函數要寫在helloworld_functions
這裡吧
zend_module_entry helloworld_module_entry = { STANDARD_MODULE_HEADER, "helloworld", /* Extension name */ helloworld_functions, /* zend_function_entry */ PHP_MINIT(helloworld), /* PHP_MINIT - Module initialization */ NULL, /* PHP_MSHUTDOWN - Module shutdown */ PHP_RINIT(helloworld), /* PHP_RINIT - Request initialization */ NULL, /* PHP_RSHUTDOWN - Request shutdown */ PHP_MINFO(helloworld), /* PHP_MINFO - Module info */ PHP_HELLOWORLD_VERSION, /* Version */ PHP_MODULE_GLOBALS(pib), NULL, NULL, NULL, STANDARD_MODULE_PROPERTIES_EX };
現在看看擴充的幾個鉤子函數
/* start Zend extensions */ zend_startup_extensions();
這裡的核心就是func(element->data)
也就是執行擴充
PHP_MINIT
函數
element=l->head; while (element) { next = element->next; if (func(element->data)) { DEL_LLIST_ELEMENT(element, l); } element = next; }
現在就知道PHP_MINIT
鉤子可以做很多初始化的功能,怎麼註冊一個自訂擴展的功能類,怎麼把擴展的變數寫到php.ini裡面,怎麼重寫php內建函數,
original = zend_hash_str_find_ptr(CG(function_table), "var_dump", sizeof("var_dump")-1); if (original != NULL) { original->internal_function.handler = my_overwrite_var_dump; } zend_class_entry person; INIT_CLASS_ENTRY(person,CLASS_NAME,person_functions); zend_register_internal_class_ex(&person,NULL);
這裡就是重寫var_dump函數,註冊了一個person類,先介紹到這裡,下篇就介紹怎麼把php程式碼透過詞法分析語法分析產生AST,然後編譯opcode指令,供zend虛擬機器呼叫。
推薦學習:《PHP影片教學》