>  기사  >  백엔드 개발  >  PHP용 Zend 메모리 관리자

PHP용 Zend 메모리 관리자

coldplay.xixi
coldplay.xixi앞으로
2020-07-29 17:14:152307검색
zndmm

또는 PHP용 Zend 메모리 관리자zmm

로 약칭)는 동적 역학을 배포하고 해제하는 기능을 제공하는 것을 목표로 하는 C 계층입니다.

위 문장의 "요청 바인딩"에 주의하세요.

ZendMM은 주로 두 개의 API 호출 malloc()/free()로 표현되는 libc의 동적 메모리 할당자 위에 있는 고전적인 레이어가 아닙니다. ZendMM은 요청을 처리할 때 PHP가 할당해야 하는 요청 바인딩 메모리에 관한 것입니다.

관련 학습 권장사항: 초보부터 마스터까지 PHP 프로그래밍

PHP의 두 가지 주요 동적 메모리 풀 유형

PHP는 비공유 아키텍처입니다. 글쎄요, 100%는 아닙니다. 설명하겠습니다.
Note

계속 진행하기 전에 PHP 수명 주기 장을 읽어보면 PHP 수명 주기의 다양한 단계와 주기에 대한 자세한 정보를 얻을 수 있습니다.

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 核心或扩展时,存在两种动态内存分配方式:

    • 请求绑定的动态分配。
    • 永久动态分配。
  • 请求绑定动态内存分配

    • 仅在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()PHP는 동일한 프로세스에서 수백 또는 수천 개의 요청을 처리할 수 있습니다. 기본적으로 PHP는 요청이 완료된 후 현재 요청에 대한 모든 정보를 잊어버립니다.

🎜"forget" 메시지는 요청을 처리하는 동안 할당된 동적 버퍼를 해제하는 것으로 해석됩니다. 이는 요청을 처리하는 동안 동적 메모리를 할당하기 위해 기존 libc 호출을 사용할 수 없음을 의미합니다. 이것은 완벽하게 유효하지만 버퍼를 해제하는 것을 잊어버릴 기회를 스스로에게 제공합니다. 🎜🎜ZendMM에는 API를 복사하여 libc의 동적 할당자를 대체하는 API가 함께 제공됩니다. 프로그래머는 요청을 처리할 때 libc 할당자 대신 이 API를 사용해야 합니다. 🎜🎜예를 들어, PHP가 요청을 처리하면 PHP 파일을 구문 분석합니다. 예를 들어, 함수 및 클래스 선언이 발생합니다. 컴파일러가 PHP 파일 컴파일을 시작하면 발견한 클래스와 함수를 저장하기 위해 일부 동적 메모리를 할당합니다. 그러나 PHP는 요청이 끝나면 이를 릴리스합니다. 기본적으로 PHP는 한 요청에서 다른 요청까지 많은 정보를 잊어버립니다. 🎜🎜그러나 여러 요청에 걸쳐 유지해야 하는 매우 드문 정보가 있습니다. 그러나 이것은 흔한 일이 아닙니다. 🎜🎜요청에 따라 변경되지 않고 유지될 수 있는 것은 무엇인가요? 우리는 이를 🎜지속적🎜객체라고 부릅니다. 다시 말하지만, 이는 일반적인 상황이 아닙니다. 예를 들어 현재 PHP 실행 파일 경로는 요청 간에 변경되지 않습니다. 해당 정보는 영구적으로 할당됩니다. 이는 이를 할당하기 위해 기존 libc의 malloc()를 호출한다는 의미입니다. 🎜🎜또 뭐가 있나요? 어떤 문자열. 예를 들어, 각 요청에 대해 $_SERVER PHP 배열이 생성되므로 🎜"_SERVER"🎜 문자열은 요청 간에 재사용됩니다. 따라서 🎜"_SERVER"🎜 문자열 자체는 한 번만 할당되므로 영구적으로 할당할 수 있습니다. 🎜🎜기억해야 할 사항: 🎜
  • 🎜PHP 코어 또는 확장을 작성할 때 동적 메모리 할당에는 두 가지 방법이 있습니다: 🎜
    • 요청 바인딩을 통한 동적 할당.
    • 영구 동적 할당.
  • 🎜요청 바인딩 동적 메모리 할당🎜
    • PHP가 요청을 처리할 때만 실행됩니다(이전이나 이후가 아님).
    • ZendMM 동적 메모리 할당 API를 통해서만 수행되어야 합니다.
    • 확장 설계에서는 매우 일반적입니다. 기본적으로 동적 할당의 95%는 요청 바인딩입니다.
    • ZendMM에 의해 추적되며 누출에 대해 알려드립니다.
  • 🎜영구적인 동적 메모리 할당🎜
    • PHP가 요청을 처리하는 동안 수행하면 안 됩니다(금지되는 것은 아니지만 나쁜 생각입니다).
    • ZendMM은 추적하지 않으며 유출에 대한 알림을 받지 않습니다.
    • 확장 프로그램에서는 드물어야 합니다.
🎜또한 모든 PHP 소스 코드는 이 메모리 수준을 기반으로 한다는 점을 기억하세요. 따라서 Zend 메모리 관리자를 사용하여 많은 내부 구조가 할당됩니다. 대부분은 호출 시 전통적인 libc 할당이 발생하는 "지속적" API를 호출합니다. 🎜🎜요청에 따른 할당입니다. zend_string:🎜
PHP_RINIT_FUNCTION(example)
{
    void *foo = emalloc(128);
}
🎜영구 할당입니다:🎜
[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 ===
🎜동일한 해시 테이블입니다.
요청 바인딩 할당: 🎜rrreee🎜지속적 할당: 🎜rrreee🎜 모든 다른 Zend API에서 항상 동일합니다. 일반적으로 마지막 매개변수로 전달되는 🎜"0"🎜은 "ZendMM을 사용하여 이 구조를 할당하고 싶으므로 바인딩을 요청합니다"를 의미하거나 🎜"1"🎜은 "ZendMM malloc( )이 구조를 할당합니다." 🎜🎜분명히 이러한 구조는 파괴될 때 올바른 할당 해제 기능이 사용되도록 구조를 할당한 방법을 기억하는 API를 제공합니다. 따라서 다음과 같은 코드에서: 🎜rrreee🎜API는 이러한 구조가 요청 바인딩을 사용하여 할당되었는지 아니면 영구적으로 할당되었는지를 알고 있으며, 첫 번째 경우는 efree()를 사용하여 이를 해제하고 두 번째 경우 libc의 무료(). 🎜

Zend Memory Manager API

API는 Zend/zend_alloc.h에 있습니다

API는 함수가 아닌 대부분 C 매크로이므로 디버깅하고 작동 방식을 이해하고 싶다면 준비하세요. 이러한 API는 종종 함수 이름에 "e"가 추가된 libc의 함수를 복사하므로 실수해서는 안 됩니다. API에 대한 세부 정보가 많지 않습니다.

기본적으로 가장 일반적으로 사용하는 것은 emaloc(size_t)efree(void *)입니다. 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

size 크기의 단일 nmemb를 할당하고 영역을 0으로 만드는 ecalloc(size_t nmemb, size_t size)도 제공됩니다. 숙련된 C 프로그래머라면 가능하면 ecalloc() 대신 ecalloc()을 사용하는 것이 가장 좋다는 점을 알아야 합니다. ecalloc()는 메모리 영역을 0으로 만들어 포인터 오류 감지에 매우 도움이 될 수 있습니다. <code>emaloc()은 기본적으로 libc malloc()과 동일하게 작동한다는 점을 명심하세요. 다른 풀에서 충분히 큰 영역을 찾아 이를 제공합니다. 적당한 공간. 따라서 가비지 수집 포인터가 발생할 수 있습니다.

그 다음에는 emalloc(size * nmemb + offset)safe_emalloc(size_t nmemb, size_t size, size_t offset)이 있지만 오버플로를 자동으로 확인합니다. 이 API 호출은 제공해야 하는 번호가 신뢰할 수 없는 소스(예: userland)에서 나온 경우 사용해야 합니다.

문자열의 경우 estrdup(char *)estrndup(char *, size_t len)을 사용하면 문자열이나 바이너리 문자열을 복사할 수 있습니다.

무슨 일이 일어나더라도 ZendMM이 반환한 포인터는 libc의 free()가 아닌이 아닌 ZendMM의 efree()를 호출하여 해제되어야 합니다.

참고

🎜영구 할당에 대한 참고 사항입니다. 내구성 있는 할당은 요청 간에 유효하게 유지됩니다. 이를 수행하려면 일반적으로 일반적인 libc malloc/ free를 사용하지만 ZendMM에는 libc 할당자에 대한 몇 가지 지름길인 "영구" API가 있습니다. API는 문자 "p"로 시작하며 ZendMM 할당 또는 영구 할당 중에서 선택할 수 있습니다. 따라서 pemalloc(size_t, 1)malloc()이고 pefree(void *, 1)free() 코드>, <code>pestrdup(void *, 1)strdup()입니다. 그냥 말하는 거야. 🎜🎜🎜Zend Memory Manager Debug Shield🎜🎜ZendMM은 다음 기능을 제공합니다: 🎜
  • 메모리 소비 관리.
  • 메모리 누수 추적 및 자동 해제.
  • 알려진 크기의 버퍼를 사전 할당하고 핫 캐시를 유휴 상태로 유지하여 할당 속도를 높입니다.
🎜메모리 소비 관리🎜🎜ZendMM은 PHP 사용자 영역 "memory_limit" 기본 기능입니다. . ZendMM 레이어를 사용하여 할당된 모든 단일 바이트가 계산되고 합산됩니다. INI의 memory_limit에 도달하면 어떤 일이 발생하는지 알 수 있습니다. 이는 또한 ZendMM을 통해 수행된 모든 할당이 PHP 사용자 영역의 memory_get_usage()에 반영된다는 의미이기도 합니다. 🎜🎜확장 개발자로서 이는 PHP 프로세스의 힙 크기를 추적하는 데 도움이 되기 때문에 좋은 것입니다. 🎜🎜메모리 제한 오류가 시작되면 엔진은 현재 코드 위치에서 캡처 블록으로 해제된 다음 정상적으로 종료됩니다. 하지만 한도를 초과한 코드 위치로는 다시 돌아갈 수 없습니다. 이에 대비해야 합니다. 🎜🎜이론적으로 이는 ZendMM이 NULL 포인터를 사용자에게 반환할 수 없음을 의미합니다. 운영 체제의 할당이 실패하거나 할당으로 인해 메모리 제한 오류가 발생하는 경우 코드는 catch 블록으로 실행되고 할당 호출로 돌아가지 않습니다. 🎜🎜어떤 이유로든 이 보호를 우회해야 하는 경우 malloc()과 같은 기존 libc 호출을 사용해야 합니다. 어쨌든 조심하고 무엇을 하고 있는지 알아두시기 바랍니다. ZendMM을 사용하는 경우 많은 양의 메모리를 할당해야 할 수 있으며 PHP의 memory_limit를 초과할 수 있습니다. 따라서 다른 할당자(예: libc)를 사용하되 주의하세요. 확장을 사용하면 현재 프로세스 힙 크기가 늘어납니다. memory_get_usage()는 PHP에서 볼 수 없지만 OS 기능(예: /proc/{pid}/maps)을 사용하여 현재 힙을 분석할 수 있습니다🎜
🎜참고 🎜🎜ZendMM을 완전히 비활성화해야 하는 경우 USE_ZEND_ALLOC=0 환경 변수를 사용하여 PHP를 시작할 수 있습니다. 이렇게 하면 ZendMM API(예: emalloc())에 대한 모든 호출이 libc 호출로 연결되고 ZendMM이 비활성화됩니다. 이는 메모리 디버깅의 경우 특히 유용합니다. 🎜🎜🎜메모리 누수 추적🎜🎜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:  &#39;/tmp/foobar.php&#39;
/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 메모리 관리자의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 learnku.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제