1.PHP7内核剖析 --- PHP 基础架构

1.PHP7 的变化:
	1.抽象语法树
		在 php 之前的版本,php 代码在语法解析阶段直接生成了 ZendVM 指令,也就是 zend_language_parser.y 中直接生成 opline 指令,
	  这使得编译器与执行器耦合在一起。编译生成的指令供执行引起使用,该指令是在语法解析时直接生成的,加入要把执行引擎换成其他的,就需要
	  修改语法解析规则;或者如果 php 语法规则变了,但对应的执行指令没有变化,那么也需要修改语法解析规则。
	    php7 中增加了抽象语法树,首先是将 php 代码解析成抽象语法树,然后将抽象语法树编译为 ZendVM 指令。抽象语法树的加入使得 php 的
	  编译器和执行器很好的隔离开,编译器不需要关心指令的生成规则,然后执行器根据自己的规则将抽象语法树编译为对应的指令,执行器同样不需要
	  关心该指令的语法规则是什么样子的。

	2.Native TLS
		TSRM_CC,TSRM_DC 这2个宏是用于线程安全的。php 中有很多变量需要在不同函数之间共享,多线程的环境下不能简单的通过全局变量来实现,
	  为了适应多线程的应用环境,php 提供了一个线程安全资源管理器,将全局资源进行了线程隔离,不同的线程之间互不干扰。
	    使用全局资源需要先获得本地线程的资源池,这个过程比较占用时间,因此,php5.x 通过参数传递的方式将本线程的资源池传递给其他函数,避免
	  重复查找。这种实现方式使得几乎所有的函数都需要加上接收资源池的参数,也就是 TSRM_DC 宏所加的参数,然后调用其他函数时再把这个参数传递
	  下去,不仅容易遗漏,而且这种方式极不优雅。
	    php7 中使用了 Native TLS(线程局部安全)来保存线程的资源池,简单的说就是通过 __thread 标识一个全局变量,这样这个全局变量就是线程独享
	  的了,不同线程的修改不会互相影响。

	3.指定函数参数,返回值类型
		php7 中可以指定函数参数以及返回值的类型。如 
		function foo(string $anme) : array { ... }
		这个函数参数必须为 字符串,返回值必须是数组,否则报error错

	4.zval 结构的变化
		php5.x 中的:
		struct _zval_struct {
			zvalue_value value; //具体值
			zend_uint refcount_gc;  //引用计数
			zend_uchar type; //类型
			zend_uchar is_ref_gc;  //是否引用
		}

		typedef union _zvalue_value {
			long lval;
			double dval;
			struct {
				char *val;
				int len;
			} str;
			HashTable *ht;
			zend_object_value obj;
			zend_ast *ast;
		} zvalue_value;

		refcount_gc 它记录变量的引用计数,引用计数是php实现自动回收的基础,也就是记录一个变量有多少个地方在使用的一种机制。php5.x 中引用计数是
	  在 zval 中而不是在具体的 value 中,这样一来,导致变量复制时需要复制两个结构,zval,zvalue_value 始终绑定在一起。php7 中将引用计数转移到
	  了具体的 value 中,这样更合理。因为 zval 只是变量的载体,可以简单的认为是变量名,而 value 才是真正的值,这个改变使得 php 变量之间的复制,
	  传递更加简洁,易懂。除此之外,zval 结构的大小也从24byte 减少到了 16byte,这是 php7 能够降低系统资源占用的一个优化点所在。

	5.异常处理
		php5.x 中很多操作会直接抛出 error, php7 中会将许多错误改为了异常抛出,这样一来就可以通过 try catch 捕获到,例如:
		try {
			test();
		} catch (Throwble $e) {
			echo $e->getMessage();
		}


	6.HashTable 的变化
		HashTable,哈希表,散列表,它是 php 中强大的 array() 类型的内部实现结构,也是内核中使用非常频繁的一个结构,函数符号表,类符号表,常量符号表
	  等都是通过 HashTable 实现的。
	    php7 中HashTable 有非常大的变化,HashTable 大小从 72byte 减小到 56byte,同时数组元素 Bucket 结构从 72byte 减小到 32byte。
    
    7.执行器
    	execute_data,opline 采用寄存器变量存储,执行器的调度函数为 execute_ex(),这个函数负责执行 php 代码编译生成的 ZendVM 指令。在执行期间会频繁
      的用到 execute_data,opline 两个变量,在 php5.x 中,这2个变量是由  execute_ex() 通过参数传递给各指令 handle 的,在 php7 中不再采用传参的方式,
      而是将 execute_data,opline 通过寄存器来进行存储,避免了传参导致的频繁出入栈操作,同时,寄存器相比内存的访问速度快很多。这个优势使得 php 的性能提升
      了 5% 左右。

    8.新的参数解析方式
    	php5.x 通过 zend_parse_parameters() 解析函数的参数,php7 中提供了另外一种方式,同时保留了原来的方式,但新的解析方式速度更快。


2.PHP 的构成
	其中 SAPI 是 php 的应用接口层;main 为 php 的主要代码,主要是输入/输出,web通信,以及 php 框架的初始化操作等,比如 fastcgi协议的解析,扩展的加载,
  php 配置的解析等工作都是它完成的,它位于 ZendVM 的上一层;Zend 目录是 php 解析器的主要实现,即 ZendVM,它是 php 语言的核心实现,php 代码的解释,执行
  就是 Zend 完成的;ext 是 php 的扩展目录;TSRM 为线程安全相关的实现。
    
    1.SAPI
    	PHP 是一个脚本解析器,提供脚本的解析与执行,它的输入是普通的文本,然后由 php 解析器按照预先定义好的语法规则进行解析执行。我们可以在不同的环境中应用
      这个解析器,比如命令行下,web 环境中,嵌入其他应用中使用。为此,php 提供了一个 SAPI 层适配不同的应用环境,SAPI 可以认为是 php 的宿主环境。SAPI 也是
      整个 php 框架的最外层一部分,它主要负责 php 框架的初始化工作。如果 SAPI 是一个独立的应用程序,比如 cli,fpm,那么 main 函数也会定义在 SAPI 中。SAPI
      的代码位于 PHP 源码的 /sapi 目录下。

    2.ZendVM
    	ZendVM 是一个虚拟的计算机,它介于 php 应用于实际计算机中间,我们编写的 php代码就是被它解释执行的。ZendVM 是 php 语言的核心实现,它主要由2个部分组成:
      编译器,执行器。其中编译器负责将 php 代码解释为执行器可识别的指令,执行器负责执行编译器解释出指令。ZendVM 的角色相当于 Java 中的 JVM,它们都是抽象出来的
      虚拟计算机,与C/C++ 这类编译型语言不通,虚拟机上的指令并不是机器指令。虚拟机的一个突出优点是跨平台,只需要按照不通平台编译出对应的解释器就可以实现代码的
      跨平台执行。

    3.Extension
    	扩展是 php 内核提供的一套用于扩充 php 功能的一种方式。扩展分为 php 扩展,zend 扩展。php 扩展比较常见,而 zend 扩展主要用于 ZendVM,它可以做到更多事情。
      我们所熟知的 Opcache 就是 Zend 扩展。


3.生命周期
	php 的整个生命周期被划分为:模块初始化阶段(module startup),请求初始化(request startup),执行脚本阶段(execute script),请求关闭阶段(request shutdown),
  模块关闭阶段(module shutdown)。根据不同 SAPI 的实现,各个阶段的执行情况会有一些差异。比如,命令行下,每执行一个脚本都会完整的经历这些阶段,而 fastcgi 模式下
  则在启动的时候执行一次模块初始化,然后各个请求只经历请求初始化,执行请求脚本,请求关闭阶段,在 SAPI 关闭时经历模块关闭阶段。
    main() => php_module_startup() => php_request_startup() => php_execute_script() => php_request_shutdown() => php_module_shutdown()
    
    1.模块初始化阶段
    	这个阶段主要进行 php 框架,zend 引擎的初始化操作。该阶段的入口函数为 php_module_startup(),这个阶段一般只在 SAPI 启动的时候执行一次,对于 fpm 而言,
      就是在 fpm 的 master 进程启动时执行的。

      	该阶段的几个主要处理如下:
      	1.激活 SAPI : sapi_activate(),初始化请求信息SG(request_info),设置读取 POST 请求的 handler 等,在 module_startup 阶段处理完成后将调用
      	  sapi_deactivate()。
      	2.启动 PHP 输出 : php_output_startup()。
      	3.初始化垃圾回收器 : gc_globals_ctor(),分配 zend_gc_globals 内存
      	4.启动 Zend 引擎 : zend_startup(),主要包括如下:
      		1.启动内存池 start_memory_maneger()
      		2.设置一些 util 函数句柄(如 zend_error_cb, zend_printf, zend_write 等)
      		3.设置 Zend 虚拟机编译,执行器的函数句柄 zend_compile_file,zend_execute_ex,以及垃圾回收的函数句柄 gc_collect_cycles
      		4.分配函数符号表(CG(function_table)),类符号表(CG(class_table)),常量符号表(EG(zend_constant))表等,如果是多线程的话,还会分配编译器,执行器的全局
      		  变量
      		5.注册 Zend 核心扩展 : zend_startup_builtin_functions(),这个扩展是内核提供的,该过程将注册 Zend 核心扩展提供的函数,比如 strlen,define,等;
      	 	6.注册 Zend 定义的标准常量 : zend_register_standard_constants(),比如 E_ERROR,E_WARNING,E_ALL
      	 	7.注释 $GLOBALS 超全局变量的获取 handler
      	 	8.分配 php.ini 配置的存储符号表:EG(ini_directives)
      	5.注册 PHP 定义的常量 : PHP_VERSION,PHP_ZTS,PHP_SAPI 等
      	6.解析 php.ini : 解析完成后所有的 php.ini 配置信息保存在 configuration_hash 哈希表中
      	7.映射 PHP,Zend 核心的 php.ini 配置:根据解析出的 php.ini,获取对应的配置值,将最终的配置插入到 EG(ini_directive)哈希表
      	8.注册用于获取 $_GET,$_POST,$_COOKIE,$_SERVER,$_ENV,$_REQUEST,$_FILES 变量的 $handler
      	9.注册静态编译的扩展 : php_register_internal_extensions_func()
      	10.注册动态加载的扩展 : php_ini_register_register_extensions(),将 php.ini 中配置的扩展加载到 PHP 中
      	11.回调各扩展定义的 module start 钩子函数,即通过 PHP_MINIT_FUNCTION() 定义的函数
      	12.注册 php.ini 中禁用的函数,类 : disable_functions,disable_classes

    2.请求初始化阶段
    	该阶段是在请求处理前每一个请求都会经历的一个阶段,对于 fpm 而言,是在 worker 进程 accept 一个请求且读取,解析完请求数据后的一个阶段。该阶段的处理函数为
      php_request_startup()。

      	主要的处理有以下几个:
      	1.激活输出:php_output_activate()
      	2.激活 Zend 引擎:zend_activate(),主要操作如下所述:
      		1.重置垃圾回收器:gc_reset()
      		2.初始化编译器:init_compiler()
      		3.初始化执行器:init_executor(),将 EG(function_table),EG(class_table)分别指向 CG(function_table),CG(class_table),所以 PHP 的编译,执行期间,
      		  EG(function_table)与CG(function_table),EG(class_table)与CG(class_table)是同一个值;另外还会初始化全局符号变量表EG(symbol_table),include过的
      		  文件符号表EG(included_files)
      	3.激活SAPI:sapi_activate()
      	4.回调各扩展定义的 request startup 钩子函数:zend_activate_modules()

    3.执行脚本阶段
    	该阶段包括 PHP 代码的编译,执行两个核心阶段,这也是 Zend 引擎最重要的功能。在编译阶段,PHP 脚本将经历从 PHP 源代码到抽象语法树再到 opline 指令的转化过程,最终
      生成的 opline 指令就是 Zend 引擎可识别的执行指令,这些指令接着被执行器执行,这就是 PHP 代码解释执行的过程。入口函数为 php_execute_script()。

    4.请求关闭阶段
    	在 PHP 脚本解释执行完成后将进入请求关闭阶段,这个阶段将 flush 输出内容,发送 http 应答头,清理全局变量,关闭编译器,关闭执行器等。另外,在该阶段将回调各个扩展的 
      request shutdown 钩子函数。该阶段是与请求初始化阶段的相反操作,与请求初始化时的处理一一对应。

    5.模块关闭阶段
    	该阶段在 SAPI 关闭时执行,与模块初始化阶段对应,这个阶段主要进行资源的清理,PHP 各个模块的关闭操作,同时,将回调各扩展的 module shutdown 钩子函数。具体处理的函数
      为 php_module_shutdown()。

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值