作者:有你真好-LOVE | 来源:互联网 | 2020-09-28 06:56
请求绑定分配:
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)
,它分配单个大小size
的nmemb
,并将区域归零。如果你是一位经验丰富的 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 时最常见的错误,以及你应该怎么做。
- 不处理请求时使用 ZendMM。
获取有关 PHP 生命周期的信息,以了解在扩展中何时处理请求,何时不处理。如果在请求范围之外使用 ZendMM(例如在MINIT()中),在处理第一个请求之前,ZendMM 会静默清除分配,并且可能会使用after-after-free:根本没有。
- 缓冲区上溢和下溢。
使用内存调试器。如果你在 ZendMM 返回的内存区域以下或过去写入内容,则将覆盖关键的 ZendMM 结构并触发崩溃。如果 ZendMM 能够为你检测到混乱,则可能会显示“zend_mm_heap损坏”的消息。堆栈追踪将显示从某些代码到某些 ZendMM 代码的崩溃。ZendMM 代码不会自行崩溃。如果你在 ZendMM 代码中间崩溃,那很可能意味着你在某个地方弄乱了指针。插入你喜欢的内存调试器,查找有罪的部分并进行修复。
- 混合 API 调用
如果分配一个 ZendMM 指针(即emalloc()
)并使用 libc 释放它(free()
),或相反的情况:你将崩溃。要严谨对待。另外,如果你将其不知道的任何指针传递给 ZendMM 的efree()
:将会崩溃。
以上就是php之 Zend 内存管理器的详细内容,更多请关注 第一PHP社区 其它相关文章!