标签 - PHP内核

PHP内核    2019-04-25 16:12:16    24    0    0

前面介绍了函数的定义,函数的定义只是一个将函数名注册到函数列表的过程,在了解了函数的定义后,我们来看看函数的参数。 这一小节将包括用户自定义函数的参数和内部函数的参数两部分,详细内容如下:

用户自定义函数的参数

我们对于参数的类型提示做了分析,这里我们在这一小节的基础上,进行一些更详细的说明。 在经过词语分析,语法分析后,我们知道对于函数的参数检查是通过 zend_do_receive_arg 函数来实现的。在此函数中对于参数的关键代码如下:

01CG(active_op_array)->arg_info = erealloc(CG(active_op_array)->arg_info,
02        sizeof(zend_arg_info)*(CG(active_op_array)->num_args));
03cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-1];
04cur_arg_info->name = estrndup(varname->u.constant.value.str.val,
05        varname->u.constant.value.str.len);
06cur_arg_info->name_len = varname->u.constant.value.str.len;
07cur_arg_info->array_type_hint = 0;
08cur_arg_info->allow_null = 1;
09cur_arg_info->pass_by_reference = pass_by_reference;
10cur_arg_info->class_name = NULL;
11cur_arg_info->class_name_len = 0;

整个参数的传递是通过给中间代码的arg_info字段执行赋值操作完成。关键点是在arg_info字段。arg_info字段的结构如下:

01typedef struct _zend_arg_info {
02    const char *name;   /* 参数的名称*/
03    zend_uint name_len;     /* 参数名称的长度*/
04    const char *cl
PHP内核    2019-04-25 16:11:33    4    0    0

在PHP中,用户函数的定义从function关键字开始。如下所示简单示例:

1function foo($var) {
2    echo $var;
3}

这是一个非常简单的函数,它所实现的功能是定义一个函数,函数有一个参数,函数的内容是在标准输出端输出传递给它的参数变量的值。

函数的一切从function开始。我们从function开始函数定义的探索之旅。

词法分析

在 Zend/zend_language_scanner.l中我们找到如下所示的代码:

1<st_in_scripting>"function" {
2    return T_FUNCTION;
3}
4</st_in_scripting>

它所表示的含义是function将会生成T_FUNCTION标记。在获取这个标记后,我们开始语法分析。

语法分析

在 Zend/zend_language_parser.y文件中找到函数的声明过程标记如下:

01function:
02    T_FUNCTION { $$.u.opline_num = CG(zend_lineno); }
03;
04  
05is_reference:
06        /* empty */ { $$.op_type = ZEND_RETURN_VAL; }
07    |   '&'         { $$.op_type = ZEND_RETURN_REF; }
08;
09  
10unticked_function_declaration_statement:
11        function is_reference T_STRING {
12zend_do_begin_function_declaration(&$1, &$3, 0, $2.op_type, NULL TSRMLS_CC); }
13            '(' parameter_list ')' '{' inner_statement_list '}' {
14                zend_do_end_function_declaration(&$1 TSRMLS_CC); }
15;

关注点在 function is_reference T_STRING,表示function关键字,是否引用,函数名。

T_FUNCTION标记只是用来定位函数的声明,表示这是一个函数,而更多

PHP内核    2019-04-25 16:10:58    33    0    0

在函数调用的执行代码中我们会看到这样一些强制转换:

1EX(function_state).function = (zend_function *) op_array;
2  
3或者:
4  
5EG(active_op_array) = (zend_op_array *) EX(function_state).function;

这些不同结构间的强制转换是如何进行的呢?

首先我们来看zend_function的结构,在Zend/zend_compile.h文件中,其定义如下:

01typedef union _zend_function {
02    zend_uchar type;    /* MUST be the first element of this struct! */
03  
04    struct {
05        zend_uchar type;  /* never used */
06        char *function_name;
07        zend_class_entry *scope;
08        zend_uint fn_flags;
09        union _zend_function *prototype;
10        zend_uint num_args;
11        zend_uint required_num_args;
12        zend_arg_info *arg_info;
13        zend_bool pass_rest_by_reference;
14        unsigned char return_reference;
15    } common;
16  
17    zend_op_array op_array;
18    zend_internal_function internal_function;
19} zend_function;

这是一个联合体,我们来温习一下联合体的一些特性。 联合体的所有成员变量共享内存中的一块内存,在某个时刻只能有一个成员使用这块内存, 并且当使用某一个成员时,其仅能按照它的类型和内存大小修改对应的内存空间。 我们来看看一个例子:

01#include <stdio.h>
02#include <stdlib.h>
03  
04int m
PHP内核    2019-04-25 16:10:19    8    0    0

在PHP中,函数有自己的作用域,同时在其内部可以实现各种语句的执行,最后返回最终结果值。 在PHP的源码中可以发现,PHP内核将函数分为以下类型:

1#define ZEND_INTERNAL_FUNCTION              1
2#define ZEND_USER_FUNCTION                  2 
3#define ZEND_OVERLOADED_FUNCTION            3
4#define ZEND_EVAL_CODE                      4
5#define ZEND_OVERLOADED_FUNCTION_TEMPORARY  5

其中的ZEND_USER_FUNCTION是用户函数,ZEND_INTERNAL_FUNCTION是内置的函数。也就是说PHP将内置的函数和用户定义的函数分别保存。

用户函数(ZEND_USER_FUNCTION)

用户自定义函数是非常常用的函数种类,如下面的代码,定义了一个用户自定义的函数:

1<?php
2function nowamagic( $name ){
3    $return "Hi! " $name;
4    echo $return;
5    return $return;
6}
7  
8?>

这个示例中,对自定义函数传入了一个参数,并将其与Hi! 一起输出并做为返回值返回。 从这个例子可以看出函数的基本特点:运行时声明、可以传参数、有值返回。 当然,有些函数只是进行一些操作,并不一定显式的有返回值,在PHP的实现中,即使没有显式的返回, PHP内核也会“帮你“返回NULL。

ZE在执行过程中,会将运行时信息存储于_zend_execute_data中:

1struct _zend_execute_data {
2    //...省略部分代码
3    zend_function_state function_state;
4    zend_function *fbc; /* Function Being Called */
5    //...省略部分代码
6};

在程序初始化的过程中,function_state也会进行初始化,function_state由两个部分组成:

1typedef struct _zend_function_state {
2    zend_
PHP内核    2019-04-25 16:09:52    48    0    0

函数是一种可以在任何被需要的时候执行的代码块。它不仅仅包括用户自定义的函数,还包括程序语言实现的库函数。

用户定义的函数

如下所示手册中的展示函数用途的伪代码:

1function foo($arg_1$arg_2, ..., $arg_n) {
2    echo "Example function.\n";
3    return $retval;
4}

任何有效的 PHP 代码都可以编写在函数内部,甚至包括其它函数和类定义。

在 PHP 3 中,函数必须在被调用之前定义。而 PHP 4 则不再有这样的条件。除非函数如以下两个范例中有条件的定义。

内部函数

PHP 有很多标准的函数和结构。如我们常见的count、strpos、implode等函数,这些都是标准函数,它们都是由标准扩展提供的; 如我们经常用到的isset、empty、eval等函数,这些结构被称之为语言结构。 还有一些函数需要和特定的PHP扩展模块一起编译并开启,否则无法使用。也就是有些扩展是可选的。

标准函数的实现存放在ext/standard扩展目录中。

匿名函数

有时我们的一代代码并不需要为它指定一个名称,而只需要它完成特定的工作, 匿名函数的作用是为了扩大函数的使用功能,在PHP 5.3以前,传递函数回调的方式,我们只有两种选择:

  • 字符串的函数名
  • 使用create_function创建的返回

在PHP5.3以后,我们多了一个选择--Closure。在实现上PHP 5.3中对匿名函数的支持,采用的是把要保持的外部变量, 做为Closure对象的”Static属性”来实现的,关于如何实现我们将在后面的章节介绍。

变量函数

PHP 支持变量函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数, 并且将尝试执行它。除此之外,这个可以被用于实现回调函数,函数表等。 一个变量函数的简单例子:

1$func 'print_r';
2$func('i am print_r function.');

变量函数不能用于语言结构(echo等)

PHP内核    2019-04-25 16:09:25    3    0    0

PHP中经常使用数组,使用数组最大的好处便是速度!读写都可以在O(1)内完成,因为它每个元素的大小都是一致的,只要知道下标,便可以瞬间计算出其对应的元素在内存中的位置,从而直接取出或者写入。那么内核中是如何实现的呢?

PHP大部分功能,都是通过HashTable来实现,其中就包括数组。HashTable即具有双向链表的优点,同时具有能与数据匹敌的操作性能。PHP中的定义的变量保存在一个符号表里,而这个符号表其实就是一个HashTable,它的每一个元素都是一个zval*类型的变量。不仅如此,保存用户定义的函数、类、资源等的容器都是以HashTable的形式在内核中实现的。

下面分别来看在PHP、内核中如何定义数组。

PHP中定义数组:

1<?php 
2    $array array(); 
3    $array["key"] = "values"
4?> 

在内核中使用宏来实现:

1zval* array; 
2array_init(array); 
3add_assoc_string(array, "key""value", 1); 

将上述代码中的宏展开:

01zval* array; 
02      ALLOC_INIT_ZVAL(array); 
03      Z_TYPE_P(array) = IS_ARRAY; 
04   
05      HashTable *h; 
06      ALLOC_HASHTABLE(h); 
07      Z_ARRVAL_P(array)=h; 
08      zend_hash_init(h, 50, NULL,ZVAL_PTR_DTOR, 0); 
09   
10      zval* barZval; 
11      MAKE_STD_ZVAL(barZval); 
12      ZVAL_STRING(barZval, "value", 0); 
13      zend_hash_add(h, "key", 4, &barZval, sizeof(zval*), NULL); 

内核为我们提供了方便的宏来管理数组。

01//add_assoc_*系列函数: 
02add_assoc_null(zval *aval, char *key); 
03add_assoc_bool(zval *aval, 
PHP内核    2019-04-25 16:07:15    13    0    0

PHP中经常使用数组,使用数组最大的好处便是速度!读写都可以在O(1)内完成,因为它每个元素的大小都是一致的,只要知道下标,便可以瞬间计算出其对应的元素在内存中的位置,从而直接取出或者写入。那么内核中是如何实现的呢?

PHP大部分功能,都是通过HashTable来实现,其中就包括数组。HashTable即具有双向链表的优点,同时具有能与数据匹敌的操作性能。PHP中的定义的变量保存在一个符号表里,而这个符号表其实就是一个HashTable,它的每一个元素都是一个zval*类型的变量。不仅如此,保存用户定义的函数、类、资源等的容器都是以HashTable的形式在内核中实现的。

下面分别来看在PHP、内核中如何定义数组。

PHP中定义数组:

1<?php 
2    $array array(); 
3    $array["key"] = "values"
4?> 

在内核中使用宏来实现:

1zval* array; 
2array_init(array); 
3add_assoc_string(array, "key""value", 1); 

将上述代码中的宏展开:

01zval* array; 
02      ALLOC_INIT_ZVAL(array); 
03      Z_TYPE_P(array) = IS_ARRAY; 
04   
05      HashTable *h; 
06      ALLOC_HASHTABLE(h); 
07      Z_ARRVAL_P(array)=h; 
08      zend_hash_init(h, 50, NULL,ZVAL_PTR_DTOR, 0); 
09   
10      zval* barZval; 
11      MAKE_STD_ZVAL(barZval); 
12      ZVAL_STRING(barZval, "value", 0); 
13      zend_hash_add(h, "key", 4, &barZval, sizeof(zval*), NULL); 

内核为我们提供了方便的宏来管理数组。

01//add_assoc_*系列函数: 
02add_assoc_null(zval *aval, char *key); 
03add_assoc_bool(zval *aval, char *key, zend_b
PHP内核    2019-04-25 16:06:35    8    0    0

当你在扩展中使用HashTable时候,95%是要存储用户端的变量,就像PHP语言中数组那样。为此,内核中已经准备好了相应的工具,来让我们更加的方便的操作HashTable存储zval*,也就是PHP语言中的数组,即IS_ARRAY常量代表的zval,以下用{数组}来代替PHP语言中的数组这个词。

创建数组

创建HashTable有些繁琐,虽然有辅助的宏但还是不能一步完成,而创建数组便简单多了,直接使用array_init(zval *arrval)函数即可,注意它的参数是zval*类型的!这样,我们像用户端返回数组便简单多了:

1ZEND_FUNCTION(sample_array)
2{
3    array_init(return_value);
4}
5 
6//return_value是zval*类型的,所以我们直接对它调用array_init()函数即可,即把它初始化成了一个空数组。

添加元素

将数组初始化后,接下来就要向其添加元素了。因为PHP语言中有多种类型的变量,所以也对应的有多种类型的add_assoc_*()、add_index_*、add_next_index_*()函数。如:

1array_init(arrval);
2 
3add_assoc_long(zval *arrval, char *key, long lval);
4add_index_long(zval *arrval, ulong idx, long lval);
5add_next_index_long(zval *arrval, long lval);

这三个函数的第一个参数都要被操作的{数组}指针,然后是索引值,最后是变量,唯一不同的是add_next_index_long()函数的索引值是其自己计算出来的。根据上一节的内容我们可以知道,这三个函数分别在内部使用了zend_hash_update()、zend_hash_index_update()与zend_hash_next_index_insert函数。

01//add_assoc_*系列函数:
02add_assoc_null(zval *aval, char *key);
03add_assoc_bool(zval *aval, char *key, zend_bool bval);
04add_assoc_long(zval *aval, cha
PHP内核    2019-04-25 16:05:59    17    0    0

Zend把与HashTable有关的API分成了好几类以便于我们寻找,这些API的返回值大多都是常量SUCCESS或者FAILURE。

创建HashTable

下面在介绍函数原型的时候都使用了ht名称,但是我们在编写扩展的时候,一定不要使用这个名称,因为一些PHP宏展开后会声明这个名称的变量,进而引发命名冲突。

创建并初始化一个HashTable非常简单,只要使用zend_hash_in

PHP内核    2019-04-25 16:04:17    22    0    0

在C语言中,我们可以自定义各种各样的数据结构,用来把很多数据保存在一个变量里面,但是每种数据结构都有自己的优缺点,PHP内核规模如此庞大,是否已经找到了一些非常棒的解决方法呢?

我们在选择各种数据结构时,往往会考虑我们需要处理的数据规模以及需要的性能。下面让我们简要的看一下看C语言中数组和链表的一些事情。

数组

作者这里用的不是Array,而是Vector,可能指的是C++里的Vector,它与数组几乎是完全一样的,唯一的不同便是可以实现动态存储。本节下文都是用数组一词代替之,请各位注意。数组是内存中一块连续的区域,其每一个元素都具有一个唯一的下标值。

1int a[3];
2a[0]=1;
3a[2]=3;

不仅是整数,其它类型的变量也可以保存在数组中,比如我们前面用到的zend_get_parameters_array_ex(),便把很多zval**类型的变量保存到一个数组里,为了使其正常工作,我们提前向系统申请了相应大小的内存空间。

1zval ***args = safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval**), 0);

这里我们仍然可以用一个整数来当作下标去数组中取出我们想要的数据,就像var_dump()的实现中通过args[i]来获取参数并把它传递给php_var_dump()函数那样。

使用数组最大的好处便是速度!读写都可以在O(1)内完成,因为它每个元素的大小都是一致的,只要知道下标,便可以瞬间计算出其对应的元素在内存中的位置,从而直接取出或者写入。

链表

链表也是一种经常被使用的一种数据结构。链表中的每一个元素都至少有两个元素,一个指向它的下一个元素,一个用来存放它自己的数据,就像下面定义的那样:

1typedef struct _namelist namelist;
2struct
3{
4    struct _namelist *next;
5    char *name;
6}_namelist;

我们可以声明一个其类型的元素:

1static namelist *people;

假设每一个元素都代表一个人,元素中的name属性便是这个人的名字,我们通过这样的语句来得到它:people->name; 第二个属性指向后面的一个元素,那我们便可以这样来访问下一个人的名字:people->next->name, 或者下一个人的下一个人的名字:pe

4/9