• 技术文章 >后端开发 >php教程

    php之 Zend 内存管理器

    coldplay.xixicoldplay.xixi2020-07-29 17:14:58转载709

    Zend 内存管理器

    Zend 内存管理器,经常缩写为 ZendMMZMM,是一个 C 层,旨在提供分配和释放动态请求绑定内存的能力。

    注意上面句子中的“请求绑定”。

    ZendMM 不仅仅是 libc 的动态内存分配器上的一个经典层,主要由两个 API 调用 malloc()/free()表示。ZendMM 是关于 PHP 在处理请求时必须分配的请求绑定内存。

    相关学习推荐:PHP编程从入门到精通

    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 源代码都基于这种内存级别。因此,许多内部结构使用 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_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 泄漏跟踪是一个不错的奖励工具,但它不能代替真正的 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 内存管理器的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:learnku,如有侵犯,请联系admin@php.cn删除
    专题推荐:php Zend 内存管理器
    上一篇:php如何进行内存调试 下一篇:php的字符串管理 zend_string
    大前端线上培训班

    相关文章推荐

    • 关于PHP源代码中Zend HashTable的解析• Zend框架是什么• php7 安装指南(windows)之开启zend opcache• php zend加密乱码怎么办

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网