当前位置: 首页 > Linux

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

时间:2023-04-07 03:25:41 Linux

葡萄视频传送门:【日常学习记录】用视频设备记录日常学习今天我们来看看PHP的生命周期,大家都知道PHP有生命周期的五个步骤,那么如何在源码层面实现PHP的生命周期呢?本文首先抛出几个问题:php的生命周期是什么?每个阶段做了什么?为什么会有FPM?cli执行代码和通过fpm请求执行有什么区别?思维。...好了,接下来就对上面的三个问题进行解释。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中的方法初始化模块,申请模块需要的一些变量,内存分配等。这一步完成的主要工作如下:-初始化zend_utility_functions结构。这个结构体是设置zend的函数指针,比如错误处理函数、输出函数、流操作函数等。-设置环境变量。-加载php.ini配置。-加载php内置扩展。-写入日志。-注册php内部函数集。-调用php_ini_register_extensions,加载所有外部扩展-启用所有扩展-一些清理操作。我们来看一下加载php.ini配置,代码如下:/*这会在php.ini中读取,设置配置参数,加载zend扩展并注册稍后加载的php函数扩展*/if(php_init_config()==失败){返回失败;}//php_init_config函数将检查所有php.ini配置,并找到所有加载的模块并将它们添加到php_extension_lists结构中。/*注册PHP核心ini条目*/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()会根据配置名查找哈希表,//如果找到,则表示用户配置了此项inphp.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)等概念。执行会涉及到操作码的概念。这些概念在之前的文章中都有讲解,有兴趣的读者可以自行前往。传送门:笔记汇总。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()Allpossibleshutdownfunctions2.zend_call_destructors()//调用所有可能的__destruct()函数3.php_output_discard_all()/php_output_end_all()://flushalloutputbuffers4.zend_unset_timeout()//resetmax_execution_time(php代码不会响应发送后执行)5.zend_deactivate_modules()//调用所有扩展RSHUTDOWN函数6.php_output_deactivate()//关闭输出层(发送设置的HTTP头文件,清除输出处理程序等)7.php_free_shutdown_functions()//释放关闭函数8.zval_ptr_dtor()//销毁超级全局变量9.php_free_request_globals()//释放请求绑定的全局变量10.zend_deactivate()//关闭扫描器/执行器/编译器并恢复ini入口11??.zend_post_deactivate_modules//调用rshutdown后的所有扩展12.sapi_deactivate//SAPI相关的关闭(免费的东西)13.virtual_cwd_deactivate//释放虚拟CWD内存14.php_shutdown_stream_hashes//销毁流哈希表15.zend_interned_strings_deactivate()/shutdown_memory_manager():FreeWilly(这里是崩溃)16.zend_unset_timeout():重置最大值_execution_timephp_module_shutdown模块关闭阶段:与模块初始化阶段相反,该阶段会进行资源清理,关闭各个php模块等操作。具体代码函数调用不再赘述。2、为什么会有FPM?看完cli下生命周期的五个阶段,我们就会发现一个问题。这种形式好像有个问题,就是每次请求都会有这五个阶段,会造成很大的资源浪费。于是为了解决这个问题,FPM应运而生,FPM(FastCGIProcessManager)是PHPFastCGI运行方式的进程管理器。简单来说,fpm的实现就是创建一个master进程,在master进程中创建和监听socket,然后fork出多个子进程。这些子进程各自接受请求。子流程的处理非常简单。它在启动后阻止接受。上面是一个请求到达后,开始读取请求数据。读取完成后开始处理,然后返回。在此期间,不会接收到其他请求,也就是说fpm的子进程同时只能响应一个请求。只有请求处理完成后才会接受下一个请求,这与nginx的事件驱动有很大区别。nginx的子进程通过epoll来管理socket。如果一个请求数据还没有发送完,就会去处理下一个请求,也就是一个进程会同时连接多个请求。它是一个非阻塞模型,只处理活动套接字。知道了它的工作机制,我们就可以想象它会如何改善cli方式下每个请求完成一个初始化的问题。我们猜测它会在master进程初始化一次后一直循环到request阶段,直到结束,这样就达到了不用初始化多次的目的。好的,让我们看看它是如何工作的?首先执行fpm_init,这一步主要是初始化fpm,加载fpm配置文件,分配与worker通信的共享内存,创建worker_poolsockets,启动mastereventmanager(fpm实现了一个eventmanager用于管理IO和定时事件,其中IO事件由kqueue、epoll、poll、select等管理,定时事件是定时器,在一定时间后触发事件)等。接下来就是fpm_run的过程了。master会fork出worker进程,worker进程会回到main()继续往下执行。后续的流程就是worker进程会继续接受请求,然后执行PHP脚本并返回。fpm_run的整体流程如下:1.等待请求:worker进程阻塞在fcgi_accept_request()等待请求;2、解析请求:fastcgi请求到达后,被worker接收,然后开始接收并解析请求数据,直到请求数据完全到达;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执行请求有什么区别?其实看了上面两个问题我觉得这个问题的答案已经出来了~,就让聪明的你解决这个问题吧。