首页 > PHP 阅读:36

PHP运行原理和机制

PHP 由内核 Zend 引擎和扩展层组成,PHP 内核负责处理请求、完成文件流错误处理等操作,Zend 引擎可以将 PHP 程序文件转换成可在虚拟机上运行的机器语言,扩展层提供一些应用层操作需要的函数类库等,比如数组和 MySQL 数据库的操作等。

Zend 引擎是用 C 语言实现的,将 PHP 代码通过词法语法解析成可执行的 opcode 并实现相应的处理方法、基本的数据结构内存分配和管理等,对外提供相应的可供调用的 API 方法。

Zend 引擎是 PHP 的核心,所有的外围功能都是围绕它实现的。扩展层通过组件的方式提供各种基础服务、内置函数,标准库都是通过它实现的。用户也可以编写自己的扩展来实现特定的需求。

服务端应用编程接口(Server Application Programming Interface,SAPI),通过一系列钩子函数使得 PHP 可以和外围交互数据。我们平时编写的 PHP 程序就是通过不同的 SAPI 方式得到不同的应用模式,如通过 WebServer 实现的 Web 应用和在命令行下运行的脚本等。

一段 PHP 程序被执行的时候会先被解析成 opcode 指令,然后在虚拟机中按顺序执行,由于 PHP 本身是用 C 语言开发的,所以其在执行的时候调用的都是 C 的函数。opcode 是 PHP 程序执行的最基本单位。

HashTable

HashTable 是 Zend 的核心数据结构,实现了 PHP 里几乎所有的功能,支持 key->value 查询,添加删除的复杂度是 O(1),支持线性遍历和混合类型。

在 HashTable 中既有 key->value 形式的散列结构,也有双向链表模式,使得它能够非常方便地支持快速查找和线性遍历。Zend 的散列结构是典型的 hash 表模型,通过链表的方式来解决冲突。

Zend 的 HashTable 是一个自增长的数据结构,当 hash 表数目满了之后,其本身会动态地以 2 倍的方式扩容并重新布置元素位置,初始大小均为 8。

另外,在进行 key->value 快速查找的时候,Zend 本身还做了一些优化,通过空间换时间的方式加快速度。比如在每个元素中都会用一个变量 nKeyLength 标识 key 的长度以做快速判定。

Zend HashTable 通过一个链表结构实现了元素的线性遍历。理论上,做遍历使用单向链表就够了,使用双向链表的主要目的是为了快速删除链表元素,避免遍历。

Zval

PHP 是一门弱类型语言,本身不严格区分变量的类型。PHP 在声明变量的时候不需要指定类型。PHP 在程序运行期间可能进行变量类型的隐式转换。和其他强类型语言一样,程序中也可以进行显式的类型转换。Zval 是 Zend 中另一个非常重要的数据结构,用来标识并实现 PHP 变量。

Zval 主要由以下 3 部分组成。
  • Type:指定了变量所述的类型(整数、字符串、数组等)。
  • refcount&is_ref:用来实现引用计数。
  • value:是核心部分,存储了变量的实际数据。

Zval 用来保存一个变量的实际数据。因为要存储多种类型,所以 zval 是一个 union,也由此实现了弱类型。

引用计数在内存回收、字符串操作等地方使用得非常广泛。PHP 中的变量就是引用计数的典型应用。Zval 的引用计数通过成员变量 is_ref 和 ref_count 实现。通过引用计数,多个变量可以共享同一份数据,避免频繁复制带来的大量消耗。

在进行赋值操作时,Zend 将变量指向相同的 Zval,同时 ref_count++,在 unset 操作时,对应的 ref_count-1。只有 ref_count 为 0 时才会真正执行销毁操作。如果是引用赋值,Zend 就会修改 is_ref 为 1。

PHP 变量通过引用计数实现变量共享数据,当试图写入一个变量时,Zend 若发现该变量指向的 Zval 被多个变量共享,则为其复制一份 ref_count 为 1 的 Zval,并递减原 Zval 的 refcount,这个过程称为“Zval分离”。可见,只有在有写操作发生时,Zend 才进行复制操作,因此也叫 copy-on-write(写时复制)。

对于引用型变量,其要求和非引用型相反,引用赋值的变量间必须是捆绑的,修改一个变量就修改了所有捆绑变量。