扩展的实现原理
无论是 PHP
扩展还是 Zend
扩展,它们实现的基本原理都是开发者按照扩展规范和 API,实现自己的功能,然后要么以静态编译方式编译到 PHP
的可执行文件,要么以动态编译方式生成动态链接库 .so
文件。加载扩展时,PHP
将动态链接库文件加载到内存,校验其符合规范后,PHP
即可以使用此扩展。
那么这里提到的库是什么呢?库可以看作可复用代码的二进制形式。在计算机世界中,每个程序都要依赖很多基础的底层库。库分为两种:静态链接库和动态链接库。在编译生成可执行程序时,一起被打包到可执行文件的库称为静态链接库,Linux 下一般以 .a
为扩展名(Windows 下为 .lib
);而生成可执行文件时并未被打包,在运行时才被载入的库,称为动态链接库,Linux 下一般以 .so
为扩展名(Windows 下为 .dll
)。动态链接库使用起来比静态链接库稍微麻烦,但有着非常明显的优势。
-
相对于静态链接库,使用动态链接库可以有效地缩小程序体积,节省空间,在同一个运行环境下,不同的程序可以调用相同的库。
-
程序更新时,使用了静态链接库的程序需要重新编译整个程序,用户也需要重新下载安装完整的程序,而使用了动态链接库的程序可以只更新库,实现增量更新。
-
有助于节省内存。当我们需要某个扩展时,才将其加载到内存中。
-
有助于资源共享。这里讲的资源共享,是指在多个进程中实现共享。
下面我们来实现一个简单的动态链接库 libhelloworld.so
并调用:
#include <stdio.h>
void helloworld()
{
printf("hello, world! \n");
}
代码很简单,只有一个函数——helloworld
,调用这个函数,会输出字符串“hello,world!”
。我们将其编译生成动态链接库:
$ gcc -shared -fPIC -o libhelloworld.so helloworld.c
$ ll
-rw-r--r--1 vagrant vagrant 71 1月 25 14:15 helloworld.c
-rwxr-xr-x 1 vagrant vagrant 7.9K 1月 25 14:30 libhelloworld.so
现在我们显式加载此动态链接库:
#include <stdio.h>
#include <dlfcn.h>
int main(int argc, char* argv[])
{
void* handle = dlopen("./libhelloworld.so", RTLD_LAZY);
char* error = dlerror();
if(! handle || error)
{
printf("load so error! \n");
return 1;
}
void (*func)() = dlsym(handle, "helloworld");
if(! func)
{
printf("load func error! \n");
dlclose(handle);
return 1;
}
func();
dlclose(handle);
return 0;
}
编译、链接此代码生成可执行程序:
$ gcc test.c -L. -lhelloworld -ldl
$ ll
-rwxr-xr-x 1 vagrant vagrant 8.6K 1月 25 14:34 a.out
-rw-r--r--1 vagrant vagrant 71 1月 25 14:15 helloworld.c
-rwxr-xr-x 1 vagrant vagrant 7.9K 1月 25 14:30 libhelloworld.so
-rw-r--r--1 vagrant vagrant 459 1月 25 14:34 test.c
$ ./a.out
Hello, world!
可以看到已成功生成可执行文件 a.out
,运行后输出字符串 “hello, world!”
。由此可见,动态链接库 libhelloworld.so
中的函数执行成功。
仔细研究下这段代码,会发现它一共使用了 4 个函数来加载动态链接库并执行其中的函数,它们分别是 dlopen
、dlerror
、dlsym
、dlclose
,分别用来加载动态链接库、获得相关错误信息、获得函数地址、关闭动态链接库。
PHP 的扩展实现原理和这段代码极其相似,也是用这 4 个函数完成了扩展的加载和函数的调用。当在我们在 PHP 程序中动态通过 dl
函数或通过 php.ini
来动态加载扩展时,PHP 会从 extension_dir
配置项指定的目录加载扩展,这个目录默认为 <install-dir>/lib/php/extensions/<debug-or-not>-<zts-or-not>-ZEND_MODULE_API_NO
,如 /usr/local/php/lib/php/extensions/debug-non-zts-20160303
或 /usr/local/php/lib/php/extensions/no-debug-zts-20160303
。