### 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。
各步驟之間的轉化關系如下圖:

編寫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超時控制的思考