深入理解php内核 php7,深入理解PHP7内核之OBJECT | 码农网

前面的几篇,我系统的介绍了 PHP 7以后的Zval

,Hashtable(zend_array)

, 以及Reference

, 今天我来讲讲zend_object。

PHP5

按照惯例,我先带大家回顾下 PHP 5时的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也有一套引用计数。

$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));

这块,大家在写扩展的时候,如果用到自定义的类,一定要注意。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值