目录
前言" >前言
跟踪运行轨迹" >跟踪运行轨迹
词法语法分析->opcode" >词法语法分析->opcode
虚拟机执行opcode" >虚拟机执行opcode
结语" >结语
首页 后端开发 php教程 PHP-Zend发动机剖析之Hello World(二)

PHP-Zend发动机剖析之Hello World(二)

Jun 13, 2016 pm 12:32 PM
echo handler zend

PHP-Zend引擎剖析之Hello World(二)

前言

这一次,我围绕Hello World来展开Zend虚拟机的执行过程。Hello World的PHP版本:


? ? ?echo 'Hello World';

?>


前一篇文章聊到的词法分析阶段就会把上边的脚本分析出一个Token序列:

我们得到一个Token序列:T_OPEN_TAG, T_ECHO, T_CONSTANT_ENCAPSED_STRING, ';', T_CLOSE_TAG。但在Zend虚拟机执行的过程中,是怎么去分析这个Token序列的?

跟踪运行轨迹

我们还是从命令行入手,在$PHPSRC/sapi/cli/php_cli.c中的do_cli函数里边接收了命令行的参数输入(php -f HelloWorld.php表示执行HelloWorld.php文件)。

我们追踪到$PHPSRC/main/main.c里边有php_execute_script的定义,紧接着调用了zend_execute_scripts() ,在zend_execute_scripts的定义里边我们发现了:


?EG(active_op_array) =?zend_compile_file(file_handle, type TSRMLS_CC); zend_execute(EG(active_op_array) TSRMLS_CC);


首先通过zend_compile_file把文件解析成opcode中间代码(这一步会经过词法语法分析),然后用zend_execute执行这个生成的中间代码(这里就是所谓的运行时)。

这里很像C语言的编译方式,先编译成汇编,然后再转成机器码,这里的opcode就类似C语言编译过程中生成的汇编。

还可以延伸出一个思路,因为每次解析PHP文件时,都需要经过词法语法分析得到对应的opcode,其实在脚本文件不变化的时候,生成的opcode也不需要变化,因此为了减少PHP脚本的执行时间,可以把脚本的opcode缓存起来(例如缓存在共享内存里边)。

我给出一个流程图,然后随着这个流程图,看看Zend做了些什么事情:

我们先看看如何编译出opcode的。

词法语法分析->opcode

从上节知道我们通过zend_compile_file(实际上为compile_file())把脚本文件编译出opcode,实际上通过zendparse这个API来编译出opcode的。
PHP的语法解析器是用bison来生成,安装完之后在$PHPSRC/Zend目录运行:
bison -o zend_language_parser.c?zend_language_parser.y
在Zend目录下就会生成语法解析器zend_language_parser.c。而这里的zendparse就是语法解析器里边的yyparse!
我们忽略掉生成的语法解析器,就Hello World的例子来跟踪一下bison的声明文件(我去掉不想关的声明):

start:
top_statement_list???? { zend_do_end_compilation(TSRMLS_C); }
;top_statement_list:
top_statement_list? { zend_do_extended_info(TSRMLS_C); } top_statement { HANDLE_INTERACTIVE(); }
|???? /* empty */
;top_statement:
statement????????????????????????????? { zend_verify_namespace(TSRMLS_C); }
;statement:
unticked_statement { DO_TICKS(); }
|???? T_STRING ':' { zend_do_label(&$1 TSRMLS_CC); }
;unticked_statement:
|???? T_ECHO echo_expr_list ';'echo_expr_list:
echo_expr_list ',' expr { zend_do_echo(&$3 TSRMLS_CC); }
|???? expr???????????????????????? { zend_do_echo(&$1 TSRMLS_CC); }
;

expr:
r_variable???????????????????????? { $$ = $1; }
|???? expr_without_variable????????? { $$ = $1; }
;

expr_without_variable:
|???? scalar??????????????????? { $$ = $1; }

scalar:
|???? common_scalar?????????????? { $$ = $1; }
;

common_scalar:
|???? T_CONSTANT_ENCAPSED_STRING???? { $$ = $1; }
;
登录后复制

语法分析从start开始,自上而下的分析,一个PHP脚本就是对应一个top_statement_list,接着分成每一行一条语句statement,发现echo 'Hello World'是一条unticked_statement(留意一下echo_expr_list的声明,?我们还可以发现语法上是支持echo 'Hello', ' World'的)。最后递归到T_CONSTANT_ENCAPSED_STRING状态就结束了这一行的语法解析。在这里我们忽略掉编译原理在语法分析阶段是怎么去做回溯等等东西,我们关注一下Zend引擎自身的的问题。

在规则后边的块"{}"里边的代码就是用来处理扫描到此规则时的动作,可以看到echo的执行是调用了zend_do_echo函数的。在动作声明的块里边我们看到了$$, $1,$2,$3等,这些对应的就是该条规则里边的返回值,参数1,参数2……,这里的返回值以及参数都是YYSTYPE类型,这个类型在43行里边有定义:#define YYSTYPE znode。znode的定义在zend_compile.h里边:

留意到zend_op这个结构,于是跟踪发现这个就是最后每条语句对应的opcode结构了!!!!

opcode的结构跟汇编有很大的相似之处,一个操作符,两个操作数。

在Zend引擎中,每个opcode主要的东西就是那个handler,一会我们会看到Zend里边是怎么生成这个handler的。到了这里先Hold住一下,回过头,我们看一下Hello World这个例子生成的opcode是什么。

装上vld,然后运行:php -dvld.active=1 HelloWorld.php,我们就可以看到这个PHP文件编译出来的opcode列表了:

可以看到echo这个语句的opcode类型是ECHO,同时return没有返回值,只有一个操作数"Hello World"。

现在经过了语法分析,我们对每条语句都编译出了opcode,Zend就会把它放入一个op_array里边(其实就是一个opcode的列表)。

回过头我们看一下zend_do_echo做了什么事情:

首先通过get_next_op在当前的op_array的最后边生成一条opcode,然后设置其opcode类型是ZEND_ECHO,然后设置它的第一个参数op1,同时标记第二个参数op2是不需要使用的(unused的)。

经过了这么多步骤之后我们得到了一个op_array的列表,这个列表里边的每一条opcode都绑定了自己的类型,接着我们看一下每个opcode节点是如何绑定handler的。

zend_vm_def.h定义了ZEND_ECHO的handler,留意到这里的40,一会需要用到,因为echo的参数可以有几种:常量,变量等等,所以对应着不同的handler

在zend_vm_execute.h定义了opcode对应的所有的handler,我们只关注echo相关的handler,注意到其中的代码:


void zend_init_opcodes_handlers(void) { static const opcode_handler_t labels[] = {//40913行 ZEND_ECHO_SPEC_CONST_HANDLER,//41914行 ZEND_ECHO_SPEC_CONST_HANDLER, ZEND_ECHO_SPEC_CONST_HANDLER, ZEND_ECHO_SPEC_CONST_HANDLER, ZEND_ECHO_SPEC_CONST_HANDLER };


请花费短暂的时间先记住这里的labels以及行数。

发现了获取handler的方法最后边return语句的计算,根据前面说的echo的opcode是40(假设两个参数op1,op2的type都是0),于是乎其对应的handler就是:

zend_opcode_handlers[40*25+0*5+0*5] =?zend_opcode_handlers[1000] =?labels[1000] =?ZEND_ECHO_SPEC_CONST_HANDLER(怎么来的?因为:41914行-40913行-1=1000)。

虚拟机执行opcode

前边我们已经解释了zend_compile_file把一个脚本编译成一个opcode的list:


?EG(active_op_array) =?zend_compile_file(file_handle, type TSRMLS_CC); zend_execute(EG(active_op_array) TSRMLS_CC);


在这之后,Zend引擎用zend_execute执行返回的opcode。

我们定位到了zend_execute最后执行到Zend/zend_vm_execute.h的337行:

可以看到,虚拟机执行的时候会循环当前的opcode列表,然后调用每一行opcode的handler,根据handler返回值确定下一步做啥(例如函数调用等,以后再展开)。

在这篇文章中我们只关注跟Hello World相关的东西,我们前边知道echo的handler是ZEND_ECHO_SPEC_CONST_HANDLER,通过最后的定位你会发现其调用了:


zend_write = (zend_write_func_t) utility_functions->write_function;


这里的utility_functions里边包含了一些基础的handler,每个sapi接入层自己修改了这里的基础函数指针,例如在命令行模式下,最后调用到了

sapi_cli_single_write:

从源码中,我们看到了最后的写操作就是调用了write/fwrite写入到标准输出流(也即是终端屏幕上)。

结语

最后根据前边的过程,再展开一下流程图就是:

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它们
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

五个精选的Go语言开源项目,带你探索技术世界 五个精选的Go语言开源项目,带你探索技术世界 Jan 30, 2024 am 09:08 AM

在当今科技快速发展的时代,编程语言也如雨后春笋般涌现出来。其中一门备受瞩目的语言就是Go语言,它以其简洁、高效、并发安全等特性受到了许多开发者的喜爱。Go语言以其强大的生态系统而著称,其中有许多优秀的开源项目。本文将介绍五个精选的Go语言开源项目,带领读者一起探索Go语言开源项目的世界。KubernetesKubernetes是一个开源的容器编排引擎,用于自

Go语言开发必备:5个热门框架推荐 Go语言开发必备:5个热门框架推荐 Mar 24, 2024 pm 01:15 PM

《Go语言开发必备:5个热门框架推荐》Go语言作为一门快速、高效的编程语言,受到越来越多开发者的青睐。为了提高开发效率,优化代码结构,很多开发者选择使用框架来快速搭建应用。在Go语言的世界中,有许多优秀的框架可供选择。本文将介绍5个热门的Go语言框架,并提供具体的代码示例,帮助读者更好地理解和使用这些框架。1.GinGin是一个轻量级的Web框架,拥有快速

PHP实现框架:Zend Framework入门教程 PHP实现框架:Zend Framework入门教程 Jun 19, 2023 am 08:09 AM

PHP实现框架:ZendFramework入门教程ZendFramework是PHP开发的一种开源网站框架,目前由ZendTechnologies维护,ZendFramework采用了MVC设计模式,提供了一系列可重用的代码库,服务于实现Web2.0应用程序和Web服务。ZendFramework深受PHP开发者的欢迎和推崇,拥有广泛

如何在Zend框架中使用ACL(Access Control List)进行权限控制 如何在Zend框架中使用ACL(Access Control List)进行权限控制 Jul 29, 2023 am 09:24 AM

如何在Zend框架中使用ACL(AccessControlList)进行权限控制导言:在一个Web应用程序中,权限控制是至关重要的一项功能。它可以确保用户只能访问其有权访问的页面和功能,并防止未经授权的访问。Zend框架提供了一种方便的方法来实现权限控制,即使用ACL(AccessControlList)组件。本文将介绍如何在Zend框架中使用ACL

PHP中echo关键字的作用和使用方法详解 PHP中echo关键字的作用和使用方法详解 Jun 28, 2023 pm 08:12 PM

PHP中echo关键字的作用和使用方法详解PHP是一种广泛使用的服务器端脚本语言,它在网页开发中被广泛应用。而echo关键字是在PHP中用于输出内容的一种方法。本文将详细介绍echo关键字的作用和使用方法。作用:echo关键字的主要作用是将内容输出到浏览器。在网页开发中,我们需要将数据动态地呈现到前端页面上,这时就可以使用echo关键字将数据输出到页面上。e

Laravel开发:如何使用Laravel Echo和Pusher实现WebSockets通信? Laravel开发:如何使用Laravel Echo和Pusher实现WebSockets通信? Jun 13, 2023 pm 05:01 PM

Laravel是一个流行的PHP框架,具有高度可扩展性和高效性,它提供了很多强大的工具和库,让开发者可以快速构建高质量的Web应用程序。其中,LaravelEcho和Pusher是两个非常重要的工具,通过它们可以很容易地实现WebSockets通信,本文将详细介绍如何在Laravel应用程序中使用这两个工具。什么是WebSockets?WebSockets

使用Golang的Web框架Echo框架实现分布式任务调度 使用Golang的Web框架Echo框架实现分布式任务调度 Jun 24, 2023 am 11:49 AM

随着互联网的发展和信息技术的进步,大数据时代已经来临,数据分析、机器学习等领域也得到了广泛的应用。在这些领域中,任务调度是一个不可避免的问题。如何实现高效的任务调度,对于提高效率至关重要。在本篇文章中,将介绍如何使用Golang的Web框架Echo框架实现分布式任务调度。一、介绍Echo框架Echo是一个高性能、可伸缩、轻量级的GoWeb框架。它基于HTT

探索Go语言框架:5个不容错过的选择! 探索Go语言框架:5个不容错过的选择! Feb 19, 2024 pm 02:29 PM

Go语言作为一种快速、高效的编程语言,一直受到程序员的青睐。在Go语言的生态系统中,框架扮演着至关重要的角色,帮助开发者更快速地构建应用程序。本文将介绍五个Go语言框架,让你了解其特点和用法。1.Gin框架Gin框架是一个轻量级的Web框架,具有快速、高性能的特点。使用Gin框架可以快速构建RESTfulAPI和Web应用程序。以下是一个简单的示例代码:

See all articles