2 PHP的生命周期

未匹配的标注

1 请求的生命周期

参考:

PHP的生命周期

PHP7内核学习笔记 - 请求的生命周期

  1. php_module_startup 模块初始化阶段(注册内部扩展、加载外部扩展、启动附加的PHP扩展、启动各个模块、禁用php.ini里面的禁用函数和类)
  2. php_request_startup 请求初始化阶段 (初始化输出handler的栈,并把OG(FLAGS置为使用)、调用zend_activate(初始化GC、初始化编译器、初始化执行器、初始化扫描器)、对信号进行处理、设置超时时间、初始化相关全局变量)
  3. php_execute_script 脚本执行阶段(读取的PHP代码进行解析,先词法解析并使词法分析指针指向第一个位置,解析成token,然后语法解析,生成抽象语法树,然后通过对抽象语法树进行遍历生成opcode,opcode在虚拟机上执行得到对应的结果)
  4. php_request_shutdown 请求关闭阶段(调用各个模块中注册的关闭函数和析构函数、将输出缓冲器重内容输出、调用所有扩展注册的钩子rshutdown函数、销毁request相关的全局变量、关闭编译器和执行器、还原ini配置)(完成这些工作,fpm模式会循环等待请求到来)
  5. php_module_shutdown 模块关闭阶段(调用sapi_flush() 然后进行销毁所有模块、全局变量、关闭扩展、销毁ini对应的HashTable、关闭ini config、关闭内存管理器、关闭输出output、析构垃圾回收)

2 Opcodes

参考:深入理解PHP原理之Opcodes

在 php_execute_script 阶段会生成 Opcodes,Opcode 是一种 PHP 脚本编译后的中间语言,就像 Java 的 ByteCode ,或者 .NET 的 MSL,举个例子,比如你写下了如下的PHP代码:

 <?php
   echo "Hello World";
   $a = 1 + 1;
   echo $a;
?>

PHP 的语言引擎 Zend 执行这段代码会经过如下 4 个步骤:

  1. Scanning(Lexing) ,将 PHP 代码转换为语言片段(Tokens)。
  2. Parsing, 将 Tokens 转换成简单而有意义的表达式。
  3. Compilation,将表达式编译成 Opocdes。
  4. Execution,顺次执行 Opcodes,每次一条,从而实现 PHP 脚本的功能。

2.1 Lexing

Lex 就是一个词法分析的依据表。 Zend/zend_language_scanner.c会根据Zend/zend_language_scanner.l(Lex文件),来输入的 PHP代码进行词法分析,从而得到一个一个的“词”。PHP4.2 开始提供了一个函数叫 token_get_all,这个函数就可以讲一段 PHP 代码 Scanning 成 Tokens。

<?php

$phpcode = '<?php echo "Hello World"; $a = 1 + 1; echo $a; ?>';
$tokens  = token_get_all($phpcode);
print_r($tokens);

// foreach ($tokens as $key => $token) {
//     $tokens[$key][0] = token_name($token[0]);
// }
// print_r($tokens);
?>

token_name() 将 Token ID 转换为 Token 对应码。

Array
(
    [0] => Array
        (
            [0] => 321   // Token ID
            [1] =>       // 源码中的原来的内容

            [2] => 1     // 该词在源码中是第几行
        )
    [1] => Array
        (
            [0] => 379
            [1] => <?php
            [2] => 2
        )
    [2] => Array
        (
            [0] => 382
            [1] =>

            [2] => 2
        )
    [3] => Array
        (
            [0] => 328
            [1] => echo
            [2] => 3
        )
    [4] => Array
        (
            [0] => 382
            [1] =>
            [2] => 3
        )
    [5] => Array
        (
            [0] => 323
            [1] => "Hello World"
            [2] => 3
        )
    ......
)

2.2 Parsing

Parsing 首先会丢弃 Tokens Array 中的多于的空格,然后将剩余的 Tokens 转换成一个一个的简单的表达式:

  1. echo a constant string

  2. add two numbers together

  3. store the result of the prior expression to a variable

  4. echo a variable

如果有安装 vld 扩展,可以使用 vld 来查看opcodes,如下:

2 PHP执行阶段

字段说明:

  • Branch analysis from position 这条信息多在分析数组时使用。
  • Return found 是否返回,这个基本上有都有
  • filename 分析的文件名
  • function name 函数名,针对每个函数VLD都会生成一段如上的独立的信息,这里显示当前函数的名称
  • number of ops 生成的操作数
  • compiled vars 编译期间的变量,它是一个缓存优化(以 IS_CV 标记)。

2.3 Compilation

Compilation 会把表达式编译成 Opcodes。

在 PHP 实现内部,opcode 由如下的结构体表如下:

struct _zend_op {
    opcode_handler_t handler;  // 执行该 opcode 时调用的处理函数
    znode result;              // 存放结果
    znode op1;                 // 操作数1
    znode op2;                 // 操作数2
    ulong extended_value;      // 扩展
    uint lineno;
    zend_uchar opcode;         // opcode代码
};

opcodes 保存在 op_array 中,其内部存储的结构如下:

struct _zend_op_array {
    ......    
    zend_op *opcodes; // opcode 数组
}

2.4 Execution

Zend 引擎调用 zend_executor 来顺序执行 Opcodes,每次一条,从而实现PHP脚本的功能。

如果文章有帮到你的话,别忘了点赞收藏噢:smile:

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~