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)
上一篇 2022年6月13日 23:28
下一篇 2022年6月13日 23:28

相关推荐

  • PHP中str_replace高级使用你知道吗?

    “str_replace高级使用你应该了解一下”在阅读PHP框架ThinkPHP源码的过程中有很多方法的冷门使用,也就是不常用的使用方法。这里咔咔先对str_replace这个方法进行解析,这个方法也就是替换字符串中的一些字符(区分...

    2022年6月25日 PHP自学教程
    0122
  • PHP中如何进行即时聊天开发。

    随着即时通讯技术的不断发展,越来越多的网站和应用程序开始提供即时聊天功能。对于一个有经验的开发者来说,开发一个即时聊天功能似乎并不是很困难。本文将探讨如何在PHP中进行即时聊天开发,帮助初学者和有经验...

    2023年5月23日
    01
  • PHP实现Redis主从复制自动切换的方法。

    Redis是一款非常流行的键值对存储数据库,在现代Web应用中扮演着至关重要的角色。为了保证Redis的高可用性和稳定性,通常需要使用主从复制的技术。同时,由于主节点故障时需要自动切换到备用节点,使得复制集群具...

    2023年5月21日
    05
  • 自学PHP的绘图具体演示(附代码)

    (1)绘制线条  imageline() (2)绘制三角形:imageline() (3)绘制矩形:imagerectangle()、imagefilledrectangle() 描边矩形:imagerectangle() 填充矩形:imagefilledrectangle() (4)绘制圆形 和 椭圆形:...

    2018年9月1日 PHP自学教程
    0461
  • 如何用PHP获取referer判断来路防止非法访问

    本篇文章给大家介绍如何用PHP获取referer判断来路防止非法访问?有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。下载页面down.php 的php代码 现在我发现,用迅雷,谷歌浏览器直接打开,就能输...

    2023年3月29日
    06
  • PHP入门指南:PHP和Spark。

    PHP是一种非常流行的服务器端编程语言,因为它简单易学、开放源代码和跨平台。目前,很多大企业都采用PHP语言来构建应用程序,例如Facebook和WordPress等。Spark是一种快速且轻量级的开发框架,可用于构建Web应用...

    2023年5月22日
    04
  • PHP可变变量的理解

    可变变量 所谓可变变量,就是一个变量的名,又是一个变量。 可变变量的语法是php的很特殊的语法——其他语言中少见。 $v1 = “abc”;          //这是一个字符串变量,其内容是字符串“abc” $abc = 10;             //...

    2017年11月6日
    0454
  • PHP入门指南:共享内存。

    在Web开发领域中,PHP是一种非常流行的编程语言。它被广泛应用于动态网站的开发,也用于开发各种类型的框架和应用程序。在这个快速发展的行业中,学习PHP编程语言可以为您打开许多机会。在本篇文章中,我们将分享...

    2023年5月22日
    02

联系我们

QQ:951076433

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