php内核分析(二)-ZTS和zend_try

本文探讨了PHP中的线程安全(TSRM)概念及其如何应用于多线程环境中,同时解析了zend_try_catch机制背后的setjmp和longjmp原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux

ZTS

我们会看到文章中有很多地方是:

#ifdef ZTS
# define CG(v) ZEND_TSRMG(compiler_globals_id, zend_compiler_globals *, v)
#else
# define CG(v) (compiler_globals.v)
extern ZEND_API struct _zend_compiler_globals compiler_globals;
#endif

这里的ZTS是个什么概念呢。我们经常使用的php都是运行在单进程,单线程环境,比如cgi,都是一个请求进来,就一个进程为它服务,当请求结束了,进程也就结束了。所以比如像全局变量,php内核就没有考虑多线程同时修改获取的时候线程安全问题。后来,php渐渐也在往单进程多线程服务器方向发展。那么这个时候,就会需要有一个层来专门处理线程安全问题。这个就是TSRM(Thread Safe Resource Management)。

但是php默认是关闭线程安全的。在编译的时候,你可以指定参数开启编译一个线程安全版本的php。(--enable-maintainer-zts 选项, Windows 平台为 --enable-zts)这个就是这里的ZTS的由来。

比如上面的例子,CG(V) 在非线程安全下获取的是全局结构compiler_globals结构的v属性,在线程安全下获取的是通过ZEND_TSREMG方法来获取。

zend_try

我们会看到zend_try_catch相关的代码如下:

    zend_try {
        ...exec_try
    } zend_catch {
        ...exec_catch
    } zend_end_try();

把宏展开,我们可以看到大概代码如下:

{                                                            \
        JMP_BUF *__orig_bailout = EG(bailout);                    \
        JMP_BUF __bailout;                                        \
                                                                \
        EG(bailout) = &__bailout;                                \
        if (SETJMP(__bailout)==0) {
          {
               ...exec_try
          }
        } else {                                                \
            EG(bailout) = __orig_bailout;
           {
               ...exec_catch
           }
       }                                                        \
       EG(bailout) = __orig_bailout;                            \
}

这个是什么意思呢,需要先理解下setjmp和longjmp,这两个函数是linux提供的方法。他们是组合起来使用的,达到协同程序的功能

#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void foo() {
    printf("before jmp\n");
    int ret = setjmp(env);
    if(ret == 0) {
        return;
    } else {
        printf("return %d\n", ret);
    }
    printf("after jmp\n");
}
int main(int argc, char* argv[]) {
    foo();
    longjmp(env, 999);
    return 0;
}

// 输出:
/*
before jmp
return 999
after jmp
*/

上面的这个例子,setjmp的时候相当于程序片段1把主动权交出来,然后执行if(ret == 0)下面的程序,直到遇到longjmp,把执行权还给了片段1,并且设置jmp_buf为999,片段1继续执行,发现了ret!=0,就输出return 999。

好了,回到这个程序:

{                                                            \
        JMP_BUF *__orig_bailout = EG(bailout);                    \
        JMP_BUF __bailout;                                        \
                                                                \
        EG(bailout) = &__bailout;                                \
        if (SETJMP(__bailout)==0) {
          {
               ...exec_try
          }
        } else {                                                \
            EG(bailout) = __orig_bailout;
           {
               ...exec_catch
           }
       }                                                        \
       EG(bailout) = __orig_bailout;                            \
}

这个程序里面的exec_try代码段里面,在遇到错误的时候,需要返回的时候,就会包含一个longjmp函数的调用。这样,就形成了我们平时调用try...catch...finnal的功能:
1 先保存全局变量里面的bailout
2 使用setjmp来做跳转执行下面的程序
3 执行exec_try
4 如果exec_try这个代码段里面有longjmp,并且longjmp返回非0(一般也确实非0),就执行exec_catch
5 最后,把全局变量里面的bailout恢复

这里可能会有两个疑惑,如果exec_try里面没有longjmp怎么办,那就直接只执行了exec_try,就跳过exec_catch了。这个也是标准的用setjmp和longjmp实现try catch的写法。

这两个的实现弥补了goto关键字只能在函数内部进行跳转的限制。这个叫做“长跳转”。

所以在PHP代码中,如果你执行的函数有可能抛出异常。不妨使用这个方式把你要执行的程序放在里面。

参考

http://blog.lucode.net/skills/talk-about-setjmp-and-longjmp.html

PHP Warning: PHP Startup: Unable to load dynamic library 'libphp83_dm.so' (tried: /usr/local/lib/php/extensions/no-debug-non-zts-20230831/libphp83_dm.so (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/libphp83_dm.so: cannot open shared object file: No such file or directory), /usr/local/lib/php/extensions/no-debug-non-zts-20230831/libphp83_dm.so.so (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/libphp83_dm.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0 Warning: PHP Startup: Unable to load dynamic library 'libphp83_dm.so' (tried: /usr/local/lib/php/extensions/no-debug-non-zts-20230831/libphp83_dm.so (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/libphp83_dm.so: cannot open shared object file: No such file or directory), /usr/local/lib/php/extensions/no-debug-non-zts-20230831/libphp83_dm.so.so (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/libphp83_dm.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0 PHP Warning: PHP Startup: Unable to load dynamic library 'php_pdo_dm.so' (tried: /usr/local/lib/php/extensions/no-debug-non-zts-20230831/php_pdo_dm.so (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/php_pdo_dm.so: cannot open shared object file: No such file or directory), /usr/local/lib/php/extensions/no-debug-non-zts-20230831/php_pdo_dm.so.so (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/php_pdo_dm.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0 Warning: PHP Startup: Unable to load dynamic library 'php_pdo_dm.so' (tried: /usr/local/lib/php/extensions/no-debug-non-zts-20230831/php_pdo_dm.so (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/php_pdo_dm.so: cannot open shared object file: No such file or directory), /usr/local/lib/php/extensions/no-debug-non-zts-20230831/php_pdo_dm.so.so (/usr/local/lib/php/extensions/no-debug-non-zts-20230831/php_pdo_dm.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
最新发布
06-17
<think>我们正在解决PHP启动时无法加载动态库`libphp83_dm.so``php_pdo_dm.so`的问题。报错信息显示,PHP在尝试加载这些库时遇到了问题,具体是找不到文件(Nosuchfileordirectory)或者符号未定义(undefinedsymbol)。我们将根据引用中提供的线索一般解决步骤来逐步解决。###问题分析1.**文件不存在或路径错误**:报错信息中提到了`cannotopensharedobjectfile:Nosuchfileordirectory`,这通常意味着PHP扩展的路径配置不正确,或者文件确实不存在。2.**符号未定义(undefinedsymbol)**:对于`libphp83_dm.so`,还出现了`undefinedsymbol:core_globals`,这可能是由于扩展库与PHP版本不兼容,或者编译时使用的PHP头文件版本与当前运行的PHP版本不一致。###解决步骤####步骤1:确认扩展文件是否存在首先,我们需要确认动态库文件`libphp83_dm.so``php_pdo_dm.so`是否存在于正确的目录中。通常,PHP扩展的默认目录是`/usr/lib/php/extensions/`或类似路径,具体取决于安装方式。使用以下命令查找扩展文件:```bashfind/-name'libphp83_dm.so'2>/dev/nullfind/-name'php_pdo_dm.so'2>/dev/null```如果文件不存在,可能需要重新安装达梦数据库的PHP驱动。####步骤2:检查PHP配置中的扩展路径在PHP的配置文件(如`php.ini`)中,检查`extension_dir`设置是否正确指向包含这些扩展的目录。使用以下命令查看当前PHP的`extension_dir`:```bashphp-i|grepextension_dir```如果`extension_dir`设置错误,可以在`php.ini`中修改:```iniextension_dir="/正确的路径"```####步骤3:检查扩展加载指令在PHP的配置目录(如`/etc/php.d/`)中,通常会有单独的ini文件来加载扩展。检查是否存在对应的ini文件(如`dm.ini`或`pdo_dm.ini`),并确保其内容正确:对于`libphp83_dm.so`:```iniextension=libphp83_dm.so```对于`php_pdo_dm.so`:```iniextension=php_pdo_dm.so```如果这些文件中的路径不正确,或者文件名与实际的so文件不匹配,需要修正。####步骤4:处理符号未定义错误如果出现`undefinedsymbol:core_globals`错误,这通常是因为扩展库与当前PHP版本不兼容。达梦数据库的PHP驱动可能需要针对特定的PHP版本进行编译。解决方案:1.**重新编译驱动**:确保使用与当前PHP版本完全一致的PHP开发包重新编译驱动。2.**检查驱动版本兼容性**:确认下载的驱动版本支持PHP8.3。####步骤5:删除或修正错误的配置参考引用[2]中的方法,如果存在错误的配置,可以尝试删除或修正:-检查`/etc/php.d/`目录下与达梦驱动相关的ini文件,如果有问题,可以删除或修正这些文件。####步骤6:重启PHP服务修改配置后,重启PHP服务(或Web服务器)以使更改生效:```bashsystemctlrestartphp-fpm#如果使用PHP-FPMsystemctlrestartapache2#或者httpd,nginx等```###总结通过以上步骤,应该能解决大部分动态库加载问题。如果问题仍然存在,请检查达梦数据库PHP驱动的官方文档或寻求官方支持。§§相关问题§§1.如何确认PHP扩展是否加载成功?2.如何编译达梦数据库的PHP扩展?3.如何解决PHP扩展中的符号未定义错误?</think>### PHP启动时无法加载动态库`libphp83_dm.so``php_pdo_dm.so`的解决方案 #### 问题原因分析 1. **文件路径错误** PHP无法找到动态库文件,通常由于: - 库文件未放置在PHP的`extension_dir`目录 - `php.ini`中指定的路径与实际路径不匹配 - 文件权限问题(如PHP进程无读取权限) 2. **符号未定义错误** 出现`undefined symbol: core_globals`表明: - 扩展库与PHP版本不兼容(如为PHP 7.4编译的库用于PHP 8.3) - 依赖库缺失或编译环境不一致 3. **配置冲突** 冗余的`.ini`配置文件可能导致加载冲突(参考引用[2]) #### 解决步骤 ##### 步骤1:确认扩展文件存在 ```bash # 查找库文件位置 find / -name libphp83_dm.so 2>/dev/null find / -name php_pdo_dm.so 2>/dev/null # 检查文件权限(确保PHP可读) ls -l /path/to/libphp83_dm.so ``` ##### 步骤2:验证PHP扩展目录 ```bash # 查看PHP扩展目录路径 php -i | grep extension_dir # 若库文件不在该目录,有两种方案: # 方案A:将库文件移动到该目录 sudo cp /原路径/libphp83_dm.so $(php -i | grep extension_dir | awk '{print $NF}') # 方案B:修改php.ini指定路径 sudo nano /etc/php/8.3/cli/php.ini # 添加:extension=/完整路径/libphp83_dm.so ``` ##### 步骤3:解决符号未定义错误 1. **检查版本兼容性** 确认驱动是否支持PHP 8.3: ```bash ldd libphp83_dm.so | grep php # 输出应包含与当前PHP版本匹配的符号 ``` 2. **重新编译驱动** 从达梦官网下载对应PHP 8.3的源码重新编译: ```bash tar zxvf dm_php_pdo.tar.gz cd dm_php_pdo phpize && ./configure && make sudo cp modules/*.so $(php -i | grep extension_dir | awk '{print $NF}') ``` ##### 步骤4:清理冲突配置(关键步骤) ```bash # 删除冗余配置文件(参考引用[2]) sudo rm /etc/php.d/dm.ini # 或类似名称的配置文件 # 确认只在php.ini中加载一次扩展 grep -r "libphp83_dm.so" /etc/php/ ``` ##### 步骤5:验证加载结果 ```bash # 检查扩展加载状态 php -m | grep dm # 查看详细错误日志 php --ini | grep Loaded tail -f /var/log/php/error.log ``` #### 典型修复案例 > 某用户环境配置后仍报错`undefined symbol: core_globals`,最终解决方案: > 1. 删除`/etc/php.d/50-dm.ini` > 2. 在`php.ini`中直接添加: > ```ini > extension=/opt/dmdbms/drivers/php_pdo/libphp83_dm.so > extension=/opt/dmdbms/drivers/php_pdo/php_pdo_dm.so > ``` > 3. 重启PHP-FPM后成功加载[^2] #### 预防措施 1. **环境一致性** 开发/生产环境使用相同PHP版本编译驱动 2. **配置管理** 使用`php --ini`检查所有配置文件,避免重复加载 3. **依赖检查** 编译前安装PHP开发包: ```bash sudo apt install php8.3-dev # Ubuntu sudo yum install php83-devel # CentOS ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值