[ PHP 内核与扩展开发系列] 配置编译环境并编译安装 PHP

准备工作

从一个 PHP 程序员,到一个想为 PHP 开发扩展的程序员,此间的进化有一步是跳不过去的,那就是你必须熟知如何编译 PHP 的源码。

*nix Tools

C 语言的编译器是我们使用 C 语言的必备工具,你的系统应该已经自带了一种 C 语言的编译器,而且它极有可能是大名鼎鼎的 gcc。通过检测你本机 gcc 或者 cc 程序的版本,可以很方便的知道你机器上是否已经安装的某种 C 语言的编译器:

如果你还没有安装编译器,那你需要安装一个。最简单的办法便是去下载一个与你系统相符的 rpm 或者 deb 包,当然你也可以通过以下命令的一种来方便的安装:yum install gcc(for CentOS)、apt-get install gcc(for Ubuntu)。

除了编译器,你还需要以下程序:makeautoconfautomakelibtool。不过除非RP太低,不然一般系统中都会自备了,而且 phpize 会把这些需要的脚本给生成好的。对于编译需要的程序以及它们的版本我们可以在 PHP 官网找到最新的答案:

  • autoconf: 2.13+(PHP < 5.4.0),2.59+(PHP >= 5.4.0)
  • automake: 1.4+
  • libtool: 1.4.x+(除了 1.4.2)
  • re2c: 版本 0.13.4 或更高
  • flex: 版本 2.5.4(PHP <= 5.2)
  • bison: 版本 1.28(建议),1.35 或 1.75

你千万不要被上面的清单给吓着,其实系统应该给装备好了,如果没有的话按照系统提示手动进行安装…当然,我们也可以通过 git 从 PHP 源码库里导出一份源码:

$ git clone https://github.com/php/php-src.git
$ cd php-src

你也可以切到你想要构建到的其他分支:

  • PHP 5.3: git checkout PHP-5.3
  • PHP 5.4: git checkout PHP-5.4
  • PHP 5.5: git checkout PHP-5.5
  • PHP 5.6: git checkout PHP-5.6
  • PHP 7.0: git checkout PHP-7.0
  • PHP HEAD: git checkout master

更多信息大家可以来这查看:http://php.net/git.php

获取 PHP 源码

其实你有很多办法安装 PHP,最简单的一种就是从你系统的库或者源里通过 apt-getyum install 之类的命令直接安装 PHP,这样做的好处是你的系统可能会自动处理一些 PHP 在它上面的工作时的一些 bug,而且你还可以方便的升级与卸载。不过这样做也有缺点,那就是你的 PHP 版本永远无法是最新的,通常PHP 官方发布数周甚至数月后你才能用上相应的版本。

第二种方法,也是推荐使用的一种方法,那就是自行下载 php-x.y.z.tar.gz 的源码包,然后自行编译安装。这种包一般都是经过了海量的测试后才发布的,而且非常接近最新 beta 或者 alpha 版本。此外,你可以直接从版本库中导出此时此刻的最新源码。作为一个扩展开发者,从版本库获取 PHP 看起来并没有多大的作用,但是如果我们要将这个扩展推送到版本库中时,便需要熟练的掌握 checkout 和 checkin 的步骤了。签出的地址在上面已经说过了。

配置 config

第一章我们曾介绍过,PHP 编译前的 configure 有两个特殊的选项,打开它们对我们开发 PHP 扩展或者进行 PHP 嵌入式开发时非常有帮助。但是当我们正常使用 PHP 的时候,则不应该开启这两个选项。

–enable-debug

顾名思义,它的作用是激活调试模式。它将激活 PHP 源码中几个非常关键的函数,最典型的功能便是在每一个请求结束后给出这一次请求中内存的泄漏情况。回顾一下内存管理部分,PHP 内核中的 ZendMM(Zend Memory Manager)将会在每一个请求结束后强制释放在这个请求中申请的内存。通过对新开发的代码进行一系列积极的回归测试,泄漏点可以在任何公开发布前轻松发现和插入。来看一下下面这个代码片段:

void show_value(int n)
{
    char *message = emalloc(1024);

    sprintf(message, "The value of n is %d\n", n);
    php_printf("%s", message);
}       

上面的代码执行后,将会导致 1024B 的内存泄漏,但是在 ZendMM 的帮助下,在请求结束后会被 PHP 内核自动的释放掉。但是如果你开启了 --enable-debug 选项,在请求结束后内核便会给出一条信息,告知我们这次请求的内存泄漏情况:

/cvs/php5/ext/sample/sample.c(33) :Freeing 0x084504B8 (1024 bytes), script=-
=== Total 1 memory leaks detected === 

这条提示告知我们在这次请求结束后,ZendMM 清理了泄漏的内存以及泄漏的内存位置。在它的帮助下,我们可以很快的定位到有问题的代码,然后通过 efree 等函数修正这个 bug。其实,内存泄漏并不是我们在开发中碰到的唯一错误,还有很多其它的 bug 很难被检测出来。有时候这些 bug 是致命的,但很难定位到出问题的代码。很多时候我们忙活了大半个晚上,修改了很多文件,最后 make,但是当我们运行脚本的时候却得到下面的段错误。

$ sapi/cli/php -r 'myext_samplefunc();'
Segmentation Fault
//如果中文环境,则显示段错误

Orz…错误出在哪呢?我们遍历 myext_samplefuc 的所有实现代码也没有发现问题,扔进 gdb 里也仅仅显示几行无关紧要的信息而已。这种情况下,enable-debug 就能帮你大忙了,打开这个选项后,你编译出来的 PHP 则会嵌入 gdb 或其它文件需要的所有调试信息。现在我们重新编译这个扩展,再扔进 gdb 里调试,便会得到如下的信息:

#0 0x1234567 php_myext_find_delimiter(str=0x1234567 "foo@#(FHVN)@\x98\xE0...",
                                      strlen=3, tsrm_ls=0x1234567)
p = strchr(str, ',');

现在所有的问题都水落石出了,字符串变量 str 没有以 NULL 结尾,而我们却把它当作一个参数传给了二进制不安全的字符串处理函数,str 将会扫描 str 直到找到 NULL 为止,它的扫描肯定是越界了,然后引发了一个段错误。找到问题根源后,我们只要用 memchr 来替换 strchr 函数就能修复这个bug 了。

–enable-maintainer-zts

第二个重要的参数便是激活 PHP 的线程安全机制(Thread Safe Resource Manager(TSRM)/Zend Thread Safety(ZTS)),使我们开发出的程序是线程安全的。在平时的开发中,建议打开这个选项。

–enable-embed

其实还有一个选项比较重要,那就是 enable-embed,它主要用在你做 PHP 的嵌入式开发的场景中。平时我们把 PHP 作为 Apache 的一个 module 进行编译,得到 libphp5.so,而这个选项便使 PHP 编译后得到一个与我们设定的 SAPI 相对应的结果。

Unix/Linux平台下的编译

编译之前如果需要了解一下 PHP 的 configure 脚本的各个配置,./configure --help 一下即可,或者参考一下网络上的资料。当你确定了应该开启哪几个选项,选项都应该赋什么值后,便可以开始正式的编译我们的 PHP 了。这里假设你将 Git 分支切换到了 PHP 5.4 的源码,进入终端,通过 cd 命令进入 /php-src/ 目录,执行 ./configure 脚本,然后 makemake test,比如:

cd /php-src
./buildconf
./configure --prefix=/usr/local/php54/ --enable-debug --enable-maintainer-zts
make
make test
make clean
make install

如果 make 过程中报错:

Zend/zend_language_parser.y:50.1-5: invalid directive: `%code'

则是由于 bison 版本 <=2.3 造成的,需要安装更高版本的 bison:

wget http://ftp.gnu.org/gnu/bison/bison-2.4.1.tar.gz
tar -zxvf bison-2.4.1.tar.gz
cd bison-2.4.1/
./configure
sudo make && make install

如果出现如下报错:

cp: sapi/cli/php.1: No such file or directory

解决办法是注释掉 Makefile.global 中的这行代码:

find . -name \*.1 | xargs rm -f  

小结

单就开发一个最基本的 PHP 扩展来说,该掌握的前置知识我们已经都掌握了。在接下来的章节里我们将会深入的研究如何制作一个 PHP 扩展,以及制作一个优秀的 PHP 扩展所需的其它知识。 此外,如果你只想把 PHP 当作一个嵌入式应用来使用,我们也强烈的建议你不要直接跳到最后几章,因为在接下来的章节里我们将详细的介绍与 PHP 内核密切相关的一些内容,比如 HashTable、数组、对象等等的实现方式与应用方法。

学院君 has written 716 articles

资深PHP工程师,Laravel学院院长

发表评论

标记为*的字段是必填项(邮箱地址不会被公开)

你可以使用这些HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>