PHP7内核-Zend执行引擎
作者:互联网
深入理解Zend执行引擎(PHP5)
PHP:一种解释型语言
PHP经常会被定义为“脚本语言”或者是“解释型语言”,什么是“解释型语言”呢?
所谓“解释型语言”就是指用这种语言写的程序不会被直接编译为本地机器语言(native machine language),而是会被编译为一种中间形式(代码),很显然这种中间形式不可能直接在CPU上执行(因为CPU只能执行本地机器指令),但是这种中间形式可以在使用本地机器指令(如今大多是使用C语言)编写的软件上执行。
这个软件就是所谓的软件虚拟机(software virtual machine)。我们先看下Wikipedia上对软件虚拟机的定义:
(…)进程虚拟机(process virtual machine)通过提供一个抽象的平台独立的程序执行环境来执行某种计算机程序。进程VM(译注:VM是virtual machine的缩写)有时候也被称为应用程序虚拟机,或者是可管理运行环境(Manged Runtime Environment,简称MRE),它会以一个普通应用的形式运行在宿主操作系统中(host OS),在运行中,他是宿主操作系统中一个单独的进程。在这个进程启动时,VM会被创建,退出时则会被销毁。它的目的是提供一种平台独立的程序执行环境,它可以对底层硬件或操作系统进行抽象处理,从而保证应用程序可以在任何平台上以一致的行为执行。
跟任何解释型语言一样,PHP语言也被设计为一种可以跨平台执行抽象指令的程序,尽可能地抽离掉底层操作系统的细节。这是技术上的解释。在功能应用上,PHP的主要领域是Web应用(PHP的目的是解决Web应用的相关问题)。
当前市面上还有其他一些基于软件虚拟机的语言(不完全列表):Java、Python、C#、Ruby、Pascal、Lua、Perl、Javascript等等。基本上使用这些语言编写的程序都不会被直接编译为本地机器指令,这些程序都是运行在一个软件虚拟机上。为了性能考虑,有些软件虚拟机可以把部分(并非全部)语言的中间代码直接转换为机器指令来执行:这个过程被称为“JIT编译”。在我写这篇文章时PHP并未使用JIT编译技术,不过有些实验性的工作正在进行,PHP社区也经常会提及和探讨这个话题。(译注:这篇文章写于15年2月份,此时HHVM应该已经发布,而HHVM就使用了JIT,另外最新版的PHP7貌似也使用了JIT)
软件虚拟机之所以如此流行,主要是因为我们不想为了在屏幕上输出一个“Hello”而写几千行的C代码(译注:这个有点夸张了吧,不过考虑到平台相关性,使用C写程序确实要考虑更多,至少是需要在不同的机器上编译所写的程序,但为了输出一个“hello”写几千行的代码就太夸张了,因为输出“hello”的主要工作都是由C运行库提供的函数完成的,尽管不同平台运行库的代码肯定有差别,但接口还是基本上完全一致的)。使用软件虚拟机相对于基于本地平台的程序开发有如下优点:
- 使用简单,便于开发
- 基本上都支持自动内存管理
- 支持抽象目标数据类型,没必要进行底层的数学运算(译注:这个应该指的是地址运算,这个在C中和汇编编程中都非常常见),没必要因为切换目标硬件平台而重新编写代码
- 不可能精确地管理内存或全局资源的使用(唯有信任VM)
- 执行速度无法比拟本地机器代码:完成相同的任务需要更多的CPU周期(JIT就是用于减小这个差距的,但不可能消除)
- 可能会抽象很多东西,通常程序员会远离硬件,从而无法全面理解代码的确切影响,特别是在负载过大的情况下
- 编译栈(compile stack):识别PHP语言指令,把它们转换为中间形式
- 执行栈(execution stack):获取中间形式的代码指令并在引擎上执行,引擎是用C或者汇编编写成的
- ZEND_ADD :执行两个操作数的算术加法运算
- ZEND_NEW :创建一个对象(一个PHP对象)
- ZEND_EXIT :退出PHP执行
- ZEND_FETCH_DIM_W : 取一个操作数在某个维度(dimension)下的值,然后执行写入操作(译注:这里的“维度”指的一维数组,二维数组的“维度”,给数组中的某个元素赋值,或者是给字符串所在某个位置的字符赋值都会用到这个OPCode)
- 等等
- 把8赋值(assign)给$a
- 把'foo'赋值给$b
- 把$a和$b中的值相加然后保存在一个临时变量“~2”中
- echo临时变量“~2”
- 返回(return)
- 当前脚本的文件名(const char *filename),以及会被编译为OPArray的PHP脚本在文件中的开始行数(zend_uint line_start)和结束行数(zend_uint line_end)
- 文档注释的信息(const char *doc_comment):PHP脚本中使用“/**”注释的信息
- 引用计数(zend_uint *refcount),OPArray本身也可能会在其他地方共享,所以需要记录它的引用情况
- 编译变量列表(zend_compiled_variable *vars)。编译变量(compiled variable)是所有PHP脚本中使用的变量(以$something的形式出现)
- 临时变量列表:临时变量用于保存运算的临时结果,这些结果不会显示地在PHP脚本中使用(不是使用$something的形式在PHP脚本中出现,但是是会被真正用到的中间数据)(译注:从上面的结构体中我看不出哪个是保存临时变量的字段)
- try-catch-finally的信息(zend_try_catch_element *try_catch_array),executor需要这些信息来实现正确的跳转
- break-continue的信息(zend_brk_cont_element *brk_cont_array),executor需要这些信息来实现正确的跳转
- 静态变量的列表(HashTable *static_variables)。静态变量会被特殊处理,因为它们的信息需要维持到PHP生命周期的最后时刻(大体上是这样)
- 字面量(zend_literal *literals)。字面量是指任何在编译期就知道它的值的东西(译注:实际上就是常量),例如我们在代码中使用的字符串'foo',或者是整型42
- 运行时缓存槽(cache slot):这个地方用于缓存引擎执行过程中还用会用到的东西
- IS_CV :编译变量(Compiled Variable):这个操作数类型表示一个PHP变量:以$something形式在PHP脚本中出现的变量
- IS_VAR : 供VM内部使用的变量,它可以被其他的OPCode重用,跟$php_variable很像,只是只能供VM内部使用
- IS_TMP_VAR : VM内部使用的变量,但是不能被其他的OPCode重用
- IS_CONST : 表示一个常量,它们都是只读的,它们的值不可改变
- IS_UNUSED :这个表示操作数没有值:这个操作数没有包含任何有意义的东西,可以忽略
- zend_vm_def.h并非有效的C文件,它描述了每个OPCode的handler的特点(使用一种与C接近的自定义语法),每个handler的特点依赖于它的op1和op2的类型,每个操作数最多支持5种类型
- zend_vm_def.h会被传递给一个名为zend_vm_gen.php的PHP脚本,这个脚本位于PHP源码中,它会分析zend_vm_def.h中的特殊语法,会用到很多正则表达式匹配,最终会生成出zend_vm_execute.h这个文件
- zend_vm_def.h在编译PHP源码时不会被处理(这是很显然的)
- zend_vm_execute.h是解析zend_vm_def.h后的输出文件,它包含了符合C语法的代码,它是VM executor的心脏:每个OPCode的专有handler函数都存放在这个文件里,很显然这是一个非常重要的文件
- 当你从源码编译PHP时,PHP源码会提供一个默认的zend_vm_execute.h文件,不过如果你想修改(hack)PHP源码,例如你想添加一个新的OPCode,或者是修改一个已存在的OPCode的行为,你必须先修改(hack)zend_vm_def.h,然后再重新生成zend_vm_execute.h文件
- 检查操作数1是否是字符串,如果不是则把它转换为字符串(重处理,heavy process)(译注:在这里我把heavy process直接翻译为“重处理”,它的意思就是要花一些时间来某个操作)
- 检查操作数2是否是字符串,如果不是则把它转换为字符串(重处理)
- 分配一个缓冲区,确定它的尺寸,然后把连接的结果拷贝到这个缓冲区中,再返回
- define()是函数调用,所以它必然会承受一些函数调用的开销
- const是一个关键字,它不会被编译成一个会产生函数调用的OPCode,所以它会比define()要更轻量一些
- const不能声明条件常量
- const(DECLARE_CONST)不能使用任何除IS_CONST外的其他操作数类型
标签:PHP7,zend,Zend,ZEND,handler,OPCode,内核,VM,PHP 来源: https://www.cnblogs.com/xiaoxi-jinchen/p/14339142.html