• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

深入理解PHP内存管理之一个低概率Core的分析

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

一个同事forward过来一个, 公司某产品线遇到的一个低概率, 但长时间出现了几次的Core的bt信息, 找我帮忙分析下原因.

bt栈如下(路径信息以*代替):


  1. #0 0x00000000004a75e5 in _zend_mm_alloc_int (heap=0xd61260, size=79)
  2. at /*/php-5.2.6/Zend/zend_alloc.c:1879
  3. #1 0x000000000048d3cd in vspprintf (pbuf=0x7fbffe9cd8, max_len=1024, format=Variable "format" is not available.
  4. )
  5.     at /*/php-5.2.6/main/spprintf.c:224
  6. #2 0x0000000000489747 in php_error_cb (type=1, error_filename=0x2a9a787ee8 "/*/application/helpers/util.php",
  7.     error_lineno=1149, format=Variable "format" is not available.
  8. ) at /*/php-5.2.6/main/main.c:799
  9. #3 0x000000000061db35 in soap_error_handler (error_num=1,
  10.     error_filename=0x2a9a787ee8 "/*/application/helpers/util.php", error_lineno=1149,
  11.     format=0x7b9cb8 "Maximum execution time of %d second%s exceeded", args=0x7fbffea3b0)
  12.     at /*/php-5.2.6/ext/soap/soap.c:2178
  13. #4 0x00000000004c2576 in zend_error (type=1, format=0x7b9cb8 "Maximum execution time of %d second%s exceeded")
  14.     at /*/php-5.2.6/Zend/zend.c:976
  15. #5 <signal handler called>
  16. #6 0x00000000004a720f in _zend_mm_free_int (heap=0xd61260, p=Variable "p" is not available.
  17. ) at /*/php-5.2.6/Zend/zend_alloc.c:844
  18.  
  19. ...以下省略

观察这个调用栈, 会发现#5 ginal handler called, 怎么我记得在zend_mm_free_int的时候, 是屏蔽了interruption的啊? 怎么还会转到信号处理过程呢? 后来证明, 之前看的时候没有深究, 原来在zend_mm_free_int中的:


  1. HANDLE_BLOCK_INTERRUPTIONS();
  2. ....
  3. HANDLE_UNBLOCK_INTERRUPTIONS();

就目前来说, 只是个摆设(php-5.2.17之前).

好吧, 不管他了, 那么这个core到底是怎么产生的呢?

首先要介绍下PHP内存管理中的核心结构zend_mm_heap, 如下图:

Zend MM heap

对于free_buckets来说, 它存储了所有小块内存的指针, 对应的通过一个free_bitmap来指明在free_buckets中, 那些索引是可用的(有实际内存),

然后, 在zend_mm_free_int的函数调用中, 在回收内存的时候, 如果发现内存相邻的内存是空闲的, 则会进行合并, 具体的逻辑:


  1.     HANDLE_BLOCK_INTERRUPTIONS();
  2.     heap->size -= size;
  3.     next_block = ZEND_MM_BLOCK_AT(mm_block, size);
  4.     if (ZEND_MM_IS_FREE_BLOCK(next_block)) {
  5.         zend_mm_remove_from_free_list(heap, (zend_mm_free_block *) next_block);
  6.         size += ZEND_MM_FREE_BLOCK_SIZE(next_block);
  7.     }
  8.     //以下省略

而在对zend_mm_remove_from_free_list的调用中, 会把原相邻的内存从free_buckets中拿掉:


  1. //有省略
  2.     prev->next_free_block = next;
  3.     next->prev_free_block = prev;
  4.  
  5.         if (EXPECTED(ZEND_MM_SMALL_SIZE(ZEND_MM_FREE_BLOCK_SIZE(mm_block)))) {
  6.             if (EXPECTED(prev == next)) {
  7.                 size_t index = ZEND_MM_BUCKET_INDEX(ZEND_MM_FREE_BLOCK_SIZE(mm_block));
  8.  
  9.                 if (EXPECTED(heap->free_buckets[index*2] == heap->free_buckets[index*2+1])) {
  10. //注意这一行
  11. heap->free_bitmap &= ~(ZEND_MM_LONG_CONST(1) << index);
  12.                 }
  13.             }
  14.         } else if (UNEXPECTED(mm_block->parent != NULL)) {
  15.             goto subst_block;
  16.         }
  17. //有省略

如上面的代码所示, 在从free_buckets中拿掉这块内存以后, 如果这块内存是对应的index中的唯一一块block, 则会关闭free_bitmap中的可用指示..

问题就出在这里

当代码执行到这句的时候, 业务逻辑超时信号被触发, 导致这行代码没有被执行, 就转入了信号处理流程.


  1. #4 0x00000000004c2576 in zend_error (type=1, format=0x7b9cb8 "Maximum execution time of %d second%s exceeded")
  2.     at /*/php-5.2.6/Zend/zend.c:976
  3. #5 <signal handler called>
  4. #6 0x00000000004a720f in _zend_mm_free_int (heap=0xd61260, p=Variable "p" is not available.
  5. ) at /*/php-5.2.6/Zend/zend_alloc.c:844

而在错误处理逻辑中, 又为错误信息申请内存, 而刚好就找到了这个错误的free_bitmap指示的内存:


  1. #0 0x00000000004a75e5 in _zend_mm_alloc_int (heap=0xd61260, size=79)
  2. at /*/php-5.2.6/Zend/zend_alloc.c:1879
  3. #1 0x000000000048d3cd in vspprintf (pbuf=0x7fbffe9cd8, max_len=1024, format=Variable "format" is not available.
  4. )

而此时, free_buckets中对应的index的指针是一个不可用的指针(指向自身).

从而导致在alloc_init中的逻辑, 段错误退出:


  1. # define ZEND_MM_CHECK_BLOCK_LINKAGE(block) \
  2.     if (UNEXPECTED((block)->info._size != ZEND_MM_BLOCK_AT(block, ZEND_MM_FREE_BLOCK_SIZE(block))->info._prev) || \
  3.         UNEXPECTED(!UNEXPECTED(ZEND_MM_IS_FIRST_BLOCK(block)) && \
  4.         UNEXPECTED(ZEND_MM_PREV_BLOCK(block)->info._size != (block)->info._prev))) { \
  5.         zend_mm_panic("zend_mm_heap corrupted"); \
  6.     }

原因找到了, 那这个问题怎么避免, 或者处理呢?

其实, 在出core的时候, 业务逻辑已经超时出错了, 而这个core是PHP本身的一些特点造就的, 也不能算是PHP的bug(起码它有一个看似屏蔽信号的操作), 只能说, 低概率的事件吧…


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Centos 安装PHP5.5发布时间:2022-07-10
下一篇:
ArchLinux下PHP安装memcached扩展发布时间:2022-07-10
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap