@@ -220,10 +220,139 @@ Beware however:
220220What you must remember is that ZendMM leak tracking is a nice bonus tool to have, but it does not replace a
221221:doc: `true C memory debugger <./memory_debugging >`.
222222
223+ Lifecycle
224+ *********
225+
226+ PHP will call the ``start_memory_manager() `` function during its startup phase, specifically when the PHP process is
227+ started (for instance, when the PHP-FPM service is started, or when a PHP CLI script is run). This will allocate the
228+ heap and the first chunk.
229+
230+ During a request the ZendMM will allocate chunks as needed.
231+
232+ On every request shutdown (during the ``RSHUTDOWN `` phase), the Zend Engine will call the ``shutdown_memory_manager() ``
233+ function (which calls the ``zend_mm_shutdown() `` function) with the boolean argument ``full `` set to ``false ``. This
234+ will cleanup for the next request, but not do a full shutdown of the memory manager. For example it will not free the
235+ heap and keep the average amount of chunks used during the current request alive in the ``cached_chunks `` pointer on the
236+ heap to be reused in the next request.
237+
238+ In the module shutdown phase (``MSHUTDOWN ``) the Zend Engine will call the ``shutdown_memory_manager() `` function (which
239+ calls the ``zend_mm_shutdown() `` function) with the boolean argument ``full `` set to ``true ``, which will trigger a full
240+ shutdown and free all cached chunks as well as the heap itself.
241+
223242ZendMM internal design
224243**********************
225244
226- .. todo :: todo
245+ The root of the ZendMM is the ``_zend_mm_heap `` struct (as defined in `Zend/zend_alloc.c
246+ <https://github.com/php/php-src/blob/c3b910370c5c92007c3e3579024490345cb7f9a7/Zend/zend_alloc.c#L239> `__) which will be
247+ created for every request during request init and stored in the ``alloc_globals->mm_heap ``. This heap also comes with
248+ the first chunk that is allocated with it. Chunks are then subdivided into pages. Smaller allocations are stored in bins
249+ which may fit into one page but some also span multiple pages.
250+
251+ Internal memory organisation
252+ ---------------------------
253+
254+ Heap
255+ ++++
256+
257+ The heap, as defined in the struct ``_zend_mm_heap ``, holds links to chunks (``main_chunk `` and ``cached_chunks ``, for
258+ small and large allocations), ``huge_list `` for huge allocations (>= 2MB) and to bins (for small allocations) in
259+ ``free_slots[BIN] ``. After initialisation only the ``main_chunk `` exists and none or some ``cached_chunks ``.
260+
261+ Chunks
262+ ++++++
263+
264+ Each chunk is 2 MB in size and consists of 512 pages. The first page of every chunk is reserved for the chunk header as
265+ defined in the struct ``_zend_mm_chunk `` (as defined in `Zend/zend_alloc.c
266+ <https://github.com/php/php-src/blob/c3b910370c5c92007c3e3579024490345cb7f9a7/Zend/zend_alloc.c#L286> `__). Chunks are
267+ organised in a linked list with ``prev `` and ``next `` pointers.
268+
269+ Each chunk holds a bit mask in ``free_map `` (512 bits) where a single bit indicates if a page is in use or free.
270+ Information on what is in a page is stored in ``map `` which is an array of 512 32 bit integers. Each of those integers
271+ is used as a bitmap and holds the meta information about that page.
272+
273+ Pages
274+ +++++
275+
276+ A page is 4096 bytes in size and can either hold a bin (for small allocations) or be part of a large allocation. What is
277+ in it can be found in the map of the chunk the page belongs to.
278+
279+ Bins
280+ ++++
281+
282+ Small allocations are grouped together in bins. Bin sizes are predefined and come in 30 different sizes (8, 16, 24, 32,
283+ ... 3072 bytes). A bin holds same sized values and is linked from the heap directly.
284+
285+ A bin can consist of multiple pages. Example: There exists a bin that holds elements ranging from 257 bytes to 320 bytes
286+ which occupies 5 pages, and therefore has room for 64 (derived from 4096*5/320) elements of that size.
287+
288+ Allocation categories
289+ ---------------------
290+
291+ Small allocations
292+ +++++++++++++++++
293+
294+ Allocations less or equal than 3072 bytes are organised in bins.
295+
296+ If a bin is already initialised, the ``free_slot `` pointer on the ``zend_mm_heap `` struct is the address to be used
297+ (this address will be returned by the call to ``emalloc() `` and will be incremented to point to the next free slot, see
298+ implementation in ``zend_mm_alloc_small ``).
299+
300+ If the bin for this specific size is not initialised already, it will be created in the ``zend_mm_alloc_small_slow ``
301+ function and a pointer to the first element of the bin is returned.
302+
303+ Large allocations
304+ +++++++++++++++++
305+
306+ Allocations bigger than 3072 bytes, but small enough to fit in a chunk (2 MB chunk size - 4096 bytes chunk header (first
307+ page) makes 2093056 bytes) are directly stored in the pages. The first page will be marked ``LRUN `` in the map of the
308+ chunk and also hold the number of allocated pages.
309+
310+ Huge allocations
311+ ++++++++++++++++
312+
313+ If an allocation is larger than the chunk size minus one page (2 MB chunk size - 4096 bytes chunk header (first page)
314+ makes 2093056 bytes) the memory is allocated using ``mmap() `` and put on the ``huge_list `` linked list on the heap.
315+
316+ Hooking into the ZendMM
317+ ***********************
318+
319+ You can call the ``zend_mm_set_custom_handlers() `` function and give it pointers to your ``malloc ``, ``free `` and
320+ ``realloc `` handlers as well as your custom heap or the current heap that you may fetch via ``zend_mm_get_heap() ``.
321+
322+ .. code-block :: c
323+
324+ void* my_malloc(size_t len) {
325+ return malloc(len);
326+ }
327+
328+ void my_free(void* ptr) {
329+ free(ptr);
330+ }
331+
332+ void* my_realloc(void* ptr, size_t len) {
333+ return realloc(ptr, len);
334+ }
335+
336+ PHP_MINIT_FUNCTION(my_extension) {
337+ zend_mm_set_custom_handlers(
338+ zend_mm_get_heap(),
339+ my_malloc,
340+ my_free,
341+ my_realloc
342+ );
343+ return SUCCESS;
344+ }
345+
346+ You may also bring your own heap and inject it via ``zend_mm_set_heap() `` which returns a pointer to the current (or
347+ old) heap. Beware that on a heap with custom handlers, ZendMM's behaviour will be different:
348+
349+ * ZendMM will not run cleanup during ``zend_mm_shutdown() `` (which is called during PHP request shutdown phase), leaving
350+ you with a memory leak if your custom handlers just forward calls to the ZendMM internal functions.
351+ * ZendMM's garbage collector implemented in ``zend_mm_gc() `` will not be doing anything. This also means it will not try
352+ to free chunks in case you reach the memory limit during an allocation in one of the ZendMM internal functions.
353+ * The only way to detect that a full shutdown is in progress in your heap with custom handlers is that your ``free ``
354+ function will be called with the address of your heap.
355+ * There is no chance of knowing when ``zend_mm_shutdown() `` will perform a request shutdown.
227356
228357Common errors and mistakes
229358**************************
0 commit comments