您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關php中Zend 內存管理器是什么,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
Zend 內存管理器,經常縮寫為 ZendMM 或 ZMM,是一個 C 層,旨在提供分配和釋放動態請求綁定內存的能力。
注意上面句子中的“請求綁定”。
ZendMM 不僅僅是 libc 的動態內存分配器上的一個經典層,主要由兩個 API 調用 malloc()/free()
表示。ZendMM 是關于 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 源代碼都基于這種內存級別。因此,許多內部結構使用 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()
。
該 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()
。只是說。
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_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 泄漏跟蹤是一個不錯的獎勵工具,但它不能代替真正的 C 內存調試器。
這是使用 ZendMM 時最常見的錯誤,以及你應該怎么做。
獲取有關 PHP 生命周期的信息,以了解在擴展中何時處理請求,何時不處理。如果在請求范圍之外使用 ZendMM(例如在MINIT()中),在處理第一個請求之前,ZendMM 會靜默清除分配,并且可能會使用after-after-free:根本沒有。
使用內存調試器。如果你在 ZendMM 返回的內存區域以下或過去寫入內容,則將覆蓋關鍵的 ZendMM 結構并觸發崩潰。如果 ZendMM 能夠為你檢測到混亂,則可能會顯示“zend_mm_heap損壞”的消息。堆棧追蹤將顯示從某些代碼到某些 ZendMM 代碼的崩潰。ZendMM 代碼不會自行崩潰。如果你在 ZendMM 代碼中間崩潰,那很可能意味著你在某個地方弄亂了指針。插入你喜歡的內存調試器,查找有罪的部分并進行修復。
如果分配一個 ZendMM 指針(即emalloc()
)并使用 libc 釋放它(free()
),或相反的情況:你將崩潰。要嚴謹對待。另外,如果你將其不知道的任何指針傳遞給 ZendMM 的efree()
:將會崩潰。
關于php中Zend 內存管理器是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。