此文章是讀書筆記,個(gè)人底層欠火候绘迁。文章的圖或找或自己試著畫一下楼吃。盡量少的抄書躲叼。
準(zhǔn)備
php 5.6忌锯、php 7.0.12 各一份
使用 vscode 凭疮,配置(vscode代替source insight)
phpstudy 方便切換各種版本測代碼。
centos7的虛擬機(jī)膛腐,方便后續(xù)的使用睛约。
流程圖用的是https://www.processon.com
php 7變化
抽象語法樹
php5.x
PHP代碼在語法解析階段直接生成了ZendVM指令。zend_language_parse.y中生成opline指令
缺點(diǎn):編譯器與執(zhí)行器耦合在一起
php7
將php代碼解析成抽象語法樹,將抽象語法樹編譯為ZendVM指令
優(yōu)點(diǎn): php的編譯器與執(zhí)行器很好地隔離開哲身,編譯器不需要關(guān)心指令的生成規(guī)則痰腮,然后執(zhí)行器根據(jù)自己的規(guī)則將抽象語法樹編譯為對應(yīng)的指令。
Native TLS
php 5.x :
多線程環(huán)境不能簡單通過全局變量實(shí)現(xiàn)律罢,為適應(yīng)多線程應(yīng)用環(huán)境膀值。
php提供了一個(gè)線程安全資源管理器,將全局資源進(jìn)行線程隔離误辑,不同的線程互不干擾
php 7
使用Native TLS(線程局部存儲(chǔ))保存線程的資源池沧踏,__tread標(biāo)識(shí)一個(gè)全局變量,全局變量就是線程獨(dú)享巾钉,不同線程修改不會(huì)影響
指定函數(shù)參數(shù)翘狱、返回值類型
zval結(jié)構(gòu)變化
php 5.x
zend.h
zval
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
value
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
zend_ast *ast;
} zvalue_value;
缺點(diǎn)
php5.x的引用計(jì)數(shù)在在zval中而不是在 value中,復(fù)制變量需要復(fù)制兩個(gè)結(jié)構(gòu)砰苍,zval跟value 始終綁定在一起
php 7
zend_types.h
zval
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
} u2;
};
value
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
優(yōu)點(diǎn):
引用計(jì)數(shù)在具體value(元素zend_refcounted )中潦匈,zval只是載體,value才是真正的值
php變量之間復(fù)制赚导、傳遞更加簡潔茬缩、易懂
zval結(jié)構(gòu)大小從24byte減少到了16byte,也是php7能夠降低系統(tǒng)資源的一個(gè)優(yōu)化點(diǎn)
異常處理
php5.x
很多操作會(huì)拋出error錯(cuò)誤
php7
將多數(shù)錯(cuò)誤改為了異常拋出吼旧,這樣就可以通過try catch 捕捉到了
調(diào)用未定義函數(shù)凰锡。示例代碼:
try {
call();
} catch (Throwable $e) {
echo $e->getMessage();
}
php5.6:Fatal error: Call to undefined function call() in E:\isoftbox\phpstudy\WWW\1.php on line 4
php7輸出:Call to undefined function call()
HashTable
php7,hashtable結(jié)構(gòu)的大小從72byte減小到了56byte圈暗。數(shù)組元素bucket從72byte減少到了32byte
php的構(gòu)成
SAPI層適配不同的執(zhí)行場景掂为。常用的如下:
apache2handler
cgi
cli
embed #嵌入式
fpm
litespeed #LiteSpeed 一種被特別設(shè)計(jì)用作大型網(wǎng)站的商業(yè)web服務(wù)器。 其中一個(gè)優(yōu)勢就是它能直接讀取Apache 的配置信息员串。并輕易將它現(xiàn)有的產(chǎn)品結(jié)合在一起來代替Apache 勇哗。這種服務(wù)器是輕量級的就如它的名字暗示出非常快寸齐。
ZendVM
ZendVM兩部分組成:編譯器欲诺、執(zhí)行器抄谐。
Extension
擴(kuò)展分為PHP擴(kuò)展、Zend擴(kuò)展(應(yīng)用于ZendVM比如Opcache)
目錄
build/
ext/
main/
netware/
pear/
sapi/
scripts/
tests/
travis/
TSRM/
win32/
Zend/
生命周期
模塊初始化瞧栗、請求初始化斯稳、執(zhí)行腳本階段海铆、請求關(guān)閉階段迹恐、模塊關(guān)閉階段
在main/main.c文件中能看到對應(yīng)的函數(shù)定義。
不同的sapi場景使用不同的方法卧斟。
拿fpm舉例
main() ,殴边,在文件/sapi/fpm/fpm/fpm_main.c
。main函數(shù)中能看到調(diào)用的情況珍语。
模塊初始化階段
php_module_startup 函數(shù)(./main/main.c)
主要干了:
- 激活SAPI:sapi_activate()(函數(shù)的定義在./main/SAPI.c锤岸,之后看到sapi開頭就找sapi.c)
初始化請求信息
……
/*初始化請求信息*/
SG(sapi_headers).send_default_content_type = 1;
/*
SG(sapi_headers).http_response_code = 200;
*/
SG(sapi_headers).http_status_line = NULL;
SG(sapi_headers).mimetype = NULL;
SG(headers_sent) = 0;
ZVAL_UNDEF(&SG(callback_func));
SG(read_post_bytes) = 0;
SG(request_info).request_body = NULL;
……
處理請求,讀取post 數(shù)據(jù)板乙,讀取cookie是偷。
/* Handle request method 處理請求方法*/
if (SG(server_context)) {
if (PG(enable_post_data_reading)
&& SG(request_info).content_type
&& SG(request_info).request_method
&& !strcmp(SG(request_info).request_method, "POST")) {
/* HTTP POST may contain form data to be processed into variables
* depending on given content type
* HTTP POST可能包含要根據(jù)給定內(nèi)容類型處理為變量的表單數(shù)據(jù)
* */
sapi_read_post_data();
} else {
SG(request_info).content_type_dup = NULL;
}
/* Cookies 讀取Cookie*/
SG(request_info).cookie_data = sapi_module.read_cookies();
if (sapi_module.activate) {
sapi_module.activate();
}
}
if (sapi_module.input_filter_init) {
sapi_module.input_filter_init();
}
- 啟動(dòng)php輸出:php_output_startup()。
PHPAPI void php_output_startup(void)
{
ZEND_INIT_MODULE_GLOBALS(output, php_output_init_globals, NULL);
zend_hash_init(&php_output_handler_aliases, 8, NULL, NULL, 1);
zend_hash_init(&php_output_handler_conflicts, 8, NULL, NULL, 1);
zend_hash_init(&php_output_handler_reverse_conflicts, 8, NULL, reverse_conflict_dtor, 1);
php_output_direct = php_output_stdout;
}
分析參考 跟廠長學(xué)PHP7內(nèi)核(五):系統(tǒng)分析生命周期
- 初始化垃圾回收器:gc_globals_ctor()(文件./Zend/zend_gc.c募逞,含有g(shù)c_ 開頭的你懂的)蛋铆,分配zend_gc_globals內(nèi)存
ZEND_API void gc_globals_ctor(void)
{
#ifdef ZTS /* 線程安全 執(zhí)行ts_allocate_id,繼續(xù)追這個(gè)函數(shù)就能看到tsrm_mutex_lock放接、tsrm_mutex_unlock*/
ts_allocate_id(&gc_globals_id, sizeof(zend_gc_globals), (ts_allocate_ctor) gc_globals_ctor_ex, (ts_allocate_dtor) root_buffer_dtor);
#else
gc_globals_ctor_ex(&gc_globals);
#endif
}
更多關(guān)于內(nèi)存分配參考 PHP新的垃圾回收機(jī)制:Zend GC詳解
- 啟動(dòng)zend引擎:zend_startup()
……
start_memory_manager(); /*啟動(dòng)內(nèi)存池*/
/* Set up utility functions and values
設(shè)置一些util函數(shù)句柄
*/
zend_error_cb = utility_functions->error_function;
zend_printf = utility_functions->printf_function;
zend_write = (zend_write_func_t) utility_functions->write_function;
zend_fopen = utility_functions->fopen_function;
/* 設(shè)置 Zend 虛擬機(jī)編譯刺啦、執(zhí)行器的函數(shù)句柄 zend_compile_file、zend_execute_ex */
#if HAVE_DTRACE
/* build with dtrace support */
zend_compile_file = dtrace_compile_file;
zend_execute_ex = dtrace_execute_ex;
zend_execute_internal = dtrace_execute_internal;
#else
zend_compile_file = compile_file;
zend_execute_ex = execute_ex;
zend_execute_internal = NULL;
#endif /* HAVE_SYS_SDT_H */
zend_compile_string = compile_string;
zend_throw_exception_hook = NULL;
/* Set up the default garbage collection implementation.
設(shè)置垃圾回收的函數(shù)句柄
*/
gc_collect_cycles = zend_gc_collect_cycles;
/*分配函數(shù)符號表(CG(function_table))纠脾、類符號表(CG(class_table))玛瘸、常量符號表
(EG(zend_constants))等,這個(gè)CG是非線程安全下的一個(gè)宏定義 # define GLOBAL_FUNCTION_TABLE CG(function_table)*/
GLOBAL_FUNCTION_TABLE = (HashTable *) malloc(sizeof(HashTable));
GLOBAL_CLASS_TABLE = (HashTable *) malloc(sizeof(HashTable));
GLOBAL_AUTO_GLOBALS_TABLE = (HashTable *) malloc(sizeof(HashTable));
GLOBAL_CONSTANTS_TABLE = (HashTable *) malloc(sizeof(HashTable));
#ifdef ZTS
/*如果是多線程的話苟蹈,還會(huì)分配編譯器糊渊、執(zhí)行器的全局變
量*/
ts_allocate_id(&executor_globals_id, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor);
……
/* 注冊 zend核心擴(kuò)展,擴(kuò)展是內(nèi)核提供的慧脱,該過程將注冊Zend核心擴(kuò)展提供的函數(shù)再来,比如:strlen、define磷瘤、func_get_args芒篷、class_exists等*/
zend_startup_builtin_functions();
/*注冊 Zend 定義的標(biāo)準(zhǔn)常量:zend_register_standard_constants(),比如:E_ERROR采缚、
E_WARNING针炉、E_ALL、TRUE扳抽、FALSE 等*/
zend_register_standard_constants();
/* 注冊$GLOBALS 超全局變量的獲取 handler */
zend_register_auto_global(zend_string_init("GLOBALS", sizeof("GLOBALS") - 1, 1), 1, php_auto_globals_create_globals);
/*分配 php.ini 配置的存儲(chǔ)符號表:*/
zend_ini_startup();
- 注冊php定義的常量
REGISTER_MAIN_STRINGL_CONSTANT("PHP_VERSION", PHP_VERSION, sizeof(PHP_VERSION)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_MAJOR_VERSION", PHP_MAJOR_VERSION, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_MINOR_VERSION", PHP_MINOR_VERSION, CONST_PERSISTENT | CONST_CS);
……
- 解析php.ini:解析完成后所有的 php.ini 配置保存在 configuration_hash 哈希表中(php_init_config()篡帕,在php_ini.c)
if (php_init_config() == FAILURE) {
return FAILURE;
}
*映射 PHP殖侵、Zend 核心的 php.ini 配置:根據(jù)解析出的 php.ini,獲取對應(yīng)的配置值镰烧,將
最終的配置插入 EG(ini_directives)哈希表拢军。
/* Register PHP core ini entries 注冊 php 核心 ini 入口*/
REGISTER_INI_ENTRIES();
- 注冊用于獲取
_POST怔鳖、
_SERVER、
_REQUEST度陆、$_FILES
變量的 handler。
php_startup_auto_globals();/* 具體定義在./main/php_variables.c*/
注冊靜態(tài)編譯的擴(kuò)展:php_register_internal_extensions_func()
注冊動(dòng)態(tài)加載的擴(kuò)展:php_ini_register_extensions()
回調(diào)各擴(kuò)展定義的 module starup 鉤子函數(shù)献幔,即通過 PHP_MINIT_FUNCTION()定義的函數(shù)懂傀。
禁用php.ini 配置的函數(shù)與類。默認(rèn)
disable_functions =
蜡感、disable_classes =
蹬蚁。
php_disable_functions();
php_disable_classes();
請求初始化
int php_request_startup(void)
主要處理:
激活輸出:php_output_activate()
激活Zend引擎:zend_activate()
ZEND_API void zend_activate(void) /* {{{ */
{
#ifdef ZTS
virtual_cwd_activate();
#endif
gc_reset();/* 重置垃圾回收器 */
init_compiler(); /* 初始化編譯器 */
init_executor(); /* 初始化執(zhí)行器 */
startup_scanner();/* 初始化詞法掃描器*/
}
激活SAPI:sapi_activate()(注意:模塊初始化階段也做了這件事情)
回調(diào)個(gè)擴(kuò)展定義的request startup鉤子函數(shù):zend_activate_modules()(文件./Zend/zend_API.c)
ZEND_API void zend_activate_modules(void) /* {{{ */
{
zend_module_entry **p = module_request_startup_handlers;
while (*p) {
zend_module_entry *module = *p;
if (module->request_startup_func(module->type, module->module_number)==FAILURE) {
zend_error(E_WARNING, "request_startup() for %s module failed", module->name);
exit(1);
}
p++;
}
}
執(zhí)行腳本階段
php_execute_script(),包括php代碼編譯郑兴、執(zhí)行兩個(gè)核心階段(zend引擎最重要的功能)犀斋。編譯階段,php腳本經(jīng)歷從PHP源碼到抽象語法樹再到opline指令的轉(zhuǎn)化過程杈笔,生成zend引擎識(shí)別的執(zhí)行指令(opline指令)闪水,指令被執(zhí)行器執(zhí)行,php解釋執(zhí)行的過程蒙具。
ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...) /* {{{ */
{
va_list files;
int i;
zend_file_handle *file_handle;
zend_op_array *op_array;
va_start(files, file_count);
for (i = 0; i < file_count; i++) {
file_handle = va_arg(files, zend_file_handle *);
if (!file_handle) {
continue;
}
/* 編譯 opcodes(詞法球榆、語法分析—)*/
op_array = zend_compile_file(file_handle, type);
if (file_handle->opened_path) {
zend_hash_add_empty_element(&EG(included_files), file_handle->opened_path);
}
zend_destroy_file_handle(file_handle);
if (op_array) {
/* 指令執(zhí)行 */
zend_execute(op_array, retval);
zend_exception_restore();
zend_try_exception_handler();
if (EG(exception)) {
zend_exception_error(EG(exception), E_ERROR);
}
destroy_op_array(op_array);
efree_size(op_array, sizeof(zend_op_array));
} else if (type==ZEND_REQUIRE) {
va_end(files);
return FAILURE;
}
}
va_end(files);
return SUCCESS;
}
ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, int type)
{
zend_lex_state original_lex_state;
zend_op_array *op_array = NULL;
zend_save_lexical_state(&original_lex_state);
if (open_file_for_scanning(file_handle)==FAILURE) {
if (type==ZEND_REQUIRE) {
zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, file_handle->filename);
zend_bailout();
} else {
zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, file_handle->filename);
}
} else {
zend_bool original_in_compilation = CG(in_compilation);
CG(in_compilation) = 1;
CG(ast) = NULL;
CG(ast_arena) = zend_arena_create(1024 * 32);
/* yacc 不斷調(diào)用re2cc掃描token生成抽象語法樹*/
if (!zendparse()) {
zval retval_zv;
zend_file_context original_file_context;
zend_oparray_context original_oparray_context;
zend_op_array *original_active_op_array = CG(active_op_array);
op_array = emalloc(sizeof(zend_op_array));
init_op_array(op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE);
CG(active_op_array) = op_array;
ZVAL_LONG(&retval_zv, 1);
if (zend_ast_process) {
zend_ast_process(CG(ast));
}
zend_file_context_begin(&original_file_context);
zend_oparray_context_begin(&original_oparray_context);
/* 從抽象語法樹 生成 op_array*/
zend_compile_top_stmt(CG(ast));
zend_emit_final_return(&retval_zv);
op_array->line_start = 1;
op_array->line_end = CG(zend_lineno);
pass_two(op_array);
zend_oparray_context_end(&original_oparray_context);
zend_file_context_end(&original_file_context);
CG(active_op_array) = original_active_op_array;
}
zend_ast_destroy(CG(ast));
zend_arena_destroy(CG(ast_arena));
CG(in_compilation) = original_in_compilation;
}
zend_restore_lexical_state(&original_lex_state);
return op_array;
}
請求關(guān)閉階段
php_request_shutdown()
這個(gè)階段將flush輸出內(nèi)容、發(fā)送HTTP應(yīng)答header頭禁筏、清理全局變量持钉、關(guān)閉編譯器、關(guān)閉執(zhí)行器等篱昔。還會(huì)回調(diào)各擴(kuò)展的request shutdown 鉤子函數(shù)每强。這個(gè)階段是請求初始化相反的操作,與初始化階段處理一一對應(yīng)州刽。
/* 1. Call all possible shutdown functions registered with register_shutdown_function() */
if (PG(modules_activated)) zend_try {
php_call_shutdown_functions();
} zend_end_try();
/* 2. Call all possible __destruct() functions */
zend_try {
zend_call_destructors();
} zend_end_try();
/* 3. Flush all output buffers */
zend_try {
zend_bool send_buffer = SG(request_info).headers_only ? 0 : 1;
if (CG(unclean_shutdown) && PG(last_error_type) == E_ERROR &&
(size_t)PG(memory_limit) < zend_memory_usage(1)
) {
send_buffer = 0;
}
if (!send_buffer) {
php_output_discard_all();
} else {
php_output_end_all();
}
} zend_end_try();
/* 4. Reset max_execution_time (no longer executing php code after response sent) */
zend_try {
zend_unset_timeout();
} zend_end_try();
/* 5. Call all extensions RSHUTDOWN functions */
if (PG(modules_activated)) {
zend_deactivate_modules();
}
/* 6. Shutdown output layer (send the set HTTP headers, cleanup output handlers, etc.) */
zend_try {
php_output_deactivate();
} zend_end_try();
/* 7. Free shutdown functions */
if (PG(modules_activated)) {
php_free_shutdown_functions();
}
/* 8. Destroy super-globals */
zend_try {
int i;
for (i=0; i<NUM_TRACK_VARS; i++) {
zval_ptr_dtor(&PG(http_globals)[i]);
}
} zend_end_try();
/* 9. free request-bound globals */
php_free_request_globals();
/* 10. Shutdown scanner/executor/compiler and restore ini entries */
zend_deactivate();
/* 11. Call all extensions post-RSHUTDOWN functions */
zend_try {
zend_post_deactivate_modules();
} zend_end_try();
/* 12. SAPI related shutdown (free stuff) */
zend_try {
sapi_deactivate();
} zend_end_try();
/* 13. free virtual CWD memory */
virtual_cwd_deactivate();
/* 14. Destroy stream hashes */
zend_try {
php_shutdown_stream_hashes();
} zend_end_try();
/* 15. Free Willy (here be crashes) */
zend_interned_strings_restore();
zend_try {
shutdown_memory_manager(CG(unclean_shutdown) || !report_memleaks, 0);
} zend_end_try();
/* 16. Reset max_execution_time */
zend_try {
zend_unset_timeout();
} zend_end_try();
模塊關(guān)閉階段
php_module_shutdown()
該階段與模塊初始化階段對應(yīng)空执,主要進(jìn)行資源清理、php各模塊的關(guān)閉操作穗椅,回調(diào)各擴(kuò)展的module shutdown鉤子函數(shù)辨绊。
sapi_flush();
zend_shutdown();/* 清理持久化符號表 */
/* Destroys filter & transport registries too */
php_shutdown_stream_wrappers(module_number);
UNREGISTER_INI_ENTRIES(); /* 清理ini hashTable 元素*/
/* close down the ini config */
php_shutdown_config();
#ifndef ZTS
zend_ini_shutdown();
shutdown_memory_manager(CG(unclean_shutdown), 1);
#else
zend_ini_global_shutdown(); /* 銷毀 EG(ini_directive*/
#endif
php_output_shutdown(); /* 關(guān)閉output */
module_initialized = 0;
#ifndef ZTS
core_globals_dtor(&core_globals); /*釋放 PG*/
gc_globals_dtor();
#else
ts_free_id(core_globals_id);
#endif
參考資料:
- 《php內(nèi)核剖析》秦明
- 《TIPI深入理解php內(nèi)核》http://www.php-internals.com
- 《vscode代替source insight》https://blog.csdn.net/dtw11502/article/details/80798167
- 《__declspec(dllexport) & __declspec(dllimport)》https://www.cnblogs.com/xd502djj/archive/2010/09/21/1832493.html
- 《使用PHP Embed SAPI實(shí)現(xiàn)Opcodes查看器》http://www.laruence.com/2008/09/23/539.html
- 《跟廠長學(xué)PHP7內(nèi)核(五):系統(tǒng)分析生命周期》https://www.cnblogs.com/enochzzg/p/9595417.html
- 《PHP新的垃圾回收機(jī)制:Zend GC詳解》https://www.cnblogs.com/orlion/p/5350844.html
*《PHP7源碼分析之CG和EG》https://www.bo56.com/php7%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8Bcg%E5%92%8Ceg/