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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。