PHP-FPM中-D命令的实现
家喻户晓,php-fpm是fastcgi的管理程序,环境部署中咱们应用php-fpm -D
来启动fpm过程,从而监听9000端口来解决nginx转发过去的request工作。对于fpm的启动之后也筹备梳理一篇,本文次要是说一下 -D 这个命令,既而通过这个命令钻研下在Linux下如何编写daemon过程。
什么是Daemon过程
Daemon过程是运行在后盾的一种过程,它独立于管制终端并且周期性地执行某种工作或期待解决某些产生的事件。它不须要用户输出就能运行而且提供某种服务,不是对整个零碎就是对某个用户程序提供服务。
一个守护过程的父过程是init过程,因为它真正的父过程在fork出子过程后就先于子过程exit退出了,所以它是一个由init继承的孤儿过程。守护过程是非交互式程序,没有管制终端,所以任何输入,无论是向规范输出设备stdout还是规范出错设施stderr的输入都须要非凡解决。
对于Daemon过程的一些原理
Linux中的过程与管制终端,登录会话和过程组之间的关系
过程属于一个过程组,过程组号(GID)就是过程组长的过程号(PID)。登录会话能够蕴含多个过程组。这些过程组共享一个管制终端。这个管制终端通常是创立过程的登录终端。
管制终端,登录会话和过程组通常是从父过程继承下来的。咱们的目标就是要解脱它们,使之不受它们的影响。
如何实现
对于如何实现?个别有几个步骤须要解决:
- fork()一个子过程继续执行父过程的工作,同时将父过程进行。这样就使得你的过程从管制端进入后盾。
- 脱离管制终端,登录会话和过程组。调用setsid()使子过程成为会话组长。
- 敞开父过程关上的文件描述符。
- 解决SIGCHLD信号。
这4步中下面两步是必须的,前面是个别依据须要会须要解决的。
PHP-FPM的实现
在fpm中实现daemon的办法与上述统一,最初咱们通过对fpm源码的跟踪来看一下具体实现的例子。(代码有删减)
所有从启动开始
/sapi/fpm/fpm/fpm_main.c
int main(int argc, char *argv[]){ // 接管到-D参数,设置以daemon模式init fpm while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { switch (c) { case 'D': /* daemonize */ force_daemon = 1; break; } } if (0 > fpm_init(argc, argv, fpm_config ? fpm_config : CGIG(fpm_config), fpm_prefix, fpm_pid, test_conf, php_allow_to_run_as_root, force_daemon, force_stderr)) { return FPM_EXIT_CONFIG; }}
int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon, int force_stderr){ // fpm_conf_init_main : 设置fpm的配置,其中daemon=1 if (0 > fpm_php_init_main() || 0 > fpm_stdio_init_main() || 0 > fpm_conf_init_main(test_conf, force_daemon) || 0 > fpm_unix_init_main() || 0 > fpm_scoreboard_init_main() || 0 > fpm_pctl_init_main() || 0 > fpm_env_init_main() || 0 > fpm_signals_init_main() || 0 > fpm_children_init_main() || 0 > fpm_sockets_init_main() || 0 > fpm_worker_pool_init_main() || 0 > fpm_event_init_main()) { if (fpm_globals.test_successful) { exit(FPM_EXIT_OK); } else { zlog(ZLOG_ERROR, "FPM initialization failed"); return -1; } }}
int fpm_unix_init_main(){ if (fpm_global_config.daemonize) { struct timeval tv; fd_set rfds; int ret; if (pipe(fpm_globals.send_config_pipe) == -1) { zlog(ZLOG_SYSERROR, "failed to create pipe"); return -1; } /* then fork */ pid_t pid = fork(); switch (pid) { case -1 : /* error */ zlog(ZLOG_SYSERROR, "failed to daemonize"); return -1; case 0 : /* children */ break; default : /* parent */ FD_ZERO(&rfds); FD_SET(fpm_globals.send_config_pipe[0], &rfds); tv.tv_sec = 10; tv.tv_usec = 0; zlog(ZLOG_DEBUG, "The calling process is waiting for the master process to ping via fd=%d", fpm_globals.send_config_pipe[0]); ret = select(fpm_globals.send_config_pipe[0] + 1, &rfds, NULL, NULL, &tv); if (ret == -1) { zlog(ZLOG_SYSERROR, "failed to select"); exit(FPM_EXIT_SOFTWARE); } if (ret) { /* data available */ int readval; ret = read(fpm_globals.send_config_pipe[0], &readval, sizeof(readval)); if (ret == -1) { zlog(ZLOG_SYSERROR, "failed to read from pipe"); exit(FPM_EXIT_SOFTWARE); } if (ret == 0) { zlog(ZLOG_ERROR, "no data have been read from pipe"); exit(FPM_EXIT_SOFTWARE); } else { if (readval == 1) { zlog(ZLOG_DEBUG, "I received a valid acknoledge from the master process, I can exit without error"); fpm_cleanups_run(FPM_CLEANUP_PARENT_EXIT); exit(FPM_EXIT_OK); } else { zlog(ZLOG_DEBUG, "The master process returned an error !"); exit(FPM_EXIT_SOFTWARE); } } } else { /* no date sent ! */ zlog(ZLOG_ERROR, "the master process didn't send back its status (via the pipe to the calling process)"); exit(FPM_EXIT_SOFTWARE); } exit(FPM_EXIT_SOFTWARE); } } // 使以后子过程成为会话组长 setsid();}
这个里是实现daemon的外围代码,fpm在这里做了两步fork()一个子过程,setsid()将子过程设置为会话组长。重点说一下 fpm_globals.send_config_pipe
。这是一个数组做共享变量,fpm中将它当过程间通信的管道应用,父过程fork()子过程后期待10秒来接管子过程init的状况,如果子过程init失败,当失败信号写入管道,父过程获取到管道信息后将与子过程返回统一的错误信息,否则父过程返回失常退出。