• php变量的实现


     1.php变量的实现
    变量名 zval ,变量值 zend_value,php7的变量内存管理的引用计数 在zend_value结构上,变量的操作也都是zend_value实现的。

    //zend_types.h
    typedef struct _zval_struct     zval;
    
    typedef union _zend_value {
        zend_long         lval;    //int整形
        double            dval;    //浮点型
        zend_refcounted  *counted;
        zend_string      *str;     //string字符串
        zend_array       *arr;     //array数组
        zend_object      *obj;     //object对象
        zend_resource    *res;     //resource资源类型
        zend_reference   *ref;     //引用类型,通过&$var_name定义的
        zend_ast_ref     *ast;     //下面几个都是内核使用的value
        zval             *zv;
        void             *ptr;
        zend_class_entry *ce;
        zend_function    *func;
        struct {
            uint32_t w1;
            uint32_t w2;
        } ww;
    } zend_value;
    
    struct _zval_struct {
        zend_value        value; //变量实际的value
        union {
            struct {
                ZEND_ENDIAN_LOHI_4( //这个是为了兼容大小字节序,小字节序就是下面的顺序,大字节序则下面4个顺序翻转
                    zend_uchar    type,         //变量类型
                    zend_uchar    type_flags,  //类型掩码,不同的类型会有不同的几种属性,内存管理会用到
                    zend_uchar    const_flags,
                    zend_uchar    reserved)     //call info,zend执行流程会用到
            } v;
            uint32_t type_info; //上面4个值的组合值,可以直接根据type_info取到4个对应位置的值
        } u1;
        union {
            uint32_t     var_flags;
            uint32_t     next;                 //哈希表中解决哈希冲突时用到
            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结构比较简单,内嵌一个union类型的zend_value保存具体变量类型的值或指针,zval中还有两个union:u1u2:

             u1: 它的意义比较直观,变量的类型就通过u1.v.type区分,另外一个值type_flags为类型掩码,在变量的内存管理、gc机制中会用到,第三部分会详细分析,至于后面两个const_flagsreserved暂且不管                                                      

            u2: 这个值纯粹是个辅助值,假如zval只有:valueu1两个值,整个zval的大小也会对齐到16byte,既然不管有没有u2大小都是16byte,把多余的4byte拿出来用于一些特殊用途还是很划算的,比如next在哈希表解决哈希冲突时会用到,还有fe_pos在foreach会用到......

         从zend_value可以看出,除longdouble类型直接存储值外,其它类型都为指针,指向各自的结构。

         zval.u1.type类型

    /* 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

          简单的类型是true、false、long、double、null,其中true、false、null没有value,直接根据type区分,而long、double的值则直接存在value中

    字符串结构

    struct _zend_string {
        zend_refcounted_h gc;
        zend_ulong        h;                /* hash value */
        size_t            len;
        char              val[1];
    };
    • gc: 变量引用信息,比如当前value的引用数,所有用到引用计数的变量类型都会有这个结构,3.1节会详细分析
    • h: 哈希值,数组中计算索引时会用到
    • len: 字符串长度,通过这个值保证二进制安全
    • val: 字符串内容,分配时按len长度申请内存

         字符串分类:

                 IS_STR_PERSISTENT(通过malloc分配的)

                IS_STR_INTERNED(php代码里写的一些字面量,比如函数名、变量值)

                IS_STR_PERMANENT(永久值,生命周期大于request)

                IS_STR_CONSTANT(常量)

                IS_STR_CONSTANT_UNQUALIFIED

    数组结构   php数组的底层结构是hashTabe   (php内核的函数 类 文件索引表 全局符号表页都是用hash结构实现的)

              hash结构根据hash码进行访问的key-value数据结构,根据key映射函数直接索引到value,采用直接寻址直接映射key到内存地址,查找的期望时间O(1)

    typedef struct _zend_array HashTable;
    
    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索引时的掩码
        Bucket           *arData; //bucket数组
        uint32_t          nNumUsed; //已用bucket数
        uint32_t          nNumOfElements; //已有元素数,nNumOfElements <= nNumUsed,因为删除的并不是直接从arData中移除
        uint32_t          nTableSize; //数组的大小,为2^n
        uint32_t          nInternalPointer; //数值索引
        zend_long         nNextFreeElement;
        dtor_func_t       pDestructor;
    };

                 

        arData指向存储元素数组的第一个Bucket,插入元素时按顺序 依次插入 数组,比如第一个元素在arData[0]、第二个在arData[1]...arData[nNumUsed]。PHP数组的有序性正是通过arData保证的,arData并不是按key映射的散列表,列表在ht->arData内存之前,分配内存时这个散列表与Bucket数组一起分配,arData向后移动到了Bucket数组的起始位置,并不是申请内存的起始位置,这样散列表可以由arData指针向前移动访问到,即arData[-1]、arData[-2]、arData[-3]......散列表的结构是uint32_t,它保存的是value在Bucket数组中的位置。

                             

                   

                                                        

         hash碰撞     不同的key可能计算得到相同的哈希值(数值索引的哈希值直接就是数值本身),但是这些值又需要插入同一个散列表。一般解决方法是将Bucket串成链表,查找时遍历链表比较key。

         数组扩容      容量不够时检查 删除元素的比例,达到一个阈值时重建索引,没有达到时直接扩容为当前大小的两倍,复制bucket到新的内存空间重建索引

         重建散列表   当删除元素达到一定数量或扩容后都需要重建散列表,因为value在Bucket位置移动了或哈希数组nTableSize变化了导致key与value的映射关系改变,重建过程实际就是遍历Bucket数组中的value,然后重新计算映射值更新到散列表。

         hash元素删除 一个元素从哈希表删除时并不会将对应的Bucket移除,而是将Bucket存储的zval修改为IS_UNDEF,只有扩容时发现nNumOfElements与nNumUsed相差达到一定数量(这个数量是:ht->nNumUsed - ht->nNumOfElements > (ht->nNumOfElements >> 5))时才会将已删除的元素全部移除,重新构建哈希表  。

    对象/资源结构

    struct _zend_object {
        zend_refcounted_h gc;
        uint32_t          handle;
        zend_class_entry *ce; //对象对应的class类
        const zend_object_handlers *handlers;
        HashTable        *properties; //对象属性哈希表
        zval              properties_table[1];
    };
    
    struct _zend_resource {
        zend_refcounted_h gc;
        int               handle;
        int               type;
        void             *ptr;
    };

             对象比较常见,资源指的是tcp连接、文件句柄等等类型,这种类型比较灵活,可以随意定义struct,通过ptr指向。

     引用类型结构

    struct _zend_reference {
        zend_refcounted_h gc;
        zval              val;
    };

           引用是PHP中比较特殊的一种类型,它实际是指向另外一个PHP变量,对它的修改会直接改动实际指向的zval,可以简单的理解为C中的指针,但是PHP中的 引用只可能有一层 ,不会出现一个引用指向另外一个引用的情况 ,也就是没有C语言中指针的指针的概念。

    2.内存管理

              引用计数 使用的是refcount字段,变量复制和函数传参的时候直接refcount++,销毁变量refcount--,refcount=0销毁变量。

                        简单的标量数据类型不会用到引用计数直接是硬拷贝,只有value是指针的数据类型使用引用计数。

                        字符串的内部字符串类型不会用到引用计数,函数名 类名 静态字符串 变量名等字面量都是这种数据类型

            写时复制  

                        引用计数之后,如果其中一个变量试图更改value的内容则会重新拷贝一份value修改,同时断开旧的指向。

            变量回收   主动销毁(unset)、自动销毁()

                      PHP变量的回收是根据refcount实现的,当unset、return时会将变量的引用计数减掉,如果refcount减到0则直接释放value,这是变量的简单gc过程,但是实际过程中array、object两种类型中出现gc无法回收导致内存泄漏的bug。

                     当销毁一个变量时,如果发现减掉refcount后仍然大于0,且类型是IS_ARRAY、IS_OBJECT则将此value放入gc可能垃圾双向链表(缓冲buffer)中,等这个链表达到一定数量(10000个值)后启动检查程序将所有变量检查一遍,如果确定是垃圾则销毁释放。

                     遍历 buffer链表,把value标记 灰色-》 深度遍历value成员 refcount-- 标记灰色   -》 再次遍历buffer链表检查value的refcount是否是0 -》 是0表示是垃圾标记为白色,不是0表示不是垃圾 -》深度遍历不是0的value把所有成员变量的refcount++标记为黑色 -〉遍历buffer把白色的几点删除  清除这些垃圾

                     

            

  • 相关阅读:
    探讨e.target与e.currentTarget
    JavaScript事件模型
    博客
    angular创建自定义指令的四种方式
    jqueryMobile模块整理—图标(icons)
    jqueryMobile模块整理—按钮(buttons)
    Visio 2010,如何打开多个窗口
    Ajax.ActionLink浏览器中代码解析问题
    响应式布局
    jquery的each
  • 原文地址:https://www.cnblogs.com/hellohell/p/9100653.html
Copyright © 2020-2023  润新知