标签 - php7内核剖析

php7内核剖析    2019-09-16 14:37:07    15    0    0

2.5 常量

常量是一个简单值的标识符(名字)。如同其名称所暗示的,在脚本执行期间该值不能改变。常量默认为大小写敏感。通常常量标识符总是大写的。

常量名和其它任何 PHP 标签遵循同样的命名规则。合法的常量名以字母或下划线开始,后面跟着任何字母,数字或下划线。

PHP中的常量通过define()函数定义:

define('CONST_VAR_1', 1234);

2.5.1 常量的存储

在内核中常量存储在EG(zend_constants)哈希表中,访问时也是根据常量名直接到哈希表中查找,其实现比较简单。

常量的数据结构:

typedef struct _zend_constant {
    zval value;   //常量值
    zend_string *name; //常量名
    int flags;  //常量标识位
    int module_number; //所属扩展、模块
} zend_constant;

常量的几个属性都比较直观,这里只介绍下flags,它的值可以是以下三个中任意组合:

#define CONST_CS                (1<<0)  //大小写敏感
#define CONST_PERSISTENT        (1<<1)  //持久化的
#define CONST_CT_SUBST          (1<<2)  //允许编译时替换

介绍下三种flag代表的含义:

  • CONST_CS: 大小写敏感,默认是开启的,用户通过define()定义的始终是区分大小写的,通过扩展定义的可以自由选择
  • CONST_PERSISTENT: 持久化的,只有通过扩展、内核定义的才支持,这种常量不会在request结束时清理掉
  • CONST_CT_SUBST: 允许编译时替换,编译时如果发现有地方在读取常量的值,那么编译器会尝试直接替换为常量值,而不是在执行时再去读取,目前这个flag只有TRUE、FALSE、NULL三个常量在使用

2.5.2 常量的销毁

非持久化常量在request请求结束时销毁,具体销毁操作在:php_request_shutdown()->zend_deactivate()->shutdown_executor()->clean_non_persistent_constants()

void clean_non_persisten
php7内核剖析    2019-09-16 14:36:01    20    0    0

2.4 全局变量

PHP中把定义在函数、类之外的变量称之为全局变量,也就是定义在主脚本中的变量,这些变量可以在函数、成员方法中通过global关键字引入使用。

function test() {
    global $id;
    $id++;
}

$id = 1;
test();
echo $id;

2.4.1 全局变量初始化

全局变量在整个请求执行期间始终存在,它们保存在EG(symbol_table)中,也就是全局变量符号表,与静态变量的存储一样,这也是一个哈希表,主脚本(或include、require)在zend_execute_ex执行开始之前会把当前作用域下的所有局部变量添加到EG(symbol_table)中,这一步操作后面介绍zend执行过程时还会讲到,这里先简单提下:

ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value)
{
    ...
    i_init_execute_data(execute_data, op_array, return_value);
    zend_execute_ex(execute_data);
    ...
}

i_init_execute_data()这个函数中会把局部变量插入到EG(symbol_table):

ZEND_API void zend_attach_symbol_table(zend_execute_data *execute_data)
{
    zend_op_array *op_array = &execute_data->func->op_array;
    HashTable *ht = execute_data->symbol_table;

    if (!EXPECTED(op_array->last_var)) { 
        return;
    }

    zend_string **str = op_array->vars;
    zend_string **end = str + op_array->last_var;
    //局部变量数组起始位置
    zval *var = EX_VAR_NUM(0);

    do{
        zval *zv
php7内核剖析    2019-09-16 14:35:24    6    0    0

2.3 静态变量

PHP中局部变量分配在zend_execute_data结构上,每次执行zend_op_array都会生成一个新的zend_execute_data,局部变量在执行之初分配,然后在执行结束时释放,这是局部变量的生命周期,而局部变量中有一种特殊的类型:静态变量,它们不会在函数执行完后释放,当程序执行离开函数域时静态变量的值被保留下来,下次执行时仍然可以使用之前的值。

PHP中的静态变量通过static关键词创建:

function my_func(){
    static $count = 4;
    $count++;
    echo $count,"\n";
}
my_func();
my_func();
===========================
5
6

2.3.1 静态变量的存储

静态变量既然不会随执行的结束而释放,那么很容易想到它的保存位置:zend_op_array->static_variables,这是一个哈希表,所以PHP中的静态变量与普通局部变量不同,它们没有分配在执行空间zend_execute_data上,而是以哈希表的形式保存在zend_op_array中。

静态变量只会初始化一次,注意:它的初始化发生在编译阶段而不是执行阶段,上面这个例子中:static $count = 4;是在编译阶段发现定义了一个静态变量,然后插进了zend_op_array->static_variables中,并不是执行的时候把static_variables中的值修改为4,所以上面执行的时候会输出5、6,再次执行并没有重置静态变量的值。

这个特性也意味着静态变量初始的值不能是变量,比如:static $count = $xxx;这样定义将会报错。

2.3.2 静态变量的访问

局部变量通过编译时确定的编号进行读写操作,而静态变量通过哈希表保存,这就使得其不能像普通变量那样有一个固定的编号,有一种可能是通过变量名索引的,那么究竟是否如此呢?我们分析下其编译过程。

静态变量编译的语法规则:

statement:
    ...
    |   T_STATIC static_var_list ';'    { $$ = $2; }
    ...
;

static_var_list:
        static_var_list ',' static
php7内核剖析    2019-09-16 14:34:22    25    0    0

2.2 数组

数组是PHP中非常强大、灵活的一种数据类型,它的底层实现为散列表(HashTable,也称作:哈希表),除了我们熟悉的PHP用户空间的Array类型之外,内核中也随处用到散列表,比如函数、类、常量、已include文件的索引表、全局符号表等都用的HashTable存储。

散列表是根据关键码值(Key value)而直接进行访问的数据结构,它的key - value之间存在一个映射函数,可以根据key通过映射函数直接索引到对应的value值,它不以关键字的比较为基本操作,采用直接寻址技术(就是说,它是直接通过key映射到内存地址上去的),从而加快查找速度,在理想情况下,无须任何比较就可以找到待查关键字,查找的期望时间为O(1)。

2.2.1 数组结构

存放记录的数组称做散列表,这个数组用来存储value,而value具体在数组中的存储位置由映射函数根据key计算确定,映射函数可以采用取模的方式,key可以通过一些譬如“times 33”的算法得到一个整形值,然后与数组总大小取模得到在散列表中的存储位置。这是一个普通散列表的实现,PHP散列表的实现整体也是这个思路,只是有几个特殊的地方,下面就是PHP中HashTable的数据结构:

//Bucket:散列表中存储的元素
typedef struct _Bucket {
    zval              val; //存储的具体value,这里嵌入了一个zval,而不是一个指针
    zend_ulong        h;   //key根据times 33计算得到的哈希值,或者是数值索引编号
    zend_string      *key; //存储元素的key
} Bucket;

//HashTable结构
typedef struct _zend_array HashTable;
struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                    zend_uchar    flags,
                    zend_uchar    nApplyCount,
             
4/4