共计 3394 个字符,预计需要花费 9 分钟才能阅读完成。
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 失败,当失败信号写入管道,父过程获取到管道信息后将与子过程返回统一的错误信息,否则父过程返回失常退出。