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() ]
02 | zend_hash_init(&EG(symbol_table), 50, NULL, ZVAL_PTR_DTOR, 0); |
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); |
上面的代码的关键点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)等等。我们通过下面这个例子来应用一下这几个宏:
01 | void display_value(zval zv,zval *zv_p,zval **zv_pp) |
03 | if ( Z_TYPE(zv) == IS_NULL ) |
05 | php_printf( "类型是 IS_NULL!\n" ); |
08 | if ( Z_TYPE_P(zv_p) == IS_LONG ) |
10 | php_printf( "类型是 IS_LONG,值是:%ld" , Z_LVAL_P(zv_p)); |
13 | if (Z_TYPE_PP(zv_pp) == IS_DOUBLE ) |
15 | php_printf( "类型是 IS_DOUBLE,值是:%f" , Z_DVAL_PP(zv_pp) ); |
String型变量比较特殊,因为内核在保存String型变量时,不仅保存了字符串的值,还保存了它的长度,所以它有对应的两种宏组合STRVAL和STRLEN,即:Z_STRVAL、Z_STRVAL_P、Z_STRVAL_PP与Z_STRLEN、Z_STRLEN_P、Z_STRLEN_PP。前一种宏返回的是char *型,即字符串的地址;后一种返回的是int型,即字符串的长度。
1 | void display_string(zval *zstr) |
3 | if (Z_TYPE_P(zstr) != IS_STRING) { |
4 | php_printf( "这个变量不是字符串!\n" ); |
7 | PHPWRITE(Z_STRVAL_P(zstr), Z_STRLEN_P(zstr)); |
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文件里,简短精炼,只有四个成员组成:
03 | zend_uint refcount__gc; |
05 | zend_uchar is_ref__gc; |
07 | typedef struct _zval_struct zval; |
10 | typedef unsigned int zend_uint; |
11 | typedef 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文件里:
01 | typedef union _zvalue_value { |
09 | zend_object_value obj; |
PHP内核
2019-04-25 15:39:37
0
0
0
常量,顾名思义是一个常态的量值。它与值只绑定一次,它的作用在于有肋于增加程序的可读性和可靠性。 在PHP中,常量的名字是一个简单值的标识符,在脚本执行期间该值不能改变。 和变量一样,常量默认为大小写敏感,但是按照我们的习惯常量标识符总是大写的。 常量名和其它任何 PHP 标签遵循同样的命名规则。合法的常量名以字母或下划线开始,后面跟着任何字母,数字或下划线。
- 在设定以后,常量的值无法更改
- 常量名不需要开头的美元符号 ($)
- 作用域不影响对常量的访问
- 常量值只能是字符串或数字
在这一小节我们一起看下常量与我们常见的变量有啥区别,它在执行期间的不可改变的特性是如何实现的以及常量的定义过程。
首先看下常量与变量的区别,常量是在变量的zval结构的基础上添加了一额外的元素。如下所示为PHP中常量的内部结构。
常量的内部结构
1 | typedef struct _zend_constant { |
在Zend/zend_constants.h文件的33行可以看到如上所示的结构定义。 在常量的结构中,除了与变量一样的zval结构,它还包括属于常量的标记,常量名以及常量所在的模块号。
在了解了常量的存储结构后,我们来看PHP常量的定义过程。一个例子。
1 | define( 'NOWAMAGIC' , 'www.nowamagic.net' ); |
这是一个很常规的常量定义过程,它使用了PHP的内置函数define。常量名为NOWAMAGIC,值为一个字符串,存放在zval结构中。 从这个例子出发,我们看下define定义常量的过程实现。
define定义常量
define是PHP的内置函数,在Zend/zend_builtin_functions.c文件中定义了此函数的实现。如下所示为部分源码:
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等。对于字符串而言这是目前所知道的最好的哈希算法,原因在于该算法的速度非常快,而且分类非常好(冲突小,分布均匀)。
算法的核心思想就是:
1 | hash(i) = hash(i-1) * 33 + str[i] |
在zend_hash.h中,我们可以找到在PHP中的这个算法:
01 | static inline ulong zend_inline_hash_func( char *arKey, uint nKeyLength) |
03 | register ulong hash = 5381; |
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++; |
PHP内核
2019-04-25 15:29:31
6
0
0
在PHP的Zend引擎中,有一个数据结构非常重要,它无处不在,是PHP数据存储的核心,各种常量、变量、函数、类、对象等都用它来组织,这个数据结构就是HashTable。
HashTable在通常的数据结构教材中也称作散列表,哈希表。其基本原理比较简单(如果你对其不熟悉,请查阅随便一本数据结构教材或在网上搜索),但PHP的实现有其独特的地方。理解了HashTable的数据存储结构,对我们分析P