从内核层面解析PHP的声明周期、变量zval、引用计数。
《Extending and Embedding PHP》读后总结
不论是使用cli指令行还是使用webserver(apache、nginx)形式解释执行php程序。最终的进程模型都可以归为三类:
因为php本身并没有主进程的概念,因此它的声明周期均是以单进程
为维度讨论。
因此如果是多进程的模型,启动时,每一个进程都会进行上述的初始化操作,因此每一个进程中的数据是完全隔离的。
关于线程安全,默认是关闭线程安全模式。在正常的多进程模型下也不需要开启线程安全模式,因为数据本身就是进程间隔离的,不存在线程安全问题。开启反而会造成无意义的额外开销。
php中弱类型的实现是因为php底层所有的变量均为一个zval的结构体。
typedef struct _zval_struct {
zval_value value;
zend_uint refcount;
zend_uchar type;
zend_uchar is_ref;
} zval;
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *var;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;
当创建一个的变量的时候,zend引擎会将将这个zval的指针存储到一个内部map(符号表)中。
struct _zend_execution_globals {
...
HashTable symbol_table;
HashTable *active_symbol_table;
...
};
$foo = 'bar'; ?>
/*上面php创建变量的C实现*/
{
zval *fooval;
MAKE_STD_ZVAL(fooval); //zend引擎宏命令,创建一个空zval
ZVAL_STRING(fooval, "bar", 1); //zend引擎宏命令,创建一个字符串,会自动设置字符串本身+长度,最后一个参数为1时,会分配新的内存并cp字符串,如果为0则简单的指向字符串已有的地址。
ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", fooval); //设置符号表
}
Zend引擎内部对于内存的管理做过了许多优化,虽然在用户侧,PHP的表现一直是在非引用的情况下都是以值传递的,但是实际上在内部,大部分情况都是传递的指针,通过引用计数+是否是引用来确定是否需要cp内容。
$a = 'Hello World';
$b = $a;
?>
在用户侧我们会想当然的理解为:
但是实际上Zend引擎并没有copy字符串,而只是将那个Hello World的zval结构体中的refcount+1。
那么为什么我们改变$b的字符串的值并不会改变$a的值呢?那么是因为Zend引擎使用了写时拷贝
。
$a = 'Hello World';
$b = $a;
$b = 'aaa';
?>
这时Zend引擎的操作是:
$a = 'Hello World';
$b = &$a;
$b = 'aaa';
?>
如果是引用呢?此时在用户侧我们知道$a的值也应该变成aaa。实际在引擎层面很简单,因为原本内部就是通过指针指向的同一个地址,并不需要特殊处理,只需要在判断zval的refcount的值的时候,多判断一下zval的is_ref是否是1,如果是那么直接返回该值就可以了。
zval *get_var_and_separate(char *varname, int varname_len TSRMLS_DC) {
zval **varval, *varcopy;
if (zend_hash_find(EG(active_symbol_table),
varname, varname_len + 1, (void**)&varval) == FAILURE) {
/* 变量不存在 */
return NULL;
}
if ( (*varval)->is_ref || (*varval)->refcount < 2) {
/* 变量名只有⼀个引用或是引用, 不需要隔离 */
return *varval;
}
/* 其他情况, 对zval *做⼀一次浅拷贝 */
MAKE_STD_ZVAL(varcopy);
varcopy = *varval;
/* 对zval *进行⼀一次深拷贝 */
zval_copy_ctor(varcopy);
/* 破坏varname和varval之间的关系, 这⼀一步会将varval的引用计数减小1 */
zend_hash_del(EG(active_symbol_table), varname, varname_len + 1);
/* 初始化新创建的值的引用计数, 并为新创建的值和varname建立关联 */
varcopy->refcount = 1;
varcopy->is_ref = 0;
zend_hash_add(EG(active_symbol_table), varname, varname_len + 1,
/* 返回新的zval * */
return varcopy;
}