6

Old drivers who write PHP CLI programs may often write some resident processes, such as message queue consumer processes. These processes will run all the time. Unless a version is released, they will generally not be restarted, so the program cannot be controlled by us. Log in to the server through supervisor and start it directly through the terminal (because once the systemd process is disconnected, it will exit) 守护进程 , so that the process can always run, and it can be automatically restarted when it encounters an error and unexpectedly exits.

If you are eager to learn, you may think about how the daemon process is implemented? Why can some programs not only become daemons themselves, but also run in the background through systemd ? How can our PHP program become a daemon process without relying on the outside?

Steps to become a daemon

In fact, you only need to create a child process and exit the parent process, and the work to be processed can be implemented in the child process to implement a daemon process. But just doing this, if the follow-up tasks are very complicated, or some third-party packages are introduced, then there may be strange problems.

In the book Advanced Programming in the UNIX Environment (APUE), there are coding specifications about daemons. We can avoid those strange problems by implementing our daemons according to the specifications. . And the specification is not complicated, it only takes a few steps:

  • Create child process, exit parent process
  • The child process creates a new session and becomes session leader
  • reset file mask
  • change working directory
  • Close standard input and output

accomplish

 <?php

function daemon()
{
    // [1] 创建子进程
    $pid = pcntl_fork();
    if ($pid == -1) {
        die('fork failed');
    }

    // [2] 如果是父进程,则退出
    if ($pid > 0) {
        exit(0);
    }

    ///////////////// 以下是子进程 /////////////////

    // [3] 创建一个新的会话并成为 session leader
    if ( ($sid = posix_setsid()) <= 0 ) {
        die("Set sid failed.\n");
    }

    // [4] 重设文件掩码
    umask(0);

    // [5] 改变工作目录
    if (chdir('/') === false) {
        die("chdir failed.\n");
    }

    // [6] 关闭标准输入输出
    fclose(STDIN);
    fclose(STDOUT);
    fclose(STDERR);
}

daemon();

// ... 真正的处理逻辑

illustrate

The above short dozen or twenty lines of code implement a daemon process. Next, I will explain why some steps are done.

Create child process and exit parent process

The return value of pcntl_fork() has three cases, the above code ( [1] and [2] ) has dealt with the corresponding cases.

Create a new session

Calling posix_setsid() to create a new session will make the current process the "session leader" in the new session, and also make the current process the "process group leader", and make the current process detach from the controlling terminal.

reset file mask

Call umask() to reset the file mask, which is usually 0 here. Why is it 0 and not others, because the file mask inherited by the child process from the parent process may block some specific file operation permissions. For example, the imported third-party library may need to create files with specific permissions, and it does not specify the file permissions as an option parameter by you, then there may be a failure; and we pass in 0 , will cause the file created by the daemon to have the permission 0666 and the directory permission to be ---4b3984d91a1a741a2163d62f6fe1b9a0 umask() after calling 0777 , both of which are the highest permissions.

About umask() will be explained in a new chapter later, and those who are interested can search for materials and study by themselves.

change working directory

By chdir() we set the working directory to the root directory / , mainly because the daemon is long-running and usually only exits when the system is shut down/rebooted. If the working directory inherited from the parent process is a mounted file system, if the working directory is not changed, the mounted file system will never be unmounted.

Of course, it is not necessary to switch the working directory to the root directory, you can also switch to a specific directory according to the actual situation.

Close standard input and output

Because the daemon is out of terminal control, there is no standard input and output interaction, we can close it.

other

Secondary fork

You may have seen in some sources that you were recommended to do [3] 创建一个新的会话并成为 session leader again after fork . This step is for systems based on System V to ensure that your daemon is not a "session leader" and prevents it from reapplying for a controlling terminal.

close unnecessary file descriptors

According to the coding specification, there is actually one more step to close unnecessary file descriptors. But for the sake of simplicity, the above code creates a daemon process after the process starts and then performs other operations, so only three file descriptors are opened here: 0 , 1 2 (ie 标准输入 , 标准输出 , 标准错误 ).

Precautions

Because the above code closes standard input and output, that is if you have output such as echo "Hello world"; daemon() after ---12cba785cae35f1eba2ad5677eb9e391---, then your program will error and exit, and You won't see any error messages (since standard error is also turned off).

There are two solutions, one is to use file_put_contents instead of echo , but this is not elegant, and in case the third-party package introduced writes echo Or file_put_contents(STDOUT, ...) , then your program will hang "inexplicably", and you will be asked to investigate what went wrong for a long time.

So we can also add after [6] :

 // [7] 重定向输入输出
    global $stdin, $stdout, $stderr;
    $stdin = fopen('/dev/null', 'r');
    $stdout = fopen('/dev/null', 'wb'); // 你也可以将标准输出重定向到指定的文件,相当于是日志
    $stderr = fopen('/dev/null', 'wb'); // 同上

References


This article was first published on my blog: https://yian.me/blog/what-is/php-daemon.html


Yian
1.2k 声望307 粉丝

土木狗,不会写代码