内容简介:前面的几篇,我系统的介绍了PHP7以后的按照惯例,我先带大家回顾下PHP5时的zend_object(此部分内容之前的文章中也有涉及,如果熟悉可以跳过):zend_object(以下简称object)在PHP5中其实是一种相对特殊的存在, 在PHP5中,只有resource和object是引用传递,也就是说在赋值,传递的时候都是传递的本身,也正因为如此,Object和Resource除了使用了Zval的引用计数以外,还采用了一套独立自身的计数系统。
前面的几篇,我系统的介绍了 PHP 7以后的 Zval , Hashtable(zend_array) , 以及 Reference , 今天我来讲讲zend_object。
PHP5
按照惯例,我先带大家回顾下PHP5时的zend_object(此部分内容之前的文章中也有涉及,如果熟悉可以跳过):
typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; } zend_object;
zend_object(以下简称object)在PHP5中其实是一种相对特殊的存在, 在PHP5中,只有resource和object是引用传递,也就是说在赋值,传递的时候都是传递的本身,也正因为如此,Object和Resource除了使用了Zval的引用计数以外,还采用了一套独立自身的计数系统。
这个我们从zval中也能看出object和其他的类似字符串的的不同:
typedef union _zvalue_value { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj; } zvalue_value;
对于字符串和数组,zval中都直接保存它们的指针,而对于object却是一个zend_object_value的结构体:
typedef unsigned int zend_object_handle; typedef struct _zend_object_value { zend_object_handle handle; const zend_object_handlers *handlers; } zend_object_value;
真正获取对象是需要通过这个zend_object_handle,也就是一个int的索引去全局的object buckets中查找:
ZEND_API void *zend_object_store_get_object_by_handle(zend_object_handle handle TSRMLS_DC) { return EG(objects_store).object_buckets[handle].bucket.obj.object; }
而EG(objects_store).object_buckets则是一个数组,保存着:
typedef struct _zend_object_store_bucket { zend_bool destructor_called; zend_bool valid; zend_uchar apply_count; union _store_bucket { struct _store_object { void *object; zend_objects_store_dtor_t dtor; zend_objects_free_object_storage_t free_storage; zend_objects_store_clone_t clone; const zend_object_handlers *handlers; zend_uint refcount; gc_root_buffer *buffered; } obj; struct { int next; } free_list; } bucket; } zend_object_store_bucket;
其中,zend_object_store_bucket.bucket.obj.object才保存着真正的zend_object的指针,注意到此处是void *, 这是因为我们很多扩展的自定义对象,也是可以保存在这里的。
另外我们也注意到zend_object_store_bueckt.bucket.obj.refcount, 这个既是我刚刚讲的object自身的引用计数,也就是zval有一套自己的引用计数,object也有一套引用计数。
<?php $o1 = new Stdclass(); //o1.refcount == 1, object.refcount == 1 $o2 = $o1; //o1.refcount == o2.refcoun == 2; object.refcount = 1; $o3 = &$o2; //o3.isref == o2.isref==1 //o3.refcount == o2.refcount == 2 //o1.isref == 0; o1.refcount == 1 //object.refcount == 2
这样,可以让object可以保证不同于普通的zval的COW机制,可以保证object可以全局传引用。
可见,从一个zval到取到实际的object,我们需要首先获取zval.value.obj.handle, 然后拿着这个索引再去EG(objects_store)查询,效率比较低下。
对于另外一个常见的操作,就是获取一个zval对象的类的时候,我们甚至需要调用一个函数:
#define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC)
PHP7
到了PHP7,如我前面的文章 深入理解PHP7内核之ZVAL 所说, zval中直接保存了zend_object对象的指针:
struct _zend_object { zend_refcounted_h gc; uint32_t handle; zend_class_entry *ce; const zend_object_handlers *handlers; HashTable *properties; zval properties_table[1]; };
而EG(objects_store)也只是简单的保存了一个zend_object**等指针:
typedef struct _zend_objects_store { zend_object **object_buckets; uint32_t top; uint32_t size; int free_list_head; } zend_objects_store;
而对于前面的COW的例子,对于IS_OBJECT来说, 用IS_TYPE_COPYABLE来区分,也就是,当发生COW的时候,如果这个类型没有设置 IS_TYPE_COPYABLE,那么就不会发生“复制".
#define IS_ARRAY_EX (IS_ARRAY | ((IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE | IS_TYPE_COPYABLE) << Z_TYPE_FLAGS_SHIFT)) #define IS_OBJECT_EX (IS_OBJECT | ((IS_TYPE_REFCOUNTED | IS_TYPE_COLLECTABLE) << Z_TYPE_FLAGS_SHIFT))
如上,大家可以看到对于ARRAY来说定义了IS_TYPE_REFCOUNTED, IS_TYPE_COLLECTABLE和IS_TYPE_COPYABLE, 但是对于OBJECT, 则缺少了IS_TYPE_COPYABLE.
在SEPARATE_ZVAL中:
#define SEPARATE_ZVAL(zv) do { \ zval *_zv = (zv); \ if (Z_REFCOUNTED_P(_zv) || \ Z_IMMUTABLE_P(_zv)) { \ if (Z_REFCOUNT_P(_zv) > 1) { \ if (Z_COPYABLE_P(_zv) || \ Z_IMMUTABLE_P(_zv)) { \ if (!Z_IMMUTABLE_P(_zv)) { \ Z_DELREF_P(_zv); \ } \ zval_copy_ctor_func(_zv); \ } else if (Z_ISREF_P(_zv)) { \ Z_DELREF_P(_zv); \ ZVAL_DUP(_zv, Z_REFVAL_P(_zv)); \ } \ } \ } \ } while (0)
如果不是Z_COPYABLE_P, 那么就不发生写时分离。
这里有的同学会问,那既然已经在zval中直接保存了zend_object*了,那为啥还需要EG(objects_store)呢?
这里有2个主要原因:
- 1. 我们需要在PHP请求结束的时候保证所有的对象的析构函数都被调用,因为object存在循环引用的情况,那如何快速的便利所有存活的对象呢? EG(objects_store)是一个很不错的选择。
- 2. 在PHPNG开发点时候,为了保证最大向后兼容,我们还是需要保证获取一个对象的handle的接口, 并且这个handle还是要保证原有的语义。
但实际上来说呢, 其实EG(objects_store)已经没啥太大的用处了, 我们是可以在将来去掉它的。
好,接下来出现了另外一个问题,我们再看看zend_object的定义, 注意到末尾的properties_table[1], 也就是说,我们现在会把object的属性跟对象一起分配内存。 也就是说zend_object这个结构体现在是可能变长的。
那在当时写PHPNG的时候就给我带来了一个问题, 在PHP5时代,很多的自定义对象是这么定义的:
typedef struct _mysqli_object { zend_object zo; void *ptr; HashTable *prop_handler; } mysqli_object; /* extends zend_object */
也就是说zend_object都在自定义的内部类的头部,这样当然有一个好处是可以很方便的做cast, 但是因为目前zend_object变成变长了,并且更严重的是你并不知道用户在PHP继承了你这个类以后,他新增了多少属性的定义。
于是没有办法,在写PHPNG的时候,我做了大量的调整如下:
typedef struct _mysqli_object { void *ptr; HashTable *prop_handler; zend_object zo; } mysqli_object; /* extends zend_object */
相对的,再新增定义:
static inline mysqli_object *php_mysqli_fetch_object(zend_object *obj) { return (mysqli_object *)((char*)(obj) - XtOffsetOf(mysqli_object, zo)); }
这样以来就规避了这个问题, 而在实际的分配自定义对象的时候,我们也需要采用如下的方法:
obj = ecalloc(1, sizeof(mysqli_object) + zend_object_properties_size(class_type));
这块,大家在写扩展的时候,如果用到自定义的类,一定要注意。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 《深入Linux内核架构》摘要
- 深入理解PHP内核之HashTable(一)
- 探索 React 内核:深入 Fiber 架构和协调算法
- 深入浅出网络编程与Swoole内核 原 荐
- golang内核系列--深入理解plan9汇编&实践
- 深入理解PHP7内核之FAST_ZPP
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Convergence Culture
Henry Jenkins / NYU Press / 2006-08-01 / USD 30.00
"Convergence Culture" maps a new territory: where old and new media intersect, where grassroots and corporate media collide, where the power of the media producer, and the power of the consumer intera......一起来看看 《Convergence Culture》 这本书的介绍吧!