首页 > 编程知识 正文

php windows vldPHP之opcode及VLD使用

时间:2023-05-04 01:13:30 阅读:264080 作者:4744

OpCode,即Operation Code,操作码。通常是指计算机指令中的一部分,用于指定要执行的操作, 指令的格式和规范由处理器的指令规范指定。

通常opcode还有另一种称谓:字节码(byte codes)。例如Java编译后生成的class文件。

PHP中的opcode则属于后者,PHP构建在Zend虚拟机(Zend VM)之上。PHP的opcode就是Zend虚拟机中的指令。当Zend 引擎完成对脚本代码的编译后,便将它们生成可以直接运行的中间代码,即OpCode。

比如写了如下PHP代码:

echo "Hello World!";

$a = 1 +1;

echo $a;

?>

PHP执行这段代码会经过如下4个步骤(确切的来说,应该是PHP的语言引擎Zend):

1. Scanning(Lexing) ,Zend Engine(Zend引擎),调用词法分析器(Lex生成的,源文件在Zend/zend_language_sanner.l),将我们要执行的PHP源文件,去掉空格 ,注释,分割成一个一个的语言片段(Tokens)。

2. Parsing, Zend引擎会将得到的token forward给语法分析器(yacc生成, 源文件在 Zend/zend_language_parser.y),将Tokens转换成简单而有意义的表达式。

3. Compilation, 将表达式编译成Opocdes,opcode一般会以op array的形式存在。

4. Execution, Zend引擎调用zend_executor来顺次执行Opcodes,每次一条,从而实现PHP脚本的功能。

每一次请求到来的时候,都是这样一个过程,但是很明显每次都需要编译,是非常低效的。因为PHP源码不变,OpCode就不会改变,所以前面的步骤就不需要每次都进行,这就是所谓的OpCode缓存,就是将第三步生成的OpCode缓存下来,这样下一次请求来的时候就不需要前三步,直接执行OpCode就可以了,从而可以大大的提升执行速度。

关于Opcode缓存

详细的opcode编译过程可以看鸟哥的博客《深入理解PHP原理之OpCode》。

那如何可以看到我们的PHP脚本,最终被编译成什么样的呢? 也就是说,OpCode长的什么样子呢? 在PECL中已经有这样的模块,可以让我们直接使用了,那就是由 Derick Rethans开发的VLD (Vulcan Logic Dissassembler)模块。只需要下载这个模块,并把他作为PHP扩展载入,就可以通过简单的设置,来得到脚本编译的结果了。

1. 安装VLD

下载地址:http://pecl.php.net/package/vld

windows直接下载dll拷贝到php的ext目录中,然后在php.ini中加上extension=php_vld.dll就可以了。

Linux下载编译安装,然后同样php.ini加上extension=vld.so即可。

2. 使用VLD

php -dvld.active=1 test.php

比如上面的代码,就可以得到:

PHP构建在Zend 引擎之上。PHP的opcode就是Zend虚拟机中的指令,在PHP源码({PHPSRC}/Zend/zend_vm_opcodes.c)中可以发现,一共有187条指令。顺便说一下,目前PHP源码托管到github上了,源码地址:https://github.com/php/php-src/

在PHP7之前的源码中,opcode({PHPSRC}/Zend/zend_compile.h)是这样的:

struct _zend_op {

opcode_handler_t handler; // 执行该opcode时调用的处理函数

znode result;

znode op1;

znode op2;

ulong extended_value;

uint lineno;

zend_uchar opcode;  // opcode代码

};

最新的PHP7中的是这样,可以看到增加了三个字段,表示操作数的数据类型,这大概是为PHP7可以支持强类型修改的吧(猜的)。

struct _zend_op {

const void *handler;

znode_op op1;

znode_op op2;

znode_op result;

uint32_t extended_value;

uint32_t lineno;

zend_uchar opcode;

zend_uchar op1_type;

zend_uchar op2_type;

zend_uchar result_type;

};

各个字段的含义:

(1)handler:op的执行句柄。

(2)op1, op2, result:这三个字段是op的操作数和操作结果载体,当然并不是每个op都需要使用这三个字段,根据op的功能不同,会使用其中某些字段。比如类型为ZEND_ECHO的op只需要使用op1,功能就是将op1中的相应的值输出。

void zend_do_echo(const znode *arg TSRMLS_DC) {

zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

opline->opcode = ZEND_ECHO;

opline->op1 = *arg;

SET_UNUSED(opline->op2);

}

PHP7之前的代码是znode类型,而php7中是znode_op类型。

PHP7中znode_op和znode的定义:

typedef union _znode_op {

uint32_t constant;

uint32_t var;

uint32_t num;

uint32_t opline_num; /* Needs to be signed */

#if ZEND_USE_ABS_JMP_ADDR

zend_op *jmp_addr;

#else

uint32_t jmp_offset;

#endif

#if ZEND_USE_ABS_CONST_ADDR

zval *zv;

#endif

} znode_op;

typedef struct _znode { /* used only during compilation */

zend_uchar op_type;

zend_uchar flag;

union {

znode_op op;

zval constant; /* replaced by literal/zv */

} u;

} znode;

PHP7之前版本znode:

typedef struct _znode {

int op_type;

union {

zval constant;

zend_uint var;

zend_uint opline_num; /*  Needs to be signed */

zend_op_array *op_array;

zend_op *jmp_addr;

struct {

zend_uint var;  /* dummy */

zend_uint type;

} EA;

} u;

} znode;

具体变化的原因由于并没有仔细的看源码,所以还不太明白,有空研究一下源码。

其中op_type这个int类型的字段定义操作数的类型,这些类型一共有五种:

#define IS_CONST    (1<<0)

#define IS_TMP_VAR  (1<<1)

#define IS_VAR      (1<<2)

#define IS_UNUSED   (1<<3)    /* Unused variable */

#define IS_CV       (1<<4)    /* Compiled variable */

IS_CONST:

表示常量,例如$a = 123; $b = “hello”;这些代码生成OP后,123和”hello”都是以常量类型操作数存在。

IS_TMP_VAR:表示临时变量,临时变量一般在前面加~来表示,这是一些OP执行过程中需要用到的中间变量,例如初始化一个数组的时候,就需要一个临时变量来暂时存储数组zval,然后将数组赋值给变量。

IS_VAR:一般意义上的变量,以$开发表示,此种变量本人目前研究的较少,暂不介绍

IS_UNUSED :暂时不介绍,从名字来看应该是标识为不使用

IS_CV:这种类型的操作数比较重要,此类型是在PHP后来的版本中(大概5.1)中才出现,CV的意思是compiled variable,即编译后的变量,变量都是保存在一个符号表中,这个符号表是一个哈希表,试想如果每次读写变量的时候都需要到哈希表中去检索,势必会对效率有一定的影响,因此在执行上下文环境中,会将一些编译期间生成的变量缓存起来,此过程以后再详细介绍。此类型操作数一般以!开头表示,比如变量$a=123;$b=”hello”这段代码,$a和$b对应的操作数可能就是!0和!1, 0和1相当于一个索引号,通过索引号从缓存中取得相应的值。

(3)opcode:与CPU的指令类似,有一个标示指令的opcode字段。此字段保存的整形值即为op的编号,用来区分不同的op类型,opcode的可取值都被定义成了宏,可以在{PHPSRC}/Zend/zend_vm_opcodes.h中看到这些宏的定义,类似如({PHPSRC}/Zend/zend_vm_opcodes.h):

#define ZEND_NOP 0

#define ZEND_ADD 1

#define ZEND_SUB 2

#define ZEND_MUL 3

#define ZEND_DIV 4

#define ZEND_MOD 5

#define ZEND_SL 6

#define ZEND_SR 7

#define ZEND_CONCAT 8

(4)lineno:对应PHP源码中的行号。

(5)extended_value:PHP不像汇编那么底层,在脚本实际执行的时候可能还需要其他更多的信息,保存在extended_value字段。

转载请注明出处fullstackdevel.com:SEAN是一只程序猿 » PHP之opcode及VLD使用

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。