fpm源码解析准备【原创】

​ 之前看过网上零散的关于php-fpm分析的文章,要么不全,要么就是讲解php-fpm的配置文件的,还有就是讲解nginx与php-fpm之间通信配置的。关于php-fpm中的进程管理、信号管理、事件处理、时钟管理、请求处理等几乎为零。故本着学习的宗旨对fpm源码进行解读。本系列采用php-7.2.0版本中的php-fpm进行分系列讲解。本系列共分为5节,具体如下:

  1. fpm源码之初探
  2. fpm源码之配置解析
  3. fpm源码之进程管理
  4. fpm源码之信号处理
  5. fpm源码之事件管理
  6. fpm源码之请求处理

源码地址:https://github.com/php/php-src

源码查看工具

sublime+vim

什么是 Zend ? 什么是 PHP ?

Zend是语言引擎,PHP内核。PHP是从外层展现的完整系统。咋一听似乎有点模糊不清,但是其实并不复杂,为了实现一个 web 脚本解释器,你需要三个部分:

  1. 第一:解释器部分 分析输入代码,翻译代码,然后执行代码。
  2. 第二:功能部分 完成语言的功能(函数,等等)。
  3. 第三:接口部分 与web通信

img

组成扩展的文件

经过上面的复习,我们知道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
2
3
4
5
6
7
8
9
10
.
├── CREDITS //纯文本格式列出了扩展的贡献者和(或 )维护者
├── EXPERIMENTAL
├── config.m4
├── config.w32
├── php_xxx.h
├── tests //测试扩展的测试文件,php扩展使用php来测试
│   └── 001.phpt
├── xxx.c
└── xxx.php

其中xxx.php内容如下,主要用于测试扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$br = (php_sapi_name() == "cli")? "":"<br>";

if(!extension_loaded('xxx')) {
dl('xxx.' . PHP_SHLIB_SUFFIX);
}
$module = 'xxx';
$functions = get_extension_funcs($module);
echo "Functions available in the test extension:$br\n";
foreach($functions as $func) {
echo $func."$br\n";
}
echo "$br\n";
$function = 'confirm_' . $module . '_compiled';
if (extension_loaded($module)) {
$str = $function($module);
} else {
$str = "Module $module is not compiled into PHP";
}
echo "$str\n";
?>

C语言基础知识复习

extern

用在变量或函数的声明前,用来说明”此变量/函数是在别处定义的,要在此处引用”。

对变量而言,变量的声明有两种情况:

1、 一种是需要建立存储空间的,不用加extern;

2、另一种是不需要建立存储空间,需要加extern 。如果你想在本源文件中使用另一个源文件的变量,就需要在使用前用extern声明该变量,或者在头文件中用extern声明该变量;

举例:

1
2
3
4
5
extern int a;//声明一个全局变量a
int a; //定义一个全局变量a

extern int a =0 ;//定义一个全局变量a 并给初值
int a =0;//定义一个全局变量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
2
3
4
5
6
7
struct Student //结构体定义
{
char name[20];
int age;
int score[5];
};
struct Student s1 = {"zhangsan",22,{1,2,3,4,10}}; //结构体变量声明并初始化

尾部定义

1
2
3
4
5
6
struct
{
char name[10];
int age;
int score[5];
}stu1 = {"张三",20,{150,110,20,34,114}},stu2; //匿名结构体定义时声明变量并初始化

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
2
3
4
5
6
7
/* Executor */
#ifdef ZTS
# define EG(v) ZEND_TSRMG(executor_globals_id, zend_executor_globals *, v)
#else
# define EG(v) (executor_globals.v)
extern ZEND_API zend_executor_globals executor_globals;
#endif

CG宏:存在于zend_globals_macros.h中,zend编译器相关的全局变量compiler_globals,源码如下:

1
2
3
4
5
6
7
#ifdef ZTS
# define CG(v) ZEND_TSRMG(compiler_globals_id, zend_compiler_globals *, v)
#else
# define CG(v) (compiler_globals.v)
extern ZEND_API struct _zend_compiler_globals compiler_globals;
#endif
ZEND_API int zendparse(void);

PHP-named Zend macro wrappers: 位于php.h文件中

1
2
3
4
5
6
#define PHP_FN					ZEND_FN
#define PHP_MN ZEND_MN
#define PHP_NAMED_FUNCTION ZEND_NAMED_FUNCTION
#define PHP_FUNCTION ZEND_FUNCTION
#define PHP_METHOD ZEND_METHOD
#define PHP_FE ZEND_FE

Zend 宏定义:位于zend_API.h文件中

1
2
3
4
5
#define ZEND_FN(name) zif_##name
#define ZEND_MN(name) zim_##name
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_METHOD(classname, name) ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))
  • 本文作者: 风月
  • 本文链接: /php-fpm/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!