手机版

PHP的运行机制和原理(底层)

时间:2021-09-22 来源:互联网 编辑:宝哥软件园 浏览:

说到php的运行机制,首先要介绍php的模块。PHP有三个模块:内核、Zend引擎和扩展层。PHP内核用于处理请求、文件流、错误处理和其他相关操作。Zend引擎(ZE)用于将源文件转换为机器语言,然后在虚拟机上运行。扩展层是PHP用来执行特定操作的一组函数、类库和流。例如,我们需要mysql扩展来连接到MySQL数据库。当ZE执行程序时,它可能需要连接几个扩展。此时,ZE将控制权交给扩展,然后在处理特定任务后返回它们。

最后,ZE将程序的运行结果返回到PHP内核,PHP内核再将结果传输到SAPI层,最后输出到浏览器。

PHP说起来容易,但是掌握它并不是一件容易的事情。除了能够使用之外,我们还需要知道它在底层是如何工作的。

PHP是一种适合web开发的动态语言。具体来说,它是一个用C语言实现了大量组件的软件框架。更狭义地说,它可以被认为是一个强大的UI框架。

了解PHP底层实现的目的是什么?要很好地使用动态语言,我们必须首先理解它。内存管理和框架模型值得借鉴。通过扩展开发,我们可以实现越来越强大的功能,优化程序的性能。

1.PHP的设计理念和特点。

多进程模型:由于PHP是多进程模型,不同的请求不会相互干扰,保证了一个请求的挂起不会影响整体服务。当然,随着时代的发展,PHP已经支持了多线程模型。

弱类型语言:与C/C、Java和C#不同,PHP是一种弱类型语言。变量的类型在开始时不是固定的,在操作过程中可能会发生隐式或显式类型转换。这种机制的灵活性在web开发中非常方便和高效,这将在后面的PHP变量中详细描述。

发动机(Zend)部件(ext)的模式减少了内部耦合。

中间层(sapi)将web服务器与PHP隔离开来。

语法简单灵活,没有太多标准。缺点导致风格混杂,但即使是最差的程序员也不会写出太离谱而伤害大局的程序。

2.PHP的四层体系。

PHP的核心架构如下:

从图中可以看出,PHP自下而上是一个4层系统:

Zend引擎:Zend整体用纯C实现,是PHP的核心部分。它将PHP代码(词法和语法解析等一系列编译过程)翻译成可执行的操作码处理并实现相应的处理方法,实现基本的数据结构(如hashtable和oo),分配和管理内存,并为外部调用提供相应的api方法,这是一切的核心。所有外围功能都是围绕Zend实现的。

扩展:围绕Zend引擎,扩展以基于组件的方式提供各种基本服务。我们常见的内置函数(如数组系列)和标准库都是由Extensions实现的。用户也可以根据自己的需求实现自己的Extensions,达到功能扩展和性能优化的目的(比如贴吧正在使用的PHP中间层和富文本解析就是Extensions的典型应用)。

SAPI:SAPI的全称是服务器应用编程接口,即服务器应用编程接口。SAPI使PHP能够通过一系列钩子函数与外围设备交换数据。这是一个非常优雅和成功的PHP设计。通过成功地将PHP本身与上层应用进行解耦隔离,PHP就可以不再考虑如何兼容不同的应用,应用本身可以根据自身的特点实现不同的处理方式。

上层应用:这是我们平时写的PHP程序,通过不同的sapi方法得到各种应用模式,比如通过webserver实现web应用,命令行下以脚本模式运行等等。

如果PHP是汽车,那么汽车的框架就是PHP本身,Zend就是汽车的引擎,Ext下的各种组件就是汽车的轮子。Sapi可以看作是一条高速公路,汽车可以在不同类型的高速公路上行驶,而PHP程序一旦执行,汽车就在高速公路上行驶。因此,我们需要:优秀的发动机,合适的车轮和正确的ru

如前所述,Sapi使外部应用程序能够通过一系列接口与PHP交换数据,并可以根据不同的应用程序特性实现特定的处理方法。我们常见的sapi有:

2 apache2handler:这是使用apache作为webserver,使用mod_PHP模式时的处理模式,也是现在使用最广泛的一种。

Cgi:这是webserver和PHP之间的另一种直接交互方式,就是著名的fastcgi协议。今年fastcgi PHP的使用越来越多,也是异步webserver唯一支持的模式。

命令行调用的应用程序模式。

4.PHP操作码的执行过程。

我们先来看看PHP代码执行的流程。

从图中可以看出,PHP实现了一个典型的动态语言执行过程:获取一段代码后,经过词法解析和语法解析,将源程序翻译成操作码,然后ZEND虚拟机依次执行这些指令完成操作。PHP本身是用c实现的,所以c的所有功能最后都被调用了。事实上,我们可以把PHP看作是一个由c开发的软件。

PHP执行的核心是逐指令翻译,也就是操作码。

操作码是PHP程序执行最基本的单元。操作码由两个参数(op1、op2)、一个返回值和一个处理函数组成。PHP程序最终被翻译成一组操作码处理函数,用于顺序执行。

以下几种常见的处理功能:

Zend _ assign _ spec _ cv _ cv _ handler :变量赋值($a=$b)。

Zend _ do _ fcall _ by _ name _ spec _ handler:函数调用。

ZEND_CONCAT_SPEC_CV_CV_HANDLER:字符串串联$ a. $ b。

Zend _ add _ spec _ cv _ const _ handler :加法运算$a 2。

ZEND_IS_EQUAL_SPEC_CV_CONST:判断equality $a==1。

Zend _ is _ identical _ spec _ cv _ const:判断相等$a===1。

5.哈希表核心数据结构。

HashTable是zend的核心数据结构,用来实现PHP中几乎所有的常用功能。我们知道的PHP数组就是它的典型应用。此外,在zend中,如函数符号表和全局变量,也是基于哈希表实现的。

PHP的哈希表具有以下特点:

支持典型的键值查询。

可以用作数组。

添加和删除节点是O(1)复杂度。

Key支持混合类型:同时有关联的数字组合索引数组。

该值支持混合类型:数组(“字符串”,2332)。

支持线性遍历:如foreach。

Zend哈希表实现了哈希表的典型哈希表结构,并通过附加一个双向链表提供了前后遍历数组的功能。其结构如下:

可以看到哈希表中既有键值哈希结构,也有双链表模式,支持快速搜索和线性遍历非常方便。

哈希结构:Zend的哈希结构是典型的哈希表模型,通过链表的方式解决冲突。需要注意的是,zend的哈希表是一个自增长的数据结构,当哈希表的数量满了,它会动态的扩展和更新元素位置两次。最初的尺寸都是8。此外,zend本身在快速搜索键值时做了一些优化,通过变空间为时间来加快速度。例如,在每个元素中,使用变量nKeyLength来标识键的长度,以便快速判断。

双向链表:Zend哈希表通过链表结构实现元素的线性遍历。理论上,使用单向链表进行遍历就足够了。使用双向链表的主要目的是快速删除,避免遍历。Zend哈希表是一种复合结构。当用作数组时,它不仅支持常见的关联数组,还可以用作顺序索引号,甚至允许两者混合使用。

PHP关联数组:关联数组是一个典型的hash_table应用。查询过程经历以下步骤(从代码中可以看出,这是一个常见的哈希查询过程,并添加了一些快速决策来加快搜索速度。):

getKeyHashValueindex=n问题询问;bucket * p=AbOcket[index];while(p){ if((p-h==h)(p-nKeyLength==nKeyLength)){ RETURN p-data;} p=p-next;}PHP索引数组:index数组是我们常用的数组,通过下标访问。例如,$arr[0],Zend哈希表在内部规范化,哈希值和nKeyLength (0)也分配给索引类型键。内部成员变量nNextFreeElement是当前分配的最大id,每次推送后会自动增加一个。正是这个规范化的过程,使得PHP实现了关联和非关联的混合。由于推送操作的特殊性,PHP数组中索引键的顺序不是由下标的大小决定的,而是由推送的顺序决定的。例如,$ arr[1]=2;$ arr[2]=3;对于双精度类型的键,Zend哈希表会将其视为索引键。

6.PHP变量。

PHP是弱类型语言,没有严格区分变量的类型。PHP在声明变量时不需要指定类型。PHP可能在程序运行期间执行变量类型的隐式转换。像其他强类型语言一样,显示类型可以在程序中转换。PHP变量可以分为简单类型(int、string、bool)、数组资源对象和常量(const)。上述所有变量在底部都具有相同的结构zval。

Zval是zend中另一个非常重要的数据结构,用于识别和实现PHP变量。其数据结构如下:

Zval主要由三部分组成:

类型:指定变量(整数、字符串、数组等)描述的类型。).

Refcountis_ref:用于实现引用计数(后面会详细介绍)。

值:核心部分,存储变量的实际数据。

Zvalue是用于存储变量的实际数据。因为要存储许多类型,zvalue是一个并集,因此实现了弱类型。

PHP变量类型和它们的实际存储之间的对应关系如下:

IS _ LONG-lvalueIS _ DOUBLE-dvalueIS _ ARRAY-htIS _ STRING-strIS _ RESOURCE-lvalue

引用计数广泛应用于内存回收、字符串操作等场合。PHP中的变量是引用计数的典型应用。Zval的引用计数是通过成员变量is_ref和ref_count实现的。通过引用计数,多个变量可以共享相同的数据。避免频繁复制造成的大量消耗。

在赋值操作中,zend将变量指向相同的zval和ref_count,在unset操作中,指向对应的ref_count-1。只有当ref_count减少到0时,才会实际执行销毁操作。如果是引用赋值,zend会修改is_ref为1。

PHP变量通过引用计数来共享数据,但是如果其中一个变量值发生了变化呢?当试图写一个变量时,Zend复制一个ref_count为1的zval,如果发现该变量指向的zval被多个变量共享,则递减原始zval的refcount。这个过程叫做“zval分离”。可以看出,zend只在发生写操作时才执行复制操作,因此也称为写时复制。

对于参考变量,要求与非参考变量相反。引用分配的变量必须绑定。修改一个变量会修改所有绑定变量。

整数和浮点数是PHP中的基本类型之一,也是简单变量。对于整数和浮点数,将相应的值直接存储在zvalue中。类型有长型和双型。

从zvalue结构可以看出,PHP不区分int、无符号int、long long等。对于整数类型,这与c等强类型语言不同,对于PHP,整数只有一种类型,即long。由此可以看出,在PHP中,整数值的范围是由编译器的位数决定的,而不是固定的。

对于浮点数,和整数一样,它不区分浮点和双精度,只统一双精度。

在PHP中,如果整数范围越界怎么办?在这种情况下,它会自动转换为双类型,所以要小心,因为许多技巧都是由此生成的。

像整数一样,字符变量是PHP中的基本类型和简单变量。从zvalue结构可以看出,在PHP中,字符串是由指向实际数据和长度结构的指针组成的,类似于C中的string,因为长度是用实际变量来表示的,不像C,它的字符串可以是二进制数据(包括),在PHP中,求字符串长度strlen是O(1)运算。

在添加、修改或追加字符串时,PHP会重新分配内存来生成新的字符串。最后,出于安全考虑,PHP在生成字符串时,仍然会在末尾添加一个字符串。

常见的字符串拼接方法及速度比较;

假设有如下四个变量:$ stra=' 123$ strB=' 456$ intA=123intB=456

现在对以下字符串拼接方法进行比较和说明:

$res=$strA。$strB和$ RES="$ stra $ strb "。

在这种情况下,zend将以平均速度重新管理一个内存块并相应地处理它。

$strA=$strA。$strB

这是最快的。zend将在当前strA的基础上直接relloc,以避免重复复制。

$res=$intA。$intB

这种速度比较慢,因为需要做隐式格式转换,在实际编程中要尽量避免。

$strA=sprintf ("%s%s ",$strA。$ STrb);

这是最慢的方式,因为sprintf不是PHP中的语言结构,格式识别和处理需要花费大量时间。此外,它自己的机制是malloc。但是sprintf的方法可读性最强,在实践中可以根据具体情况灵活选择。

PHP的数组自然是由Zend HashTable实现的。

如何实现foreach操作?数组的Foreach是通过遍历哈希表中的双向链表来完成的。对于索引数组,遍历Foreach比遍历for要高效得多,这节省了对键值的搜索。计数操作直接调用HashTable-NumOfElements,O(1)操作。对于像“123”这样的字符串,zend会将其转换为整数形式。$ arr ['123']和$ $arr['123']是等价的。

资源类型变量是PHP中最复杂的变量,也是一种复合结构。

PHP的zval可以表示各种各样的数据类型,但是很难完全描述用户定义的数据类型。因为没有有效的方法来描述这些复合结构,所以没有办法对它们使用传统的运算符。为了解决这个问题,我们只需要通过一个本质上任意的标签来引用指针,这个标签叫做资源。

在zval中,对于资源,lval用作直接指向资源所在地址的指针。资源可以是任何复合结构,熟悉的mysqli、fsock和memcached都是资源。

如何使用资源:

注册:对于用户定义的数据类型,如果您想将其用作资源。你需要先注册,zend会给它分配一个全球唯一的标签。

获取一个资源变量:对于资源,zend维护一个id- actual数据的hash_tale。对于资源,只有它的id记录在zval中。提取时,通过id在hash_table中找到特定的值并返回。

资源破坏:资源的数据类型多种多样。Zend本身没有办法摧毁它。因此,要求用户在注册资源时提供销毁功能。当取消资源设置时,zend调用相应的函数来完成析构函数。同时将其从全局资源表中删除。

资源可以停留很长时间,不仅是在引用它的所有变量都超出范围之后,甚至是在请求结束并生成新请求之后。这些资源被称为持久资源,因为它们在SAPI的整个生命周期中持续存在,除非它们被故意破坏。在许多情况下,持久资源可以在一定程度上提高性能。例如,我们常见的mysql_pconnect通过pemalloc为持久资源分配内存,这样当请求结束时就不会释放内存。

对于zend来说,两者没有区别。

PHP中局部变量和全局变量是如何实现的?对于一个请求,PHP可以随时看到两个符号表(symbol_table和active_symbol_table)。前者用于维护全局变量。后者是指向当前活动变量符号表的指针。当程序进入一个函数时,zend会给它分配一个符号表x,并将active_symbol_table指向a,这样就实现了全局变量和局部变量的区分。

获取变量值:PHP的符号表是通过hash_table实现的,每个变量都被分配了一个唯一的标识符。获取时,根据标识符从表中找到对应的zval。

在函数中使用全局变量:在函数中,我们可以通过显式声明全局来使用全局变量。在active_symbol_table中,创建对symbol_table中同名变量的引用。如果symbol_table中没有同名变量,将首先创建该变量。

版权声明:PHP的运行机制和原理(底层)是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。