写 PHP CLI 程序的老司机们可能常常会写一些常驻过程,比方音讯队列消费者过程,这些过程会始终运行,除非要发版,不然个别不会重启的,所以程序程序是不可能由咱们通过 ssh 登录到服务器上通过终端来间接启动的(因为一旦断开 ssh 过程就退出了),常见的做法就是用 systemd
或者 supervisor
来使其成为 守护过程
,这样过程就能够始终运行,遇到谬误意外退出也能被主动重启。
好学的你可能会思考守护过程到底是怎么实现的?为什么有的程序既能够本人就成为守护过程,又能够通过 systemd
来后盾运行?如果不依赖内部,咱们的 PHP 程序该怎么变成守护过程呢?
成为守护过程的步骤
其实只须要创立子过程并退出父过程,将要解决的工作在子过程中进行就能够实现一个守护过程了。然而仅仅是这么做的话,如果后续工作很简单,或者引入了一些第三方包,那么可能就会呈现奇奇怪怪的问题了。
而在《UNIX环境高级编程》(英语:Advanced Programming in the UNIX Environment,简称APUE)一书中有介绍对于守护过程的编码标准,咱们依照标准来实现咱们的守护过程就能够避免出现那些奇怪的问题了。而且标准也不简单,只须要几步就能够了:
- 创立子过程,退出父过程
- 子过程创立一个新的会话并成为
session leader
- 重设文件掩码
- 扭转工作目录
- 敞开规范输入输出
实现
<code class="PHP"><?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(); // ... 真正的解决逻辑
阐明
下面短短的十几二十行代码就实现了一个守护过程,接下来解释一下有些步骤为什么要这么做。
创立子过程并退出父过程
pcntl_fork()
的返回值有三种状况,下面的代码([1]
和 [2]
)曾经解决了对应的状况。
创立新的会话
调用 posix_setsid()
创立新会话会使得以后过程成为新会话中的“会话首过程”,同时也会使以后过程成为“过程组组长”,并且使得以后过程脱离管制终端。
重设文件掩码
调用 umask()
重设文件掩码,这里通常是 0。为什么是 0 而不是其余呢,因为子过程从父过程继承来的文件掩码可能会屏蔽某些特定的文件操作权限。比如说引入的第三方库可能须要用特定的权限来创立文件,并且它没有将文件权限作为一个选项参数由你指定,那么就可能会呈现失败的状况;而咱们传入 0
,会使得从调用了 umask()
之后,守护过程创立的文件权限为 0666
,目录权限为 0777
,均为最高权限。
对于 umask()
前面会开展新的篇幅来阐明,感兴趣的能够先自行搜寻材料学习。
扭转工作目录
通过 chdir()
咱们将工作目录设置为根目录 /
,次要是因为守护过程是长时间运行的,通常只有零碎敞开/重启才会退出。如果从父过程继承来的工作目录是个挂载的文件系统,如果不扭转工作目录,那么将会导致这个挂载的文件系统始终没法卸载。
当然也不肯定要将工作目录切换到根目录,你也能够依据理论状况切换到特定的目录。
敞开规范输入输出
因为守护过程是脱离终端管制的,所以是没有规范输入输出交互的,咱们将其敞开即可。
其余
二次 fork
你可能在一些材料中看到有人举荐你在 [3] 创立一个新的会话并成为 session leader
之后再次进行 fork
。这一步骤是在基于 System V
的零碎中,能够保障你的守护过程不是“会话首过程”,能够阻止其从新申请获取一个管制终端。
敞开不必要的文件描述符
依照编码标准,理论还有一步是敞开不必要的文件描述符。但咱们为了简略起见,下面的代码在过程启动之后先创立守护过程再执行其余操作,因而这里只关上了三个文件描述符: 0
、1
和 2
(即规范输出
、规范输入
、规范谬误
)。
注意事项
因为下面的代码将规范输入输出敞开了,也就是说如果你在 daemon()
之后有诸如 echo "Hello world";
之类的输入,那么你的程序将会出错而后退出,并且你将看不到任何错误信息(因为规范谬误也被敞开了)。
解决方案有两种,一种是用 file_put_contents
代替 echo
,然而这样并不优雅,而且万一引入的第三方包中写了 echo
或者是 file_put_contents(STDOUT, ...)
,那你的程序也会“莫名其妙”就挂了,会让你排查半天到底是哪里出了问题。
因而咱们还能够在第 [6]
之后退出:
<code class="PHP"> // [7] 重定向输入输出 global $stdin, $stdout, $stderr; $stdin = fopen('/dev/null', 'r'); $stdout = fopen('/dev/null', 'wb'); // 你也能够将规范输入重定向到指定的文件,相当于是日志 $stderr = fopen('/dev/null', 'wb'); // 同上
参考资料
- Linux 守护过程
- Linux–过程组、会话、守护过程
本文首发于自己博客:https://yian.me/blog/what-is/php-daemon.html