PHP-FPM中-D命令的实现

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)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。
控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。

如何实现

关于如何实现?一般有几个步骤需要处理:

  1. fork()一个子进程继续执行父进程的任务,同时将父进程停止。这样就使得你的进程从控制端进入后台。
  2. 脱离控制终端,登录会话和进程组。调用setsid()使子进程成为会话组长。
  3. 关闭父进程打开的文件描述符。
  4. 处理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失败,当失败信号写入管道,父进程获取到管道信息后将与子进程返回一致的错误信息,否则父进程返回正常退出。

一不小心做了码农的历史迷

425 声望
14 粉丝
0 条评论
推荐阅读
断点续传
断点续传就是使浏览器分段下载文件的一种实现方式。在 HTTP/1.1 开始支持。当我们需要下载很大的文件时,如果一次性将文件取出然后返回给客户端,一般会导致OOM错误。此时我们就需要断点续传的技术将数据分批输出。

ethread阅读 1.6k

怎样用 PHP 来实现枚举?
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,...

唯一丶25阅读 6.4k评论 4

PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。

王中阳Go10阅读 1.9k评论 2

封面图
图片防盗链破解 解决图片防盗链问题 反向代理
当客户端(浏览器)向服务器请求内容的时候,会提交一个header,这个header中包含了如:浏览器信息、cookie等内容,那么有一个叫referer的东东,也包含在这里面。

TANKING7阅读 11.3k评论 5

Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...

王中阳Go5阅读 2.3k评论 2

封面图
Bash 常用脚本片段
这段脚本非常有用,你只要在你的脚本开头加上下面的内容,就能以 --param value 的格式解析参数。由于这段脚本尽可能写的短小不占空间,所以格式方面会要求所有的参数都有值,例如不接受无参数的 --daemon,而必...

捏造的信仰5阅读 1.7k评论 1

Redis 发布订阅模式:原理拆解并实现一个消息队列
“65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?““那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆宣传,暴击单身狗。”像这种 65 哥通过朋...

码哥字节6阅读 1.5k

封面图

一不小心做了码农的历史迷

425 声望
14 粉丝
宣传栏