## 7.2 擴展的實現原理
PHP中擴展通過`zend_module_entry`這個結構來表示,此結構定義了擴展的全部信息:擴展名、擴展版本、擴展提供的函數列表以及PHP四個執行階段的hook函數等,每一個擴展都需要定義一個此結構的變量,而且這個變量的名稱格式必須是:`{module_name}_module_entry`,內核正是通過這個結構獲取到擴展提供的功能的。
擴展可以在編譯PHP時一起編譯(靜態編譯),也可以單獨編譯為動態庫,動態庫需要加入到php.ini配置中去,然后在`php_module_startup()`階段把這些動態庫加載到PHP中:
```c
int php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_modules, uint num_additional_modules)
{
...
//根據php.ini注冊擴展
php_ini_register_extensions();
zend_startup_modules();
zend_startup_extensions();
...
}
```
動態庫就是在`php_ini_register_extensions()`這個函數中完成的注冊:
```c
//main/php_ini.c
void php_ini_register_extensions(void)
{
//注冊zend擴展
zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb);
//注冊php擴展
zend_llist_apply(&extension_lists.functions, php_load_php_extension_cb);
zend_llist_destroy(&extension_lists.engine);
zend_llist_destroy(&extension_lists.functions);
}
```
extension_lists是一個鏈表,保存著根據`php.ini`中定義的`extension=xxx.so`取到的全部擴展名稱,其中engine是zend擴展,functions為php擴展,依次遍歷這兩個數組然后調用`php_load_php_extension_cb()`或`php_load_zend_extension_cb()`進行各個擴展的加載:
```c
static void php_load_php_extension_cb(void *arg)
{
#ifdef HAVE_LIBDL
php_load_extension(*((char **) arg), MODULE_PERSISTENT, 0);
#endif
}
```
`HAVE_LIBDL`這個宏根據`dlopen()`函數是否存在設置的:
```sh
#Zend/Zend.m4
AC_DEFUN([LIBZEND_LIBDL_CHECKS],[
AC_CHECK_LIB(dl, dlopen, [LIBS="-ldl $LIBS"])
AC_CHECK_FUNC(dlopen,[AC_DEFINE(HAVE_LIBDL, 1,[ ])])
])
```
接著就是最關鍵的操作了,`php_load_extension()`:
```c
//ext/standard/dl.c
PHPAPI int php_load_extension(char *filename, int type, int start_now)
{
void *handle;
char *libpath;
zend_module_entry *module_entry;
zend_module_entry *(*get_module)(void);
...
//調用dlopen打開指定的動態連接庫文件:xx.so
handle = DL_LOAD(libpath);
...
//調用dlsym獲取get_module的函數指針
get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "get_module");
...
//調用擴展的get_module()函數
module_entry = get_module();
...
//檢查擴展使用的zend api是否與當前php版本一致
if (module_entry->zend_api != ZEND_MODULE_API_NO) {
DL_UNLOAD(handle);
return FAILURE;
}
...
module_entry->type = type;
//為擴展編號
module_entry->module_number = zend_next_free_module();
module_entry->handle = handle;
if ((module_entry = zend_register_module_ex(module_entry)) == NULL) {
DL_UNLOAD(handle);
return FAILURE;
}
...
}
```
`DL_LOAD()`、`DL_FETCH_SYMBOL()`這兩個宏在linux下展開后就是:dlopen()、dlsym(),所以上面過程的實現就比較直觀了:
* (1)dlopen()打開so庫文件;
* (2)dlsym()獲取動態庫中`get_module()`函數的地址,`get_module()`是每個擴展都必須提供的一個接口,用于返回擴展`zend_module_entry`結構的地址;
* (3)調用擴展的`get_module()`,獲取擴展的`zend_module_entry`結構;
* (4)zend api版本號檢查,比如php7的擴展在php5下是無法使用的;
* (5)注冊擴展,將擴展添加到`module_registry`中,這是一個全局HashTable,用于全部擴展的zend_module_entry結構;
* (6)如果擴展提供了內部函數則將這些函數注冊到EG(function_table)中。
完成擴展的注冊后,PHP將在不同的執行階段依次調用每個擴展注冊的當前階段的hook函數。
- 目錄
- 第1章 PHP基本架構
- 1.1 PHP簡介
- 1.2 PHP7的改進
- 1.3 FPM
- 1.4 PHP執行的幾個階段
- 第2章 變量
- 2.1 變量的內部實現
- 2.2 數組
- 2.3 靜態變量
- 2.4 全局變量
- 2.5 常量
- 3.1 PHP代碼的編譯
- 3.1.1 詞法解析、語法解析
- 3.1.2 抽象語法樹編譯流程
- 第3章 Zend虛擬機
- 3.2.1 內部函數
- 3.2.2 用戶函數的實現
- 3.3 Zend引擎執行流程
- 3.3.1 基本結構
- 3.2 函數實現
- 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.2 線程安全資源管理器
- 第7章 擴展開發
- 7.1 概述
- 6.1 什么是線程安全
- 7.2 擴展的實現原理
- 7.3 擴展的構成及編譯
- 7.4 鉤子函數
- 7.5 運行時配置
- 7.6 函數
- 7.7 zval的操作
- 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.2 命名空間的定義
- 8.2.1 定義語法
- 8.2.2 內部實現
- 8.3 命名空間的使用
- 8.3.1 基本用法
- 8.3.2 use導入
- 8.3.3 動態用法
- 附錄
- 附錄1:break/continue按標簽中斷語法實現
- 附錄2:defer推遲函數調用語法的實現
- 8.1 概述