当前位置:编程问答 > php >

[php扩展和嵌入式] -内存管理

内存管理
php和c最重要的区别就是是否控制内存指针.
内存
在php中, 设置一个字符串变量很简单: <?php $str = 'hello world'; ?>, 字符串可以自由的修改, 拷贝, 移动. 在C中, 则是另外一种方式, 虽然你可以简单的用静态字符串初始化: char *str = "hello world"; 但是这个字符串不能被修改, 因为它存在于代码段. 要创建一个可维护的字符串, 你需要分配一块内存, 并使用一个strdup()这样的函数将内容拷贝到其中.
[cpp] 
{  
   char *str;  
  
   str = strdup("hello world");  
   if (!str) {  
       fprintf(stderr, "Unable to allocate memory!");  
   }  
}  
传统的内存管理函数(malloc(), free(), strdup(), realloc(), calloc()等)不会被php的源代码直接使用, 本章将解释这么做的原因.
释放分配的内存
内存管理在以前的所有平台上都以请求/释放的方式处理. 应用告诉它的上层(通常是操作系统)"我想要一些内存使用", 如果空间允许, 操作系统提供给程序, 并对提供出去的内存进行一个记录.
应用使用完内存后, 应该将内存还给OS以使其可以被分配给其他地方. 如果程序没有还回内存, OS就没有办法知道这段内存已经不再使用, 这样就无法分配给其他进程. 如果一块内存没有被释放, 并且拥有它的应用丢失了对它的句柄, 我们就称为"泄露", 因为已经没有人可以直接得到它了.
在典型的客户端应用中, 小的不频繁的泄露通常是可以容忍的, 因为进程会在一段时间后终止, 这样泄露的内存就会被OS回收. 并不是说OS很牛知道泄露的内存, 而是它知道为已经终止的进程分配的内存都不会再使用.
对于长时间运行的服务端守护进程, 包括apache这样的webserver, 进程被设计为运行很长周期, 通常是无限期的. 因此OS就无法干涉内存使用, 任何程度的泄露无论多小都可能累加到足够导致系统资源耗尽.
考虑用户空间的stristr()函数; 为了不区分大小写查找字符串, 它实际上为haystack和needle各创建了一份小写的拷贝, 接着执行普通的区分大小写的搜索去查找相关的偏移量. 在字符串的偏移量被定位后, haystack和needle字符串的小写版本都不会再使用了. 如果没有释放这些拷贝, 那么每个使用stristr()的脚本每次被调用的时候都会泄露一些内存. 最终, webserver进程会占用整个系统的内存, 但是却都没有使用.
完美的解决方案是编写良好的, 干净的, 一致的代码, 保证它们绝对正确. 不过在php解释器这样的环境中, 这只是解决方案的一半.
错误处理
为了提供从用户脚本的激活请求和所在的扩展函数中跳出的能力, 需要存在一种方法跳出整个激活请求. Zend引擎中的处理方式是在请求开始的地方设置一个跳出地址, 在所有的die()/exit()调用后, 或者碰到一些关键性错误(E_ERROR)时, 执行longjmp()转向到预先设置的跳出地址.
虽然这种跳出处理简化了程序流程, 但它存在一个问题: 资源清理代码(比如free()调用)会被跳过, 会因此带来泄露. 考虑下面简化的引擎处理函数调用的代码:
[cpp] 
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, fname_len + 1, (void **)&fe) == FAILURE) {  
        zend_execute(fe->op_array TSRMLS_CC);  
    } else {  
        php_error_docref(NULL TSRMLS_CC, E_ERROR,  
                         "Call to undefined function: %s()", fname);  
    }  
    efree(lcase_fname);  
}  
当php_error_docref()一行执行到时, 内部的处理器看到错误级别是关键性的, 就调用longjmp()中断当前程序流, 离开call_function(), 这样就不能到达efree(lcase_fname)一行. 那你就可能会想, 把efree()行移动到php_error_docref()上面, 但是如果这个call_function()调用进入第一个条件分支呢(查找到了函数名, 正常执行)? 还有一点, fname自己是一个分配的字符串, 并且它在错误消息中被使用, 在使用完之前你不能释放它.
php_error_docref()函数是一个内部等价于trigger_error(). 第一个参数是一个可选的文档引用, 如果在php.ini中启用它将被追加到docref.root后面. 第三个参数可以是任意的E_*族常量标记错误的严重程度. 第四个和后面的参数是符合printf()样式的格式串和可变参列表.
Zend内存管理
由于请求跳出(故障)产生的内存泄露的解决方案是Zend内存管理(ZendMM)层. 引擎的这一部分扮演了相当于操作系统通常扮演的角色, 分配内存给调用应用. 不同的是, 站在进程空间请求的认知角度, 它足够底层, 当请求die的时候, 它可以执行和OS在进程die时所做的相同的事情. 也就是说它会隐式的释放所有请求拥有的内存空间. 下图展示了在php进程中ZendMM和OS的关系:
除了提供隐式的内存清理, ZendMM还通过php.ini的设置memory_limit控制了每个请求的内存使用. 如果脚本尝试请求超过系统允许的, 或超过单进程内存限制剩余量的内存, ZendMM会自动的引发一个E_ERROR消息, 并开始跳出进程. 一个额外的好处是多数时候内存分配的结果不需要检查, 因为如果失败会立即longjmp()跳出到引擎的终止部分.
在php内部代码和OS真实的内存管理层之间hook的完成, 最复杂的是要求所有内部的内存分配要从一组函数中选择. 例如, 分配一个16字节的内存块不是使用malloc(16), php代码应该使用emalloc(16). 除了执行真正的内存分配任务, ZendMM还要标记内存块所绑定请求的相关信息, 以便在请求被故障跳出时, ZendMM可以隐式的释放它(分配的内存).
很多时候内存需要分配, 并使用超过单请求生命周期的时间. 这种分配我们称为持久化分配, 因为它们在请求结束后持久的存在, 可以使用传统的内存分配器执行分配, 因为它们不可以被ZendMM打上每个请求的信息. 有时, 只有在运行时才能知道特定的分配需要持久化还是不需要, 因此ZendMM暴露了一些帮助宏, 由它们来替代其他的内存分配函数, 但是在末尾增加了附加的参数来标记是否持久化.
如果你真的想要持久化的分配, 这个参数应该被设置为1, 这种情况下内存分配的请求将会传递给传统的malloc()族分配器. 如果运行时逻辑确定这个块不需要持久化 则这个参数被设置为0, 调用将会被转向到单请求内存分配器函数.
例如, pemalloc(buffer_len, 1)映射到malloc(buffer_len), 而pemalloc(buffer_len, 0)映射到emalloc(buffer_len), 如下:
[cpp]  
#define in Zend/zend_alloc.h:  
  
#define pemalloc(size, persistent) \  
            ((persistent)?malloc(size): emalloc(size))  
ZendMM提供的分配器函数列表如下, 并列出了它们对应的传统分配器.
 
传统分配器
php中的分配器
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 *realloc(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);
 
你可能注意到了, pefree要求传递持久化标记. 这是因为在pefree()调用时, 它并不知道ptr是否是持久分配的. 在废持久分配的指针上调用free()可能导致双重的free, 而在持久化的分配上调用efree()通常会导致段错误, 因为内存管理器会尝试查看管理信息, 而它不存在. 你的代码需要记住它分配的数据结构是不是持久化的.
除了核心的分配器外, ZendMM还增加了特殊的函数:
[cpp]  
void *estrndup(void *ptr, int len);  
它分配len + 1字节的内存, 并从ptr拷贝len个字节到
补充:Web开发 , php ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,