[share]深入探讨PHP中的内存管理问题

上传人:woxinch****an2018 文档编号:39301847 上传时间:2018-05-14 格式:DOCX 页数:13 大小:252.03KB
返回 下载 相关 举报
[share]深入探讨PHP中的内存管理问题_第1页
第1页 / 共13页
[share]深入探讨PHP中的内存管理问题_第2页
第2页 / 共13页
[share]深入探讨PHP中的内存管理问题_第3页
第3页 / 共13页
[share]深入探讨PHP中的内存管理问题_第4页
第4页 / 共13页
[share]深入探讨PHP中的内存管理问题_第5页
第5页 / 共13页
点击查看更多>>
资源描述

《[share]深入探讨PHP中的内存管理问题》由会员分享,可在线阅读,更多相关《[share]深入探讨PHP中的内存管理问题(13页珍藏版)》请在金锄头文库上搜索。

1、一、一、 内存内存在 PHP 中,填充一个字符串变量相当简单,这只需要一个语句“?php $str = hello world ; ?“即可,并且该字符串能够被自由地修改、拷贝和移动。而在 C 语言中,尽管你能够编写例如“char *str = “hello world “;“这样的一个简单的静态字符串;但是,却不能修改该字符串,因为它生存于程序空间内。为了创建一个可操纵的字符串,你必须分配一个内存块,并且通过一个函数(例如 strdup())来复制其内容。char *str;str = strdup(“hello world“);if (!str) fprintf(stderr, “Unab

2、le to allocate memory!“);由于后面我们将分析的各种原因,传统型内存管理函数(例如 malloc(),free(),strdup(),realloc(),calloc(),等等)几乎都不能直接为 PHP 源代码所使用。二、二、 释放内存释放内存在几乎所有的平台上,内存管理都是通过一种请求和释放模式实现的。首先,一个应用程序请求它下面的层(通常指“操作系统“):“我想使用一些内存空间“。如果存在可用的空间,操作系统就会把它提供给该程序并且打上一个标记以便不会再把这部分内存分配给其它程序。当应用程序使用完这部分内存,它应该被返回到 OS;这样以来,它就能够被继续分配给其它程序

3、。如果该程序不返回这部分内存,那么 OS 无法知道是否这块内存不再使用并进而再分配给另一个进程。如果一个内存块没有释放,并且所有者应用程序丢失了它,那么,我们就说此应用程序“存在漏洞“,因为这部分内存无法再为其它程序可用。在一个典型的客户端应用程序中,较小的不太经常的内存泄漏有时能够为 OS 所“容忍“,因为在这个进程稍后结束时该泄漏内存会被隐式返回到 OS。这并没有什么,因为 OS 知道它把该内存分配给了哪个程序,并且它能够确信当该程序终止时不再需要该内存。而对于长时间运行的服务器守护程序,包括象 Apache 这样的 web 服务器和扩展 php 模块来说,进程往往被设计为相当长时间一直运

4、行。因为 OS 不能清理内存使用,所以,任何程序的泄漏-无论是多么小-都将导致重复操作并最终耗尽所有的系统资源。现在,我们不妨考虑用户空间内的 stristr()函数;为了使用大小写不敏感的搜索来查找一个字符串,它实际上创建了两个串的各自的一个小型副本,然后执行一个更传统型的大小写敏感的搜索来查找相对的偏移量。然而,在定位该字符串的偏移量之后,它不再使用这些小写版本的字符串。如果它不释放这些副本,那么,每一个使用 stristr()的脚本在每次调用它时都将泄漏一些内存。最后,web 服务器进程将拥有所有的系统内存,但却不能够使用它。你可以理直气壮地说,理想的解决方案就是编写良好、干净的、一致的

5、代码。这当然不错;但是,在一个象 PHP 解释器这样的环境中,这种观点仅对了一半。三、三、 错误处理错误处理为了实现“跳出“对用户空间脚本及其依赖的扩展函数的一个活动请求,需要使用一种方法来完全“跳出“一个活动请求。这是在 Zend 引擎内实现的:在一个请求的开始设置一个“跳出“地址,然后在任何 die()或 exit()调用或在遇到任何关键错误(E_ERROR)时执行一个longjmp()以跳转到该“跳出“地址。尽管这个“跳出“进程能够简化程序执行的流程,但是,在绝大多数情况下,这会意味着将会跳过资源清除代码部分(例如 free()调用)并最终导致出现内存漏洞。现在,让我们来考虑下面这个简化

6、版本的处理函数调用的引擎代码:void call_function(const char *fname, int fname_len TSRMLS_DC)zend_function *fe;char *lcase_fname;/* PHP 函数名是大小写不敏感的,*为了简化在函数表中对它们的定位,*所有函数名都隐含地翻译为小写的*/lcase_fname = estrndup(fname, fname_len);zend_str_tolower(lcase_fname, fname_len);if (zend_hash_find(EG(function_table),lcase_fname,

7、fname_len + 1, (void *) else php_error_docref(NULL TSRMLS_CC, E_ERROR,“Call to undefined function: %s()“, fname);efree(lcase_fname);当执行到 php_error_docref()这一行时,内部错误处理器就会明白该错误级别是critical,并相应地调用 longjmp()来中断当前程序流程并离开 call_function()函数,甚至根本不会执行到 efree(lcase_fname)这一行。你可能想把 efree()代码行移动到zend_error()代码行的

8、上面;但是,调用这个 call_function()例程的代码行会怎么样呢?fname 本身很可能就是一个分配的字符串,并且,在它被错误消息处理使用完之前,你根本不能释放它。注意,这个 php_error_docref()函数是 trigger_error()函数的一个内部等价实现。它的第一个参数是一个将被添加到 docref 的可选的文档引用。第三个参数可以是任何我们熟悉的 E_*家族常量,用于指示错误的严重程度。第四个参数(最后一个)遵循 printf()风格的格式化和变量参数列表式样。四、四、 Zend 内存管理器内存管理器在上面的“跳出“请求期间解决内存泄漏的方案之一是:使用 Zend

9、 内存管理(ZendMM)层。引擎的这一部分非常类似于操作系统的内存管理行为-分配内存给调用程序。区别在于,它处于进程空间中非常低的位置而且是“请求感知“的;这样以来,当一个请求结束时,它能够执行与 OS 在一个进程终止时相同的行为。也就是说,它会隐式地释放所有的为该请求所占用的内存。图 1 展示了 ZendMM 与 OS 以及 PHP 进程之间的关系。图 1.Zend 内存管理器代替系统调用来实现针对每一种请求的内存分配。除了提供隐式内存清除功能之外,ZendMM 还能够根据 php.ini 中 memory_limit 的设置控制每一种内存请求的用法。如果一个脚本试图请求比系统中可用内存更

10、多的内存,或大于它每次应该请求的最大量,那么,ZendMM 将自动地发出一个 E_ERROR 消息并且启动相应的“跳出“进程。这种方法的一个额外优点在于,大多数内存分配调用的返回值并不需要检查,因为如果失败的话将会导致立即跳转到引擎的退出部分。把 PHP 内部代码和 OS 的实际的内存管理层“钩“在一起的原理并不复杂:所有内部分配的内存都要使用一组特定的可选函数实现。例如,PHP 代码不是使用 malloc(16)来分配一个 16 字节内存块而是使用了 emalloc(16)。除了实现实际的内存分配任务外,ZendMM还会使用相应的绑定请求类型来标志该内存块;这样以来,当一个请求“跳出“时,Z

11、endMM 可以隐式地释放它。经常情况下,内存一般都需要被分配比单个请求持续时间更长的一段时间。这种类型的分配(因其在一次请求结束之后仍然存在而被称为“永久性分配“),可以使用传统型内存分配器来实现,因为这些分配并不会添加 ZendMM 使用的那些额外的相应于每种请求的信息。然而有时,直到运行时刻才会确定是否一个特定的分配需要永久性分配,因此ZendMM 导出了一组帮助宏,其行为类似于其它的内存分配函数,但是使用最后一个额外参数来指示是否为永久性分配。如果你确实想实现一个永久性分配,那么这个参数应该被设置为 1;在这种情况下,请求是通过传统型 malloc()分配器家族进行传递的。然而,如果运

12、行时刻逻辑认为这个块不需要永久性分配;那么,这个参数可以被设置为零,并且调用将会被调整到针对每种请求的内存分配器函数。例如,pemalloc(buffer_len,1)将映射到 malloc(buffer_len),而pemalloc(buffer_len,0)将被使用下列语句映射到 emalloc(buffer_len):#define in Zend/zend_alloc.h:#define pemalloc(size, persistent) (persistent)?malloc(size): emalloc(size)所有这些在 ZendMM 中提供的分配器函数都能够从下表中找到其更

13、传统的对应实现。表格 1 展示了 ZendMM 支持下的每一个分配器函数以及它们的 e/pe 对应实现:表格 1.传统型相对于 PHP 特定的分配器。分配器函数e/pe 对应实现void *malloc(size_t count);void *emalloc(size_t count);void *pemalloc(size_t count,char persistent);void *calloc(size_t count);void *ecalloc(size_t count);void *pecalloc(size_t count,char persistent);void *reall

14、oc(void *ptr,size_t count);void *erealloc(void *ptr,size_t count);void *perealloc(void *ptr,size_t count,char persistent);void *strdup(void *ptr);void *estrdup(void *ptr);void *pestrdup(void *ptr,char persistent);void free(void *ptr);void efree(void *ptr);void pefree(void *ptr,char persistent);你可能会注

15、意到,即使是 pefree()函数也要求使用永久性标志。这是因为在调用 pefree()时,它实际上并不知道是否 ptr 是一种永久性分配。针对一个非永久性分配调用 free()能够导致双倍的空间释放,而针对一种永久性分配调用 efree()有可能会导致一个段错误,因为内存管理器会试图查找并不存在的管理信息。因此,你的代码需要记住它分配的数据结构是否是永久性的。 除了分配器函数核心部分外,还存在其它一些非常方便的 ZendMM 特定的函数,例如:void *estrndup(void *ptr,int len);该函数能够分配 len+1 个字节的内存并且从 ptr 处复制 len 个字节到最

16、新分配的块。这个estrndup()函数的行为可以大致描述如下:void *estrndup(void *ptr, int len)char *dst = emalloc(len + 1);memcpy(dst, ptr, len);dstlen = 0;return dst;在此,被隐式放置在缓冲区最后的 NULL 字节可以确保任何使用 estrndup()实现字符串复制操作的函数都不需要担心会把结果缓冲区传递给一个例如 printf()这样的希望以为NULL 为结束符的函数。当使用 estrndup()来复制非字符串数据时,最后一个字节实质上都浪费了,但其中的利明显大于弊。void *safe_emalloc(size_t size, size_t count, size_t addtl);void *safe_pemalloc(size_t size, size_t count,size_t addtl,char persistent);这些函数分配的内存空间最终大小是(size*count)+addtl)。你可以会问:“为什么还要提供额

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 其它相关文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号