此文章是读书笔记,个人底层欠火候。文章的图或找或自己试着画一下。尽量少的抄书。
准备
php 5.6、php 7.0.12 各一份
使用 vscode ,配置(vscode代替source insight)
phpstudy 方便切换各种版本测代码。
centos7的虚拟机,方便后续的使用。
流程图用的是https://www.processon.com
php 7变化
抽象语法树
php5.x
PHP代码在语法解析阶段直接生成了ZendVM指令。zend_language_parse.y中生成opline指令
缺点:编译器与执行器耦合在一起
php7
将php代码解析成抽象语法树,将抽象语法树编译为ZendVM指令
优点: php的编译器与执行器很好地隔离开,编译器不需要关心指令的生成规则,然后执行器根据自己的规则将抽象语法树编译为对应的指令。
Native TLS
php 5.x :
多线程环境不能简单通过全局变量实现,为适应多线程应用环境。
php提供了一个线程安全资源管理器,将全局资源进行线程隔离,不同的线程互不干扰
php 7
使用Native TLS(线程局部存储)保存线程的资源池,__tread标识一个全局变量,全局变量就是线程独享,不同线程修改不会影响
指定函数参数、返回值类型
zval结构变化
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;
缺点
php5.x的引用计数在在zval中而不是在 value中,复制变量需要复制两个结构,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;
优点:
引用计数在具体value(元素zend_refcounted )中,zval只是载体,value才是真正的值
php变量之间复制、传递更加简洁、易懂
zval结构大小从24byte减少到了16byte,也是php7能够降低系统资源的一个优化点
异常处理
php5.x
很多操作会抛出error错误
php7
将多数错误改为了异常抛出,这样就可以通过try catch 捕捉到了
调用未定义函数。示例代码:
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结构的大小从72byte减小到了56byte。数组元素bucket从72byte减少到了32byte
php的构成
SAPI层适配不同的执行场景。常用的如下:
apache2handler
cgi
cli
embed #嵌入式
fpm
litespeed #LiteSpeed 一种被特别设计用作大型网站的商业web服务器。 其中一个优势就是它能直接读取Apache 的配置信息。并轻易将它现有的产品结合在一起来代替Apache 。这种服务器是轻量级的就如它的名字暗示出非常快。
ZendVM
ZendVM两部分组成:编译器、执行器。
Extension
扩展分为PHP扩展、Zend扩展(应用于ZendVM比如Opcache)
目录
build/
ext/
main/
netware/
pear/
sapi/
scripts/
tests/
travis/
TSRM/
win32/
Zend/
生命周期
模块初始化、请求初始化、执行脚本阶段、请求关闭阶段、模块关闭阶段
在main/main.c文件中能看到对应的函数定义。
不同的sapi场景使用不同的方法。
拿fpm举例
main() ,,在文件/sapi/fpm/fpm/fpm_main.c
。main函数中能看到调用的情况。
模块初始化阶段
php_module_startup 函数(./main/main.c)
主要干了:
- 激活SAPI:sapi_activate()(函数的定义在./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 数据,读取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可能包含要根据给定内容类型处理为变量的表单数据
* */
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();
}
- 启动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;
}
分析参考 跟厂长学PHP7内核(五):系统分析生命周期
- 初始化垃圾回收器:gc_globals_ctor()(文件./Zend/zend_gc.c,含有gc_ 开头的你懂的),分配zend_gc_globals内存
ZEND_API void gc_globals_ctor(void)
{
#ifdef ZTS /* 线程安全 执行ts_allocate_id,继续追这个函数就能看到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
}
更多关于内存分配参考 PHP新的垃圾回收机制:Zend GC详解
- 启动zend引擎:zend_startup()
……
start_memory_manager(); /*启动内存池*/
/* Set up utility functions and values
设置一些util函数句柄
*/
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;
/* 设置 Zend 虚拟机编译、执行器的函数句柄 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.
设置垃圾回收的函数句柄
*/
gc_collect_cycles = zend_gc_collect_cycles;
/*分配函数符号表(CG(function_table))、类符号表(CG(class_table))、常量符号表
(EG(zend_constants))等,这个CG是非线程安全下的一个宏定义 # 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
/*如果是多线程的话,还会分配编译器、执行器的全局变
量*/
ts_allocate_id(&executor_globals_id, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor);
……
/* 注册 zend核心扩展,扩展是内核提供的,该过程将注册Zend核心扩展提供的函数,比如:strlen、define、func_get_args、class_exists等*/
zend_startup_builtin_functions();
/*注册 Zend 定义的标准常量: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 配置的存储符号表:*/
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 配置:根据解析出的 php.ini,获取对应的配置值,将
最终的配置插入 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*/
注册静态编译的扩展:php_register_internal_extensions_func()
注册动态加载的扩展:php_ini_register_extensions()
回调各扩展定义的 module starup 钩子函数,即通过 PHP_MINIT_FUNCTION()定义的函数。
禁用php.ini 配置的函数与类。默认
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(); /* 初始化执行器 */
startup_scanner();/* 初始化词法扫描器*/
}
激活SAPI:sapi_activate()(注意:模块初始化阶段也做了这件事情)
回调个扩展定义的request startup钩子函数: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++;
}
}
执行脚本阶段
php_execute_script(),包括php代码编译、执行两个核心阶段(zend引擎最重要的功能)。编译阶段,php脚本经历从PHP源码到抽象语法树再到opline指令的转化过程,生成zend引擎识别的执行指令(opline指令),指令被执行器执行,php解释执行的过程。
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) {
/* 指令执行 */
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 不断调用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;
}
请求关闭阶段
php_request_shutdown()
这个阶段将flush输出内容、发送HTTP应答header头、清理全局变量、关闭编译器、关闭执行器等。还会回调各扩展的request shutdown 钩子函数。这个阶段是请求初始化相反的操作,与初始化阶段处理一一对应。
/* 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
模块关闭阶段
php_module_shutdown()
该阶段与模块初始化阶段对应,主要进行资源清理、php各模块的关闭操作,回调各扩展的module shutdown钩子函数。
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(); /* 关闭output */
module_initialized = 0;
#ifndef ZTS
core_globals_dtor(&core_globals); /*释放 PG*/
gc_globals_dtor();
#else
ts_free_id(core_globals_id);
#endif
参考资料:
- 《php内核剖析》秦明
- 《TIPI深入理解php内核》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实现Opcodes查看器》http://www.laruence.com/2008/09/23/539.html
- 《跟厂长学PHP7内核(五):系统分析生命周期》https://www.cnblogs.com/enochzzg/p/9595417.html
- 《PHP新的垃圾回收机制: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/