php
php的声明周期
在常见的webserver环境中,你不能一直启动php解释器;一般是启动apache或者其他webserver,由他们加载php处理需要处理的脚本(请求的.php文档)
一切都是从sapi开始
SAPI是Server Application Programming Interface(服务器应用编程接口)的缩写。PHP通过SAPI提供了一组接口,供应用和PHP内核之间进行数据交互。
尽管看起来不同,但是实际上CLI的行为和web方式一致,在命令行键入php命令将启动"命令行sapi"他实际就像一个设计用于服务单个请求的迷你版webserver.当脚本运行完成后,这个迷你的php-webserver终止并返回控制给shell.
启动和终止
这里的启动和终止过程分为两个独立的启动阶段和两个独立的终止阶段,一个周期用于php解释器整体执行所需结构和值的初始化设置,他们在sapi生命周期中持久存在,另一个仅服务于单页面请求,生命周期短暂一些.
初始化启动在所有的请求发生之前,php调用每一个扩展的模块初始化方法.这里,扩展可能会定义常量,定义类,注册资源,流,过滤处理器等所有将要被请求脚本所使用的的资源.所有这些都有一个特性,就是他们被设计跨所有请求存在,也就可以成为"持久".
在请求到达时,php会安装一个操作系统,该环境包含符号表(变量存储)并会同步每个目录的配置值.php接着遍历所有的扩展,这一次调用每一个扩展的请求初始化方法.这里,扩展可能充当全局变量到默认值.预置变量到脚本的符号表,或执行其他的任务比如记录页面请求日志到文件.
在一个请求完成处理后,(到达脚本末尾或者调用die()语句),php通过调用每一个扩展的请求终止开始清理过程.
在符号表和其他资源释放前所需要做的最后一件事就是showdown,当完成方法后,符号表中的所有变量都会被立即unset().
在此期间,所有非持久化资源和对象的析构器都将被调用去释放资源.
最后,当所有的请求都等到满足,webserver和其他的sapi开始准备终止,php循环执行每个扩展MSHUTDOWN(模块终止)方法.这是MINIT周期内,扩展最后一次卸载处理器和释放持久化分配内存的机会.
生命周期
每个php实例,无论从init脚本启动还是命令行启动,接下来就是系列的请求/模块的初始化/终止事件,以及脚本自身的执行.每个启动和终止阶段会被执行多少次,上门频率执行?都依赖所使用的sapi.最常见的模式:多线程模块,多进程模块,嵌入式.
cli生命周期
cli(和cgi)sapi在他的单请求生命周期中相当特殊,因为此时整个php的生命周期只有一个请求.不过,前面讲的各个阶段仍然会全部执行.
多线程生命周期
随着发展,php逐渐被一些webserver以多线程方式使用.在多线程的webserver中永远都只有一个进程在运行,但是在进程空间中有多个线程同时执行.这样做能降低一些负载.
嵌入式生命周期
sapi接口一致的规则基本路径:模块初始化=>请求初始化=>请求=>请求终止=>模块终止.
Zend线程安全
当php嵌入多进程webserver,给定的内部变量仍然定在全局并且可以通过在每个请求启动时正确的初始化,终止时去做适当的清理工作来做到安全访问,因为在一个进程空间中同时只会有一个请求,这个时候增加了请求的内存管理,以防止资源泄露增长失去控制.
单进程多线程webserver出现后,就需要一种对全局数据处理的新方法,最后这作为新的一层TSRM(线程安全资源管理)
线程安全VS废线程安全定义
在一个简单的非线程应用中,你可能很喜欢定义全局变量,将他们放在你的源代码的顶部,编译器会在你的程序的数据段分配内存块保存信息.
在多线程应用中,每个线程需要他自己的数据元素,需要为每一个线程分配独立的内存块,一个给定线程在他需要访问自己的数据时需要能够正确的访问自己的内存块.
变量的里里外外
每一种编程语言有个共同的特点就是存储和取回信息,PHP也是同样的,很多语言都要求所有变量在使用前被定义,并且他们的类型信息时固定的.然后php允许程序员在使用时候创建变量.并且可以存储任何类型语言能表达的信息.并且还可以在需要的时候自动转换变量的类型.php是弱类型的语言,C的类型是强类型的.
当然,数据的编码只是一半工作,为了保持对所有信息片的跟踪,每个变量还需要一个标签和一个容器.从用户角度来看,你可以把他们看做变量名和作用域.
数据类型
php中的数据存储单位是zval,也被称为Zend Value.他是一个由四个成员的结构体.在Zend/zend.h中定义.
zend当前定义了下列的8中数据类型:
* ISNULL
* ISBOOL
* ISDOUBLE
* ISSTRING
* ISARRAY
* ISOBJECT
* IS_RESOURCE
数据值
和类型一样,zval的值也可以用3个一组的宏检查
对于简单的标量类型,boolean,long,double,简写为BVAL,LVAL,DVAL.
字符串包含两个成员,因此tab有一对宏分别表示char和int
数组数据类型内部以HashTable存储
数据存储
你在空间内侧使用过php,因此比较熟悉数组,我们可以将任意的数量的php变量放到容器中,并可以为他们指派数字或者字符串格式的名字.
如果不出意外,php脚本中的每一个变量都应该可以在一个数组中找到,当你创建变量时,为他赋一个值.Zend把这个值放到称为符号表的一个内部数组中.
总结
本章我们看到php变量的内部表示,你学习了区别类型,设置和取回值,将变量增加到符号表中以及将他们取回.
内存管理
php和c最重要的区别就是是否控制内存指针.
内存
在php中,设置一个字符串变量很简单:<?php $str="helloworld"; ?>,字符串可以自由的修改,拷贝,移动,在c中,则是另一种方式.虽然你可以简单的用静态字符串初始化;char*str="helloworld";但是这个字符串不能被修改.因为他存在于代码段中,要创建一个可维护的字符串,你需要分配一块内存,并使用strdup()这样的函数将内容拷贝到其中.
Zend内存管理
由于请求跳出(故障)产生的内存泄露的解决方案是Zend内存管理层.引擎在这一部分扮演相当于操作系统通常扮演的角色,分配内存给调用应用.不同的是,站在进程空间请你的认知角度.他足够底层.当请求die的时候,他执行和OS在进程die时所做的相同的事情.也就是说他会隐式的释放所有请求拥有的内存空间.
小结
php是一种托管语言,从用户空间一侧考虑,小心的控制资源和内存就意味着更容易的原型涉及和更少的崩溃.在你深入研究解开引擎的面纱后, 就不能有博彩心里,而是对运行环境完整性的开发和维护负责.
你的第一个扩展
每一个扩展的构建至少需要两个文件:一个configuration文件,它告诉编译期要构建那些文件需要哪些什么外部的库,还需要至少一个源文件,它执行实际的工作.
剖析扩展
实际上,通常会有第二或者第三个配置文件,以及一个或多个头文件,对于你的第一个扩展,你需要添加每一种类型的有个文件并使用他们工作.
配置文件
要开始了,首先在你的php源代码目录树的ext/目录下创建名为sample的目录,实际上这个心的目录可以放在任何地方,但是为了在本章后面演示win和静态页面,我们还先建立源代码目录.
头文件
当使用C开发的时候,将数据的类型定义放到外部的头文件中隔离起来,由源文件包含是最常见的做法.尽管php并不要求这样,但是这样做在模块增长到不能放到单一源文件.
返回值
用户空间函数利用return关键字向他调用空间回传信息,这一点和c语言的语法相同.
return_value变量
你可能认为你的内部函数应该直接返回一个zvel,或者说分配一个zval的内存空间并返回zval*.但是这样是不正确的,并不强制每个函数实现分配zval并返回它.而是zend引擎在函数调用之前预先分配这个空间,接着讲zval的类型初始化为isnull,并将值作为参数名returnvalue传递.
我们需要注意的是phpfunction()实现并不会直接返回任何值,取而代之的是直接将恰当的数据弹出到returnvalue参数中,zend引擎会在内部函数执行完成后处理它.
引用方式返回值
使用return(语法)结构将值和变量回传给调用方是没有问题的,但是,有时候你需要从一个函数返回多个值,你可以使用数组达到这个目的,或者你可以使用出参数栈返回值.
小结
本章你看到怎样从一个内部函数返回值,包括直接返回值和引用方式返回,以及通过参数栈应用返回,以及通过参数栈引起返回.此外还简单了解zend引擎2的参数类型.
在数组和哈希表上工作
在c语言中,有两种不同的基础方法用来在一个结构体中存储任意数量的独立数据元素,两种方法都有.
向前推移的迭代
我们也可以不使用回调进行hashtable的迭代,我们需要记得hashtable中常常别忽略的概念:内部指针.
在用户空间,函数reset(),key(),current(),next(),prev(),each(),end()可以访问数组内的元素,他们依赖于一个不可访问的"当前"位置.
资源数据类型
迄今为止,你都是工作再非常基础的用户空间数据类型上,字符串,数值,TRUE/FALSE等值.即便上一章开始接触数组,但也是收集这些基础数据类型的数组.
复杂的结构体
现实世界中,你通常需要更加复杂的数据集合下工作,通常涉及到晦涩的结构体指针,一个常见的晦涩的结构指针,即便在c语言当中也只是一个指针.
stdio的文件描述符合其他多数文件描述符一致,都像书签,你扩展的调用应用需要在feof(),fread(),fwrite(),fclose()这样的实现函数调用时传递这个值.同时书签必须是用户空间代码可以访问的,因此,就需要在标准的php变量或者说zval中有表示他的方法.
这里就需要一种新的数据类型.resource数据类型在zval中存储一个简单的整型值,使用作为已注册资源的索引来查找.资源条目包含了资源索引所表示的内部数据类型,以及存储资源数据的指针的信息.
定义资源类型
为了使用注册的资源条目所包含额资源信息更加明确,需要定义资源的类型.
注册资源
现在引擎知道你要存储一些资源数据,是时候给用户空间的代码一种方式去产生实际的资源,要做到这一点,需要如下重新实现fopen()命令.
资源解码
创建资源仅仅是第一步,因为书签的作用只是让你可以回到原来的那一页,这里是另一个函数.
小结
使用本章内容,你可以开始应用php著名的粘合性了.资源数据类型使得你的扩展可以很容易将第三方库的透明指针这样的抽象概念.
php4的对象
以前的版本,php不支持任何面相对象呢的编程语法,在php4中引入了zend引擎,出现几个新特性,其中就包括对象数据类型.
php对象类型的演化
第一次面相对象编程(OOP)支持仅实现对象关联语义.php4的对象只是将一个数组和一些方法绑定到一起.
在php5中引入一些新特性,属性和方法都可以通过修饰符来定义类类外面的可见性.函数的重载.
实现类
在进入OOP的世界前,我们需要轻装上阵.
php5对象
将php5的对象,和他的前辈[php4对象进行比较.在php5对象变量中有两个关键的组件,第一个就是数值得标识.第二是对象变量的句柄表.使用他可以自定义zend引擎对实例的处理方式.
接口
接口的定义和类的定义除了几个差异外基本是一致的么搜西安是所有的方法都是定义为抽象的.由于这些方法是抽象的,所以需要实现,接下来的第二个差异就是注册.
实现接口,假设需要实现这个接口的定义的所有的抽象方法.
访问流
php用户空间中所有的文件I/O处理都是通过php引入的php流包装层处理.在内部,扩展代码可以选择使用stdio或者posix文件处理和本地文件系统或者伯克利域套接字进行通信,或者也可以调用和用户空间流I/O相同的API.
流的概念
通常,直接的文件描述相比调用包装层消耗更少的CPU和内存,然而,这样会将实现某个特定协议的所有工作都堆积到作为扩展开发者的你身上.
打开流
尽管是一个统一的API,但实际上依赖所需的流的类型,有四种不同的路径去打开一个流.从用户空间角度来看,这四种不同的类型有: * fopen();//fopen包装 * fsockopen();//传输 * opendir();//目录流 * procopen();//特殊流 无论你打开什么类型的流,他们都存储在一个公共的结构phpstream中.
实现流
php的流最强力的特征之一是他可以访问众多数据资源:普通文件,压缩文件,网络透明通道,加密网络,命名管道以及域套接字,他们对于用户空间以及内部都是统一的API.
有趣的流
php常被提起的特性是上下文,这个可选的参数甚至在用户空间多数流创建相关的函数中都可用,他作为一个泛化的框架用于向定向包装器或流实现传入/传出额外的信息.
上下文
每一个流的上下文包含两种内部消息类型,首先最常用的是上下文选项.这些值被安排在上下文中一个二维数组中,通常用于改变流包装器的初始化行为.还有一种则是上下文参数.当前提供了一种方式用于在流包装层内部的事件通知.
高级嵌入式
php的嵌入式能够提供的可不仅仅是同步的加载和执行脚本,通过理解php的执行模块各个部分是怎样组合的,甚至给出一个脚本还可以回调到你的宿主应用中,本章将涉及SAPI层提供的I/O钩子带来的好处.
回调到php中
除了加载外部的脚本,和你在上一章看到的类似,你的php嵌入式应用,下面将实现一个类