溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

php中Zend 內(nèi)存管理器是什么

發(fā)布時(shí)間:2020-07-30 10:17:45 來源:億速云 閱讀:119 作者:Leah 欄目:編程語言

這篇文章將為大家詳細(xì)講解有關(guān)php中Zend 內(nèi)存管理器是什么,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

                                               

Zend 內(nèi)存管理器

Zend 內(nèi)存管理器,經(jīng)??s寫為 ZendMMZMM,是一個(gè) C 層,旨在提供分配和釋放動(dòng)態(tài)請(qǐng)求綁定內(nèi)存的能力。

注意上面句子中的“請(qǐng)求綁定”。

ZendMM 不僅僅是 libc 的動(dòng)態(tài)內(nèi)存分配器上的一個(gè)經(jīng)典層,主要由兩個(gè) API 調(diào)用 malloc()/free()表示。ZendMM 是關(guān)于 PHP 在處理請(qǐng)求時(shí)必須分配的請(qǐng)求綁定內(nèi)存。


PHP 中兩種主要的動(dòng)態(tài)內(nèi)存池

PHP 是一個(gè)無共享架構(gòu)。 Well, not at 100%. Let us explain.

注意

在繼續(xù)之前,你可能需要閱讀 PHP 生命周期章節(jié),你將獲得有關(guān) PHP 生命周期中的不同步驟和周期的更多信息。

PHP可以在同一個(gè)進(jìn)程中處理數(shù)百或數(shù)千個(gè)請(qǐng)求。默認(rèn)情況下,PHP 會(huì)在完成當(dāng)前請(qǐng)求后,忘記對(duì)當(dāng)前請(qǐng)求的任何信息。

“忘記” 信息解釋為釋放處理請(qǐng)求時(shí)分配的任何動(dòng)態(tài)緩沖區(qū)。這意味著在處理一個(gè)請(qǐng)求的過程中,不能使用傳統(tǒng)的 libc 調(diào)用來分配動(dòng)態(tài)內(nèi)存。這樣做是完全有效的,但是您給忘記釋放緩沖區(qū)了機(jī)會(huì)。

ZendMM 附帶了一個(gè) API,通過復(fù)制其 API 來替代 libc 的動(dòng)態(tài)分配器。在處理請(qǐng)求的過程中,程序員必須使用該 API 而不是 libc 的分配器。

例如,當(dāng) PHP 處理請(qǐng)求時(shí),它將解析 PHP 文件。例如,那些將導(dǎo)致函數(shù)和類的聲明。當(dāng)編譯器開始編譯 PHP 文件時(shí),它將分配一些動(dòng)態(tài)內(nèi)存來存儲(chǔ)它發(fā)現(xiàn)的類和函數(shù)。但是,在請(qǐng)求結(jié)束時(shí),PHP 會(huì)釋放這些。默認(rèn)情況下,PHP 會(huì)忘記從一個(gè)請(qǐng)求到另一個(gè)請(qǐng)求的大量信息。

然而,存在一些非常罕見的信息,你需要持久地跨越多個(gè)請(qǐng)求。但這并不常見。

什么可以通過請(qǐng)求保持不變?我們所說的持久對(duì)象。再次說明:那是不常見的情況。例如,當(dāng)前的 PHP 可執(zhí)行路徑不會(huì)在請(qǐng)求之間更改。其信息是永久分配的,這意味著它調(diào)用了 傳統(tǒng) libc 的 malloc ()來分配。

還有什么? 一些字符串。例如,“_SERVER” 字符串將在請(qǐng)求之間重用,因?yàn)槊總€(gè)請(qǐng)求都將創(chuàng)建 $_SERVER PHP 數(shù)組。所以 “_SERVER” 字符串本身可以永久分配,因?yàn)樗粫?huì)被分配一次。

你必須記住:

  • 在編寫 PHP 核心或擴(kuò)展時(shí),存在兩種動(dòng)態(tài)內(nèi)存分配方式:

    • 請(qǐng)求綁定的動(dòng)態(tài)分配。
    • 永久動(dòng)態(tài)分配。
  • 請(qǐng)求綁定動(dòng)態(tài)內(nèi)存分配

    • 僅在PHP處理請(qǐng)求時(shí)才執(zhí)行(不在此之前或之后)。
    • 應(yīng)該只使用 ZendMM 動(dòng)態(tài)內(nèi)存分配 API 執(zhí)行。
    • 在擴(kuò)展設(shè)計(jì)中非常常見,基本上95%的動(dòng)態(tài)分配都是請(qǐng)求綁定的。
    • 由 ZendMM 追蹤,并會(huì)通知你有關(guān)泄漏的信息。
  • 永久動(dòng)態(tài)內(nèi)存分配

    • 不應(yīng)該在PHP處理請(qǐng)求時(shí)執(zhí)行(這不是禁止的,但是是一個(gè)壞主意)。
    • 不會(huì)被 ZendMM 追蹤,你也不會(huì)被告知泄漏。
    • 在擴(kuò)展中應(yīng)該很少見。

另外,請(qǐng)記住,所有 PHP 源代碼都基于這種內(nèi)存級(jí)別。因此,許多內(nèi)部結(jié)構(gòu)使用 Zend 內(nèi)存管理器進(jìn)行分配。大多數(shù)都調(diào)用了一個(gè)“持久的” API,當(dāng)調(diào)用這個(gè)時(shí),將導(dǎo)致傳統(tǒng)的 libc 分配。

這是一個(gè)請(qǐng)求綁定的分配 zend_string:

zend_string *foo = zend_string_init("foo", strlen("foo"), 0);

這是持久分配的:

zend_string *foo = zend_string_init("foo", strlen("foo"), 1);

同樣的 HashTable。
請(qǐng)求綁定分配:

zend_array ar;
zend_hash_init(&ar, 8, NULL, NULL, 0);

持久分配:

zend_array ar;
zend_hash_init(&ar, 8, NULL, NULL, 1);

在所有不同的 Zend API中,它始終是相同的。通常是作為最后一個(gè)參數(shù)傳遞的,“0”表示“我希望使用 ZendMM 分配此結(jié)構(gòu),因此請(qǐng)求綁定”,或“1”表示“我希望通過 ZendMM 調(diào)用傳統(tǒng)的 libc 的malloc()分配此結(jié)構(gòu)”。

顯然,這些結(jié)構(gòu)提供了一個(gè) API,該 API 會(huì)記住它如何分配結(jié)構(gòu),以便在銷毀時(shí)使用正確的釋放函數(shù)。因此,在這樣的代碼中:

zend_string_release(foo);
zend_hash_destroy(&ar);

API 知道這些結(jié)構(gòu)是使用請(qǐng)求綁定分配還是永久分配的,第一種情況將使用efree()釋放它,第二種情況是libc的free()。

Zend 內(nèi)存管理器 API

該 API 位于 Zend/zend_alloc.h

API 主要是 C 宏,而不是函數(shù),因此,如果你調(diào)試它們并想了解它們的工作原理,請(qǐng)做好準(zhǔn)備。這些 API 復(fù)制了 libc 的函數(shù),通常在函數(shù)名稱中添加“e”;因此,你不應(yīng)該認(rèn)錯(cuò),關(guān)于該API的細(xì)節(jié)不多。

基本上,你最常使用的是 emalloc(size_t)efree(void *)。

還提供了ecalloc(size_t nmemb,size_t size),它分配單個(gè)大小sizenmemb,并將區(qū)域歸零。如果你是一位經(jīng)驗(yàn)豐富的 C 程序員,那么你應(yīng)該知道,只要有可能,最好在emalloc()上使用ecalloc(),因?yàn)?code>ecalloc()會(huì)將內(nèi)存區(qū)域清零,這在指針錯(cuò)誤檢測(cè)中可能會(huì)有很大幫助。請(qǐng)記住,emalloc()的工作原理基本上與libc malloc()一樣:它將在不同的池中尋找足夠大的區(qū)域,并為你提供最合適的空間。因此,你可能會(huì)得到一個(gè)指向垃圾的回收指針。

然后是 safe_emalloc(size_t nmemb,size_t size,size_t offset),這是emalloc(size * nmemb + offset),但它會(huì)為你檢查溢出情況。如果必須提供的數(shù)字來自不受信任的來源(例如用戶區(qū)),則應(yīng)使用此API調(diào)用。

關(guān)于字符串,estrdup(char *)estrndup(char *, size_t len) 允許復(fù)制字符串或二進(jìn)制字符串。

無論發(fā)生什么,ZendMM 返回的指針必須調(diào)用 ZendMM 的efree() 釋放,而不是 libc 的 free()。

注意

關(guān)于持久分配的說明。持久分配在請(qǐng)求之間保持有效。你通常使用常見的 libc malloc/ free 來執(zhí)行此操作,但是 ZendMM 有一些 libc 分配器的快捷方式:“持久” API。該 API以“p” 字母開頭,讓你在 ZendMM 分配或持久分配之間進(jìn)行選擇。因此pemalloc(size_t, 1)不過是malloc(),pefree(void *, 1)free() ,pestrdup(void *, 1)strdup()。只是說。

Zend 內(nèi)存管理器調(diào)試盾

ZendMM 提供以下功能:

  • 內(nèi)存消耗管理。
  • 內(nèi)存泄漏跟蹤和自動(dòng)釋放。
  • 通過預(yù)分配已知大小的緩沖區(qū)并保持空閑狀態(tài)下的熱緩存來加快分配速度

內(nèi)存消耗管理

ZendMM 是 PHP 用戶區(qū)“memory_limit”功能的底層。使用 ZendMM 層分配的每單個(gè)字節(jié)都會(huì)被計(jì)數(shù)并相加。當(dāng)達(dá)到 INI 的 memory_limit 后,你知道會(huì)發(fā)生什么。這也意味著通過 ZendMM 執(zhí)行的任何分配都反映在 PHP 用戶區(qū)的memory_get_usage()中。

作為擴(kuò)展開發(fā)人員,這是一件好事,因?yàn)樗兄谡莆?PHP 進(jìn)程的堆大小。

如果啟動(dòng)了內(nèi)存限制錯(cuò)誤,則引擎將從當(dāng)前代碼位置釋放到捕獲塊,然后平穩(wěn)終止。但是它不可能回到超出限制的代碼位置。你必須為此做好準(zhǔn)備。

從理論上講,這意味著 ZendMM 無法向你返回 NULL 指針。如果從操作系統(tǒng)分配失敗,或者分配產(chǎn)生內(nèi)存限制錯(cuò)誤,則代碼將運(yùn)行到 catch 塊中,并且不會(huì)返回到你的分配調(diào)用。

如果出于任何原因需要繞過該保護(hù),則必須使用傳統(tǒng)的 libc 調(diào)用,例如malloc()。無論如何請(qǐng)小心,并且知道你在做什么。如果使用 ZendMM,可能需要分配大量?jī)?nèi)存并可能超出 PHP 的 memory_limit。因此,請(qǐng)使用另一個(gè)分配器(如libc),但要注意:你的擴(kuò)展將增加當(dāng)前進(jìn)程堆的大小。在 PHP 中不能看到 memory_get_usage(),但是可以通過使用 OS 設(shè)施分析當(dāng)前堆(如/proc/{pid}/maps

注意

如果需要完全禁用 ZendMM,則可以使用USE_ZEND_ALLOC = 0環(huán)境變量啟動(dòng)PHP。這樣,每次對(duì) ZendMM API的調(diào)用(例如emalloc())都將定向到 libc 調(diào)用,并且  ZendMM 將被禁用。這在調(diào)試內(nèi)存的情況下尤其有用。

內(nèi)存泄漏追蹤

請(qǐng)記住 ZendMM 的主要規(guī)則:它在請(qǐng)求啟動(dòng)時(shí)啟動(dòng),然后在你處理請(qǐng)求時(shí)需要?jiǎng)討B(tài)內(nèi)存時(shí)期望你調(diào)用其API。當(dāng)前請(qǐng)求結(jié)束時(shí),ZendMM 關(guān)閉。

通過關(guān)閉,它將瀏覽其所有活動(dòng)指針,如果使用 PHP 的調(diào)試構(gòu)建,它將警告你有關(guān)內(nèi)存泄漏的信息。

讓我們解釋得更清楚一些:如果在當(dāng)前請(qǐng)求結(jié)束時(shí),ZendMM 找到了一些活動(dòng)的內(nèi)存塊,則意味著這些內(nèi)存塊正在泄漏。請(qǐng)求結(jié)束時(shí),ZendMM 堆上不應(yīng)存在任何活動(dòng)內(nèi)存塊,因?yàn)榉峙淞四承﹥?nèi)存的任何人都應(yīng)該釋放了它們。

如果您忘記釋放塊,它們將全部顯示在 stderr上。此內(nèi)存泄漏報(bào)告進(jìn)程僅在以下情況下有效:

  • 你正在使用 PHP 的調(diào)試構(gòu)建
  • 在 php.ini 中具有 report_memleaks = On(默認(rèn))

這是一個(gè)簡(jiǎn)單泄漏到擴(kuò)展中的示例:

PHP_RINIT_FUNCTION(example)
{
    void *foo = emalloc(128);
}

在啟動(dòng)該擴(kuò)展的情況下啟動(dòng) PHP,在調(diào)試版本上會(huì)在 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 ===

當(dāng) Zend 內(nèi)存管理器關(guān)閉時(shí),在每個(gè)已處理請(qǐng)求的末尾,將生成這些行。

但是要當(dāng)心:

  • 顯然,ZendMM 對(duì)持久分配或以不同于使用持久分配的方式執(zhí)行的分配一無所知。因此,ZendMM 只能警告你有關(guān)它知道的分配信息,在這里不會(huì)報(bào)告每個(gè)傳統(tǒng)的 libc 分配信息。
  • 如果 PHP 以錯(cuò)誤的方式關(guān)閉(我們稱之為不正常關(guān)閉),ZendMM 將報(bào)告大量泄漏。這是因?yàn)橐嬖阱e(cuò)誤關(guān)閉時(shí)會(huì)使用longjmp()調(diào)用 catch 塊,防止清理所有內(nèi)存的代碼運(yùn)行。因此,許多泄漏得到報(bào)告。尤其是在調(diào)用 PHP 的 exit()/ die()之后,或者在 PHP 的某些關(guān)鍵部分觸發(fā)了致命錯(cuò)誤時(shí),就會(huì)發(fā)生這種情況。
  • 如果你使用非調(diào)試版本的 PHP,則 stderr 上不會(huì)顯示任何內(nèi)容,ZendMM 是愚蠢的,但仍會(huì)清除程序員尚未明確釋放的所有分配的請(qǐng)求綁定緩沖區(qū)

你必須記住的是 ZendMM 泄漏跟蹤是一個(gè)不錯(cuò)的獎(jiǎng)勵(lì)工具,但它不能代替真正的 C 內(nèi)存調(diào)試器。

ZendMM 內(nèi)部設(shè)計(jì)

常見錯(cuò)誤和錯(cuò)誤

這是使用 ZendMM 時(shí)最常見的錯(cuò)誤,以及你應(yīng)該怎么做。

  1. 不處理請(qǐng)求時(shí)使用 ZendMM。

獲取有關(guān) PHP 生命周期的信息,以了解在擴(kuò)展中何時(shí)處理請(qǐng)求,何時(shí)不處理。如果在請(qǐng)求范圍之外使用 ZendMM(例如在MINIT()中),在處理第一個(gè)請(qǐng)求之前,ZendMM 會(huì)靜默清除分配,并且可能會(huì)使用after-after-free:根本沒有。

  1. 緩沖區(qū)上溢和下溢。

使用內(nèi)存調(diào)試器。如果你在 ZendMM 返回的內(nèi)存區(qū)域以下或過去寫入內(nèi)容,則將覆蓋關(guān)鍵的 ZendMM 結(jié)構(gòu)并觸發(fā)崩潰。如果 ZendMM 能夠?yàn)槟銠z測(cè)到混亂,則可能會(huì)顯示“zend_mm_heap損壞”的消息。堆棧追蹤將顯示從某些代碼到某些 ZendMM 代碼的崩潰。ZendMM 代碼不會(huì)自行崩潰。如果你在 ZendMM 代碼中間崩潰,那很可能意味著你在某個(gè)地方弄亂了指針。插入你喜歡的內(nèi)存調(diào)試器,查找有罪的部分并進(jìn)行修復(fù)。

  1. 混合 API 調(diào)用

如果分配一個(gè) ZendMM 指針(即emalloc())并使用 libc 釋放它(free()),或相反的情況:你將崩潰。要嚴(yán)謹(jǐn)對(duì)待。另外,如果你將其不知道的任何指針傳遞給 ZendMM 的efree():將會(huì)崩潰。

關(guān)于php中Zend 內(nèi)存管理器是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI