百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>网页制作> php之 Zend 内存治理器
分享文章到:

php之 Zend 内存治理器

发布时间:09/01 来源:未知 浏览: 关键词:

PHP 中两种主要的动态内存池

PHP 是一个无同享架构。 Well, not at 100%. Let us explain.

留意

在连续此前,你大概需要阅读 PHP 生命周期章节,你将获得有关 PHP 生命周期中的不一样步骤和周期的更多信息。

PHP可以在统一个进程中处置数百或数千个恳求。默许状况下,PHP 会在完成当前恳求后,健忘对当前恳求的任何信息。

“健忘” 信息说明为开释处置恳求时分配的任何动态缓冲区。这意味着在处置一个恳求的历程中,不克不及使用传统的 libc 调取来分配动态内存。这样做是完全有效的,但是您给健忘开释缓冲区了时机。

ZendMM 附带了一个 API,通过复制其 API 来替换 libc 的动态分配器。在处置恳求的历程中,程序员必需使用该 API 而不是 libc 的分配器。

例如,当 PHP 处置恳求时,它将解析 PHP 文件。例如,那些将致使函数和类的声明。当编译器开端编译 PHP 文件时,它将分配一些动态内存来储备它发明的类和函数。但是,在恳求完毕时,PHP 会开释这些。默许状况下,PHP 会健忘从一个恳求到另一个恳求的大量信息。

然而,存在一些非常很少见到的信息,你需要耐久地跨过多个恳求。但这并不常见。

什么可以通过恳求保持不变?我们所说的耐久对象。再次说明:那是不常见的状况。例如,当前的 PHP 可施行途径不会在恳求之间更换。其信息是永远分配的,这意味着它调取了 传统 libc 的 malloc ()来分配。

还有什么? 一些字符串。例如,“_SERVER” 字符串将在恳求之间重用,由于每个恳求都将创立 $_SERVER PHP 数组。所以 “_SERVER” 字符串本身可以永远分配,由于它只会被分配一次。

你必需记住:

  • 在编写 PHP 中心或扩展时,存在两种动态内存分配方式:

    • 恳求绑定的动态分配。
    • 永远动态分配。
  • 恳求绑定动态内存分配

    • 仅在PHP处置恳求时才施行(不在此此前或之后)。
    • 应当只使用 ZendMM 动态内存分配 API 施行。
    • 在扩展设计中非常常见,根本上95%的动态分配都是恳求绑定的。
    • 由 ZendMM 追踪,并会通知你有关走漏的信息。
  • 永远动态内存分配

    • 不该该在PHP处置恳求时施行(这不是制止的,但是是一个坏主意)。
    • 不会被 ZendMM 追踪,你也不会被告知走漏。
    • 在扩展中应当很少见。

别的,请记住,所有 PHP 源代码都基于这种内存级别。因此,很多内部构造使用 Zend 内存治理器停止分配。大多数都调取了一个“耐久的” API,当调取这个时,将致使传统的 libc 分配。

这是一个恳求绑定的分配 zend_string:

zend_string *foo = zend_string_init("foo", strlen("foo"), 0);

这是耐久分配的:

zend_string *foo = zend_string_init("foo", strlen("foo"), 1);

一样的 HashTable。
恳求绑定分配:

zend_array ar;
zend_hash_init(&ar, 8, NULL, NULL, 0);

耐久分配:

zend_array ar;
zend_hash_init(&ar, 8, NULL, NULL, 1);

在所有不一样的 Zend API中,它始终是雷同的。平常是作为最后一个参数传递的,“0”表示“我但愿使用 ZendMM 分配此构造,因此恳求绑定”,或“1”表示“我但愿通过 ZendMM 调取传统的 libc 的malloc()分配此构造”。

明显,这些构造供给了一个 API,该 API 会记住它怎样分配构造,以便在烧毁时使用准确的开释函数。因此,在这样的代码中:

zend_string_release(foo);
zend_hash_destroy(&ar);

API 知道这些构造是使用恳求绑定分配还是永远分配的,第一种状况将使用efree()开释它,第二种状况是libc的free()

Zend 内存治理器 API

该 API 位于 Zend/zend_alloc.h

API 主如果 C 宏,而不是函数,因此,假如你调试它们并想理解它们的工作道理,请做好预备。这些 API 复制了 libc 的函数,平常在函数名称中增加“e”;因此,你不该该认错,关于该API的细节不多。

根本上,你最常使用的是 emalloc(size_t)efree(void *)

还供给了ecalloc(size_t nmemb,size_t size),它分配单个大小sizenmemb,并将区域归零。假如你是一位经历丰硕的 C 程序员,那么你应当知道,只要有大概,最好在emalloc()上使用ecalloc(),由于ecalloc()会将内存区域清零,这在指针错误检测中大概会有很大帮忙。请记住,emalloc()的工作道理根本上与libc malloc()一样:它将在不一样的池中寻觅足够大的区域,并为你供给最适宜的空间。因此,你大概会得到一个指向垃圾的回收指针。

然后是 safe_emalloc(size_t nmemb,size_t size,size_t offset),这是emalloc(size * nmemb + offset),但它会为你检查溢出状况。假如必需供给的数字来自不受信赖的来源(例如会员区),则应使用此API调取。

关于字符串,estrdup(char *)estrndup(char *, size_t len) 同意复制字符串或二进制字符串。

不管发生什么,ZendMM 返回的指针必需调取 ZendMM 的efree() 开释,而不是 libc 的 free()

留意

关于耐久分配的说明。耐久分配在恳求之间保持有效。你平常使用常见的 libc malloc/ free 来施行此操纵,但是 ZendMM 有一些 libc 分配器的快速方式:“耐久” API。该 API以“p” 字母开头,让你在 ZendMM 分配或耐久分配之间停止选中。因此pemalloc(size_t, 1)不外是malloc()pefree(void *, 1)free()pestrdup(void *, 1)strdup()。只是说。

Zend 内存治理器调试盾

ZendMM 供给以下功效:

  • 内存耗损治理。
  • 内存走漏跟踪和主动开释。
  • 通过预分配已知大小的缓冲区并保持余暇状态下的热缓存来加快分配速度

内存耗损治理

ZendMM 是 PHP 会员区“memory_limit”功效的底层。使用 ZendMM 层分配的每单个字节都会被计数并相加。当到达 INI 的 memory_limit 后,你知道会发生什么。这也意味着通过 ZendMM 施行的任何分配都反映在 PHP 会员区的memory_get_usage()中。

作为扩展开发人员,这是一件好事,由于它有助于把握 PHP 进程的堆大小。

假如启动了内存限制错误,则引擎将从当前代码位置开释到捕捉块,然后平稳终止。但是它不成能回到超出限制的代码位置。你必需为此做好预备。

从理论上讲,这意味着 ZendMM 没法向你返回 NULL 指针。假如从操纵系统分配失败,或者分配发生内存限制错误,则代码将运转到 catch 块中,并且不会返回到你的分配调取。

假如出于任何缘由需要绕过该庇护,则必需使用传统的 libc 调取,例如malloc()。不管怎样请当心,并且知道你在做什么。假如使用 ZendMM,大概需要分配大量内存并大概超出 PHP 的 memory_limit。因此,请使用另一个分配器(如libc),但要留意:你的扩展将增添当前进程堆的大小。在 PHP 中不克不及看到 memory_get_usage(),但是可以通过使用 OS 设备剖析当前堆(如/proc/{pid}/maps

留意

假如需要完全禁用 ZendMM,则可以使用USE_ZEND_ALLOC = 0环境变量启动PHP。这样,每次对 ZendMM API的调取(例如emalloc())都将定向到 libc 调取,并且 ZendMM 将被禁用。这在调试内存的状况下特别有用。

内存走漏追踪

请记住 ZendMM 的主要规则:它在恳求启动时启动,然后在你处置恳求时需要动态内存时代望你调取其API。当前恳求完毕时,ZendMM 关闭。

通过关闭,它将阅读其所有活动指针,假如使用 PHP 的调试构建,它将警告你有关内存走漏的信息。

让我们说明得更分明一些:假如在当前恳求完毕时,ZendMM 寻到了一些活动的内存块,则意味着这些内存块正在走漏。恳求完毕时,ZendMM 堆上不该存在任何活动内存块,由于分配了某些内存的任何人都应当开释了它们。

假如您健忘开释块,它们将全部显示在 stderr上。此内存走漏报告进程仅在以下状况下有效:

  • 你正在使用 PHP 的调试构建
  • 在 php.ini 中具有 report_memleaks = On(默许)

这是一个简便走漏到扩展中的示例:

PHP_RINIT_FUNCTION(example)
{
    void *foo = emalloc(128);
}

在启动该扩展的状况下启动 PHP,在调试版本上会在 stderr 上生成:

[Fri Jun 9 16:04:59 2017]  Script:  '/tmp/foobar.php'
/path/to/extension/file.c(123) : Freeing 0x00007fffeee65000 (128 bytes), script=/tmp/foobar.php
=== Total 1 memory leaks detected ===

当 Zend 内存治理器关闭时,在每个已处置恳求的末尾,将生成这些行。

但是要留神:

  • 明显,ZendMM 对耐久分配或以不一样于使用耐久分配的方式施行的分配一问三不知。因此,ZendMM 只能警告你有关它知道的分配信息,在这里不会报告每个传统的 libc 分配信息。
  • 假如 PHP 以错误的方式关闭(我们称之为不正常关闭),ZendMM 将报告大量走漏。这是由于引擎在错误关闭时会使用longjmp()调取 catch 块,防止清算所有内存的代码运转。因此,很多走漏得到报告。特别是在调取 PHP 的 exit()/ die()之后,或者在 PHP 的某些关键部分触发了致命错误时,就会发生这种状况。
  • 假如你使用非调试版本的 PHP,则 stderr 上不会显示任何内容,ZendMM 是笨拙的,但仍会清除程序员尚未明白开释的所有分配的恳求绑定缓冲区

你必需记住的是 ZendMM 走漏跟踪是一个不错的奖励工具,但它不克不及代替真正的 C 内存调试器。

ZendMM 内部设计

常见错误和错误

这是使用 ZendMM 时最常见的错误,乃至你应当如何做。

  1. 不处置恳求时使用 ZendMM。

猎取有关 PHP 生命周期的信息,以理解在扩展中何时处置恳求,何时不处置。假如在恳求范畴之外使用 ZendMM(例如在MINIT()中),在处置第一个恳求此前,ZendMM 会静默清除分配,并且大概会使用after-after-free:基本没有。

  1. 缓冲区上溢和下溢。

使用内存调试器。假如你在 ZendMM 返回的内存区域以下或过去写入内容,则将覆盖关键的 ZendMM 构造并触发崩溃。假如 ZendMM 能够为你检测到纷乱,则大概会显示“zend_mm_heap破坏”的新闻。堆栈追踪将显示从某些代码到某些 ZendMM 代码的崩溃。ZendMM 代码不会自行崩溃。假如你在 ZendMM 代码中心崩溃,那很大概意味着你在某个地方弄乱了指针。插入你喜爱的内存调试器,查寻有罪的部分并停止修复。

  1. 混合 API 调取

假如分配一个 ZendMM 指针(即emalloc())并使用 libc 开释它(free()),或相反的状况:你将崩溃。要严谨看待。别的,假如你将其不知道的任何指针传递给 ZendMM 的efree():将会崩溃。

以上就是php之 Zend 内存治理器的具体内容,更多请关注百分百源码网其它相关文章!

打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有151人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板