【PHP7源码学习】2019-04-25 PHP生命周期浅析

Grapephp

视频传送门:【每日学习记录】使用录像设备记录天天的学习nginx


今天咱们来看下PHP的生命周期,咱们都知道PHP生命周期有五个步骤,那么在源码层级是怎么去实现PHP生命周期呢?首先,咱们抛出本文的几个问题:segmentfault

  1. php的生命周期是什么?每一个阶段作了什么?
  2. 为何会有FPM?
  3. cli执行代码和请求通过fpm执行有什么区别?

思考ing。。。。

好的,接下来咱们解释上边三个问题。api

1.什么是php的生命周期?每一个阶段作了什么?

这个问题相信你们都可以回答,php的生命周期有五个步骤:数组

- php_module_startup:模块初始化
- php_request_startup:请求初始化
- php_execute_script:执行脚本
- php_request_shutdown:请求关闭
- php_module_shutdown:模块关闭

在执行完这个个步骤以后,就走过了PHP的一辈子,感受设计者彻底借鉴了人的一辈子去设计的生命周期,出生,成长奋斗,结婚生子,完成理想以及老去,妙啊。
那么,对于这五个步骤有什么意义呢?咱们来逐个了解一下。咱们拿cli来举例子(入口在sapi/cli/php.ini),咱们假设sapi的初始化等步骤已经完成,由于本文重点是PHP生命周期,着着重讲解五个步骤。安全

php_module_startup

看名字就这道这个函数的做用,模块的初始化,即调用每一个拓展源码中的的PHP_MINIT_FUNCTION中的方法初始化模块,进行一些模块所需变量的申请,内存分配等。
这一步骤主要完成的工做有如下几点:php7

- 初始化zend_utility_functions 结构.这个结构是设置zend的函数指针,好比错误处理函数,输出函数,流操做函数等.
- 设置环境变量.
- 加载php.ini配置.
- 加载php内置扩展.
- 写日志.
- 注册php内部函数集.
- 调用 php_ini_register_extensions,加载全部外部扩展
- 开启全部扩展
- 一些清理操做.

咱们看一下加载php.ini配置,代码以下:socket

/* this will read in php.ini, set up the configuration parameters,
           load zend extensions and register php function extensions
           to be loaded later */
        if (php_init_config() == FAILURE) {
            return FAILURE;
        }//   php_init_config函数会在这里检查全部php.ini配置,而且找到全部加载的模块,添加到php_extension_lists结构中.
    
        /* Register PHP core ini entries */
        REGISTER_INI_ENTRIES();//展开后为zend_register_ini_entries(ini_entries, module_number),ini_entries是PHP_INI_BEGIN/END()两个宏生成的配置映射规则数组,一般会把这个操做放到PHP_MINIT_FUNCTION()中。
        //注意:此时php.ini已经解析到configuration_hash哈希表中,zend_register_ini_entries()将根据配置name查找这个哈希表,
        //若是找到了代表用户在php.ini中配置了该项,
        //而后将调用此规则指定的on_modify函数进行赋值,
        此处更详细的介绍请看[https://www.kancloud.cn/nickbai/php7/363320]
    对于其它的一些操做是怎么实现的,你们能够自行查看源码。

php_request_startup

请求初始化阶段, 即接受到客户端的请求后调用每一个拓展的PHP_RINIT_FUNCTION中的方法,初始化PHP脚本的执行环境。
在此函数的实现种主要有如下几个函数:函数

  • zend_interned_strings_activate():初始化内部字符串哈希表
  • php_output_activate():启动php的输出
  • zend_activate():激活Zend引擎
  • sapi_activate():激活SAPI,进行编译器,重置gc,执行器以及词法扫描器。
  • zend_signal_activate(),处理一些信号
  • zend_activate_modules():回调各扩展定义的request_startup钩子函数。

php_execute_script

执行脚本阶段,入口是php_execute_script()。此过程和2同样,均在do_cli函数内完成。首先获取真正执行的文件信息等,把要执行的文件放在included_files列表里边。而后会调用zend_execute_scripts()去真正执行。真正执行的时候就涉及到了编译,执行,op_array之类的概念。编译过程又涉及到词法分析,语法分析和抽象语法树(AST)等概念。执行的话会涉及到opcode的概念。这些概念在以前的文章中已经讲解过具体实现,感兴趣的读者能够自行前往。传送门:笔记汇总oop

php_request_shutdown

请求关闭阶段。在这个阶段总共有16个步骤,在源码里有着明确的注释,无谓就是作一些“清理”操做,咱们看下源码怎么作的。

EG(current_execute_data) = NULL;/*EG(current_execute_data) 指向nirvana,所以没法在zend_executor回调函数中安全地访问.*/
php_deactivate_ticks()//清空tick函数
1.php_call_shutdown_functions()//调用注册了register_shutdown_function()的全部可能的shutdown函数
2.zend_call_destructors()//调用全部可能的__destruct() 函数
3.php_output_discard_all()/php_output_end_all()://刷新全部输出缓冲区
4.zend_unset_timeout()//重置max_execution_time(响应发送后再也不执行php代码)
5.zend_deactivate_modules()//调用全部扩展RSHUTDOWN函数
6.php_output_deactivate()//关闭输出层(发送设置好的HTTP头文件,清除输出处理程序等)
7.php_free_shutdown_functions()//释放shutdown函数
8.zval_ptr_dtor()//销毁 super-globals
9.php_free_request_globals()//释放request-bound globals
10.zend_deactivate()//关闭扫描仪/执行器/编译器并还原ini条目
11.zend_post_deactivate_modules//调用rshutdown后的全部扩展
12.sapi_deactivate//SAPI相关的shutdown (free stuff)
13.virtual_cwd_deactivate//释放virtual CWD 内存
14.php_shutdown_stream_hashes//破坏流哈希表
15.zend_interned_strings_deactivate()/shutdown_memory_manager():Free Willy (here be crashes)
16.zend_unset_timeout():重置max_execution_time

php_module_shutdown

模块关闭阶段:与模块初始化阶段相反,这个阶段将清理资源、各php模块关闭等操做。具体的代码函数调用再也不赘述。

2. 为何会有FPM?

咱们在看过cli下生命周期的五个阶段以后会发现一个问题,这种形式好像有个问题,就是它每来一次请求就会有这五个阶段,这样会形成多大的资源浪费啊。那么为了解决这个问题,FPM应运而生,FPM(FastCGI Process Manager)是 PHP FastCGI 运行模式的一个进程管理器。
归纳来讲,fpm的实现就是建立一个 master进程,在master进程中建立并监听socket,而后fork 出多个子进程,这些子进程各自accept请求,子进程的处理很是简单,它在启动后阻塞在accept上,有请求到达后开始读取请求数据,读取完成后开始处理而后再返回,在这期间是不会接收其它请求的,也就是说fpm的子进程同时只能响应一个请求,只有把这个请求处理完成后才会accept下一个请求,这一点与nginx的事件驱动有很大的区别nginx的子进程经过epoll管理套接字,若是一个请求数据还未发送完成则会处理下一个请求,即一个进程会同时链接多个请求,它是非阻塞的模型,只处理活跃的套接字。
知道它的工做机制咱们就能够想象一下他会如何去改善cli模式下每一个请求都完成一次初始化的问题,咱们猜想一下,他会在master进程进行一次初始化以后在请求阶段循环,直至结束,这样就达到了不用屡次初始化的目的。好的咱们看下它是怎么实现的?
首先进行fpm_init,此步主要是对fpm进行初始化,加载fpm配置文件,分配用于和worker进行通讯的共享内存,建立worker_pool的套接字,启动 master 的事件管理器(fpm 实现了一个事件管理器用于管理 IO、定时事件,其中 IO 事件经过 kqueue、epoll、poll、select 等管理,定时事件就是定时器,必定时间后触发某个事件)等等操做。
接下来就是fpm_run的过程,master将fork出worker进程,worker进程返回main()中继续向下执行,后面的流程就是worker进程不断accept请求,而后执行PHP脚本并返回。fpm_run总体流程以下:

1. 等待请求:worker进程阻塞在fcgi_accept_request() 等待请求;
2. 解析请求:fastcgi请求到达后被worker接收,而后开始接收并解析请求数据,直到request数据彻底到达;
3. 请求初始化:执行php_request_startup(),此阶段会调用每一个扩展的:PHP_RINIT_FUNCTION();
4. 编译、执行:由php_execute_script() 完成 PHP 脚本的编译、执行;
5. 关闭请求:请求完成后执行php_request_shutdown(),此阶段会调用每一个扩展的:PHP_RSHUTDOWN_FUNCTION(),而后进入步骤 (1) 等待下一个请求;

在这个阶段,master进程将进入fpm_event_loop()来依赖注册的几个事件进行不一样的操做。
到此,对于fpm的简单叙述就到此为止了。能够理解fpm的诞生就是一剂灵丹妙药,拉长了PHP的生命战线

3. cli执行代码和请求通过fpm执行有什么区别?

其实我以为这个问题在看过上边两个问题以后答案就已经出来了~,那么这块就让聪明的你来解决啦。

相关文章
相关标签/搜索