您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)php中Zend 內(nèi)存管理器是什么,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
Zend 內(nèi)存管理器,經(jīng)??s寫為 ZendMM 或 ZMM,是一個(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 是一個(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)內(nèi)存分配
永久動(dòng)態(tài)內(nèi)存分配
另外,請(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()
。
該 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è)大小size
的nmemb
,并將區(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()
。只是說。
ZendMM 提供以下功能:
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)存的情況下尤其有用。
請(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)程僅在以下情況下有效:
這是一個(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 泄漏跟蹤是一個(gè)不錯(cuò)的獎(jiǎng)勵(lì)工具,但它不能代替真正的 C 內(nèi)存調(diào)試器。
這是使用 ZendMM 時(shí)最常見的錯(cuò)誤,以及你應(yīng)該怎么做。
獲取有關(guān) PHP 生命周期的信息,以了解在擴(kuò)展中何時(shí)處理請(qǐng)求,何時(shí)不處理。如果在請(qǐng)求范圍之外使用 ZendMM(例如在MINIT()中),在處理第一個(gè)請(qǐng)求之前,ZendMM 會(huì)靜默清除分配,并且可能會(huì)使用after-after-free:根本沒有。
使用內(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ù)。
如果分配一個(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ò),可以把它分享出去讓更多的人看到。
免責(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)容。