/*
|----------------------------------------
| Memory managerment
| @weiChen translate, <farwish@live.com>
| @MIT License
*/
http://php.net/manual/zh/internals2.memory.php
(中文doc,非一致)http://php.net/manual/zh/internals2.memory.management.php http://php.net/manual/en/internals2.memory.management.php
引擎的内存管理是由对于PHP系统来说重要的特性实现的。引擎的内存管理的准确功能和优化执行超出了文档范围。 然而,一个对于功能的良好理解提供对其余 Hacker's Guide 良好理解的基础,并介绍给你贯穿PHP使用的术语和功能。
对Hacker来说最重要的特性,第一件要提及的事是跟踪分配。跟踪分配允许内存管理器避免泄漏,身边大多数Hacker的一根刺。 当PHP以debug模式构建(--enable-debug),发现的泄漏会被告,在正式的环境中它们从不会这么部署。
当跟踪分配是一个重要的和高度使用的特性时,Hacker不应该懒!永远试图在部署代码前解决泄漏,在SAPI环境中的内存泄漏 会变成一个非常大的问题,很快。
另外,也许是次要的但是仍显著的特性是,内存管理器的一部分是允许对每个PHP实例的内存使用做硬限制。我们都知道, 没有无限的事情。如果一些代码运行中内存溢出,这很可能是错误的,要么是由Hacker引起,要么是由PHP程序员引起。 限制内存因此不是一个语言上的束缚而是意味着在生产环境中的经验,这是一种停止开发环境的简单方式当不断失控出现错误时, 同样地,当在生产环境中发现bug时。
从Hacker的视角,内存管理API看起来非常像libc库的malloc实现。
---------------------------------------------------------------------------------------------------------------
| 主要的内存 APIs |
|---------------------------------------------------------------------------------------------------------------|
| 原型 说明 |
|---------------------------------------------------------------------------------------------------------------|
| void *emalloc(size_t size) 分配 size 字节的内存。 |
|---------------------------------------------------------------------------------------------------------------|
| void *ecalloc(size_t nmemb, size_t size) 给 nmemb 元素分配 size 字节的缓冲区并初始化为零。 |
|---------------------------------------------------------------------------------------------------------------|
| void *erealloc(void *ptr, size_t size) 修改使用 emalloc 分配的缓冲区 ptr 的大小为 size 字节。 |
|---------------------------------------------------------------------------------------------------------------|
| void efree(void *ptr) 释放 ptr 指向的缓冲区。缓冲区必须是由 emalloc 分配的。 |
|---------------------------------------------------------------------------------------------------------------|
| void *safe_emalloc(size_t nmemb, size_t size, size_t offset) 分配缓冲区来存放每块大小为 size 字节的 nmemb 块,|
| 并附加 offset 字节。 |
| 类似于 emalloc(nmemb * size + offset), |
| 但增加了针对溢出的特殊保护。 |
|---------------------------------------------------------------------------------------------------------------|
| char *estrdup(const char *s) 分配一个可存放 NULL 结尾的字符串 s 的缓冲区, |
| 并将 s 复制到缓冲区内。 |
|---------------------------------------------------------------------------------------------------------------|
| char *estrndup(const char *s, unsigned int length) 类似于 estrdup,但 NULL 结尾的字符串长度是已知的。 |
---------------------------------------------------------------------------------------------------------------
Note: 和与 C 标准库相似的部分不同,如果内存不能在运行时被分配,Zend 引擎的内存管理函数不会返回 NULL 值, 而会跳出并中止当前请求。
小心:在部署代码前总是使用 valgrind,并作为Hacker做事方法的正常部分。引擎只能报告和发现已分配的内存的泄漏。 所有的PHP只是一层第三方封装,那些第三方不使用内存管理。另外,valgrind 会捕获那些不总是停止的错误 或即使在执行时有一个显而易见的影响,不应该有错误同样重要,如同可避免的泄漏应该被避免一样重要。
Note: 一些泄漏是不可避免的,一些库依赖进程最后来释放一些它们的结构体,在一些境况下和不受Hacker控制的地方,这是正常的。
在debug环境中执行时,用--enable-debug配置,下例中使用的泄漏函数由引擎实际上实现并且是用户可调用的。
Example #1 泄漏检测动作
ZEND_FUNCTION(leak)
{
long leakbytes = 3;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &leakbytes) == FAILURE) {
return;
}
emalloc(leakbytes);
}
上面的例子会输出一些类似:
[Thu Oct 22 02:14:57 2009] Script: '-'
/home/johannes/src/PHP_5_3/Zend/zend_builtin_functions.c(1377) : Freeing 0x088888D4 (3 bytes), script=-
=== Total 1 memory leaks detected ===
Note: USE_ZEND_ALLOC = 0 在环境中会停止内存管理器的运转,所有的分配在默认系统分配器上回退,这对debug泄漏有用。
http://php.net/manual/en/internals2.memory.persistence.php
在这个上下文中,数据持久化的意思是任何想在当前请求中存活的数据。引擎内部的内存管理非常专注于请求绑定分配, 但这并不总是实用和合适的。持久内存有时是需要的为了满足外部库的要求,在hacking的时候它同样有用。
持久内存的常见使用是支持持久的SQL服务连接,尽管这种做法是不赞成的,它依然是这个特性最常见的用法。
Note:下面所有的函数需要额外的持续性参数,应该是false,引擎会使用它的规则的分配器(emalloc)并且内存不应该认为是持久的。 内存作为持久化被分配的地方,系统分配器被调用,在大多数情况下它们仍然不能返回NULl指针, 正如主要的内存APIs(内存管理基础里面介绍的APIs)。
---------------------------------------------------------------------------------------------------------------
| 持久内存APIs |
|---------------------------------------------------------------------------------------------------------------|
| 原型 描述 |
|---------------------------------------------------------------------------------------------------------------|
| void *pemalloc(size_t size, zend_bool persistent) 分配 size 字节的内存。 |
|---------------------------------------------------------------------------------------------------------------|
| void *pecalloc(size_t nmemb, size_t size, zend_bool persistent) 为 nmemb 元素分配 size 字节的缓冲并初始化为零.|
|---------------------------------------------------------------------------------------------------------------|
| void *perealloc(void *ptr, size_t size, zend_bool persistent) 重新调整由 emalloc 分配的, |
| 大小为 size 字节的内存 ptr 缓冲大小。 |
|---------------------------------------------------------------------------------------------------------------|
| void pefree(void *ptr, zend_bool persistent) 释放 ptr 指向的缓冲区。缓冲得由 pemalloc 分配。 |
|---------------------------------------------------------------------------------------------------------------|
| void *safe_pemalloc(size_t nmemb, size_t size, site_t offset, zend_bool persistent) |
| 分配一个用来存放每个 size 字节和 |
| 额外 offset 字节的 nmemb 块的缓冲区。 |
|---------------------------------------------------------------------------------------------------------------|
| char *pestrdup(const char *s, zend_bool persistent) 分配一个可以存放 NULL结束字符串的缓冲区 |
| 并复制到缓冲区内。 |
|---------------------------------------------------------------------------------------------------------------|
| char *pestrndup(const char *s, unsigned int length, zend_bool persistent) |
| 当以 NULL 结束的字符串长度已经知道时,和 pestrdup 相似。|
---------------------------------------------------------------------------------------------------------------
Warning:重要的是记住,被分配的持久内存没有被引擎优化和追踪;它不受 memory_limit 管制,另外,Hacker 为引擎创建的所有变量 不得使用持久内存。
http://php.net/manual/en/internals2.memory.tsrm.php
当PHP以线程安全开启构建时,引擎需要一个隔离从一个到另一个上下文的方式,这样一个进程的线程们可以服务独立的请求而不互相干扰。 TSRM在PHP里是无所不能的,扩展作者需要做非常少的事来保证他们的扩展可以在一个线程安全和非线程安全架构下起作用。
Example #1 每个模块全局变量的宏访问器
#ifdef ZTS
#define COUNTER_G(v) TSRMG(counter_globals_id, zend_counter_globals *, v)
#else
#define COUNTER_G(v) (counter_globals.v)
#endif
上面的片段展示了一个扩展作者应该如何来定义他们的全局变量访问器。TSRMG 宏带有一个标示符,类型计算和元素名。 标示符由 TSRM 在模块初始化时分配。以这种方式声明全局变量访问器保证,使用相同的逻辑, 一个扩展可以在线程安全和非线程安全架构中安全地操作。
TSRM 管理PHP中所有全局结构体的隔离和安全,从执行器全局变量到扩展全局变量,一个指向被隔离存储的指针也是通过大部分 或许多API函数。宏 TSRMLS_C 和 TSRMLS_CC 分别翻译为 "线程安全的本地存储" 和 "以一个逗号为前缀的线程安全的本地存储"。
如果一个函数需要一个指向TSRM的指针,它在原型中用宏 TSRMLS_D 或 TSRMLS_DC 声明,分别翻译为 "只线程安全的本地存储" 和 "以一个逗号为前缀的线程安全的本地存储"。许多引擎中的宏指向 TSRM,所以宣布大部分事情来接受 TSRM 是一个好主意, 这样如果他们需要调用 TSRM,他们不用在执行过程中取一个指针。
因为 TSRM 是本地线程,并且一些函数(出于兼容性原因)不能直接接受 TSRM,存在的宏 TSRMLS_FETCH, 要求 TSRM 取得指向线程本地存储的指针。这应该避免,无论在哪里,因为一个线程安全启动不是没有成本。
Note:在开发扩展期间,构建错误包含 "tsrm_ls is undefined" 或者错误是事实上 TSRMLS 在当前作用域内未定义, 为了修复它,声明函数接收有合适宏的 TSRMLS,如果问题中的函数原型不能被改变,你必须在函数体内调用 TSRMLS_FETCH。
此后的功能记录是针对 TSRM 的高级使用。扩展直接与 TSRM 交互不是日常的,pecl程序员会使用 TSRM 之上的API, 如模块全局变量API。
-------------------------------------------------------------------------------------------------------------
| TSRM内部构件 |
|-------------------------------------------------------------------------------------------------------------|
| 原型 描述 |
|-------------------------------------------------------------------------------------------------------------|
| typedef int ts_rsrc_id 类型定义用数字标示符来表示一个资源。 |
|-------------------------------------------------------------------------------------------------------------|
| int tsrm_startup(int expected_threads, int expected_resources, int debug_level, char *debug_filename) |
| expected_threads 和 expected_resources 不难限制, |
| debug_level 和 debug_filename 只在windows的debug模式下或者定义了 |
| TSRM_DEBUG 才有影响。在服务器启动期间 ZTS 模式下每个进程调用一次。 |
|-------------------------------------------------------------------------------------------------------------|
| void tsrm_shutdown(void) 应该是在 ZTS 模式下进程中调用的最后一件事用来销毁和释放所有存储 TSRM 的存储。 |
|-------------------------------------------------------------------------------------------------------------|
| ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor) |
| 分配 size 字节的线程安全指针,调用 ctor 的指针,分配(和返回)id给rsrc_id, |
| dtor 在资源被释放时调用。使用这个API,模块全局变量在 ZTS 模式下是隔离的。 |
|-------------------------------------------------------------------------------------------------------------|
| void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id) 通过之前分配的线程创建的项中的id取得一个资源。 |
|-------------------------------------------------------------------------------------------------------------|
| void ts_free_thread(void) 销毁和释放调用线程的项,当前这个API只被ISAPI模块使用。 |
|-------------------------------------------------------------------------------------------------------------|
| void ts_free_id(ts_rsrc_id id) 用之前分配的id销毁资源标示符。使用这个API,模块全局变量在 ZTS 模式下被清除。|
-------------------------------------------------------------------------------------------------------------
Note:有更多 TSRM API函数用来支持解释器上下文的创建和销毁,这种功能的使用超出了这篇文档的范围, 更多信息请看 TSRM/TSRM.h。
---------------------------------------------------------------------------------
| TSRM 互斥API |
|---------------------------------------------------------------------------------|
| 原型 描述 |
|---------------------------------------------------------------------------------|
| MUTEX_T tsrm_mutex_alloc(void) 为环境分配和返回一个合适的 MUTEX_T |
|---------------------------------------------------------------------------------|
| int tsrm_mutex_lock(MUTEX_T mutexp) 为调用的线程锁住 mutexp |
|---------------------------------------------------------------------------------|
| int tsrm_mutex_unlock(MUTEX_T mutexp) 为调用的线程解锁 mutexp |
|---------------------------------------------------------------------------------|
| void tsrm_mutex_free(MUTEX_T mutexp) 销毁和释放(未锁的)和之前分配的 mutexp |
---------------------------------------------------------------------------------
Note: mutex API 被 TSRM 暴露。然而,它的内部使用太宽松而不能有效地记录在这儿,依然,基础的功能和原型记录在这里。