升级 PHP5.4.x后遇到segmentationfault
升级到5.4.x后,在rotate日志之后, 发现httpd segmentatioin fault了,排查了好长时间,还是一无所获啊,网上搜索了下,也发现了类似的问题:
https://bugs.php.net/bug.php?id=62129&thanks=3
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=694473
http://www.mail-archive.com/debian-apache@lists.debian.org/msg14183.html
先描述下表象:
1. session.c 里面有一个全局的函数指针变量 php_session_rfc1867_orig_callback
2. 在sessioin minit的时候会将 php_session_rfc1867_orig_callback 设置为 默认的php_rfc1867_callback(main/rfc1867.c中定义的一个函数指针)
3. session minit中 同时将 php_rfc1867_callback 设置为 session 中定义的一个php_session_rfc1867_callback这个函数
php_session_rfc1867_orig_callback = php_rfc1867_callback;
php_rfc1867_callback = php_session_rfc1867_callback;
4. 在 php_session_rfc1867_callback函数中有这么一段代码,如果orig函数不为空,则首先调用orig函数
static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra TSRMLS_DC) /* {{{ */
{
php_session_rfc1867_progress *progress;
int retval = SUCCESS;
if (php_session_rfc1867_orig_callback) {
retval = php_session_rfc1867_orig_callback(event, event_data, extra TSRMLS_CC);
}
5. 在 mshutdown的时候没有显式将 php_session_rfc1867_orig_callback 设定为null, 同时全局变量定义的时候也没有显式设定为null
6. reload httpd 之后,因为未知的原因, php_session_callback 保存了 reload之前的值, php_session_rfc1867_callback
7. 重新调用minit函数的时候, 将 php_rfc1867_callback赋值给 php_session_rfc1867_orig_callback, 这个时候php_session_rfc1867_orig_callback 和 php_rfc1867_callback 以及 php_session_rfc1867_callback 其实是相等的了
php_session_rfc1867_orig_callback = php_rfc1867_callback;
php_rfc1867_callback = php_session_rfc1867_callback;
8。 当再次调用 4 中的php_session_rfc1867_callback的时候,就死循环了,直到资源耗尽,segmentation fault.
那为啥 reload之后 php_rfc1867_callback 的值还保留了呢?
首先分析下httpd的启动过程以及reload的过程
1. httpd 以root身份启动, 假定进程号为 A, 然后load所有的module,监听配置的端口比如80
2. A启动守护进程(root身份运行)B,然后就退出了。
3. B加载所有module的扩展(比如php扩展)
4. B根据apache的配置启动子进程
5. 当B监听到有request来的时候,转交给子进程处理请求,然后继续监听
6. 当B接受到SIGHUP信号的时候,通知子进程退出,然后unload所有的module的扩展, 再unload所有的module
7. B 重新加载所有的module, 接下来module加载自己的扩展(比如php扩展), B根据配置创建子进程
8. B继续监听
在reload前后, libphp5.so是首先被unload掉, 然后又被load了的,而且 php_rfc1867_callback 定义的时候显式指定了是null.
难道重新加载.so的时候,全局变量不会被重新初始化?
还是没有搞明白。
实际上遇到的不仅仅是 libphp5.so 里面的全局变量有这样的问题, 我们在用的ice-php也有类似的问题,还有就是自己写的一个扩展同样也有类似的问题,如果不在mshutdown的时候将全局变量设置为 null, reload之后还是保留了原先的值,然后就会出错了。
所以在session.c 的 mshutdown 增加了几行代码将minit修改的值恢复回来:
diff -cr php-5.4.11/ext/session/session.c php-5.4.11-2/ext/session/session.c
*** php-5.4.11/ext/session/session.c 2013-01-16 15:10:30.000000000 +0800
--- php-5.4.11-2/ext/session/session.c 2013-01-20 15:22:17.427613455 +0800
***************
*** 2224,2229 ****
--- 2224,2235 ----
ps_serializers[PREDEFINED_SERIALIZERS].name = NULL;
memset(&ps_modules[PREDEFINED_MODULES], 0, (MAX_MODULES-PREDEFINED_MODULES)*sizeof(ps_module *));
+ // reset the callback pointer to null
+ php_session_rfc1867_orig_callback = NULL;
+ if(php_rfc1867_callback == php_session_rfc1867_callback) {
+ php_rfc1867_callback = NULL;
+ }
+
return SUCCESS;
}
/* }}} */
运行一段时间看看是否有问题。