深入理解PHP7内核之zval-PHP7

资源魔 45 0
PHP7曾经公布, 如承诺, 我也要开端这个系列的文章的编写, 次要想经过文章让各人了解到PHP7的微小功能晋升面前到底咱们做了甚么, 明天我想先以及各人聊聊zval的变动. 正在讲zval变动的以前咱们先来看看zval正在PHP5上面是甚么样子

zval回顾

正在PHP5的时分, zval的界说以下:

struct _zval_struct {
     union {
          long lval;
          double dval;
          struct {
               char *val;
               int len;
          } str;
          HashTable *ht;
          zend_object_value obj;
          zend_ast *ast;
     } value;
     zend_uint refcount__gc;
     zend_uchar type;
     zend_uchar is_ref__gc;
};

对PHP5内核有理解的同窗应该对这个构造比拟相熟, 由于zval能够示意所有PHP中的数据类型, 以是它蕴含了一个type字段, 示意这个zval存储的是甚么类型的值, 常见的可能选项是IS_NULL, IS_LONG, IS_STRING, IS_ARRAY, IS_OBJECT等等.

依据type字段的值没有同, 咱们就要用没有同的形式解读value的值, 这个value是个联结体, 比方关于type是IS_STRING, 那末咱们应该用value.str来解读zval.value字段, 而假如type是IS_LONG, 那末咱们就要用value.lval来解读.

另外, 咱们晓得PHP是用援用计数来做根本的渣滓收受接管的, 以是zval中有一个refcount__gc字段, 示意这个zval的援用数量, 但这里有一个要阐明的, 正在5.3之前, 这个字段的名字还叫做refcount, 5.3当前, 正在引入新的渣滓收受接管算法来凑合轮回援用计数的时分, 作者退出了年夜量的宏来操作refcount, 为了能让谬误更快的浮现, 以是更名为refcount__gc, 迫使各人都应用宏来操作refcount.

相似的, 另有is_ref, 这个值示意了PHP中的一个类型能否是援用, 这里咱们能够看到是否是援用是一个标记位.

这就是PHP5时代的zval, 正在2013年咱们做PHP5的opcache JIT的时分, 由于JIT正在实际名目中体现欠安, 咱们转而认识到这个构造体的不少成绩. 而PHPNG名目就是从改写这个构造体而开端的.

存正在的成绩

PHP5的zval界说是跟着Zend Engine 2降生的, 跟着工夫的推移, 过后设计的局限性也愈来愈显著:

起首这个构造体的巨细是(正在64位零碎)24个字节, 咱们细心看这个zval.value联结体, 此中zend_object_value是最年夜的长板, 它招致整个value需求16个字节, 这个应该是很容易能够优化掉的, 比方把它挪进去, 用个指针替代,由于究竟结果IS_OBJECT也没有是最最罕用的类型.

第二, 这个构造体的每个字段都有明白的含意界说, 不预留任何的自界说字段, 招致正在PHP5时代做不少的优化的时分, 需求存储一些以及zval相干的信息的时分, 不能不采纳其余构造体映照, 或许内部包装后打补钉的形式来裁减zval, 比方5.3的时分新引入专门处理轮回援用的GC, 它没有患上采纳以下的比拟hack的做法:

/* The following macroses override macroses from zend_alloc.h */
#undef  ALLOC_ZVAL
#define ALLOC_ZVAL(z)                                   \
    do {                                                \
        (z) = (zval*)emalloc(sizeof(zval_gc_info));     \
        GC_ZVAL_INIT(z);                                \
    } while (0)
它用zval_gc_info挟制了zval的调配:
typedef struct _zval_gc_info {
    zval z;
    union {
        gc_root_buffer       *buffered;
        struct _zval_gc_info *next;
    } u;
} zval_gc_info;

而后用zval_gc_info来裁减了zval, 以是实际下去说咱们正在PHP5时代请求一个zval其实真实的是调配了32个字节, 但其实GC只要要关怀IS_ARRAY以及IS_OBJECT类型, 这样就招致了年夜量的内存糜费.

还比方我以前做的Taint扩大, 我需求关于给一些字符串存储一些标志, zval里不任何中央能够应用, 以是我不能不采纳十分手法:

Z_STRVAL_PP(ppzval) = erealloc(Z_STRVAL_PP(ppzval), Z_STRLEN_PP(ppzval) + 1 + PHP_TAINT_MAGIC_LENGTH);
PHP_TAINT_MARK(*ppzval, PHP_TAINT_MAGIC_POSSIBLE);

就是把字符串的长度裁减一个int, 而后用magic number做标志写到前面去, 这样的做法平安性以及稳固性正在技巧上都是不保证的

第三, PHP的zval年夜局部都是按值通报, 写时拷贝的值, 然而有俩个破例, 就是工具以及资本, 他们永远都是按援用通报, 这样就造成一个成绩, 工具以及资本正在除了了zval中的援用计数之外, 还需求一个全局的援用计数, 这样能力保障内存能够收受接管. 以是正在PHP5的时代, 以工具为例, 它有俩套援用计数, 一个是zval中的, 另一个是obj本身的计数:

typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid;
    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;

除了了下面提到的两套援用之外, 假如咱们要猎取一个object, 则咱们需求经过以下形式:

EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(z)].bucket.obj

通过漫长的屡次内存读取, 能力猎取到真实的objec工具自身. 效率可想而知.

这所有都是由于Zend引擎最后设计的时分, 并无思考到起初的工具. 一个精良的设计, 一旦有了不测, 就会招致整个构造变患上复杂, 保护性升高, 这是一个很好的例子.

第四, 咱们晓得PHP中, 年夜量的较量争论都是面向字符串的, 但是由于援用计数是作用正在zval的, 那末就会招致假如要拷贝一个字符串类型的zval, 咱们别无他法只能复制这个字符串. 当咱们把一个zval的字符串作为key增加到一个数组里的时分, 咱们别无他法只能复制这个字符串. 尽管正在PHP5.4的时分, 咱们引入了INTERNED STRING, 然而仍是不克不及基本处理这个成绩.

还比方, PHP中年夜量的构造体都是基于Hashtable完成的, 增删改查Hashtable的操作盘踞了年夜量的CPU工夫, 而字符串要查找起首要求它的Hash值, 实践上咱们齐全能够把一个字符串的Hash值较量争论好当前, 就存上去, 防止再次较量争论等等

第五, 这个是对于援用的, PHP5的时代, 咱们采纳写时候离, 然而连系到援用这里就有了一个经典的功能成绩:

<?php
 
    function du妹妹y($array) {}
 
    $array = range(1, 100000);
 
    $b = &$array;
 
    du妹妹y($array);
?>

当咱们挪用du妹妹y的时分, 原本只是简略的一个传值就行之处, 然而由于$array已经援用赋值给了$b, 以是招致$array变为了一个援用, 于是此处就会发作别离, 招致数组复制, 从而极年夜的拖慢功能, 这里有一个简略的测试:

<?php
$array = range(1, 100000);
 
function du妹妹y($array) {}
 
$i = 0;
$start = microtime(true);
while($i++ < 100) {
    du妹妹y($array);
}
 
printf("Used %sS\n", microtime(true) - $start);
 
$b = &$array; //留意这里, 假定我没有小心把这个Array援用给了一个变量
$i = 0;
$start = microtime(true);
while($i++ < 100) {
    du妹妹y($array);
}
printf("Used %ss\n", microtime(true) - $start);
?>

咱们正在5.6下运转这个例子, 失去以下后果:

$ php-5.6/sapi/cli/php /tmp/1.php
Used 0.00045204162597656s
Used 4.2051479816437s

相差1万倍之多. 这就造成, 假如正在一年夜段代码中, 我没有小心把一个变质变成为了援用(比方foreach as &$v), 那末就有可能触发到这个成绩, 造成重大的功能成绩, 但是却又很难排查.

第六, 也是最首要的一个, 为何说它首要呢? 由于这点促进了很年夜的功能晋升, 咱们习气了正在PHP5的时代挪用MAKE_STD_ZVAL正在堆内存上调配一个zval, 而后对他进行操作, 最初呢经过RETURN_ZVAL把这个zval的值”copy”给return_value, 而后又销毁了这个zval, 比方pathinfo这个函数:

PHP_FUNCTION(pathinfo)
{
.....
     MAKE_STD_ZVAL(tmp);
     array_init(tmp);
.....
 
    if (opt == PHP_PATHINFO_ALL) {
        RETURN_ZVAL(tmp, 0, 1);
    } else {
.....
}

这个tmp变量, 齐全是一个暂时变量的作用, 咱们又何苦正在堆内存调配它呢? MAKE_STD_ZVAL/ALLOC_ZVAL正在PHP5的时分, 四处都有, 是一个十分常见的用法, 假如咱们能把这个变量用栈调配, 那无论是内存调配, 仍是缓存敌对, 都长短常无利的

另有不少, 我就纷歧一具体罗列了, 然而我置信你们也有了以及咱们过后同样的设法主意, zval必需患上改改了, 对吧?

如今的zval

到了PHP7中, zval变为了以下的构造, 要阐明的是, 这个是如今的构造, 曾经以及PHPNG时分有了一些没有同了, 由于咱们新添加了一些诠释 (联结体的字段), 然而总体巨细, 构造, 是以及PHPNG的时分分歧的:

struct _zval_struct {
     union {
          zend_long         lval;             /* long value */
          double            dval;             /* double value */
          zend_refcounted  *counted;
          zend_string      *str;
          zend_array       *arr;
          zend_object      *obj;
          zend_resource    *res;
          zend_reference   *ref;
          zend_ast_ref     *ast;
          zval             *zv;
          void             *ptr;
          zend_class_entry *ce;
          zend_function    *func;
          struct {
               uint32_t w1;
               uint32_t w2;
          } ww;
     } value;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         /* active type */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t     var_flags;
        uint32_t     next;                 /* hash collision chain */
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
    } u2;
};

尽管看起来变患上好年夜, 但其实你细心看, 全副都是联结体, 这个新的zval正在64位环境下,如今只要要16个字节(2个指针size), 它次要分为俩个局部, value以及裁减字段, 而裁减字段又分为u1以及u2俩个局部, 此中u1是type info, u2是各类辅佐字段.

此中value局部, 是一个size_t巨细(一个指针巨细), 能够保留一个指针, 或许一个long, 或许一个double.

而type info局部则保留了这个zval的类型. 裁减辅佐字段则会正在多个其余中央应用, 比方next, 就用正在庖代Hashtable华夏来的拉链指针, 这局部会正在当前引见HashTable的时分再来详解.

类型

PHP7中的zval的类型做了比拟年夜的调整, 总体来讲有以下17品种型:

/* regular data types */
#define IS_UNDEF                    0
#define IS_NULL                     1
#define IS_FALSE                    2
#define IS_TRUE                     3
#define IS_LONG                     4
#define IS_DOUBLE                   5
#define IS_STRING                   6
#define IS_ARRAY                    7
#define IS_OBJECT                   8
#define IS_RESOURCE                 9
#define IS_REFERENCE                10
 
/* constant expressions */
#define IS_CONSTANT                 11
#define IS_CONSTANT_AST             12
 
/* fake types */
#define _IS_BOOL                    13
#define IS_CALLABLE                 14
 
/* internal types */
#define IS_INDIRECT                 15
#define IS_PTR                      17

此中PHP5的时分的IS_BOOL类型, 如今拆分红了IS_FALSE以及IS_TRUE俩品种型. 而原来的援用是一个标记位, 如今的援用是一种新的类型.

关于IS_INDIRECT以及IS_PTR来讲, 这俩个类型是用正在外部的保存类型, 用户没有会感知到, 这局部会正在后续引见HashTable的时分也一并引见.

从PHP7开端, 关于正在zval的value字段中能保留下的值, 就再也不对他们进行援用计数了, 而是正在拷贝的时分间接赋值, 这样就免却了年夜量的援用计数相干的操作, 这局部类型有:

IS_LONG
IS_DOUBLE

当然关于那种基本不值, 只有类型的类型, 也没有需求援用计数了:

IS_NULL
IS_FALSE
IS_TRUE

而关于复杂类型, 一个size_t保留没有下的, 那末咱们就用value来保留一个指针, 这个指针指向这个详细的值, 援用计数也随之作用于这个值上, 而没有正在是作用于zval上了.

1e5c5c2e6c0866c07b2a654846308df.png

PHP7 zval表示图

以IS_ARRAY为例:

struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    nApplyCount,
                zend_uchar    nIteratorsCount,
                zend_uchar    reserve)
        } v;
        uint32_t flags;
    } u;
    uint32_t          nTableMask;
    Bucket           *arData;
    uint32_t          nNumUsed;
    uint32_t          nNumOfElements;
    uint32_t          nTableSize;
    uint32_t          nInternalPointer;
    zend_long         nNextFreeElement;
    dtor_func_t       pDestructor;
};

zval.value.arr将指向下面的这样的一个构造体, 由它实际保留一个数组, 援用计数局部保留正在zend_refcounted_h构造中:

typedef struct _zend_refcounted_h {
    uint32_t         refcount;          /* reference counter 32-bit */
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,
                zend_uchar    flags,    /* used for strings & objects */
                uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;

一切的复杂类型的界说, 开端的时分都是zend_refcounted_h构造, 这个构造里除了了援用计数之外, 另有GC相干的构造. 从而正在做GC收受接管的时分, GC没有需求关怀详细类型是甚么, 一切的它均可以当作zend_refcounted*构造来解决.

另外有一个需求阐明的就是各人可能会猎奇的ZEND_ENDIAN_LOHI_4宏, 这个宏的作用是简化赋值, 它会保障正在年夜端或许小真个机械上, 它界说的字段都依照同样程序陈列存储, 从而咱们正在赋值的时分, 没有需求对它的字段辨别赋值, 而是能够对立赋值, 比方关于下面的array构造为例, 就能够经过:

arr1.u.flags = arr2.u.flags;

一次实现相称于以下的赋值序列:

arr1.u.v.flags                    = arr2.u.v.flags;
arr1.u.v.nApplyCount           = arr2.u.v.nApplyCount;
arr1.u.v.nIteratorsCount     = arr2.u.v.nIteratorsCount;
arr1.u.v.reserve                = arr2.u.v.reserve;

另有一个各人可能会问到的成绩是, 为何没有把type类型放到zval类型的后面, 由于咱们晓得当咱们去用一个zval的时分, 起首第一点一定是先去猎取它的类型. 这里的一个缘由是, 一个是俩者差异没有年夜, 另外就是思考到假如当前JIT的话, zval的类型假如可以经过类型推导取得, 就基本不须要去读取它的type值了.

标记位

除了了数据类型之外, 之前的经历也通知咱们, 一个数据除了了它的类型之外, 还应该有不少其余的属性, 比方关于INTERNED STRING,它是一种正在整个PHP申请期都存正在的字符串(比方你写正在代码中的字面量), 它没有会被援用计数收受接管. 正在5.4的版本中咱们是经过事后请求一块内存, 而后再这个内存中调配字符串, 最初用指针地点来比拟, 假如一个字符串是属于INTERNED STRING的内存范畴内, 就以为它是INTERNED STRING. 这样做的缺陷不言而喻, 就是当内存不敷的时分, 咱们就不方法调配INTERNED STRING了, 另外也十分俊俏, 以是假如一个字符串能有一些属性界说则这个完成就能够变患上很优雅.

另有, 比方如今咱们关于IS_LONG, IS_TRUE等类型再也不进行援用计数了, 那末当咱们拿到一个zval的时分若何判别它需求没有需求援用计数呢? 想当然的咱们可能会说用:

if (Z_TYPE_P(zv) >= IS_STRING) {
  //需求援用计数
}

然而你忘了, 另有INTERNED STRING的存正在啊, 以是你兴许要这么写了:

if (Z_TYPE_P(zv) >= IS_STRING && !IS_INTERNED(Z_STR_P(zv))) {
  //需求援用计数
}

是否是曾经让你觉得到有点不合错误劲了? 嗯,别急, 另有呢, 咱们还正在5.6的时分引入了常量数组, 这个数组呢会存储正在Opcache的同享内存中, 它也没有需求援用计数:

if (Z_TYPE_P(zv) >= IS_STRING && !IS_INTERNED(Z_STR_P(zv))
    && (Z_TYPE_P(zv) != IS_ARRAY || !Z_IS_IMMUTABLE(Z_ARRVAL(zv)))) {
 //需求援用计数
}

你是否是也感觉这几乎太俊俏了, 几乎不克不及忍耐这样墨迹的代码, 对吧?

是的,咱们早想到了,转头看以前的zval界说, 留意到type_flags了么? 咱们引入了一个标记位, 叫做IS_TYPE_REFCOUNTED, 它会保留正在zval.u1.v.type_flags中, 咱们关于需求援用计数的类型就付与这个标记, 以是下面的判别就能够变患上很优雅:

if (!(Z_TYPE_FLAGS(zv) & IS_TYPE_REFCOUNTED)) {
}

而关于INTERNED STRING来讲, 这个IS_STR_INTERNED标记位应该是作用于字符串自身而没有是zval的.

那末相似这样的标记位一共有几何呢?作用于zval的有:

IS_TYPE_CONSTANT            //是常量类型
IS_TYPE_IMMUTABLE           //不成变的类型, 比方存正在同享内存的数组
IS_TYPE_REFCOUNTED          //需求援用计数的类型
IS_TYPE_COLLECTABLE         //可能蕴含轮回援用的类型(IS_ARRAY, IS_OBJECT)
IS_TYPE_COPYABLE            //可被复制的类型, 还记患上我以前讲的工具以及资本的破例么? 工具以及资本就没有是
IS_TYPE_SYMBOLTABLE         //zval保留的是全局符号表, 这个正在我以前做了一个调整当前没用了, 但还保存着兼容,
                            //下个版本会去掉

作用于字符串的有:

IS_STR_PERSISTENT             //是malloc调配内存的字符串
IS_STR_INTERNED             //INTERNED STRING
IS_STR_PERMANENT            //不成变的字符串, 用作尖兵作用
IS_STR_CONSTANT             //代表常量的字符串
IS_STR_CONSTANT_UNQUALIFIED //带有可能定名空间的常量字符串

作用于数组的有:

#define IS_ARRAY_IMMUTABLE  //同IS_TYPE_IMMUTABLE

作用于工具的有:

IS_OBJ_APPLY_COUNT          //递归维护
IS_OBJ_DESTRUCTOR_CALLED    //析构函数曾经挪用
IS_OBJ_FREE_CALLED          //清算函数曾经挪用
IS_OBJ_USE_GUARDS           //魔术办法递归维护
IS_OBJ_HAS_GUARDS           //能否有魔术办法递归维护标记

有了这些预留的标记位, 咱们就会很不便的做一些之前欠好做的事件, 就比方我本人的Taint扩大, 如今把一个字符串标志为净化的字符串就会变患上无比简略:

/* it's important that make sure
 * this value is not used by Zend or
 * any other extension agianst string */
#define IS_STR_TAINT_POSSIBLE    (1<<7)
#define TAINT_MARK(str)     (GC_FLAGS((str)) |= IS_STR_TAINT_POSSIBLE)

这个标志就会不断跟着这个字符串的生活而存正在的, 免却了我以前的不少tricky的做法.

zval事后调配

后面咱们说过, PHP5的zval调配采纳的是堆上调配内存, 也就是正在PHP预案代码中随处可见的MAKE_STD_ZVAL以及ALLOC_ZVAL宏. 咱们也晓得了原本一个zval只要要24个字节, 然而算上gc_info, 其实调配了32个字节, 再加之PHP本人的内存治理正在调配内存的时分城市正在内存后面保存一局部信息:

typedef struct _zend_妹妹_block {
    zend_妹妹_block_info info;
#if ZEND_DEBUG
    unsigned int magic;
# ifdef ZTS
    THREAD_T thread_id;
# endif
    zend_妹妹_debug_info debug;
#elif ZEND_MM_HEAP_PROTECTION
    zend_妹妹_debug_info debug;
#endif
} zend_妹妹_block;

从而招致实际上咱们只要要24字节的内存, 但最初居然调配48个字节之多.

但是年夜局部的zval, 尤为是扩大函数内的zval, 咱们想一想它承受的参数来自内部的zval, 它把前往值前往给return_value, 这个也是来自内部的zval, 而两头变量的zval齐全能够采纳栈上调配. 也就是说年夜局部的外部函数都没有需求正在堆上调配内存, 它需求的zval均可以来自内部.

于是过后咱们做了一个斗胆勇敢的设法主意, 一切的zval都没有需求独自请求.

而这个也很容易证实, PHP剧本中应用的zval, 要末存正在于符号表, 要末就以暂时变量(IS_TMP_VAR)或许编译变量(IS_CV)的方式存正在. 前者存正在于一个Hashtable中, 而正在PHP7中Hashtable默许保留的就是zval, 这局部的zval齐全能够正在Hashtable调配的时分一次性调配进去, 前面的存正在于execute_data之后, 数目也正在编译时辰确定好了, 也能够跟着execute_data一次性调配, 以是咱们的确再也不需求独自正在堆上请求zval了.

以是, 正在PHP7开端, 咱们移除了了MAKE_STD_ZVAL/ALLOC_ZVAL宏, 再也不支持存堆内存上请求zval. 函数外部应用的zval要末来自里面输出, 要末应用正在栈上调配的暂时zval.

正在起初的理论中, 总结进去的可能关于开发者来讲最年夜的变动就是, 以前的一些外部函数, 经过一些操作取得一些信息, 而后调配一个zval, 前往给挪用者的状况:

static zval * php_internal_function() {
    .....
    str = external_function();
 
    MAKE_STD_ZVAL(zv);
 
    ZVAL_STRING(zv, str, 0);
 
     return zv;
}
PHP_FUNCTION(test) {
     RETURN_ZVAL(php_internal_function(), 1, 1);
}

要末修正为, 这个zval由挪用者通报:

static void php_internal_function(zval *zv) {
    .....
    str = external_function();
 
    ZVAL_STRING(zv, str);
     efree(str);
}
 
PHP_FUNCTION(test) {
     php_internal_function(return_value);
}

要末修正为, 这个函数前往原始素材:

static char * php_internal_function() {
    .....
    str = external_function();
     return str;
}
 
PHP_FUNCTION(test) {
     str = php_internal_function();
     RETURN_STRING(str);
     efree(str);
}

总结

(这块还没想好怎样说, 原本我是要引出Hashtable再也不存正在zval**, 从而引出援用类型的存正在的须要性, 然而假如没有先讲Hashtable的构造, 这个引出貌似很突兀, 先这么着吧, 当前再来修正)

到如今咱们根本上把zval的变动详情引见终了, 形象的来讲, 其真实PHP7中的zval, 曾经变为了一个值指针, 它要末保留着原始值, 要末保留着指向一个保留原始值的指针. 也就是说如今的zval相称于PHP5的时分的zval *. 只不外相比于zval *, 间接存储zval, 咱们能够免却一次指针解援用, 从而进步缓存敌对性.

其实PHP7的功能, 咱们并无引入甚么新的技巧模式, 不外就是次要来自, 继续没有懈的升高内存占用, 进步缓存敌对性, 升高执行的指令数的这些准则而来的, 能够说PHP7的重构就是这三个准则.

以上就是深化了解PHP7内核之zval的具体内容,更多请存眷资源魔其它相干文章!

标签: PHP7 php7开发教程 php7开发资料 php7开发自学 zval

抱歉,评论功能暂时关闭!