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信号。
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失败,当失败信号写入管道,父进程获取到管道信息后将与子进程返回一致的错误信息,否则父进程返回正常退出。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。