Featured image of post php底层

php底层

本文阅读量

php底层

https://www.cnblogs.com/taek/p/3979252.html

https://blog.csdn.net/xingshangyy/article/details/104176076

https://www.cnblogs.com/wanglijun/p/8830932.html

http://niliu.me/articles/1795.html

https://segmentfault.com/a/1190000022416584 https://www.cnblogs.com/wanglijun/p/8830932.html

php底层运行机制与原理

php动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令(opcodes),然后ZEND虚拟机顺次执行这些指令完成操作

php7代码执行过程

PHP代码=> token => 抽象语法树 => opcodes => 执行

  • 源代码通过词法分析得到 Token Token 是 PHP 代码被切割成的有意义的标识。PHP7 一共有 137 种 Token,在 zend_language_parser.h 文件中做了定义。
  • 基于语法分析器将 Token 转换成抽象语法树(AST) Token 就是一个个的词块,但是单独的词块不能表达完整的语义,还需要借助一定的规则进行组织串联。所以就需要语法分析器根据语法匹配 Token,将 Token 进行串联。语法分析器串联完 Token 后的产物就是抽象语法树(AST,Abstract Syntax Tree)。 AST 是 PHP7 版本的新特性,之前版本的 PHP 代码的执行过程中是没有生成 AST 这一步的。它的作用主要是实现了 PHP 编译器和解释器的解耦,提升了可维护性。
  • 将语法树转换成 Opcode 需要将语法树转换成 Opcode,才能被引擎直接执行。
  • 执行 Opcodes opcodes 是 opcode 的集合形式,是 PHP 执行过程中的中间代码。PHP 工程优化措施中有一个比较常见的 “开启 opcache”,指的技术这里将 opcodes 进行缓存。通过省去从源码到 opcode 的阶段,引擎直接执行缓存好的 opacode,以提升性能。

php语法分析

通过一定的规则,把输入的代码 区分出哪些是 是$开头的变量, 哪些是 以两个单引号括起来的字符串,哪些是以两个双引号括起来的字符串 等等, 这些区分出来的东西 称为token ,token 之间的联系 是由语法分析来完成的, 比如 赋值,加减乘除;

token是通过yylex()函数返回的,每执行yylex()函数,会返回一个token,每个token都会有类型和相对应的值。

$name = ‘string’;

php7 内核架构

img

  • zend 引擎 词法 / 语法分析、AST 编译和 opcodes 的执行均在 Zend 引擎中实现。此外,PHP 的变量设计、内存管理、进程管理等也在引擎层实现。
  • PHP 层 zend 引擎为 PHP 提供基础能力,而来自外部的交互则需要通过 PHP 层来处理。
  • SAPI server API 的缩写,其中包含了场景的 cli SAPI 和 fpm SAPI。只要遵守定义好的 SAPI 协议,外部模块便可与 PHP 完成交互。
  • 扩展部分 依据 zend 引擎提供的核心能力和接口规范,可以进行开发扩展。

php7与php5区别

php7比php5快,主要是php7对zend引擎进行了深度优化。

增加抽象语法数

php5代码在语法解析阶段直接生成opline指令,执行器直接执行opline指令

php7代码在解析生成抽象语法树,然后将抽象语法树编译成opline指令。解耦编译器和执行器。

zval

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct _zval_struct{
    zend_value value;
    union {
       struct {
           ZEND_ENDIAN_LOHI_4(
           	   zend_uchar type,  // 标记value的类型,如type是is_string,就会取zend_value str的指针
               zend_uchar type_flags,
               zend_uchar const_flags,
               zend_uchar reserved
           )
       } v;
        uint32_t type_info; //表示变量类型,is_null,is_false,istrue,is_long,is_array,is_object,is_resource等11个与php相关的
    }u1;
    union {
        uint32_t next; // 数组中解决hash冲突的
        uint32_t cache_slot;// 做运行词缓存
        uint32_t lineno; // 标记php的哪一行
        uint32_t num_args; // 函数调用传递参数个数
        uint32_t fe_pos; // foreach位置,每foreach一次 这个值就+1
        uint32_t fe_iter_idx; // 游标索引位置
        uint32_t access_flags; // 进行类性标记,如public,private,protected
        uint32_t property_guard; 
    } u2;
}

zval

可以表示php中任意变量

zend_value是一个联合体,取值是根据u1的type来取值的ZEN

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
typedef union _zend_value{
    					// define IS_UNDEF 0
    					// define IS_NULL 1
    					// define IS_FALSE 2
                        // define is_TRUE 3
    zend_long  lval;    // define IS_LONG 4
    double     dval;   // define IS_DOUBLE 5;
    zend_refcounted *counted;
    zend_string *str;  // define IS_STRING 6;
    zend_array  *arr;	// define IS_ARRAY 7;
    zend_object *obj;  // define IS_OBJECT 8
    zend_resource *res; // define IS_RESOURCE 9
    zend_reference *ref;  // 引用类型 define IS_REFERENCE 10
    zend_function *func;
    zend_class_entry *ce; // 类
    zend_ast_ref *ast; // 抽象语法树
    void *ptr;               // IS_VOID 18
} zend_value;

使用gdb查看类型

1
2
3
4
5
6
7
8
9
gbc php
# echo handle 打断点
b ZEND_ECHO_SPEC_CV_HANDLER
#执行文件
r zval.php

p *z
# 取查看u1 type 对应的数字 4 is_long 5 is_double 6 is_string 7 is_array
# 然后对照_zend_value每个类型对应取什么,如4 取的是lval就算对应该变量存储的值

string

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct _zend_string{
    zend_refcounted_h gc; // 主要用在垃圾回收
    zend_ulong h; // 该字符串对应的hash值
    size_t len;
    char val[1];
}
// 字符串在复制的时候都是指定的同一个zend_string
// $a = 'string'; $b = $a;
// 使用 zend_refcounted_h 里的refcount进行+1标记,表示被两个zend_string 引用了
// 当$b 修改时,会复制一份zend_string(地址发生改变了),原来的zend_string的refcount会-1

引用类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// zend_reference
struct _zend_reference{
    zend_refcounted_h gc;
    zval  val;
}

// $a = 'ad';
// $b = &$a;
// 这时候a,b的type都是10,并且都指向相同的zend_reference,zend_reference里的zval指向相同的zend_string
// unset($b);
// 这时,$b的zend_reference 不会变,只是会复制一份zend_string(地址发生改变了),原来的zend_string的u1里的type会改成0(IS_UNDEF)

array

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// _zend_array HashTable
struct _zend_array{
    zend_refcounted_h gc;
    unit32_t nTableMask; // 计算索引值
    Bucket  *arData;// 存储key-value对
    unit32_t nNumUsed; // 已经用的空间
    unit32_t nNumOfElements; // 真正的元素个数
    unit32_t nTableSize; // arData大小 初始值时8,当不够用的时候会扩容(成倍扩容)
    unit32_t nNextFreeElement; // [1,2,3,4] 这类数组使用
}

php 生命周期

cli模式的生命周期

使用 Hugo 构建
主题 StackJimmy 设计