PHP 7底层设计与源码实现(txt+pdf+epub+mobi电子书下载)


发布时间:2020-05-25 05:11:30

点击下载

作者:陈雷 等

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

PHP 7底层设计与源码实现

PHP 7底层设计与源码实现试读:

前言

为什么要写这本书

工作了10多年,一直没有想过要写一本书。一年前,团队的张晶晶同学建议,我们能不能组织大家每天一起学习和讨论,这样也许会成长得更快。当时我感觉这是一个非常好的建议,但是也有隐隐的担忧,因为根据经验,多个人一起学习很难长期持续。但是看到晶晶同学渴望的眼神,我决定试一试,于是我建立了一个学习圈,发给团队的同事们,他们可以自愿参加,学习的时间是每天早上8:50~10:30,正好不影响工作,但是早晨要提前100分钟来公司,学习的内容是研读工作中使用的LNMP源码,首先学习和研究的就是PHP 7的源码。出乎意料,团队几乎所有的同事都参加了这个学习圈,包括实习生。

而这个学习圈一直坚持到现在,真是风雨无阻,大家已经习惯了按时一起讨论、研究,遇到问题时大家各自查找相关资料,顿悟难题时大家欣喜若狂。最令我欣喜的是,学习圈的同事们的快速成长,后来我们这个学习圈加入了滴滴公司的学习圈,得到了更多的资源支持。在学习过程中,我们每天都会记录日志,时间久了,发现得到了一个丰富的知识库,于是大家提议,写一本书,这样我们决定写一本PHP 7源码学习和研究的书,希望我们整理的资料能够帮助有意愿研究PHP 7源码的读者。

做了这个决定后,学习圈里的李长林、李志、黄桃、肖涛、王坤、季伟滨、朱栋和我一起编写了这本书,大家在工作之外,每天写书到深夜,周末从此是路人,经过半年的编写和校对,终于完成了这本书。我们希望给那些想要了解PHP 7底层实现的读者一些启发,帮助更多的人理解PHP 7的实现,也希望能够传播这种坚持学习的精神。读者对象

·PHP工程师

·对PHP内部实现感兴趣的读者

·有C语言基础的读者

·其他对PHP语言感兴趣的人如何阅读本书

本书内容上分为四篇,共计14章内容。

第一篇(第1~2章)介绍PHP 7的概况和源码整体框架。其中,第1章主要介绍PHP的历史和PHP 7的新特性,以及研读源码的方法,第2章介绍PHP 7源码的整体框架,包括各目录的源码和功能。

第二篇(第3~6章)详细介绍PHP 7的变量实现。其中,第3章介绍基本变量zval的结构,第4章详细阐述字符串zend_string的实现,第5章介绍PHP 7中的核心结构HashTable的实现,也就是数组的实现,第6章介绍类和对象的实现。

第三篇(第7~11章)介绍PHP 7核心的实现。其中,第7章主要的内容是PHP 7的生命周期,第8章介绍生命周期的模块初始化阶段中配置文件的解析,第9章主要阐述内存管理的原理和实现,第10章介绍词法和语法分析的实现,第11章介绍核心Zend虚拟机的实现。

第四篇(第12~14章)主要介绍PHP 7中语法的实现。其中,第12章主要介绍基本语法的实现,第13章主要介绍函数的实现,第14章主要讲解扩展的实现。

其中,第3章、第4章、第5章和第14章为本书的重点章节,如果你没有充足的时间完成全书的阅读,可以选择性地进行重点章节的阅读。如果你是一位有着一定经验的资深人员,本书可能会是一本不错的案头书。然而,如果你是一名初学者,在开始本书阅读之前,建议先进行一些C语言和PHP的基础理论知识的学习。勘误和支持

由于笔者的水平有限,编写时间仓促,书中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果你有更多的宝贵意见,欢迎加入我们,访问我们团队的技术网站https://segmentfault.com/u/php7internal进行专题讨论,我们会尽量在线上为读者提供满意的解答。同时,你也可以通过微博@PHP7内核,或者邮箱cltf@163.com联系到我们,期待能够得到你们的真挚反馈,在技术之路上互勉共进。致谢

感谢卢红波、伍星两位工作导师的支持,前者是我在滴滴公司的领导,也是我管理和技术上的导师,后者是我职业生涯的启蒙导师,一直引领我前进到现在。

感谢Swoole团队韩天峰、王晶两位兄弟的指导和支持,他们在技术上给了我非常多的指点。

感谢李长林、李志、黄桃、肖涛、王坤、季伟滨、朱栋7位兄弟在学习和研究过程中的陪伴和合作,本书是几位兄弟共同合作的结晶。特别致谢

最后,我要特别感谢我的太太梦云、儿子和女儿,为写作这本书,我牺牲了很多陪伴他们的时间,但也正因为有了他们的付出与支持,我才能坚持写下去。

同时,感谢我的父母、岳父岳母,特别是我的父母,不遗余力地帮助我们照顾儿女,有了他们的帮助和支持,我才有时间和精力去完成写作工作。

另外要感谢我团队的兄弟们,感谢他们的坚持,为他们的成长点赞!

最后要重点感谢高婧雅编辑,得益于她的耐心审稿,宝贵的建议以及用心的修改,才进一步提升了本书的质量。

谨以此书献给我最亲爱的家人,以及众多热爱PHP的朋友!陈雷第1章PHP 7概况

本章将会介绍PHP简史与新特性,以及PHP7安装和调试方法。同时还会介绍研究PHP 7源码的一些工具,希望对读者阅读和研究PHP 7源码有一定的帮助。1.1 PHP简史与新特性

PHP是一种跨平台开源语言,也是迄今为止最流行的Web开发语言,全球有超过80%的网站由PHP驱动。PHP自1994年由Rasmus Lerdorf创建以来已走过20多年,经历了6个大版本的更迭。下面就来了解PHP简史与PHP 7带来的新特性。

1.PHP简史

PHP最初是作为工具包出现的,作者Rasmus Lerdorf为了在自己的网站上追踪访客开发了PHP的雏形。而后随着功能的增多,作者发布了第一个完整的版本并称之为Personal Home Page Tools。1996年Rasmus Lerdorf发布了2.0版本,这是一个相对完善的版本,不仅可以访问数据库还可以把PHP代码嵌入到HTML页面中。2.0版本吸引了很多开发者,其中包括后来Zend引擎的核心开发者Zeev Suraski和Andi Gutmans。Zeev和Andi加入之后,重写了代码,带来了PHP 3.0。

PHP 3.0强大的可扩展性吸引了更多的开发者加入并提交新的模块。从这个版本开始,PHP被重命名为PHP:Hypertext Preprocessor。2000年夏初,4.0版本发布,Zend引擎正式登场,相比PHP 3.0,最高可以有近10倍的性能提升。此外还支持了更多的Web服务器。这个时候的PHP已经是很流行的编程语言了,但是相对于其他语言还缺乏一些关键特性,例如面向对象、异常处理等。

2004年7月,标志性的PHP 5正式发布,Zend引擎升级到2.0。PHP 5的最大特点是引入了面向对象的全部机制。另外引进了类型提示和异常处理机制,能更有效地处理异常和避免错误。2005年PHP社区发起了PHP 6的项目,主要的目的是为PHP引擎增加Unicode支持,但是由于种种原因,项目最终被取消。这个项目虽然被取消了,但是大量的功能陆续都添加进了PHP 5.x版本,例如命名空间、匿名函数、闭包等特性。

2015年夏天,备受瞩目的PHP 7发布了第一个Alpha版本。之后,经过大概3个Beta版本和8个RC版本,2016年1月PHP 7正式发布。PHP 7是PHP一个非常重要的版本,相对于PHP 5.x版本,有着非常大的革新,尤其是在性能方面。如果读者的网站使用的是PHP 5.x,那么使用PHP 7后几乎将无成本地得到一倍的性能提升。感谢开发者!

下面我们来测试一下PHP 7性能到底提升了多少。本地环境下以相同的编译参数分别安装PHP 5.5.38、PHP 7的第一个正式版本7.0.2和7.1.0版本,在CLI模式下运行PHP源码中的基准测试脚本。

1)测试环境:本地搭建的vagrant虚拟机,操作系统CentOS 7,单核CPU 2.00GHz,内存1GB。

2)基准测试指标:

·Time——执行时间,以秒为单位;

·%rel,gain——相对于上一版本节省的执行时间;

·%abs,gain——与PHP5.5.38相比,脚本节省的执行时间。

测试结果如表1-1所示。表1-1 测试结果

由上边的测试结果可以看出来,PHP 7.1.0的基准性能几乎是PHP 5.5.38的3倍左右,在开启了opcache的情况下更是达到了4.4倍之多,这是一个非常显著的提升。这些性能提升是如何做到的呢?本书后续的章节将一一介绍。注意 这里的测试是纯CPU的基准测试,5次运行取平均值,不包括其他方面的测试,在实际的项目或者其他运行环境下可能有所差异。

2.PHP 7新特性

PHP 7除了在性能方面有极大提升外,还添加了很多新的特性,如太空船操作符、标量类型声明、返回值的类型声明、全局的throwable接口、抽象语法树等,下边分别介绍。(1)太空船操作符

太空船操作符用于比较两个表达式。例如,当$a小于、等于或大于$b时,它分别返回-1、0或1。比较的原则沿用PHP的常规比较规则进行。 1; // 0echo 1 <=> 2; // -1echo 2 <=> 1; // 1// 浮点数echo 1.5 <=> 1.5; // 0echo 1.5 <=> 2.5; // -1echo 2.5 <=> 1.5; // 1 // 字符串echo "a" <=> "a"; // 0echo "a" <=> "b"; // -1echo "b" <=> "a"; // 1(2)标量类型声明和返回值的类型声明

PHP 7可以对下面几种类型的参数做声明:字符串(string)、整型(int)、浮点型(float)以及布尔型(bool)。注意参数类型声明不受制于默认模式和严格模式。默认模式下,当传入的参数不符合声明类型时,会首先尝试转换类型;而严格模式下,则直接报错。

例如下面的代码:

当注释掉第二行代码,程序才可以正常运行——PHP会首先尝试把'3.1'转为int型的3,然后再执行。(注意:这里的类型转换仅受制于可转换的类型,例如不能把'a'转为int型。)但是当开启严格模式后,代码会直接报错。因为函数的参数被声明为int型,但是传入的参数中包含一个string型和一个float型。

修改上面代码,再来看看返回值类型受限制的情况:

这段代码额外声明了返回值的类型为int型。如果返回值的类型不是int型,在默认模式下,PHP会首先尝试转换返回值的类型为int型,如果不能转换,则会直接报错。

PHP 7.1对函数返回值的声明做了扩充,可以定义其返回值为void,无论是否开启严格模式,只要函数中有“return;”以外的其他return语句都会报错。

注意:参数类型不可以是void。

PHP 7.1.0对参数类型和返回值类型还有进一步的支持,其类型可以是可空类型,在参数或返回值类型声明前边加上“?”,表示返回值要么是null,要么是声明的类型:

在PHP 7之前,人们经常会写这样的代码:

PHP 7提供了一个新的语法糖“??”,如果变量存在且值不为null,它会返回自身的值,否则返回它的第二个操作数。可以这样改写代码:

当代码中有连续的三元运算符的时候还可以像下边这样写:

看起来是不是简化了很多?(4)常量数组

在PHP 7之前是无法通过define来定义一个数组常量的,PHP 7支持了这个操作:

在PHP 7之前,如果要导入一个namespace下的多个class,我们需要这样写:

在PHP 7中支持批量导入:

在PHP 7之前,如果代码中有语法错误,或者fatal error时,程序会直接报错退出,但是在PHP 7中有了改变。PHP 7实现了全局throwable接口,原来的Exception和部分Error实现了该接口。这种Error可以像Exception一样被第一个匹配的try/catch块捕获。如果没有匹配的catch块,则调用异常处理函数进行处理。如果尚未注册异常处理函数,则按照传统方式处理(fatal error)。

Error类并非继承自Exception类,所以不能用catch(Exception$e){...}来捕获Error。可以用catch(Error$e){...},或者通过注册异常处理函数(set_exception_handler())来捕获Error:

在PHP 7之前,我们需要动态地给一个对象添加方法时,可以通过Closure来复制一个闭包对象,并绑定到一个$this对象和类作用域:num + 1;};$test = $f->bindTo(new Test, 'Test');echo $test();

在PHP 7中新添加了Closure::call(),可以通过call来暂时绑定一个闭包对象到$this对象并调用它:num + 1;};echo $f->call(new Test);(8)intdiv函数

PHP 7还增加了一个新的整除函数,在代码中不需要再手动转了:

我们知道可以通过list来实现解构赋值,如下:

PHP 7.1.0对其做了进一步的优化,可以将其写成如下方式:

注意:这里的[]并不是数组的意思,只是list的简略形式。

除了上文这些,PHP7还有很多其他的改变和特性。例如,foreach遍历数组时不再修改内部指针、移除了ASP和script PHP标签、移除了$HTTP_RAW_POST_DATA、匿名类、类常量可见性等,读者可以自行尝试。1.2 PHP 7安装和调试

学习了PHP 7的新特性后,再来了解PHP 7的编译安装和调试方式。1.2.1 编译安装

以Linux环境为例来进行安装。

首先下载PHP 7。在http://php.net/releases/上能够获取各个版本的PHP源码和修改记录(建议对PHP源码感兴趣的读者关注一下修改记录,以了解PHP源码开发者的开发思路)。本书以7.1.0版本为例,下载源码包并编译安装(源码包URL为http://cn2.php.net/distributions/php-7.1.0.tar.gz)。$ wget http://cn2.php.net/distributions/php-7.1.0.tar.gz$ tar -zxvf php-7.1.0.tar.gz$ cd php-7.1.0$ ./configure --prefix=$HOME/php7/book/php-7.1.0/output --enable-fpm

注意:默认情况下,make install命令会把执行文件和库文件安装到/usr/local/bin和/usr/local/lib目录。为了后续研究方便,我们使用--prefix将PHP 7安装到当前目录的output目录下,同时安装php-fpm。

执行make命令:$ make && make install$ cd output$ lsbin etc include lib php sbin var

到此,完成了php-7.1.0的编译安装,生成的可执行文件php-fpm在sbin中,其他部分在bin目录下:pear peardev pecl phar phar.phar php php-cgi php-config phpdbg phpize

其中,php是CLI模式下的PHP脚本执行程序。

PEAR(PHP Extension and Application Repository,PHP扩展与应用库),是PHP官方开源类库,可以使用pear list列出所有已经安装的包。通过pear install可以安装需要的包。

PECL是PHP的扩展库,可以通过PEAR的Package Manager的管理方式来下载和安装扩展代码。

以安装yaconf为例:$ ./pecl install yaconf...install ok: channel://pecl.php.net/yaconf-1.0.6configuration option "php_ini" is not set to php.ini locationYou should add "extension=yaconf.so" to php.ini

php-config是输出PHP编译信息的辅助命令。

phpdbg是一个轻量级,具有丰富功能的调试平台。PHP 5.4以上版本支持,比如可以使用它查看opcode:$ phpdbg -p* t.phpfunction name: (null)L1-5 {main}() L2 #0 ASSIGN $a 1L3 #1 ECHO $aL5 #2 RETURN 1

phpdbg的其他功能可以通过phpdbg--help查看。

phpize命令用来动态安装扩展,如果在安装PHP时没有安装某个扩展,可以通过这个命令随时安装。1.2.2 使用GDB调试PHP 7

GDB是一个由GNU开源组织发布的、UNIX/Linux操作系统下的、基于命令行的、功能强大的程序调试工具。当程序发生coredump,通过GDB可以从core文件中复现场景,定位问题。

这里演示一下如何通过GDB来调试PHP程序。首先编写一段简单的代码test.php:

下面开始进行GDB调试,运行gdb php:$ gdb php (gdb)

使用b命令在main函数入口增加断点:(gdb) b mainBreakpoint 1 at 0x797df0: file /home/vagrant/php7/php-7.1.0/sapi/cli/php_cli.c, line 1181.

使用r命令运行test.php:(gdb) r test.phpStarting program: /home/vagrant/php7/php-7.1.0/output/bin/php test.php[Thread debugging using libthread_db enabled]Breakpoint 1, main (argc=2, argv=0x7fffffffe1b8) at /home/vagrant/php7/php-7.1.0/sapi/cli/php_cli.c:11811181 {

从上面的输出中可以看到,代码执行在main函数处停止。接下来,使用n命令执行下一步:(gdb) n1288 memcpy(ini_entries + ini_entries_len + len, "\n\0", sizeof("\n\0"));

使用p命令查看某个变量的信息:(gdb) p ini_entries$1 = 0x10c2150 "html_errors=0\nregister_argc_argv=1\nimplicit_flush=1\noutput_buffering=0\nmax_execution_time=0\nmax_input_time=-1\n"(gdb)

如果出现,是由于GCC编译器在编译过程中默认使用-O2优化选项所致,使用-O0选项可以关闭编译器的优化。在这里,通过修改MakeFile禁止编译器优化。查找CFLAGS_CLEAN:CFLAGS_CLEAN = -I/usr/include -g -O2 -fvisibility=hidden -DZEND_SIGNALS $(PROF_FLAGS)

将其中的-O2改为-O0,然后执行make clean&&make&&make install。

另外,对于在php-fpm下运行的PHP程序如何调试呢?

在本地建立一个名为www.local的本地项目,来演示php-fpm运行模式下的调试:$ mkdir /data/htdocs/www.local$ touch /data/htdocs/www.local/index.php

略过Nginx的配置过程,着重看一下php-fpm的配置:$ vim ~/php7/book/php-7.1.0/output/conf/php-fpm.conf// 添加以下配置项[www.local]pm=staticpm.max_children=1pm.start_servers=1pm.min_spare_servers=1pm.max_spare_servers=1

这段配置设定php-fpm的运行模式为static,其最大进程数为1、启动进程数为1、最大和最小的空余进程数为1。为什么要这么设定呢?这是为了保证GDB调试的进程一定是我们当前访问的进程。

完成Nginx和php-fpm的配置以后,重启这两个服务,可以看到www.local的项目只有一个进程,其pid为4459(后文还会用到该pid):$ systemctl restart php-fpm.service$ systemctl restart nginx.service$ ps aux|grep php-fpmroot 4458 0.0 0.8 343816 4056 ? Ss 13:11 0:00 php-fpm: master process (/home/vagrant/php7/book/php-7.1.0/output/conf/php-fpm.conf)www 4459 0.0 1.0 344012 5140 ? S 13:11 0:00 php-fpm: pool www.local

接下来开始调试,执行如下命令:$ gdb php(gdb) attach 4459Attaching to process 4459Reading symbols ...

如果没有报错,当前GDB已经attach到www.local的php-fpm进程上了。新开一个终端2执行“curl www.local”或者使用浏览器访问www.local,然后回到终端1,就可以和CLI模式一样进行调试了。

在学习和研究PHP 7的过程中,经常需要查看opcodes,除了上文提到的phpdbg可以查看,另外还有一个vld扩展也非常好用,下面介绍下vld扩展。1.2.3 vld扩展

PHP代码的执行实际上是在执行代码解析后的各种opcode。通过vld扩展可以很方便地看到执行过程中的opcode。扩展可以从https://github.com/derickr/vld下载安装,下面是安装示例:$ git clone https://github.com/derickr/vld.git$ cd vld$ /home/vagrant/php7/book/php-7.1.0/output/phpize$ ./configure --with-php-config=/home/vagrant/php7/book/php-7.1.0/output/php-config --enable-vld$ make && make install

到这里,扩展就安装完成了,接下来只需要在PHP的配置文件php.ini中启用该扩展即可:extension=vld.so

然后执行下边的命令:$ php -m | grep vld

看到有vld的输出,即表示扩展启用成功。

现在来写一段简单的PHP代码,看看生成的opcode:

保存这段代码为vld.php,然后在命令行执行:$ php -dvld.active=1 vld.phpFinding entry pointsBranch analysis from position: 0Jump found. (Code = 62) Position 1 = -2filename: /home/vagrant/vld.phpfunction name: (null)number of ops: 5compiled vars: !0 = $strline #* E I O op fetch ext return operands------------------------------------------------------------------- 3 0 E > ASSIGN !0, 'hello+php7' 4 1 INIT_FCALL 'var_dump' 2 SEND_VAR !0 3 DO_ICALL 6 4 > RETURN 1 branch: # 0; line: 3- 6; sop: 0; eop: 4; out1: -2 path #1: 0,string(10) "hello php7"

从上边的输出可以看到这段代码一共有5个opcode。

vld扩展有下边几个参数。

1)vld.active:是否在执行PHP的同时激活vld——1激活,0不激活(默认不激活)。

2)vld.execute:是否输出程序的执行结果——1输出,0不输出(默认输出)。

3)vld.verbosity:显示更详细的opcode信息,开启后可以显示每个opcode的操作数的类型等信息。

例如:3 0 E > ASSIGN OP1[IS_CV !0 ] OP2[IS_CONST (0) 'hello+php7' ]

4)vld.skip_prepend:是否跳过php.ini配置文件中auto_prepend_file配置项指定的文件,默认为0,即不跳过包含的文件。vld.execute为0时有效;

5)vld.skip_append:是否跳过php.ini配置文件中auto_append_file指定的文件,默认为0,即不跳过包含的文件。vld.execute为0时有效;

6)vld.format:是否启用自定义输出格式——1启用,0不启用(默认不启用);

7)vld.col_sep:自定义输出格式间隔符,vld.format为1时有效;

8)vld.save_dir:指定文件输出的路径,默认路径为/tmp;

9)vld.save_paths:控制是否输出dot语言文件,默认为0,表示不输出;

10)vld.dump_paths:控制是否输出分支及路径信息——1输出,0不输出(默认输出)。小知识

dot是一种描述图形的语言,可以由Graphviz工具包来绘制dot描述的图形。vld扩展可以直接通过命令来生成dot脚本,现以下面的代码来演示一下:$ vim vld.phpnum = $num; } public function increase(){ return $this->num + 1; }}$a = new Test(10);var_dump($a->increase());

在命令行执行以下命令:$ php -dvld.active=1 -dvld.save_paths=1 vld.php$ ll /tmp-rw-rw-r-- 1 vagrant vagrant 791 11月 30 02:41 paths.dot$ dot -Tpng /tmp/paths.dot -o paths.png

这样就可以生成一张调用图片,如图1-1所示。图1-1 vld生成的图片

介绍完PHP 7的安装和调试后,下面介绍几种不同平台上的代码阅读工具,基于它们,可以有效地提高源码阅读的效率。1.3 PHP 7源码阅读工具介绍

在研究PHP 7源码之前,我们首先要掌握学习源码的方法论。首先是阅读工具,本章会介绍Windows下的Source Insight、Mac下的Understand以及Linux下的Vim+Ctags,方便读者根据自己的操作系统选择不同的阅读工具。1.3.1 Source Insight

Windows环境下有一款功能强大的IDE:Source Insight,内置了C++代码分析功能;同时还能自动维护项目内的符号数据库,使用非常方便。安装过程这里不再赘述,下边来看看具体的使用过程。

安装完成以后,打开后的界面如图1-2所示。图1-2 Source Insight示意图

首先来创建一个工程,点击Project,选择New Project命令,在弹出的窗口中输入工程名称和工程存档路径,如图1-3所示。图1-3 创建一个工程

填写好后,点击OK按钮,在接下来的窗口中选择代码的目录,如图1-4所示。图1-4 选择代码的目录

选择完毕后继续点击OK按钮,在接下来的窗口右侧点击Add All按钮,在选择后弹出的窗口中勾选recursively add lower sub-directories,点击OK按钮,IDE开始扫描目录并添加文件,扫描完成后会在下边的窗口中显示添加到工程的文件。此时,点击Close按钮完成工程创建,如图1-5所示。图1-5 完成工程创建

至此,工程就创建完了。点击工具栏上的Project Window按钮调出目录树就可以阅读代码了。如图1-6所示,可以看到左侧是代码中的符号列表,中间是编辑器,右侧是目录树。图1-6 阅读代码

Source Insight可以非常方便地显示函数之间的调用关系,点击Views→Relation Window命令,可以看到界面右侧出现一个新的窗口,在编辑窗口点击某一个函数,即可在调用关系窗口中显示函数的调用关系,如图1-7所示。图1-7 显示函数的调用关系

关于Source Insight的介绍就先到这里。读者可以自己安装体验一下其强大的功能。下面介绍下Mac环境下的阅读工具Understand。1.3.2 Understand

Understand是Mac平台下一款功能强大的IDE,具备代码依赖、图形化等实用功能。安装过程这里也不再赘述了,下边来看看具体的使用过程。

Understand打开后如图1-8所示。图1-8 Understand示意图

点击New Project来创建一个工程,在弹出的窗口中输入工程名称和工程存档路径,然后点击Next按钮,如图1-9所示。图1-9 创建一个工程

接下来选择项目代码的语言,这里选择C++和Web即可。C++后边的选项表示第一次代码分析的模式,然后继续下一步,如图1-10所示。图1-10 选择项目代码语言

这一步是确定如何导入文件和设置,如果读者使用Visual Studio或者CMake,那么选择对应的选项即可。这里选择第一个Add source files and directories manually单选按钮,然后继续下一步,如图1-11所示。图1-11 确定如何导入文件和设置

接下来选择要导入的文件目录,点击Add a Drirectory选项卡,在弹出来的窗口中选择目录和文件类型,可以选择要包含的多种文件类型到项目中,记得勾选Include subdirectories复选框,然后继续下一步,如图1-12所示。图1-12 选择要导入的文件目录

之后一个项目就创建完了。Understand会自动找到对应目录下的文件开始进行代码分析,如图1-13所示。图1-13 代码分析

Unserstand也可以为代码分析生成其调用关系图,如图1-14所示。

在某个函数上右击,选择Graphical Views→Declaration命令,可以看到该函数的调用关系,如图1-15所示。图1-14 生成调用关系图1-15 查看函数的调用关系

Understand还有很多非常有用的功能,在这里就不再一一列举了,大家可以自己实践一下。接着来介绍一下Linux下强大的代码阅读工具Vim。1.3.3 Vim+Ctags

Linux环境下可以使用Vim+Ctags来阅读代码。Ctags是Vim下阅读代码的一个辅助工具,可以生成函数、类、结构体、宏等语法结构的索引文件,它的使用也非常简单,如下:$ yum install ctags // Ubuntu 下使用 apt-get install ctags$ cd /home/vagrant/php7/book/php-7.1.0/$ ctags –R *

这一步,表示给当前目录下所有的文件(包含子文件)生成索引文件,执行完后可以看到当前目录下生成了一个tags文件:$ ll tags-rw-rw-r-- 1 vagrant vagrant 7.2M 11月 26 04:17 tags$ pwd/home/vagrant/php7/book/php-7.1.0/

打开Vim的配置文件将tags文件的目录添加进去:$ vim ~/.vimrcset tags=/home/vagrant/php7/book/php-7.1.0/tags

到这一步就完成了简单的配置。当使用Vim打开该项目的时候,Vim会自动加载其索引文件,使用Ctrl+]和Ctrl+O快捷键就可以方便地跳转和跳回了。

Vim+Ctags还有很多强大的功能,限于篇幅就不再一一列举了。1.4 本章小结

本章主要对PHP的历史以及PHP 7的新特性进行了介绍,同时介绍了PHP 7的安装和调试以及代码阅读工具,为读者深入阅读和理解PHP 7的源码和实现奠定基础。第2章初识PHP 7源码整体框架

本章将从整体上分析PHP 7的源码结构。读者可以通过这一章初步了解一下PHP 7核心源码中ext、main、sapi、TSRM、Zend目录下都有哪些文件,以及各自的功能是什么。此外,本章还以一段示例代码介绍了PHP 7代码的执行流程。通过对PHP 7执行流程的初步了解,可以为后面章节的深入研究打好基础。2.1 PHP 7语言的执行原理

我们常用的高级语言有很多种,比较出名的有C\C++、Python、PHP、Go、Pascal等。而这些语言根据运行的方式不同,大体分为两种:编译型语言和解释型语言。

其中,编译型语言包括C\C++、Pascal、Go等。这里说的编译是指在应用源程序执行之前,就将程序源代码“翻译”成汇编语言,然后进一步根据软硬件环境编译成目标文件。一般称完成编译工作的工具为编译器。而解释型语言,在程序运行时才被“翻译”为机器语言。但是执行一次“翻译”一次,所以执行效率较低。解释器的工作就是解释型语言中,负责“翻译”源代码的程序。

下面会更详细地讨论一下编译型语言和解释型语言的运行方式。2.1.1 编译型语言与解释型语言

我们知道,对于一段C语言代码,需要经过预编译、编译、汇编和链接,才能成为可执行的二进制文件。以hello.c为例:#includeint main(){ printf("hello world"); return 1;}

对于这段C代码,main是程序入口函数,实现的功能是打印字符串“hello world”到屏幕上。编译和执行过程如图2-1所示。

第1步:C语言代码预处理(比如依赖处理、宏替换等)。如以上代码示例,#inlcude就会在预处理阶段被替换。

第2步:编译。编译器会把C语言翻译成汇编语言程序,一条C语言通常编译为多条汇编代码。同时编译器会对程序进行优化,生成目标汇编程序。

第3步:编译得到的汇编语言通过汇编器再汇编成目标程序hello.o。

第4步:链接。程序中往往包含一些共享目标文件,如示例程序中的printf()函数,位于静态库,需要经过链接器(如Uinx连接器ld)进行链接。

以C语言为代表的编译型语言,代码发生更新都要经过以上步骤。图2-1 编译型语言的执行示意

我们在本章对编译型语言与解释型语言的区别的理解,立足于源代码被编译成目标平台CPU指令的时机。对于编译型语言,编译结果已经是针对当前CPU体系的指令;而解释型语言,需要先编译成中间代码,再经由该解释型语言的特定虚拟机,翻译成特定CPU体系的指令被执行。解释型语言是在运行过程中,翻译为目标平台的指令。常说解释型语言“慢”,主要也是慢在这里。

在PHP 7中,源代码首先进行词法分析,将源代码切割为多个字符串单元,分割后的字符串称为Token。而一个一个独立的Token是无法表达完整语义的,需经过语法分析阶段,将Token转换为抽象语法树(简称AST)。之后,抽象语法树被转换为机器指令执行。在PHP中,这些指令称为opcode(后文会对opcode做更详细的解释,此处读者可以将其看待为CPU指令)。

到AST的生成这一步,编译型语言与解释型语言所需经历的过程相似。从抽象语法树之后开始产生差异。

图2-2是执行PHP(如无特殊说明,本章提到的PHP均为PHP 7版本)代码的简化步骤,其中最后一步的左侧分支是编译型语言的过程。图2-2 以PHP为例,解释型语言的执行示意

第1步:源码通过词法分析得到Token。

第2步:基于语法分析器生成抽象语法树(AST)。

第3步:抽象语法树转换为opcodes(opcode指令集合),PHP解释执行opcodes。

接下来在基本步骤的基础上,细化PHP语言的执行原理,以便更清晰地建立认知。2.1.2 PHP 7的执行原理概述

首先补充说明前文提到的PHP 7程序执行过程,请见图2-3。

第1步:词法分析将PHP代码转换为有意义的标识Token。该步骤的词法分析器使用Re2c实现。

第2步:语法分析将Token和符合文法规则的代码生成抽象语法树。语法分析器基于Bison实现。语法分析使用了BNF(Backus-Naur Form,巴科斯范式)来表达文法规则,Bison借助状态机、状态转移表和压栈、出栈等一系列操作,生成抽象语法树。

第3步:上步的抽象语法树生成对应的opcode,并被虚拟机执行。opcode是PHP 7定义的一组指令标识,指令对应着相应的handler(处理函数)。当虚拟机调用opcode,会找到opcode背后的处理函数,执行真正的处理。以常见的echo语句为例,其对应的opcode便是ZEND_ECHO。注意 这里为了便于理解词法分析和语法分析过程,将两者分开描述。但实际情况下,出于效率考虑,两个过程并非完全独立。

下面通过一段示例代码,来建立PHP 7运转的初步理解。

示例代码如下:

从图2-3可知,这段代码首先会被切割为Token。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载