深⼊剖析PHP7内核源码(⼀)-PHP架构与⽣命周期
PHP7 为什么这么快?
全新的zval 更节约的空间,栈上分配内存
zend_string 存储字符串的Hash值,数组查询的时候不需要进⾏Hash计算
在HashTable桶内直接存数据,减少了内存的申请次数,提升了cache命中率和内存访问速度
zend_parse_parameters改为了宏实现,性能提升5%
增加opcode指令 call_user_function,is_init/string/array,strlen,defined函数变成opcode指令,速度更快
排序算法的改进
PHP7 架构
Zend 引擎:Zend引擎为PHP提供了基础服务,包括词法分析语法分析,AST抽象语法树编译 opcodes执⾏,PHP的变量设计、内存管理、进程管理。
PHP层:绑定了SAPI层并处理与它的通信,它同时对safe_mode和open_basedir的检测提供⼀致的控制层,将fopen()、fread()和fwrite()等⽤户空间的函数与⽂件和⽹络I/O联系起来。
php文件下载源码SAPI:包括了cli fpm等,把接⼝对外接⼝都抽象出来,只要遵守SAPI协议便可以实现⼀个server。
拓展:zend 引擎提供了核⼼能⼒和接⼝规范,在此基础上可以开发拓展
这⾥的拓展分为了两种,通常在php.ini中,通过extension=加载的扩展我们称为PHP扩展,通过zend_extension=加载的扩展我们称为Zend扩展,但从源码的⾓度来讲,PHP扩展应该称为“模块”(源码中以module命名),⽽Zend扩展称为“扩展”(源码中以extension命名)。两者最⼤的区别在于向引擎注册的钩⼦,向⽤户层⾯提供⼀些C实现的PHP函数,需要⽤到
zend_module_entry(即作为PHP扩展),⽽需要hook到Zend引擎的话,就得⽤到zend_extension(即作为Zend扩展)。
PHP7执⾏流程
1. 词法分析,把源代码切割成多个字符串单元(Token)
2. 语法分析器把Token转换成AST抽象语法树
3. 抽象语法树转换成opcodes(opcode指令集合)
4. 虚拟机解释执⾏执⾏opcodes(opcode是⼀组指令标识,对应handler处理函数)
执⾏实例
词法分析
<?php
echo "Hello world";
切割成了4部分
<?php  => #define T_OPEN_TAG 379
echo => #define T_ECHO 328
空格 =>  #define T_WHITESPACE 382
"hello world" => #define T_CONSTANT_ENCAPSED_STRING 323
语法分析
单独存在的词块不能完整表达语义,还需要语法分析器,它会检查语法,匹配Token,对Token进⾏关联,组织串联后的产物就是AST.AST 分为多种类型,对应PHP语法,⽐如赋值语句,⽣成的抽象语法树节点是ZEND_AST_ASSIGN,赋值语句的左右会被作为ZEND_AST_ASSIGN类型节点的孩⼦(AST是PHP7才加⼊的,解耦了编译器和解释器).
opcodes
opcode是PHP执⾏过程中的中间代码,⽣成后由虚拟机执⾏,⽣成的opcode是类似下⾯的样⼦
line    op
1        ECHO
2        RETURN
源码中对应的opcode及handler
ZEND_ECHO // handler:ZEND_ECHO_SPEC_CONST_HANDLER 实现的功能是输出"hello world"
ZEND_RETURN  // handler:ZEND_RETURN_SPEC_CONST_HANDLER
PHP ⽣命周期
CLI⽣命周期
php_module_startup:注册全局变量GPC等,加载内部拓展和外部拓展。
php_request_startup:重置垃圾回收器,初始化执⾏器,初始化扫描器,设置超时时间等。
php_execute_script
=> compile_file
=> open_file_for_scanning(读取PHP代码内容,并使词法分析指针指向第⼀个位置)
=> zendparse(词法分析语法分析后⽣成AST) => init_op_array(初始化op_array)
=> zend_compile_top_stmt(把AST转为op_array)
=> pass_two(设置op_array对应的zend虚拟机handler)
=> ⽣成op_array
=> zend_execute(zend虚拟机中执⾏op_array)
php_request_shutdown:调⽤所有关闭函数,调⽤所有析构函数,输出缓冲区内容,重置最⼤执⾏时间,关闭输出层(HTTP头等),释放所有request的全局变量
php_module_shutdown:调⽤module对应的flush函数,清理持久化的符号表,销毁全局变量,关闭所有拓展,关闭内存管理,关闭输出output,析构垃圾回收
FPM模式的⽣命周期
FPM跟CLI模式不同的是,FPM是常驻内存的,所以php_module_startup只在启动进程的时候做⼀次初始化,对应的
php_module_shutdown也只做⼀次。
进⼊循环,调⽤fcgi_accept_request(accept) 阻塞等待,如果请求进来,则进⼊php_request_startup,初始化请求,同时加了锁来防⽌惊效应
fcgi.c
...
FCGI_LOCK(req->listen_socket);
req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len);
FCGI_UNLOCK(req->listen_socket);
引⽤
PHP7的性能优化总结
PHP扩展与Zend扩展区别
《PHP7 底层设计与源码实现》陈雷等