### 7.3.2 编译工具
PHP提供了几个脚本工具用于简化扩展的实现:ext_skel、phpize、php-config,后面两个脚本主要配合autoconf、automake生成Makefile。在介绍这几个工具之前,我们先看下PHP安装后的目录结构,因为很多脚本、配置都放置在安装后的目录中,比如PHP的安装路径为:/usr/local/php7,则此目录的主要结构:
```c
|---php7
| |---bin //php编译生成的二进制程序目录
| |---php //cli模式的php
| |---phpize
| |---php-config
| |---...
| |---etc //一些sapi的配置
| |---include //php源码的头文件
| |---php
| |---main //PHP中的头文件
| |---Zend //Zend头文件
| |---TSRM //TSRM头文件
| |---ext //扩展头文件
| |---sapi //SAPI头文件
| |---include
| |---lib //依赖的so库
| |---php
| |---extensions //扩展so保存目录
| |---build //编译时的工具、m4配置等,编写扩展是会用到
| |---acinclude.m4 //PHP自定义的autoconf宏
| |---libtool.m4 //libtool定义的autoconf宏,acinclude.m4、libtool.m4会被合成aclocal.m4
| |---phpize.m4 //PHP核心configure.in配置
| |---...
| |---...
| |---php
| |---sbin //SAPI编译生成的二进制程序,php-fpm会放在这
| |---var //log、run日志
```
#### 7.3.2.1 ext_skel
这个脚本位于PHP源码/ext目录下,它的作用是用来生成扩展的基本骨架,帮助开发者快速生成一个规范的扩展结构,可以通过以下命令生成一个扩展结构:
```c
./ext_skel --extname=扩展名称
```
执行完以后会在ext目录下新生成一个扩展目录,比如extname是mytest,则将生成以下文件:
```c
|---mytest
| |---config.m4 //autoconf规则的编译配置文件
| |---config.w32 //windows环境的配置
| |---CREDITS
| |---EXPERIMENTAL
| |---include //依赖库的include头文件,可以不用
| |---mytest.c //扩展源码
| |---php_mytest.h //头文件
| |---mytest.php //用于在PHP中测试扩展是否可用,可以不用
| |---tests //测试用例,执行make test时将执行、验证这些用例
| |---001.phpt
```
这个脚本主要生成了编译需要的配置以及扩展的基本结构,初步生成的这个扩展可以成功的编译、安装、使用,实际开发中我们可以使用这个脚本生成一个基本结构,然后根据具体的需要逐步完善。
### 7.3.2.2 php-config
这个脚本为PHP源码中的/script/php-config.in,PHP安装后被移到安装路径的/bin目录下,并重命名为php-config,这个脚本主要是获取PHP的安装信息的,主要有:
* __PHP安装路径__
* __PHP版本__
* __PHP源码的头文件目录:__ main、Zend、ext、TSRM中的头文件,编写扩展时会用到这些头文件,这些头文件保存在PHP安装位置/include/php目录下
* __LDFLAGS:__ 外部库路径,比如:`-L/usr/bib -L/usr/local/lib`
* __依赖的外部库:__ 告诉编译器要链接哪些文件,`-lcrypt -lresolv -lcrypt`等等
* __扩展存放目录:__ 扩展.so保存位置,安装扩展make install时将安装到此路径下
* __编译的SAPI:__ 如cli、fpm、cgi等
* __PHP编译参数:__ 执行./configure时带的参数
* ...
这个脚本在编译扩展时会用到,执行`./configure --with-php-config=xxx`生成Makefile时作为参数传入即可,它的作用是提供给configure.in获取上面几个配置,生成Makefile。
#### 7.3.2.3 phpize
这个脚本主要是操作复杂的autoconf/automake/autoheader/autolocal等系列命令,用于生成configure文件,GNU auto系列的工具众多,这里简单介绍下基本的使用:
__(1)autoscan:__ 在源码目录下扫描,生成configure.scan,然后把这个文件重名为为configure.in,可以在这个文件里对依赖的文件、库进行检查以及配置一些编译参数等。
__(2)aclocal:__ automake中有很多宏可以在configure.in或其它.m4配置中使用,这些宏必须定义在aclocal.m4中,否则将无法被autoconf识别,aclocal可以根据configure.in自动生成aclocal.m4,另外,autoconf提供的特性不可能满足所有的需求,所以autoconf还支持自定义宏,用户可以在acinclude.m4中定义自己的宏,然后在执行aclocal生成aclocal.m4时也会将acinclude.m4加载进去。
__(3)autoheader:__ 它可以根据configure.in、aclocal.m4生成一个C语言"define"声明的头文件模板(config.h.in)供configure执行时使用,比如很多程序会通过configure提供一些enable/disable的参数,然后根据不同的参数决定是否开启某些选项,这种就可以根据编译参数的值生成一个define宏,比如:`--enabled-xxx`生成`#define ENABLED_XXX 1`,否则默认生成`#define ENABLED_XXX 0`,代码里直接使用这个宏即可。比如configure.in文件内容如下:
```sh
AC_PREREQ([2.63])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_HEADERS([config.h])
AC_ARG_ENABLE(xxx, "--enable-xxx if enable xxx",[
AC_DEFINE([ENABLED_XXX], [1], [enabled xxx])
],
[
AC_DEFINE([ENABLED_XXX], [0], [disabled xxx])
])
AC_OUTPUT
```
执行autoheader后将生成一个config.h.in的文件,里面包含`#undef ENABLED_XXX`,最终执行`./configure --enable-xxx`后将生成一个config.h文件,包含`#define ENABLED_XXX 1`。
__(4)autoconf:__ 将configure.in中的宏展开生成configure、config.h,此过程会用到aclocal.m4中定义的宏。
__(5)automake:__ 将Makefile.am中定义的结构建立Makefile.in,然后configure脚本将生成的Makefile.in文件转换为Makefile。
各步骤之间的转化关系如下图:
![](https://box.kancloud.cn/302fc3e158fcb689336665ddf01b47cb_537x361.png)
编写PHP扩展时并不需要操作上面全部的步骤,PHP提供了两个编辑好的配置:configure.in、acinclude.m4,这两个配置是从PHP安装路径/lib/php/build目录下的phpize.m4、acinclude.m4复制生成的,其中configure.in中定义了一些PHP内核相关的配置检查项,另外这个文件会include每个扩展各自的配置:config.m4,所以编写扩展时我们只需要在config.m4中定义扩展自己的配置就可以了,不需要关心依赖的PHP内核相关的配置,在扩展所在目录下执行phpize就可以生成扩展的configure、config.h文件了。
configure.in(phpize.m4):
```sh
AC_PREREQ(2.59)
AC_INIT(config.m4)
...
#--with-php-config参数
PHP_ARG_WITH(php-config,,
[ --with-php-config=PATH Path to php-config [php-config]], php-config, no)
PHP_CONFIG=$PHP_PHP_CONFIG
...
#加载扩展配置
sinclude(config.m4)
...
AC_CONFIG_HEADER(config.h)
AC_OUTPUT()
```
__phpize中的主要操作:__
__(1)phpize_check_configm4:__ 检查扩展的config.m4是否存在。
__(2)phpize_check_build_files:__ 检查php安装路径下的lib/php/build/,这个目录下包含PHP自定义的autoconf宏文件acinclude.m4以及libtool;检查扩展所在目录。
__(3)phpize_print_api_numbers:__ 输出PHP Api Version、Zend Module Api No、Zend Extension Api No信息。
```sh
phpize_get_api_numbers()
{
# extracting API NOs:
PHP_API_VERSION=`grep '#define PHP_API_VERSION' $includedir/main/php.h|$SED 's/#define PHP_API_VERSION//'`
ZEND_MODULE_API_NO=`grep '#define ZEND_MODULE_API_NO' $includedir/Zend/zend_modules.h|$SED 's/#define ZEND_MODULE_API_NO//'`
ZEND_EXTENSION_API_NO=`grep '#define ZEND_EXTENSION_API_NO' $includedir/Zend/zend_extensions.h|$SED 's/#define ZEND_EXTENSION_API_NO//'`
}
```
__(4)phpize_copy_files:__ 将PHP安装位置/lib/php/build目录下的mkdep.awk scan_makefile_in.awk shtool libtool.m4四个文件拷到扩展的build目录下,然后将acinclude.m4 Makefile.global config.sub config.guess ltmain.sh run-tests*.php文件拷到扩展根目录,最后将acinclude.m4、build/libtool.m4合并到扩展目录下的aclocal.m4文件中。
```sh
phpize_copy_files()
{
test -d build || mkdir build
(cd "$phpdir" && cp $FILES_BUILD "$builddir"/build)
(cd "$phpdir" && cp $FILES "$builddir")
#acinclude.m4、libtool.m4合并到aclocal.m4
(cd "$builddir" && cat acinclude.m4 ./build/libtool.m4 > aclocal.m4)
}
```
__(5)phpize_replace_prefix:__ 将PHP安装位置/lib/php/build/phpize.m4拷贝到扩展目录下,将文件中的prefix替换为PHP安装路径,然后重命名为configure.in。
```sh
phpize_replace_prefix()
{
$SED \
-e "s#/usr/local/php7#$prefix#" \
< "$phpdir/phpize.m4" > configure.in
}
```
__(6)phpize_check_shtool:__ 检查/build/shtool。
__(7)phpize_check_autotools:__ 检查autoconf、autoheader。
__(8)phpize_autotools__ 执行autoconf生成configure,然后再执行autoheader生成config.h
- 前言
- 第1章 PHP基本架构
- 1.1 PHP简介
- 1.2 PHP7的改进
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本实现
- 1.3.3 FPM的初始化
- 1.3.4 请求处理
- 1.3.5 进程管理
- 1.4 PHP执行的几个阶段
- 第2章 变量
- 2.1 变量的内部实现
- 2.2 数组
- 2.3 静态变量
- 2.4 全局变量
- 2.5 常量
- 第3章 Zend虚拟机
- 3.1 PHP代码的编译
- 3.1.1 词法解析、语法解析
- 3.1.2 抽象语法树编译流程
- 3.2 函数实现
- 3.2.1 内部函数
- 3.2.2 用户函数的实现
- 3.3 Zend引擎执行流程
- 3.3.1 基本结构
- 3.3.2 执行流程
- 3.3.3 函数的执行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向对象实现
- 3.4.1 类
- 3.4.2 对象
- 3.4.3 继承
- 3.4.4 动态属性
- 3.4.5 魔术方法
- 3.4.6 类的自动加载
- 3.5 运行时缓存
- 3.6 Opcache
- 3.6.1 opcode缓存
- 3.6.2 opcode优化
- 3.6.3 JIT
- 第4章 PHP基础语法实现
- 4.1 类型转换
- 4.2 选择结构
- 4.3 循环结构
- 4.4 中断及跳转
- 4.5 include/require
- 4.6 异常处理
- 第5章 内存管理
- 5.1 Zend内存池
- 5.2 垃圾回收
- 第6章 线程安全
- 6.1 什么是线程安全
- 6.2 线程安全资源管理器
- 第7章 扩展开发
- 7.1 概述
- 7.2 扩展的实现原理
- 7.3 扩展的构成及编译
- 7.3.1 扩展的构成
- 7.3.2 编译工具
- 7.3.3 编写扩展的基本步骤
- 7.3.4 config.m4
- 7.4 钩子函数
- 7.5 运行时配置
- 7.5.1 全局变量
- 7.5.2 ini配置
- 7.6 函数
- 7.6.1 内部函数注册
- 7.6.2 函数参数解析
- 7.6.3 引用传参
- 7.6.4 函数返回值
- 7.6.5 函数调用
- 7.7 zval的操作
- 7.7.1 新生成各类型zval
- 7.7.2 获取zval的值及类型
- 7.7.3 类型转换
- 7.7.4 引用计数
- 7.7.5 字符串操作
- 7.7.6 数组操作
- 7.8 常量
- 7.9 面向对象
- 7.9.1 内部类注册
- 7.9.2 定义成员属性
- 7.9.3 定义成员方法
- 7.9.4 定义常量
- 7.9.5 类的实例化
- 7.10 资源类型
- 7.11 经典扩展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空间
- 8.1 概述
- 8.2 命名空间的定义
- 8.2.1 定义语法
- 8.2.2 内部实现
- 8.3 命名空间的使用
- 8.3.1 基本用法
- 8.3.2 use导入
- 8.3.3 动态用法
- 附录
- break/continue按标签中断语法实现
- defer推迟函数调用语法的实现
- 一起线上事故引发的对PHP超时控制的思考