之前看过网上零散的关于php-fpm分析的文章,要么不全,要么就是讲解php-fpm的配置文件的,还有就是讲解nginx与php-fpm之间通信配置的。关于php-fpm中的进程管理、信号管理、事件处理、时钟管理、请求处理等几乎为零。故本着学习的宗旨对fpm源码进行解读。本系列采用php-7.2.0版本中的php-fpm进行分系列讲解。本系列共分为5节,具体如下:
源码地址:https://github.com/php/php-src
源码查看工具
sublime+vim
什么是 Zend ? 什么是 PHP ?
Zend是语言引擎,PHP内核。PHP是从外层展现的完整系统。咋一听似乎有点模糊不清,但是其实并不复杂,为了实现一个 web 脚本解释器,你需要三个部分:
- 第一:解释器部分 分析输入代码,翻译代码,然后执行代码。
- 第二:功能部分 完成语言的功能(函数,等等)。
- 第三:接口部分 与web通信
组成扩展的文件
经过上面的复习,我们知道PHP由三部分组成,其中最容易扩展的就是modules部分,而fpm也是以php扩展的形式存在的,那么在开始讲解fpm源码之前,我简单介绍下php扩展。这里参考的是官方文档
不管通过什么方式创建的扩展,都至少包含以下四个文件:
config.m4
unix构建系统配置 [参考](http://php.net/manual/zh/internals2.buildsys.configunix.php)
config.w32
windows构建系统配置
php_xxx.h
xxx为扩展名字,当将扩展作为静态模块构建并放入PHP二进制包时,构建系统会要求用php_加扩展的名称命名的头文件包含一个对扩展模块结构的指针定义。就像其他头文件,此文件经常包含附加的宏、原型和全局变量。
xxx.c
主要的扩展源文件。按惯例,此文件名就是扩展的名称,比如这里的xxx,但不是必需的。此文件包含模块结构定义、INI条目、管理函数、用户空间函数和其他扩展所需的内容。
扩展应包含任意数量的头文件、源文件、单元测试和其他支持文件。此四个文件仅够组成最小的扩展。
比如通过php源文件中$path/php-7.2.0/ext/ext_skel
命令生成的xxx扩展,其目录如下:
1 | . |
其中xxx.php内容如下,主要用于测试扩展:
1 |
|
C语言基础知识复习
extern
用在变量或函数的声明前,用来说明”此变量/函数是在别处定义的,要在此处引用”。
对变量而言,变量的声明有两种情况:
1、 一种是需要建立存储空间的,不用加extern;
2、另一种是不需要建立存储空间,需要加extern 。如果你想在本源文件中使用另一个源文件的变量,就需要在使用前用extern声明该变量,或者在头文件中用extern声明该变量;
举例:
1 | extern int a;//声明一个全局变量a |
注意变量定义智能出现在一处,而外部引用声明可以出现多次。
对函数而言,如果你想在本源文件中使用另一个源文件的函数,就需要在使用前用声明该函数,声明函数加不加extern都没关系,所以在头文件中函数可以不用加extern。
memset
内存申请并初始化函数:将s所指向的某一块内存中的后n个字节的内容全部设置为ch指定的ASCII值,第一个值为指定的内存地址,块的大小由第三个参数决定。
1 | void *memset(void *s, int ch, size_t n); |
malloc
动态内存分配:全称是memory allocation,用于申请一块连续的指定大小的内存块区域以void*
类型返回分配的内存区域地址。其中void*
表示未确定类型的指针。其可以通过类型转换强制转换为任何其他类型的指针。
1 | void *malloc(size_t size); |
指针
指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量。
我们指知道:C语言中的数组是指 一类 类型,数组具体区分为 int 类型数组,double类型数组,char数组 等等。同样指针 这个概念也泛指 一类 数据类型,int指针类型,double指针类型,char指针类型等等。
通常,我们用int类型保存一些整型的数据,如 int num = 97 , 我们也会用char来存储字符: char ch = ‘a’。
我们也必须知道:任何程序数据载入内存后,在内存都有他们的地址,这就是指针。而为了保存一个数据在内存中的地址,我们就需要指针变量。
void *类型指针
由于void是空类型,因此void *
类型的指针只保存了指针的值,而丢失了类型信息,我们不知道他指向的数据是什么类型的,只指定这个数据在内存中的起始地址,如果想要完整的提取指向的数据,程序员就必须对这个指针做出正确的类型转换,然后再解指针。因为,编译器不允许直接对void *
类型的指针做解指针操作。
struct
struct为结构体关键字,tag为结构体的标志,member-list为结构体成员列表,其必须列出其所有成员;variable-list为此结构体声明的变量。结构体的2种定义方式:
常规定义
1 | struct Student //结构体定义 |
尾部定义
1 | struct |
static
1. static全局变量
我们知道,一个进程在内存中的布局如图所示:
其中.text段保存进程所执行的程序二进制文件,.data段保存进程所有的已初始化的全局变量,.bss段保存进程未初始化的全局变量(其他段中还有很多乱七八糟的段,暂且不表)。在进程的整个生命周期中,.data段和.bss段内的数据时跟整个进程同生共死的,也就是在进程结束之后这些数据才会寿终就寝。
当一个进程的全局变量被声明为static之后,它的中文名叫**静态全局变量**。静态全局变量和其他的全局变量的存储地点并没有区别,都是在.data段(已初始化)或者.bss段(未初始化)内,但是**它只在定义它的源文件内有效,其他源文件无法访问它**。所以,普通全局变量穿上static外衣后,它就变成了新娘,已心有所属,只能被定义它的源文件(新郎)中的变量或函数访问。
普通的局部变量在栈空间上分配,这个局部变量所在的函数被多次调用时,每次调用这个局部变量在栈上的位置都不一定相同。局部变量也可以在堆上动态分配,但是记得使用完这个堆空间后要释放之。
2. 静态局部变量
static局部变量中文名叫静态局部变量。它与普通的局部变量比起来有如下几个区别:
1)**位置**:静态局部变量被编译器放在全局存储区.data(注意:不在.bss段内,原因见3)),所以它虽然是局部的,但是在程序的整个生命周期中存在。
2)**访问权限:**静态局部变量只能被其作用域内的变量或函数访问。也就是说虽然它会在程序的整个生命周期中存在,由于它是static的,它不能被其他的函数和源文件访问。
3)**值:**静态局部变量如果没有被用户初始化,则会被编译器自动赋值为0,以后每次调用静态局部变量的时候都用上次调用后的值。这个比较好理解,每次函数调用静态局部变量的时候都修改它然后离开,下次读的时候从全局存储区读出的静态局部变量就是上次修改后的值。
3. 私有函数
当你的程序中有很多个源文件的时候,你肯定会让某个源文件只提供一些外界需要的接口,其他的函数可能是为了实现这些接口而编写,这些其他的函数你可能并不希望被外界(非本源文件)所看到,这时候就可以用static修饰这些“其他的函数”。
所以static函数的作用域是本源文件,把它想象为面向对象中的private函数就可以了。
PHP源码知识
宏(Macro)
计算机科学里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器或编译器在遇到宏时会自动进行这一模式替换。关于宏的详细解释,可以参考这里
PHP源码中大量的使用宏定义,知道以下几个宏,能够帮助我们更好的理解PHP源码。
EG宏:存在于zend_globals_macros.h中,zend执行器相关的全局变量executor_globals,源码如下:
1 | /* Executor */ |
CG宏:存在于zend_globals_macros.h中,zend编译器相关的全局变量compiler_globals,源码如下:
1 |
|
PHP-named Zend macro wrappers: 位于php.h文件中
1 |
Zend 宏定义:位于zend_API.h文件中
1 |