php之 Zend 内存管理器

php之 Zend 内存管理器

Zend 内存管理器

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

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

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

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

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

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()

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 的主要规则:它在请求启动时启动,然后在你处理请求时需要动态内存时期望你调用其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 内存管理器的文章就分享到这,如果对你有帮助欢迎继续关注我们哦

本文来自投稿,不代表重蔚自留地立场,如若转载,请注明出处https://www.cwhello.com/40501.html

如有侵犯您的合法权益请发邮件951076433@qq.com联系删除

(0)
php学习php学习订阅用户
上一篇 2022年6月13日 23:28
下一篇 2022年6月13日 23:28

相关推荐

  • php自学教程—php运算符的错误抑制符

    错误抑制符 说明:@,目的屏蔽一些不重要错误。 作用:所有的网站不可能没有bug的时候上线。 执行运算符 说明:``,把数据用反引号括起来后,就是执行了。 递增递减运算符 说明:自己加一或自己减一,符号:++ -- …

    2017年12月6日 PHP自学教程
    0232
  • PHP实现微信小程序抽奖技巧。

    在如今的社交媒体时代中,微信作为国内最为流行的社交工具之一,拥有着庞大的用户基础。而随着微信小程序的兴起,越来越多的企业开始将其作为营销渠道,借助微信小程序开展各类活动,其中抽奖活动便属于较为流行的…

    2023年6月3日
    02
  • 利用PHP8中的str_ends_with()函数判断字符串结尾。

    随着PHP8的发布,新的函数和特性不断推出,其中一个值得注意的函数是str_ends_with()。这个函数可以用来判断一个字符串是否以特定的字符串结尾,这在实际开发中经常会用到。在这篇文章中,我们将探讨str_ends_with(…

    2023年5月21日
    016
  • 如何在PHP中进行第三方组件的使用和集成。

    PHP是一门功能非常丰富的编程语言,开源的特性也使得PHP在Web开发中被广泛应用。作为一个成熟的编程平台,PHP拥有很多优秀的第三方组件,它们可以帮助开发人员更快速、高效地开发出优秀的Web应用。本文将从基础内容…

    2023年5月22日
    01
  • PHP8.0中的时间戳处理库:Chronos

    随着现代互联网应用的日益复杂和对实时性的要求越来越高,时间戳处理成为了一个非常重要的问题。在PHP语言中,时间戳处理一直以来都是一个比较棘手的问题,因为PHP原始的时间函数库存在众多不足和限制。但是,随着P…

    2023年5月19日
    04
  • 十个PHP安全的必备技巧

    你好,PHP 开发人员。 在这篇文章中,我将尝试为你提供一些可以提高 PHP 应用程序安全性的具体步骤。我关注的是 PHP 配置本身,所以我们不会讨论 SQL 注入、HTTPS 或其他与 PHP 无关的问题。我将使用我的docker-ent…

    2022年6月21日
    0118
  • 如何在PHP-Slim框架中使用CORS跨域请求。

    在Web开发中,跨域请求是一个常见的问题。这是因为浏览器对于不同域名之间的请求有严格的限制。例如,网站A的前端代码无法直接向网站B的API发送请求,除非网站B允许跨域请求。为了解决这个问题,出现了CORS(跨域资…

    2023年6月3日
    02
  • 学习php技术的套路

    学习技术的套路 看一个实际的需求 使用现有的技术来解决   使用面向对象的方式来解决 代码来一个快速入门的案例 说明 class 是一个关键字,表示这是一个类,不能修改 public $name , 是一个成员属性, 当我们类…

    2018年4月9日
    0207

联系我们

QQ:951076433

在线咨询:点击这里给我发消息邮件:951076433@qq.com工作时间:周一至周五,9:30-18:30,节假日休息