标签 - PHP内核

PHP内核    2019-04-25 15:44:33    8    0    0

大家都知道PHP脚本在执行的时候用户全局变量(在用户空间显式定义的变量)会保存在一个HashTable数据类型的符号表(symbol_table)中, 在PHP中有一些比较特殊的全局变量例如: $_GET,$_POST,$_SERVER等变量,我们并没有在程序中定义这些变量,并且这些变量也同样保存在符号表中, 从这些表象我们不难得出结论:PHP是在脚本运行之前就将这些特殊的变量加入到了符号表中了。

$GLOBALS的初始化

我们以cgi模式为例说明$GLOBALS的初始化。 从cgi_main.c文件main函数开始。 整个调用顺序如下所示:

[main() -> php_request_startup() -> zend_activate() -> init_executor() ]

01... //  省略
02zend_hash_init(&EG(symbol_table), 50, NULL, ZVAL_PTR_DTOR, 0);
03{
04    zval *globals;
05  
06    ALLOC_ZVAL(globals);
07    Z_SET_REFCOUNT_P(globals, 1);
08    Z_SET_ISREF_P(globals);
09    Z_TYPE_P(globals) = IS_ARRAY;
10    Z_ARRVAL_P(globals) = &EG(symbol_table);
11    zend_hash_update(&EG(symbol_table), "GLOBALS"sizeof("GLOBALS"),
12        &globals, sizeof(zval *), NULL);      //  添加全局变量GLOBALS
13}
14... //  省略

上面的代码的关键点zend_hash_update函数的调用,它将变量名为GLOBALS的变量注册到EG(symbol_table)中, EG(symbol_table)是一个HashTable的结构,用来存放所有的全局变量。 这在下面将要提到的$_GET等变量初始化时也会用到。

$_GET、$_POST等变量的初始化

$_GET、$_COOKIE、$_SERVER、$_ENV、$_FILES、$_REQUEST这六个变量都是通过如下的调用序列进行初始化。 [m

PHP内核    2019-04-25 15:44:01    11    0    0

我们已经知道php变量在内核中其实是通过zval结构来实现的,也初步了如果设置一个zval结构的类型和值,这一节我们的目的便是在前两节的基础上,彻底掌握对zval结构的操控,其间将引入很多超棒的新的宏。

在code的时候,我们很希望在内核中创建的zval可以让用户在PHP语言里以变量的形式使用,为了实现这个功能,我们首先要创建一个zval。最容易想到的办法便是创建一个zval指针,然后申请一块内存并让指针指向它。如果你脑海里浮现出了malloc(sizeof(zval))的影子,那么请你立即刹车,不要用malloc来做这件事情,内核给我们提供了相应的宏来处理这件事,理由和以前一样:为了代码漂亮并保持版本升级时的兼容性。这个宏的是:MAKE_STD_ZVAL(pzv)。这个宏会用内核的方式来申请一块内存并将其地址付给pzv,并初始化它的refcount和is_ref连个属性,更棒的是,它不但会自动的处理内存不足问题,还会在内存中选个最优的位置来申请。

除了MAKE_STD_ZVAL()宏函数,ALLOC_INIT_ZVAL()宏函数也是用来干这件事的,唯一的不同便是它会将pzv所指的zval的类型设置为IS_NULL;

申请完空间后,我们便可以给这个zval赋值了。基于咱已经介绍的宏,也许我们需要Z_TYPE_P(p) = IS_NULL来设置其是null类型,并过Z_SOMEVAL形式的宏来为它赋值,但是现在你有了更好更短的选择!

内核中提供一些宏来简化我们的操作,可以只用一步便设置好zval的类型和值。

新宏其它宏的实现方法
ZVAL_NULL(pvz); (注意这个Z和VAL之间没有下划线!)Z_TYPE_P(pzv) = IS_NULL;(IS_NULL型不用赋值,因为这个类型只有一个值就是null,^_^)
ZVAL_BOOL(pzv, b); (将pzv所指的zval设置为IS_BOOL类型,值是b)Z_TYPE_P(pzv) = IS_BOOL;
Z_BVAL_P(pzv) = b ? 1 : 0;
ZVAL_TRUE(pzv); (将pzv所指的zval设置为IS_BOOL类型,值是true)ZVAL_BOOL(pzv, 1);
ZVAL_FALSE(pzv); (将pzv所指的zval设置为IS_BOOL类型,值是false)ZVAL_BOOL(pzv, 0);
ZVAL
PHP内核    2019-04-25 15:43:28    8    0    0

PHP内核提供了三个基础宏来方便我们对变量的值进行操作,这几个宏同样以Z_开头,并且P结尾和PP结尾的同上一节中的宏一样,分别代表这参数是指针还是指针的指针。此外,为了进一步方便我们的工作,内核中针对具体的数据类型分别定义了相应的宏。如针对IS_BOOL型的BVAL组合(Z_BVAL、Z_BVAL_P、Z_BVAL_PP)和针对IS_DOUBLE的DVAL组合(Z_DVAL、ZDVAL_P、ZDVAL_PP)等等。我们通过下面这个例子来应用一下这几个宏:

01void display_value(zval zv,zval *zv_p,zval **zv_pp)
02{
03    if( Z_TYPE(zv) == IS_NULL )
04    {
05        php_printf("类型是 IS_NULL!\n");
06    }
07     
08    if( Z_TYPE_P(zv_p) == IS_LONG )
09    {
10        php_printf("类型是 IS_LONG,值是:%ld" , Z_LVAL_P(zv_p));
11    }
12     
13    if(Z_TYPE_PP(zv_pp) == IS_DOUBLE )
14    {
15        php_printf("类型是 IS_DOUBLE,值是:%f" , Z_DVAL_PP(zv_pp) );
16    }
17}

String型变量比较特殊,因为内核在保存String型变量时,不仅保存了字符串的值,还保存了它的长度,所以它有对应的两种宏组合STRVAL和STRLEN,即:Z_STRVAL、Z_STRVAL_P、Z_STRVAL_PP与Z_STRLEN、Z_STRLEN_P、Z_STRLEN_PP。前一种宏返回的是char *型,即字符串的地址;后一种返回的是int型,即字符串的长度。

1void display_string(zval *zstr)
2{
3    if (Z_TYPE_P(zstr) != IS_STRING) {
4        php_printf("这个变量不是字符串!\n");
5        return;
6    }
7    PHPWRITE(Z_STRVAL_P(zstr), Z_STRLEN_P(zstr));
8    //这里用了PHP
PHP内核    2019-04-25 15:42:46    9    0    0

所有的编程语言都要提供一种数据的存储与检索机制,PHP也不例外。其它语言大都需要在使用变量之前先定义,并且它的类型也是无法再次改变的,而PHP却允许程序猿自由的使用变量而无须提前定义,甚至可以随时随意的对已存在的变量转换成其它任何PHP支持的数据类型。在程序在运行的时候,PHP还会自动的根据需求转换变量的类型。

如果你用过PHP,肯定体验过PHP的弱类型的变量体系。众所周知,PHP引擎是用C写的,而C确实一种强类型的编程语言,PHP内核中是如何用C来实现自己的这种弱类型特性的?下面谈谈变量的类型。

PHP在内核中是通过zval这个结构体来存储变量的,它的定义在Zend/zend.h文件里,简短精炼,只有四个成员组成:

01struct _zval_struct {
02    zvalue_value value; /* 变量的值 */
03    zend_uint refcount__gc;
04    zend_uchar type;    /* 变量当前的数据类型 */
05    zend_uchar is_ref__gc;
06};
07typedef struct _zval_struct zval;
08 
09//在Zend/zend_types.h里定义的:
10typedef unsigned int zend_uint;
11typedef unsigned char zend_uchar;

zval里的refcout__gc是zend_uint类型,也就是unsinged int型,is_ref__gc和type则是unsigned char型的。保存变量值的value则是zvalue_value类型(PHP5),它是一个Union,同样定义在了Zend/zend.h文件里:

01typedef union _zvalue_value {
02    long lval;  /* long value */
03    double dval;    /* double value */
04    struct {
05        char *val;
06        int len;
07    } str;
08    HashTable *ht;  /* hash table value */
09    zend_object_value obj;
10} zvalue
PHP内核    2019-04-25 15:40:09    5    0    0
PHP内核    2019-04-25 15:39:37    0    0    0

常量,顾名思义是一个常态的量值。它与值只绑定一次,它的作用在于有肋于增加程序的可读性和可靠性。 在PHP中,常量的名字是一个简单值的标识符,在脚本执行期间该值不能改变。 和变量一样,常量默认为大小写敏感,但是按照我们的习惯常量标识符总是大写的。 常量名和其它任何 PHP 标签遵循同样的命名规则。合法的常量名以字母或下划线开始,后面跟着任何字母,数字或下划线。

  • 在设定以后,常量的值无法更改
  • 常量名不需要开头的美元符号 ($)
  • 作用域不影响对常量的访问
  • 常量值只能是字符串或数字

在这一小节我们一起看下常量与我们常见的变量有啥区别,它在执行期间的不可改变的特性是如何实现的以及常量的定义过程。

首先看下常量与变量的区别,常量是在变量的zval结构的基础上添加了一额外的元素。如下所示为PHP中常量的内部结构。

常量的内部结构

1typedef struct _zend_constant {
2    zval value; /* zval结构,PHP内部变量的存储结构,在第一小节有说明 */
3    int flags;  /* 常量的标记如 CONST_PERSISTENT | CONST_CS */
4    char *name; /* 常量名称 */
5    uint name_len; 
6    int module_number;  /* 模块号 */
7} zend_constant;

在Zend/zend_constants.h文件的33行可以看到如上所示的结构定义。 在常量的结构中,除了与变量一样的zval结构,它还包括属于常量的标记,常量名以及常量所在的模块号。

在了解了常量的存储结构后,我们来看PHP常量的定义过程。一个例子。

1define('NOWAMAGIC''www.nowamagic.net');

这是一个很常规的常量定义过程,它使用了PHP的内置函数define。常量名为NOWAMAGIC,值为一个字符串,存放在zval结构中。 从这个例子出发,我们看下define定义常量的过程实现。

define定义常量

define是PHP的内置函数,在Zend/zend_builtin_functions.c文件中定义了此函数的实现。如下所示为部分源码:

01/* {{{ proto bool define(string constant_name, mixed value, boo
PHP内核    2019-04-25 15:38:19    8    0    0

最近哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招。本文结合PHP内核源码,聊一聊这种攻击的原理及实现。

哈希表碰撞攻击的基本原理

哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表。PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,还在Zend虚拟机内部用于存储上下文环境信息(

PHP内核    2019-04-25 15:37:41    3    0    0

In case you ever heard me talking about PHP internals I certainly mentioned something along the lines of "Everything in PHP is a HashTable" that's not true, but next to a zval the HashTable is one of the most important structures in PHP. While preparing my "PHP Under The Hood" talk for the Dutch PHP Conference there was a question on IRC about extension_loaded() being faster thanfunction_exists(), which might be strange as both of them are simple hash lookups and a hash lookup is said to be O(1). I started to write some slides for it but figured out that I won't have the time to go through it during that presentation, so I'm doing this now, here:

如果你以前看过我写的PHP内核相关的内容,那么你一定记得我说过PHP中的任何东西都是哈希表。这不一定是真的,但是除了zval,哈希表确实是PHP中的最重要的结构了。在为Dutch PHP 大会准备"PHP Under The Hood" 演讲时,IRC上有个关于extension_loaded()比function_exists()快的问题,这让人感到疑惑,它们底层都是简单的哈希查找,而哈希查找的时间复杂度为O(1)。我写了一些相关的幻灯片,但是那个大会上已经没有时间了,所以放在了这里。

You all should know PHP arrays. They allow you to create a list of elements where every element may

PHP内核    2019-04-25 15:36:53    11    0    0

Hash Table是PHP的核心,这话一点都不过分。PHP的数组、关联数组、对象属性、函数表、符号表等等都是用HashTable来做为容器的。

PHP的HashTable采用的拉链法来解决冲突,这个自不用多说,我今天主要关注的就是PHP的Hash算法,和这个算法本身透露出来的一些思想。

PHP的Hash采用的是目前最为普遍的DJBX33A (Daniel J. Bernstein, Times 33 with Addition),这个算法被广泛运用与多个软件项目,Apache、Perl和Berkeley DB等。对于字符串而言这是目前所知道的最好的哈希算法,原因在于该算法的速度非常快,而且分类非常好(冲突小,分布均匀)。

算法的核心思想就是:

1hash(i) = hash(i-1) * 33 + str[i]

在zend_hash.h中,我们可以找到在PHP中的这个算法:

01static inline ulong zend_inline_hash_func(char *arKey, uint nKeyLength)
02{
03    register ulong hash = 5381;
04  
05    /* variant with the hash unrolled eight times */
06    for (; nKeyLength >= 8; nKeyLength -= 8) {
07        hash = ((hash << 5) + hash) + *arKey++;
08        hash = ((hash << 5) + hash) + *arKey++;
09        hash = ((hash << 5) + hash) + *arKey++;
10        hash = ((hash << 5) + hash) + *arKey++;
11        hash = ((hash << 5) + hash) + *arKey++;
12        hash = ((hash << 5) + hash) + *arKey++;
13        hash = ((hash << 5) + hash) + *arKey++;
14        hash = ((hash << 5) + hash) + *arKey++;
15   
PHP内核    2019-04-25 15:29:31    6    0    0

在PHP的Zend引擎中,有一个数据结构非常重要,它无处不在,是PHP数据存储的核心,各种常量、变量、函数、类、对象等都用它来组织,这个数据结构就是HashTable。

HashTable在通常的数据结构教材中也称作散列表,哈希表。其基本原理比较简单(如果你对其不熟悉,请查阅随便一本数据结构教材或在网上搜索),但PHP的实现有其独特的地方。理解了HashTable的数据存储结构,对我们分析P

7/9