@@ -220,10 +220,139 @@ Beware however:
220
220
What you must remember is that ZendMM leak tracking is a nice bonus tool to have, but it does not replace a
221
221
:doc: `true C memory debugger <./memory_debugging >`.
222
222
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
+
223
242
ZendMM internal design
224
243
**********************
225
244
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.
227
356
228
357
Common errors and mistakes
229
358
**************************
0 commit comments